STM32_SD卡的SDIO通信_DMA读写

本篇,将使用CubeMX+Keil,创建一个SD卡的DMA读写工程。

目录

一、简述

二、CubeMX 配置 SDIO + DMA

三、Keil 编辑代码

四、实验效果


实现效果,如下图:


一、简述

上篇已简单介绍了SD、SDIO,本篇不再啰嗦,有兴趣的可翻看上篇:

STM32_SD卡的SDIO通信_基础读写-CSDN博客

使用SD卡存储数据,常用的两种方式:

  • DMA读写(不带文件系统):逻辑简单、代码体积小,适合只做数据存储的应用。
  • 模拟U盘方式(FATFS+USB):方便电脑端文件存储,但代码体积大、文件解释麻烦。

两种方式各有优劣,得按硬件限制、方案需求而进行选择。

本篇示范的是:不带FATFS的DMA读写。


二、CubeMX 配置 SDIO + DMA

新建工程部分,略过。

1、使能SDIO

  • Mode :  选择 SD 4 bits Wide bus ;
  • DMA Settings :  添加SDIO_RX、SDIO_TX这两项;  其它参数默认;
  • 其它参数 :F4系列不用修改配置,默认即可。F103系列,需把时钟分频系数修改为 6,即SDIOCLK Clock divide factor这一项,默认0,修改为6, 不然会通信失败。

2、开启SDIO全局中断,修改中断优先级

3、时钟设置

进入时钟树配置页面。

如果之前没配置过SDIO、USB,这时就会弹窗:是否自动配置所需时钟?

选择:NO ,手动修改即可。

不推荐 Yes,因为它将针对已使能的SDIO、USB进行必须值的配置,而已设置好的系统时钟,将会被修改成其它值。

F4系列,如果板子用25M晶振,使用下图配置即可;如果是8M晶振,修改晶振、分频两处为8即可。

重点:箭头所指的Q值,它用于控制USB 、SDIO和随机数生成器的时钟,这个时钟必须是 48M !  因此,这里设置为 7; 

好了,已完成配置。

重新生成工程,即可!


三、Keil 编辑代码

1、打开keil 工程,先重新编译一次。

  • 正常情况,编译是0 Error的。
  • 如果有Error,  应该是新建工程时,路径、名称有中文了,重新开建工程,改为英文即可。

2、重要修改:SD卡的初始化,使用 1-bit 模式

CubeMX生成的SDIO初始化代码,有一个bug,需要手动修改,操作如下: 

  • 右击 main.c 文件中函数 MX_SDIO_SD_Init(), 
  • 在弹出菜单中:Go To Ddfinition Of ...;  将跳转到SD卡初始化函数内部;

跳转到 sdio.c文件的 MX_SDIO_SD_Init()函数内部后,

把下图位置中的 4B,改为 1B ;

它下面还有一个4B,不用修改,只修改刚才那个即可。不要改错位置了!

重要!CubeMX每次重新生成后,都要手动修改一次。如果不修改,初始化过程会导致程序卡死。

3、DMA 读写函数 介绍

我们在上篇介绍SD卡的基础读写时,使用的4个函数,如下:

这4个函数,在本工程中,还是可用的。

1、获取SD卡信息
HAL_SD_CardInfoTypeDef pCardInfo = {0};          // SD卡信息结构体
HAL_SD_GetCardInfo(&hsd, &pCardInfo);            // 获取 SD 卡的信息2、读数据
HAL_SD_ReadBlocks(&hsd, aOldData, 7, 2, 3000);   //  SD卡的句柄、数据、块地址、块数量、超时ms3、写数据
HAL_SD_WriteBlocks(&hsd, aTestData, 7, 2, 3000)  //  SD卡的句柄、数据、块地址、块数量、超时ms4、擦除数据
HAL_SD_Erase(&hsd, 7, 8)  //  SD卡的句柄、块起始地址、块结束地址

本篇通过DMA读写,将使用以下两个读写函数:

(和基础读写的函数名称近似,只是多了后辍"_DMA")

4、读取数据_通过DMA
HAL_SD_ReadBlocks_DMA(&hsd, aOldData, 7, 2);    // 读取SD卡指定块的数据; 参数:SD句柄、数据地址、块起始地址、需要读取的块数量;5、写入数据_通过DMA
HAL_SD_WriteBlocks_DMA(&hsd, aTestData, 7, 2);  // 向指定块写入数据; 参数:SD句柄、数据地址、块起始地址、需要写入的块数量;

