LV16-21-SDIO-03-读写SD卡实例
本文主要是STM32开发——读写SD卡实例的相关笔记,若笔记中有错误或者不合适的地方,欢迎批评指正😃。
点击查看使用工具及版本
Windows | windows11 |
Ubuntu | Ubuntu16.04的64位版本 |
VMware® Workstation 16 Pro | 16.2.3 build-19376536 |
SecureCRT | Version 8.7.2 (x64 build 2214) - 正式版-2020年5月14日 |
开发板 | 正点原子 i.MX6ULL Linux阿尔法开发板 |
uboot | NXP官方提供的uboot,NXP提供的版本为uboot-imx-rel_imx_4.1.15_2.1.0_ga(使用的uboot版本为U-Boot 2016.03) |
linux内核 | linux-4.15(NXP官方提供) |
STM32开发板 | 正点原子战舰V3(STM32F103ZET6) |
点击查看本文参考资料
- 通用
分类 | 网址 | 说明 |
官方网站 | https://www.arm.com/ | ARM官方网站,在这里我们可以找到Cotex-Mx以及ARMVx的一些文档 |
https://www.st.com/content/st_com/zh.html | ST官方网站,在这里我们可以找到STM32的相关文档 | |
https://www.stmcu.com.cn/ | 意法半导体ST中文官方网站,在这里我们可以找到STM32的相关中文参考文档 | |
http://elm-chan.org/fsw/ff/00index_e.html | FatFs文件系统官网 | |
教程书籍 | 《ARM Cortex-M3权威指南》 | ARM公司专家Joseph Yiu(姚文祥)的力作,中文翻译是NXP的宋岩 |
《ARM Cortex-M0权威指南》 | ||
《ARM Cortex-M3与Cortex-M4权威指南》 | ||
开发论坛 | http://47.111.11.73/forum.php | 开源电子网,正点原子的资料下载及问题讨论论坛 |
https://www.firebbs.cn/forum.php | 国内Kinetis开发板-野火/秉火(刘火良)主持的论坛,现也做STM32和i.MX RT | |
https://www.amobbs.com/index.php | 阿莫(莫进明)主持的论坛,号称国内最早最火的电子论坛,以交流Atmel AVR系列单片机起家,现已拓展到嵌入式全平台,其STM32系列帖子有70W+。 | |
http://download.100ask.net/index.html | 韦东山嵌入式资料中心,有些STM32和linux的相关资料也可以来这里找。 | |
博客参考 | http://www.openedv.com/ | 开源网-原子哥个人博客 |
http://blog.chinaaet.com/jihceng0622 | 博主是原Freescale现NXP的现场应用工程师 | |
cortex-m-resources | 这其实并不算是一个博客,这是ARM公司专家Joseph Yiu收集整理的所有对开发者有用的官方Cortex-M资料链接(也包含极少数外部资源链接) |
- STM32
STM32 | STM32 HAL库开发实战指南——基于F103系列开发板 | 野火STM32开发教程在线文档 |
STM32库开发实战指南——基于野火霸道开发板 | 野火STM32开发教程在线文档 |
- SD卡
SD Association | 提供了SD存储卡和SDIO卡系统规范 |
点击查看相关文件下载
STM32F103xx英文数据手册 | STM32F103xC/D/E系列的英文数据手册 |
STM32F103xx中文数据手册 | STM32F103xC/D/E系列的中文数据手册 |
STM32F10xxx英文参考手册(RM0008) | STM32F10xxx系列的英文参考手册 |
STM32F10xxx中文参考手册(RM0008) | STM32F10xxx系列的中文参考手册 |
Arm Cortex-M3 处理器技术参考手册-英文版 | Cortex-M3技术参考手册-英文版 |
STM32F10xxx Cortex-M3编程手册-英文版(PM0056) | STM32F10xxx/20xxx/21xxx/L1xxxx系列Cortex-M3编程手册-英文版 |
SD卡相关资料——最新版本 | 有关SD卡的一些资料可以从这里下载 |
SD卡相关资料——历史版本 | 有关SD卡的一些历史版本资料可以从这里下载,比如后边看的SD卡2.0协议 |
SD 2.0 协议标准完整版 | 这是一篇关于SD卡2.0协议的中文文档,还是比较有参考价值的,可以一看 |
一、硬件设计
我使用的是正点原子战舰V3的开发板,硬件连接如下图:
二、STM32CubeMX配置
像LED、按键和串口的配置这里就不再赘述,这里只写SDIO相关配置。注意,这一部分中的中断和DMA相关配置是作为笔记写在这里,后边的测试函数并没有使用DMA以及中断相关的内容,都是使用的查询方式,当使用查询方式,但是又开启了DMA以及相关中断的时候,数据读写可能会失败,这里需要注意。
1. SDIO参数配置
①、选择SDIO外设;
②、选择模式,这里选择SD 4 bits Wire bus;
③、Clock transition on which the bit capture is made: Rising transition。主时钟 SDIOCLK 产生 CLK 引脚时钟有效沿选择,可选上升沿或下降沿,它设定 SDIO 时钟控制寄存器(SDIO_CLKCR)的 NEGEDGE 位的值,一般选择设置为上升沿。
④、SDIO Clock divider bypass: Disable。时钟分频旁路使用,可选使能或禁用,它设定 SDIO_CLKCR 寄存器的 BYPASS 位。如果使能旁路,SDIOCLK 直接驱动 CLK 线输出时钟;如果禁用,使用 SDIO_CLKCR 寄存器的 CLKDIV 位值分频 SDIOCLK,然后输出到 CLK 线。一般选择禁用时钟分频旁路。
⑤、SDIO Clock output enable when the bus is idle: Disable the power save for the clock。节能模式选择,可选使能或禁用,它设定 SDIO_CLKCR 寄存器的 PWRSAV 位的值。如果使能节能模式,CLK 线只有在总线激活时才有时钟输出;如果禁用节能模式,始终使能 CLK 线输出时钟。
⑥、SDIO hardware flow control: The hardware control flow is disabled。硬件流控制选择,可选使能或禁用,它设定 SDIO_CLKCR 寄存器的 HWFC_EN 位的值。硬件流控制功能可以避免 FIFO 发送上溢和下溢错误。
⑦、SDIOCLK clock divide factor: 1。时钟分频系数,它设定 SDIO_CLKCR 寄存器的 CLKDIV 位的值,设置 SDIOCLK 与 CLK 线输出时钟分频系数
1 | CLK 线时钟频率=SDIOCLK/([CLKDIV+2]) |
SDIO_CK 引脚的时钟信号在卡识别模式时要求不超过 400KHz,而在识别后的数据传输模式时则希望有更高的速度(最大不超过 25MHz),所以会针对这两种模式配置 SDIOCLK 的时钟。这里暂时将SDIOCLK clock divede factor 参数选择为 1,SDIOCLK为72MHz,可以得到最大频率24MHz,但还是要注意,我们使用的SD卡支不支持这么高的一个频率,所以还是要以实际情况而定。
2. DMA配置
SDIO 外设支持生成 DMA 请求,使用 DMA 传输可以提高数据传输效率,因此在 SDIO 的控制代码中,可以把它设置为 DMA 传输模式或轮询模式,ST 标准库提供 SDIO 示例中针对这两个模式做了区分处理。应用中一般都使用DMA 传输模式。
①、选择SDIO外设;
②、选择DMA Settings;
③、添加一个DMA请求,选择我们开启的SDIO;
④、Direction:数据传输的方向,这里是外设到内存;
⑤、Priority:当发生多个 DMA 通道请求时,就意味着有先后响应处理的顺序问题,这个就由仲裁器也管理。仲裁器管理 DMA 通道请求分为两个阶段。第一阶段属于软件阶段,可以在 DMA_CCRx 寄存器中设置,有 4 个等级:非常高、高、中和低四个优先级。第二阶段属于硬件阶段,如果两个或以上的 DMA 通道请求设置的优先级一样,则他们优先级取决于通 道编号,编号越低优先权越高,比如通道 0 高于通道 1。在大容量产品和互联型产品中,DMA1 控制器拥有高于 DMA2 控制器的优先级。
⑥、Mode:Normal,表示单次传输,传输一次后终止传输。Circular,表示循环传输,传输完成后又重新开始继续传输,不断循环永不停止。
⑦、Increment Address:Peripheral,表示外设地址自增。Memory,表示内存地址自增。
⑧、Data Width: Byte,一个字节。 Half Word,半个字,等于两字节。 Word,一个字,等于四字节。
3. NVIC配置
DMA及SDIO中断这里也进行配置,原则是全局中断优先级高于DMA中断:
4. 我的时钟树
我的时钟树配置如下:
三、读写SD卡
STM32CubeMX创建的工程会直接帮我们完成一些流程,比如SD卡初始化,读写等。我们其实也可以通过HAL固件库中的例程来学习,例如HAL库和标准库的相关sample路径如下:
1. SD卡初始化
1.1 初始化流程
1.2 初始化函数
1 | void MX_SDIO_SD_Init(void) |
这里就不往里边深究了,这里有一个坑,需要注意:
(1) BusWide 我们通过STM32CubeMX配置的工程,在开启SDIO的时候选择的 SD 4 bits Wide bus,SDIOCLK clock divide factor选择0的话(此时最高频率为36M了),我们生成的代码为:
1 | // 中间部分省略...... |
这种情况下,直接卡死在初始化函数中,无法运行。其实就 SDIOCLK clock divide factor 一点就把这个代码干掉了,因为选择0的话,分频出来就是72/2=36M,我们使用的卡并不支持这么高频率,所以这里最小要设置为1,但是即便改为1,程序也是无法运行的,依然卡死。这是因为BusWide宽度为4,其实我并没有找到手册中说明在初始化阶段的时候总线宽度必须为1位,但是网上的资料都是1位,而STM32CubeMX软件生成的,这里就被配置成4位,我们改为1位的话,程序就可以正常跑,具体哪里有要求的话,后边发现了再补充。
(2)SDIO的时候选择的 SD 1 bit ,SDIOCLK clock divide factor选择 1 的话,默认生成的代码如下
1 | // 中间部分省略...... |
这样是没有问题的,程序可以正常执行,后边若是不设置 4 位的话,无非就是传输的慢一些。
1.3 GPIO配置
对于SDIO的引脚的配置在函数 HAL_SD_MspInit 中:
1 | /**SDIO GPIO Configuration |
1.4 DMA配置
我们在配置工程的时候是选择了DMA传输的,DMA的相关配置也在函数 HAL_SD_MspInit 中:
1 | /* SDIO DMA Init */ |
每一行什么意思,可以看前边DMA传输的笔记,这里就不再赘述了。
1.5 NVIC配置
NVIC的配置也写在函数 HAL_SD_MspInit 中了:
1 | HAL_NVIC_SetPriority(SDIO_IRQn, 0, 0); |
这里我们看到主要是配置了SDIO的中。那DMA的中断去哪了?在哪设置的?我们前边也开了啊。在这里:
1 | void MX_DMA_Init(void) |
DMA的初始化中,开启了DMA的时钟和中断,在SD卡初始化的时候是对DMA做了配置,在SD卡初始化流程前已经做了DMA的初始化啦。
2. 获取SD卡状态
2.1 HAL库函数获取SD卡状态
初始化结束后,我们可以判断一下SD卡是否处于数据传输的状态:
1 | HAL_SD_CardStateTypeDef HAL_SD_GetCardState(SD_HandleTypeDef *hsd) |
SD卡的状态定义如下:
1 | typedef enum |
2.2 我们自己封装一个
有时候我们其实并不需要这么多状态,我们可以封装一层,用与读写SD卡。
1 | // 需要定义两个宏 |
3. 显示SD卡信息
1 | //通过串口打印SD卡相关信息 |
4. 数据操作
SD 卡数据操作一般包括数据读取、数据写入以及存储区擦除。数据读取和写入都可以分为单块操作和多块操作。
4.1 擦除函数
1 | /** |
函数比较长,这里就不全贴了,额,看了一下,我对寄存器也不是很熟,这个里边有一些没搞懂,知道这有个函数,知道怎么用就算了,后边有必要相惜学习的话,再补充。
4.2 开关中断函数
后边我们读写的时候,若是采取查询方式,需要关闭总中断,读写完成后再打开。
- 开关中断定义
1 | //关闭所有中断 |
- 超时时间的宏
1 |
4.3 数据写入操作
数据写入可分为单块数据写入和多块数据写入,这里只分析单块数据写入,多块的与之类似。 SD卡数据写入之前并没有硬性要求擦除写入块,这与 SPI Flash 芯片写入是不同的。 ST 官方的 SD卡写入函数包括扫描查询方式和 DMA 传输方式,都有分别对应的函数,这里先用查询方式。
4.3.1 查询方式
1 | HAL_StatusTypeDef HAL_SD_WriteBlocks(SD_HandleTypeDef *hsd, uint8_t *pData, uint32_t BlockAdd, uint32_t NumberOfBlocks, uint32_t Timeout); |
我们来写一个写入数据的函数:
1 | uint8_t SD_WriteDisk(uint8_t *buf, uint32_t sector, uint32_t cnt) |
4.3.2 DMA方式
注意:STM32F103的SDIO DMA每次由读数据变为写数据或者由写数据变为读数据时,都需要重新初始化DMA(主要是为了更改数据传输的方向)。
1 | HAL_StatusTypeDef HAL_SD_WriteBlocks_DMA(SD_HandleTypeDef *hsd, uint8_t *pData, uint32_t BlockAdd, uint32_t NumberOfBlocks); |
我们这个就可以参考HAL库文件中给我们提供的参考例程啦,参考文件路径如下:
1 | STM32Cube_FW_F1_V1.8.0/Drivers/BSP/STM3210E_EVAL/stm3210e_eval_sd.c |
这个还需要移植,暂时先搁置,后边有需要再补充。
4.4 数据读取操作
同向 SD 卡写入数据类似,从 SD 卡读取数据可分为单块读取和多块读取。
4.4.1 查询方式
1 | HAL_StatusTypeDef HAL_SD_ReadBlocks(SD_HandleTypeDef *hsd, uint8_t *pData, uint32_t BlockAdd, uint32_t NumberOfBlocks, uint32_t Timeout); |
我们封装一下:
1 | uint8_t SD_ReadDisk(uint8_t* buf,uint32_t sector,uint32_t cnt) |
4.4.2 DMA方式
注意:STM32F103的SDIO DMA每次由读数据变为写数据或者由写数据变为读数据时,都需要重新初始化DMA(主要是为了更改数据传输的方向)。
1 | HAL_StatusTypeDef HAL_SD_ReadeBlocks_DMA(SD_HandleTypeDef *hsd, uint8_t *pData, uint32_t BlockAdd, uint32_t NumberOfBlocks); |
我们这个就可以参考HAL库文件中给我们提供的参考例程啦,参考文件路径如下:
1 | STM32Cube_FW_F1_V1.8.0/Drivers/BSP/STM3210E_EVAL/stm3210e_eval_sd.c |
这个还需要移植,暂时先搁置,后边有需要再补充。
5. 测试函数
注意:由于上边我开启了SDIO中断和DMA中断,但是最后却又使用了查询的方式去读写数据,所以会出现读写失败的情况,当我关掉SDIO中断和DMA中断后,恢复正常。
5.1 相关宏和变量定义
1 |
|
5.2 读数据测试函数
1 | //测试SD卡的读取,从secaddr地址开始,读取seccnt个扇区的数据 |
5.3 写数据测试函数
1 | //测试SD卡的写入(慎用,最好写全是0XFF的扇区,否则可能损坏SD卡.),从secaddr地址开始,写入seccnt个扇区的数据 |
5.4 测试函数
1 | void SD_Test(void) |
6. 踩的几个坑
6.1 时钟问题
初始化的时候,时钟分频设置为1,发现写入总是失败,后来加大,改为4正常,这说明我使用的SD卡并不支持24Mz,,所以有的时候若是写入总是失败的话,可以考虑降低SDIO时钟。
5.2 擦除问题
很奇怪,我擦除后直接写入的话,写不进去,写入操作是失败的,但是擦除是成功的,而且读取擦除的扇区的数据为全0,当关闭擦除部分代码的时候,重新烧写,是可以重新写入成功的,其实有些SD卡是不需要擦除的,因为SD卡内部控制器会帮我们做这件事,等后边找到详细说明了再补充。