注意机制的不同:

  • 基础读、写函数: HAL_SD_ReadBlocks() 和 HAL_SD_WriteBlocks()  ,它俩是”死等式“执行的,即,读写完成、操作超时,才能结束函数、并返回执行状态,执行下一行代码。如果数据量大,会"卡"程序。
  • DMA读、写函数:HAL_SD_ReadBlocks_DMA() 和 HAL_SD_WriteBlocks_DMA() ,将按函数的参数配置、启动DMA后,就会退出函数,执行下一行代码。

4、中断回调函数

通过DMA读、写后,有两种方式可以知道读写操作是否完成:

  1. 通过HAL_SD_GetState(&hsd)获取状态;
  2. 通过它俩的中断回调函数。本篇将使用中断回调函数的方式,这样操作性更好;

中断回调函数需要自行手写,可以写在 it.c 或 main.c底部 ,它俩分别是:

1、DMA TX 传输完成 中断回调函数void HAL_SD_TxCpltCallback(SD_HandleTypeDef *hsd)
{printf("写入完成\r\n");
}2、DMA RX 传输完成 中断回调函数
void HAL_SD_RxCpltCallback(SD_HandleTypeDef *hsd)
{printf("读取完成\r\n");
}

5、具体读写操作示例

首先说明一点,本示例示范如何通过DMA读写,而示范代码中的状态判断,只是提供一个思路,这种写法是不高效的,需按自身方案作出适合的修改。

第一步:新建两个变量,作为DMA读写完成的标志。

在main()的上方,新建两个变量,作为读写结束的标志:

volatile uint8_t myFlagSDReadReady = 0;    // 读取传输完成标志; 重要:CubeMX生成的Keil工程,编译优化等级默认是3,如果变量没有标记为易变的(volatile),编译器可能会认为其值在循环中不会改变,从而导致优化后的代码无法正确检测到变量值的变化,特别是在空循环体中。
volatile uint8_t myFlagSDWriteReady = 0;   // 写入传输完成标志; 重要:CubeMX生成的Keil工程,编译优化等级默认是3,如果变量没有标记为易变的(volatile),编译器可能会认为其值在循环中不会改变,从而导致优化后的代码无法正确检测到变量值的变化,特别是在空循环体中。

技巧:

这里用了volatile关键字修饰变量。

因为示范代码中有几段 while (myFlagSDReadReady == 0) 这样的空循环判断变量状态变化。

而CubeMX生成的工程,默认编译优化等级是3。编译时会把这一段中的变量误判为是不变的,编译运行后,就会导致循环体“卡死”。

使用 volatile 修饰,是告诉编译器不要优化它,每次循环时都必须从内存中读一次这变量的真实值。

编写完成后,位置如下图:

第二步:在main() 下方,即/* USER CODE BEGIN 4 */下面,编写需要的两个中断回调函数:

/*DMA Tx传输完成中断回调函数*/
void HAL_SD_TxCpltCallback(SD_HandleTypeDef *hsd)
{myFlagSDWriteReady = 1;   // 写入传输完成标志; 重要:CubeMX生成的Keil工程,编译优化等级默认是3,如果变量没有标记为易变的(volatile),编译器可能会认为其值在循环中不会改变,从而导致优化后的代码无法正确检测到变量值的变化,特别是在空循环体中。
}/*DMA Rx传输完成中断回调函数*/
void HAL_SD_RxCpltCallback(SD_HandleTypeDef *hsd)
{myFlagSDReadReady = 1;    // 读取传输完成标志; 重要:CubeMX生成的Keil工程,编译优化等级默认是3,如果变量没有标记为易变的(volatile),编译器可能会认为其值在循环中不会改变,从而导致优化后的代码无法正确检测到变量值的变化,特别是在空循环体中。
}

编写完成后,位置如下图:

第三步:在 main()函数内,/* USER CODE BEGIN 2 */ 注释下方,编写读写代码:

有点点长,建议直接复制。

其实,有效的读写只是简单的几行而已,这里写了一堆,只是为了展示。

代码里已附详细注释,整体流程是:

  • 获取SD卡信息
  • 读取测试块的原数据 
  • 写入测试
  • 擦除测试
  • 写回原数据
    /***************** SD卡读写通信测试 *****************//* 1、获取卡信息,打印到串口助手                    *//* 2、读测试:读出测试位置原数据,保存在 aOldData[] *//* 3、写测试:在测试的块上,写入指定数据            *//*    读出刚才写入的块数据,打印到串口助手观察      *//* 4、擦除测试:擦除指定块上的数据                  *//*    读出刚才擦除块的数据,打印到串口助手观察      *//* 5、写回原数据到指定位置                          *//*    读出刚才写入的块数据,打印到串口助手观察      */#define SD_TEST_SIZE    1024                                       // 测试数据的字节数,刚好是2个块大小:2x512static uint8_t aOldData[SD_TEST_SIZE] = {0};                       // 用于存放旧数据,先读出来,测试完了,再把旧数据写回去static uint8_t aTestData[SD_TEST_SIZE] = {0};                      // 临时缓存,用来存放测试数据HAL_SD_CardInfoTypeDef pCardInfo = {0};                            // SD卡信息结构体uint8_t status = HAL_SD_GetCardState(&hsd);                        // SD卡状态标志值if (status == HAL_SD_CARD_TRANSFER){/* 1、获取卡信息,打印到串口助手 */HAL_SD_GetCardInfo(&hsd, &pCardInfo);                          // 获取 SD 卡的信息printf("\r1、获取SD卡信息 ... \r\n");printf("卡类型:%d \r\n", pCardInfo.CardType);                 // 类型返回:0-SDSC、1-SDHC/SDXC、3-SECUREDprintf("卡版本:%d \r\n", pCardInfo.CardVersion);              // 版本返回:0-CARD_V1、1-CARD_V2printf("块数量:%d \r\n", pCardInfo.BlockNbr);                 // 可用的块数量printf("块大小:%d \r\n", pCardInfo.BlockSize);                // 每个块的大小; 单位:字节printf("卡容量:%lluG \r\n", ((unsigned long long)pCardInfo.BlockSize * pCardInfo.BlockNbr) / 1024 / 1024 / 1024);  // 计算卡的容量HAL_Delay(1000);                                               // 重要:稍作延时再开始读写测试; 避免有些仿真器烧录期间的多次复位,短暂运行了程序,导致下列读写数据不完整。/* 2、读测试:读出测试位置原数据,保存在 aOldData[] */printf("\r2、读取测试块的原数据 ... \r\n");memset(aOldData, 0, SD_TEST_SIZE);                             // 清0数组的数据myFlagSDReadReady = 0;                                         // 读取传输完成标志。当DMA传输结束后,在DMA中断回调函数里(已写在main.c底部), 把这个变量赋值1.HAL_SD_ReadBlocks_DMA(&hsd, aOldData, 7, 2);                   // 读取SD卡指定块的数据; 参数:SD句柄、数据地址、块起始地址、需要读取的块数量;while (myFlagSDReadReady == 0);                                // 等待传输完成; 重要:CubeMX生成的Keil工程,编译优化等级默认是3,如果变量没有标记为易变的(volatile),编译器可能会认为其值在循环中不会改变,从而导致优化后的代码无法正确检测到变量值的变化,特别是在空循环体中。for (uint32_t i = 0; i < SD_TEST_SIZE; i++)                    // 打印 原始数据printf("%X ",  aOldData[i]);printf("\r\n");/* 3-1、写测试:在测试的块上写入数据 */printf("\r3、SD卡 写入测试 ...\r\n");memset(aTestData, 0x8, SD_TEST_SIZE);                          // 整个数组填充指定值,作为测试写入的数据myFlagSDWriteReady = 0;                                        // 写入传输完成标志。当DMA传输结束后,在DMA中断回调函数里(已写在main.c底部), 把这个变量赋值1.HAL_SD_WriteBlocks_DMA(&hsd, aTestData, 7, 2);                 // 向指定块写入数据; 参数:SD句柄、数据地址、块起始地址、需要写入的块数量;while (myFlagSDWriteReady == 0);                               // 等待传输完成; 重要:CubeMX生成的Keil工程,编译优化等级默认是3,如果变量没有标记为易变的(volatile),编译器可能会认为其值在循环中不会改变,从而导致优化后的代码无法正确检测到变量值的变化,特别是在空循环体中。printf("对指定块写入结束! \r写入的数据是:\n");for (uint32_t i = 0; i < SD_TEST_SIZE; i++)                    // 打印 写入的数据printf("%X ",  aTestData[i]);printf("\r\n");/* 3-2、读出现在块内的数据 */printf("\r现在块内的数据是:\r\n");memset(aTestData, 0, SD_TEST_SIZE);                            // 清0数组的数据myFlagSDReadReady = 0;                                         // 读取传输完成标志。当DMA传输结束后,在DMA中断回调函数里(已写在main.c底部), 把这个变量赋值1.HAL_SD_ReadBlocks_DMA(&hsd, aTestData, 7, 2);                  // 读SD卡数据块; 参数:SD句柄、数据地址、块起始地址、读的块数量;while (myFlagSDReadReady == 0);                                // 等待传输完成; 重要:CubeMX生成的Keil工程,编译优化等级默认是3,如果变量没有标记为易变的(volatile),编译器可能会认为其值在循环中不会改变,从而导致优化后的代码无法正确检测到变量值的变化,特别是在空循环体中。for (uint32_t i = 0; i < SD_TEST_SIZE; i++)                    // 打印 写入后块内现在数据printf("%X ",  aTestData[i]);printf("\r\n");/* 4-1、擦除测试:擦除指定块上的数据  */printf("\r4、擦除块测试 ...\r\n");if (HAL_SD_Erase(&hsd, 7, 8) == HAL_OK)                        // 擦除SD卡上的数据; 参数:SD结构体、块的起始地址、块的结束地址{while (HAL_SD_GetCardState(&hsd) != HAL_SD_CARD_TRANSFER); // 等待卡的读写操作结束printf("擦除 成功! \r\n");}else{printf("擦除 失败! \r\n");}/* 4-2、读取,擦除后指定块上的数据  */printf("擦除后,现在块内的数据是:\r\n");memset(aTestData, 0, SD_TEST_SIZE);                            // 清0数组的数据myFlagSDReadReady = 0;                                         // 读取传输完成标志。当DMA传输结束后,在DMA中断回调函数里(已写在main.c底部), 把这个值赋值1.HAL_SD_ReadBlocks_DMA(&hsd, aTestData, 7, 2);                  // 读SD卡数据块; 参数:SD句柄、数据地址、块起始地址、读的块数量;while (myFlagSDReadReady == 0);                                // 等待传输完成; 重要:CubeMX生成的Keil工程,编译优化等级默认是3,如果变量没有标记为易变的(volatile),编译器可能会认为其值在循环中不会改变,从而导致优化后的代码无法正确检测到变量值的变化,特别是在空循环体中。for (uint32_t i = 0; i < SD_TEST_SIZE; i++)                    // 打印 块内现在的数据printf("%X ",  aTestData[i]);printf("\r\n");/* 5-1、写回测试块上的原始数据 */printf("\r5、写回原数据 ...\r\n");//memset(aOldData, 0, SD_TEST_SIZE);                           // 这行是备用的,为了测试后写入特定数据myFlagSDWriteReady = 0;                                        // 写入传输完成标志。当DMA传输结束后,在DMA中断回调函数里(已写在main.c底部), 把这个变量赋值1.HAL_SD_WriteBlocks_DMA(&hsd, aOldData, 7, 2);                  // 向指定块写入数据; 参数:SD句柄、数据地址、块起始地址、需要写入的块数量;while (myFlagSDWriteReady == 0);                               // 等待传输完成; 重要:CubeMX生成的Keil工程,编译优化等级默认是3,如果变量没有标记为易变的(volatile),编译器可能会认为其值在循环中不会改变,从而导致优化后的代码无法正确检测到变量值的变化,特别是在空循环体中。printf("写入结束! \n");/* 5-2、读取现在块内的数据 */printf("现在块内的数据是: \r\n");memset(aTestData, 0, SD_TEST_SIZE);                            // 清0数组的数据myFlagSDReadReady = 0;                                         // 读取传输完成标志。当DMA传输结束后,在DMA中断回调函数里(已写在main.c底部), 把这个值赋值1.HAL_SD_ReadBlocks_DMA(&hsd, aTestData, 7, 2);                  // 读SD卡数据块; 参数:SD句柄、数据地址、块起始地址、读的块数量;while (myFlagSDReadReady == 0);                                // 等待传输完成; 重要:CubeMX生成的Keil工程,编译优化等级默认是3,如果变量没有标记为易变的(volatile),编译器可能会认为其值在循环中不会改变,从而导致优化后的代码无法正确检测到变量值的变化,特别是在空循环体中。for (uint32_t i = 0; i < SD_TEST_SIZE; i++)                    // 打印 块内现在的数据printf("%X ",  aTestData[i]);printf("\r\n\r\n");printf("SD卡 读写测试结束!\r\n");}

编写完成后,位置如下图:

至此,代码编写完成,可以编译、烧录了。


四、实验效果

程序运行后,串口助手输出如下:

如有错漏 ,望指正~~~!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/11868.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

智能小区物业管理系统推动数字化转型与提升用户居住体验

内容概要 在当今快速发展的社会中&#xff0c;智能小区物业管理系统的出现正在改变传统的物业管理方式。这种系统不仅仅是一种工具&#xff0c;更是一种推动数字化转型的重要力量。它通过高效的技术手段&#xff0c;将物业管理与用户居住体验紧密结合&#xff0c;无疑为社区带…

基于STM32景区环境监测系统的设计与实现(论文+源码)

1系统方案设计 根据系统功能的设计要求&#xff0c;展开基于STM32景区环境监测系统设计。如图2.1所示为系统总体设计框图。系统以STM32单片机作为系统主控模块&#xff0c;通过DHT11传感器、MQ传感器、声音传感器实时监测景区环境中的温湿度、空气质量以及噪音数据。系统监测环…

八. Spring Boot2 整合连接 Redis(超详细剖析)

八. Spring Boot2 整合连接 Redis(超详细剖析) 文章目录 八. Spring Boot2 整合连接 Redis(超详细剖析)2. 注意事项和细节3. 最后&#xff1a; 在 springboot 中 , 整合 redis 可以通过 RedisTemplate 完成对 redis 的操作, 包括设置数据/获取数据 比如添加和读取数据 具体整…

【Unity3D】Tilemap俯视角像素游戏案例

目录 一、导入Tilemap 二、导入像素风素材 三、使用Tilemap制作地图 3.1 制作Tile Palette素材库 3.2 制作地图 四、实现A*寻路 五、待完善 一、导入Tilemap Unity 2019.4.0f1 已内置Tilemap 需导入2D Sprite、2D Tilemap Editor、以及一个我没法正常搜出的2D Tilemap…

企微SCRM驱动企业私域流量营销与客户关系管理的智慧革新

内容概要 在当今竞争激烈的商业环境中&#xff0c;企微SCRM逐渐成为企业实现私域流量营销和优化客户关系管理的重要工具。它的出现不仅提升了企业的工作效率&#xff0c;也改变了传统的营销方式。那么&#xff0c;究竟什么是企微SCRM呢&#xff1f;简单来说&#xff0c;它是将…

数据库、数据仓库、数据湖有什么不同

数据库、数据仓库和数据湖是三种不同的数据存储和管理技术&#xff0c;它们在用途、设计目标、数据处理方式以及适用场景上存在显著差异。以下将从多个角度详细说明它们之间的区别&#xff1a; 1. 数据结构与存储方式 数据库&#xff1a; 数据库主要用于存储结构化的数据&…

前端力扣刷题 | 6:hot100之 矩阵

73. 矩阵置零 给定一个 m x n 的矩阵&#xff0c;如果一个元素为 0 &#xff0c;则将其所在行和列的所有元素都设为 0 。请使用 原地 算法。 法一&#xff1a; var setZeroes function(matrix) {let setX new Set(); // 用于存储需要置零的行索引let setY new Set(); //…

【编译系列】Torch.compile()训练编译——算子融合逻辑 工程化

1. 背景: torch.compile()中,Dynamo作为前端负责计算图的捕获,后端有inductor、tvm等进行编译优化。 Dynamo:在Python字节码层面注入pass,实现bytecode-to-bytecode的优化,通过对bytecode逐行进行解析构建FX GraphInductor:负责对FX Graph进行AOTAutograd生成joint-gra…

Docker 部署教程jenkins

Docker 部署 jenkins 教程 Jenkins 官方网站 Jenkins 是一个开源的自动化服务器&#xff0c;主要用于持续集成&#xff08;CI&#xff09;和持续交付&#xff08;CD&#xff09;过程。它帮助开发人员自动化构建、测试和部署应用程序&#xff0c;显著提高软件开发的效率和质量…

2025/2/3 云服务器数据库与idea相连

幸福就摆在你面前&#xff0c;你却把阴影当成山川瀑布&#xff0c;你说你无法幸福。 轻量应用服务器https://swasnext.console.aliyun.com/servers/cn-heyuanhttps://swasnext.console.aliyun.com/servers/cn-heyuanhttps://swasnext.console.aliyun.com/servers/cn-heyuanhttp…

【memgpt】letta 课程1/2:从头实现一个自我编辑、记忆和多步骤推理的代理

llms-as-operating-systems-agent-memory llms-as-operating-systems-agent-memory内存 操作系统的内存管理

6. 【Vue实战--孢子记账--Web 版开发】-- 主币种设置

从这篇文章开始我们将一起实现孢子记账的功能&#xff0c;这篇文章实现主币种设置。这个功能比较简单&#xff0c;因此我们从这个功能开始做。 一、功能 根据项目前期的需求调研&#xff0c;用户需要在设置主币种的时候查看汇率信息&#xff08;别问为什么有这么个需求&#…

51单片机(STC89C52)开发:点亮一个小灯

软件安装&#xff1a; 安装开发板CH340驱动。 安装KEILC51开发软件&#xff1a;C51V901.exe。 下载软件&#xff1a;PZ-ISP.exe 创建项目&#xff1a; 新建main.c 将main.c加入至项目中&#xff1a; main.c:点亮一个小灯 #include "reg52.h"sbit LED1P2^0; //P2的…

GESP2023年9月认证C++六级( 第三部分编程题(2)小杨的握手问题)

参考程序1&#xff08;暴力枚举&#xff09; #include <iostream> using namespace std;int main() {int n 0;cin >> n; // 读入同学的数量int num[300000]; // 存储同学的学号for (int i 0; i < n; i) {cin >> num[i]; // 读入同学的进入顺序}long…

【C++篇】哈希表

目录 一&#xff0c;哈希概念 1.1&#xff0c;直接定址法 1.2&#xff0c;哈希冲突 1.3&#xff0c;负载因子 二&#xff0c;哈希函数 2.1&#xff0c;除法散列法 /除留余数法 2.2&#xff0c;乘法散列法 2.3&#xff0c;全域散列法 三&#xff0c;处理哈希冲突 3.1&…

GPT与Deepseek等数据驱动AI的缺点

当前数据驱动的AI&#xff08;包括GPT与Deepseek等各种大小模型&#xff09;只进行了数/物理性的初步探索&#xff0c;尚未触及人机环境生态系统的复杂性。也就是说&#xff0c;当前的数据驱动型 AI&#xff0c;虽然在处理大量数据、解决特定任务方面取得了显著进展&#xff0c…

阿里云盘PC端打不开解决办法

阿里云盘服务中心 搜索&#xff1a;PC端无法启动怎么办 选择问题 PC端双击云盘图标没有反应&#xff08;windows系统&#xff09; 下载null.sys&#xff0c;先执行压缩包里面的 null.reg 注册表&#xff0c;再按官方文档操作&#xff0c;不然会报错&#xff0c;搞完建议重启一…

树莓派pico入坑笔记,故障解决:请求 USB 设备描述符失败,故障码(43)

今天心血来潮&#xff0c;拿出吃灰的pico把玩一下&#xff0c;打开thonny&#xff0c;上电&#xff0c;然后...... 上电识别不到端口&#xff0c;windows报错&#xff0c;请求 USB 设备描述符失败&#xff0c;故障码&#xff08;43&#xff09; 一开始以为是坏了&#xff08;磕…

Linux——文件系统

一、从硬件出发 1&#xff09;磁盘的主要构成 通常硬盘是由盘片、主轴、磁头、摇摆臂、马达、永磁铁等部件组成&#xff0c;其中一个硬盘中有多块盘片和多个磁头&#xff0c;堆叠在一起&#xff0c;工作时由盘片旋转和摇摆臂摇摆及逆行寻址从而运作&#xff0c;磁头可以对盘片…

FPGA 时钟多路复用

时钟多路复用 您可以使用并行和级联 BUFGCTRL 的组合构建时钟多路复用器。布局器基于时钟缓存 site 位置可用性查找最佳布局。 如果可能&#xff0c;布局器将 BUFGCTRL 布局在相邻 site 位置中以利用专用级联路径。如无法实现&#xff0c;则布局器将尝试将 BUFGCTRL 从…