HAL库 CubeMX STM32采用SDIO实现对SD卡和NAND Flash的读写

目录

完整项目源代码下载地址:HAL库CubeMX STM32采用SDIO实现对SD卡和NAND Flash的读写资源-CSDN文库

一、选择合适的存储芯片。

可以去雷龙官网白嫖,白嫖链接:免费样品

二、SD卡/SD NAND底层原理

三、硬件设计

1、SD NAND引脚图

2、芯片外观及封装:

3、硬件电路原理图

4、测试用转接板实物图

四、 CubeMX配置STM32具体步骤

1、时钟和系统配置

2、配置SDIO

3、配置DMA (可选)

4、设置串口

五、代码编写

1、公共代码

2、常规方式读写

3、DMA方式读写

六、结果分析

1、输入的函数参数是扇区编号,而不是实际偏移地址。

2、测试结果

完整项目源代码下载地址:HAL库CubeMXSTM32采用SDIO实现对SD卡和NANDFlash的读写资源-CSDN文库

一、选择合适的存储芯片。

最近在做一个项目,需要实现大量存储读取数据,但是stm32上自带的存储器容量太小了,比如我用的这款STM32F103ZET6本身的flash容量为512K,不够用。

相关单片机芯片型号资源如下:

最终项目采用的方案是:雷龙公司提供的CSNP4GCR01这款SD NAND。当然也可以用TF卡,使用方法都是采用SDIO总线驱动,程序都是一模一样的,但是这款相较于普通的TF卡有更多的优势,具体体现在以下几点:

在一些贴片芯片的PCB设计中,无论是在面积有着严格要求中还是在实际恶劣环境中,并且胜在价格、封装以及稳定性上有优势,综合来说性价比更高,雷龙公司提供的CS SD NAND FLASH方案占有明显优势,这也正是我在项目中选择使用它的原因。

可以去雷龙官网白嫖,白嫖链接:免费样品

二、SD卡/SD NAND底层原理

根据SD卡的容量,可划分为SDSC、SDHC、SDXC三种标准。现今,市场的主流SD产品是SDHC和SDXC这两种较大容量的存储卡,而SDSC卡因容量过小,已逐渐被市场淘汰。SD卡(三种卡的统称)的存储空间是由一个一个扇区组成的,SD卡的扇区大小是512byte,若干个扇区又可以组成一个分配单元(也被成为簇),分配单元常见的大小为4K、8K、16K、32K、64K。

具体原理这里我就不具体写了,网上有很多,可以参考以下链接:

SD NAND 的 SDIO在STM32上的应用详解(上篇)_sdio接两个芯片_深圳市雷龙发展有限公司的博客-CSDN博客

三、硬件设计

1、SD NAND引脚图

2、芯片外观及封装:

3、硬件电路原理图

如下,可以看到,我们只需要在芯片外围加上几个电阻、电容即可使用,这样可以很轻易的集成在我们自己的PCB封装上。

4、测试用转接板实物图

此次实验用的开发实物图:

运行中的实物图:

四、 CubeMX配置STM32具体步骤

1、时钟和系统配置

2、配置SDIO

(1)Clock transition on which the bit capture is made: Rising transition。主时钟 SDIOCLK 产生 CLK 引脚时钟有效沿选择,可选上升沿或下降沿,它设定 SDIO 时钟控制寄存器(SDIO_CLKCR)的 NEGEDGE 位的值,一般选择设置为上升沿。(参考链接)

(2)SDIO Clock divider bypass:Disbale。时钟分频旁路使用,可选使能或禁用,它设定 SDIO_CLKCR 寄存器的 BYPASS 位。如果使能旁路,SDIOCLK 直接驱动 CLK 线输出时钟;如果禁用,使用 SDIO_CLKCR 寄存器的 CLKDIV 位值分频 SDIOCLK,然后输出到 CLK 线。一般选择禁用时钟分频旁路。

(3)SDIO Clock output enable when the bus is idle: Disable the power save for the clock。节能模式选择,可选使能或禁用,它设定 SDIO_CLKCR 寄存器的 PWRSAV 位的值。如果使能节能模式,CLK 线只有在总线激活时才有时钟输出;如果禁用节能模式,始终使能 CLK 线输出时钟。

(4)SDIO hardware flow control: The hardware control flow is disabled。硬件流控制选择,可选使能或禁用,它设定 SDIO_CLKCR 寄存器的 HWFC_EN 位的值。硬件流控制功能可以避免 FIFO 发送上溢和下溢错误。

SDIOCLK clock divide factor: 6。时钟分频系数,它设定 SDIO_CLKCR 寄存器的 CLKDIV 位的值,设置 SDIOCLK 与 CLK 线输出时钟分频系数:

CLK 线时钟频率=SDIOCLK/([CLKDIV+2])。

  • 识别卡阶段:时钟频率 FOD,最高为 400kHz

  • 数据传输模式:时钟频率FPP,默认最高为 25MHz

  • 如果通过相关寄存器配置使 SDIO 工作在高速模式,此时数据传输模式最高频率为 50MHz

### 注意:刚开始我做的过程中,参考了下面的SDIOCLK,然后就设置了时钟分频系数为0,导致CLK 线时钟频率=72/([0+2])=36M大于最高数据传输速率25M,生成代码后,程序一直卡死在SDIO初始化函数中,导致SD卡初始化失败。

仔细查看上面的第二点(2),由于在我们之前配置中禁用了时钟分频旁路,所以我们不能参考下面这个SDIOCLK,所以实际使用SDIOCLK=72M,应该设置时钟分频系数为2以上。

3、配置DMA (可选)

  • 在一些实际使用场合中,比如需要把摄像头帧数据存储到SD卡,需要高效的存储和取用大量数据时,我们往往采用DMA,减轻CPU的负担。

  • SDIO 外设支持生成 DMA 请求,使用 DMA 传输可以提高数据传输效率,因此在 SDIO 的控制代码中,可以把它设置为 DMA 传输模式。

4、设置串口

打开串口,方便通过串口实时打印出SD卡的信息以及查看调试信息。

五、代码编写

1、公共代码

以下两种模式下添加的公共代码部分

  1. #include <stdio.h>

  2. #include <string.h>

  3. #define BLOCK_START_ADDR 0 /* Block start address */

  4. #define NUM_OF_BLOCKS 1 /* 扇区编号 */

  5. #define BUFFER_WORDS_SIZE ((BLOCKSIZE * NUM_OF_BLOCKS) >> 2) /* Total data size in bytes */

  6. //这里定义大小为512byte,正好是SD卡一个扇区,偏移地址0x00000200

  7. uint8_t Buffer_Tx[512],Buffer_Rx[512] = {0};

  8. uint8_t Buffer_Tx_DMA[1024],Buffer_Rx_DMA[1024] = {0};

  9. uint32_t i;

  10. extern DMA_HandleTypeDef hdma_sdio;

  11. int fputc(int c, FILE *stream) //重写fputc函数

  12. {

  13. /*

  14. huart1是工具生成代码定义的UART1结构体,

  15. 如果以后要使用其他串口打印,只需要把这个结构体改成其他UART结构体。

  16. */

  17. HAL_UART_Transmit(&huart1, (unsigned char *)&c, 1, 1000);

  18. return 1;

  19. }

  20. // 打印SD卡基本信息

  21. void show_sdcard_info(void)

  22. {

  23. printf("Micro SD Card Test...\r\n");

  24. /* 检测SD卡是否正常(处于数据传输模式的传输状态) */

  25. if(HAL_SD_GetCardState(&hsd) == HAL_SD_CARD_TRANSFER)

  26. {

  27. printf("Initialize SD card successfully!\r\n");

  28. // 打印SD卡基本信息

  29. printf(" SD card information! \r\n");

  30. printf(" CardCapacity : %llu \r\n", (unsigned long long)hsd.SdCard.BlockSize * hsd.SdCard.BlockNbr);// 显示容量

  31. printf(" CardBlockSize : %d \r\n", hsd.SdCard.BlockSize); // 块大小

  32. printf(" LogBlockNbr : %d \r\n", hsd.SdCard.LogBlockNbr); // 逻辑块数量

  33. printf(" LogBlockSize : %d \r\n", hsd.SdCard.LogBlockSize);// 逻辑块大小

  34. printf(" RCA : %d \r\n", hsd.SdCard.RelCardAdd); // 卡相对地址

  35. printf(" CardType : %d \r\n", hsd.SdCard.CardType); // 卡类型

  36. // 读取并打印SD卡的CID信息

  37. HAL_SD_CardCIDTypeDef sdcard_cid;

  38. HAL_SD_GetCardCID(&hsd,&sdcard_cid);

  39. printf(" ManufacturerID: %d \r\n",sdcard_cid.ManufacturerID);

  40. }

  41. else

  42. {

  43. printf("SD card init fail!\r\n" );

  44. }

  45. }

  46. /* 擦除SD卡块 */

  47. void erase_sdcard(SD_HandleTypeDef *hsd, uint32_t BlockStartAdd, uint32_t BlockEndAdd)

  48. {

  49. printf("------------------- Block Erase -------------------------------\r\n");

  50. if(HAL_SD_Erase(hsd, BlockStartAdd, BlockEndAdd) == HAL_OK)

  51. {

  52. /* Wait until SD cards are ready to use for new operation */

  53. while(HAL_SD_GetCardState(hsd) != HAL_SD_CARD_TRANSFER)

  54. {

  55. }

  56. printf("\r\nErase Block Success!\r\n");

  57. }

  58. else

  59. {

  60. printf("\r\nErase Block Failed!\r\n");

  61. }

  62. }

2、常规方式读写

  1. /* 填充缓冲区数据 */

  2. void write_sdcard(SD_HandleTypeDef *hsd, uint8_t *pData, uint32_t BlockAdd, uint32_t NumberOfBlocks, uint32_t Timeout)

  3. {

  4. /* 向SD卡块写入数据 */

  5. printf("------------------- Start Write SD card block data ------------------\r\n");

  6. if(HAL_SD_WriteBlocks(hsd, pData, BlockAdd, NumberOfBlocks, Timeout) == HAL_OK)

  7. {

  8. while(HAL_SD_GetCardState(hsd) != HAL_SD_CARD_TRANSFER)

  9. {

  10. }

  11. printf("\r\nWrite Block Success!\r\n");

  12. }

  13. else

  14. {

  15. printf("\r\nWrite Block Failed!\r\n");

  16. }

  17. }

  18. /* 读取操作之后的数据 */

  19. void read_sdcard(SD_HandleTypeDef *hsd, uint8_t *pData, uint32_t BlockAdd, uint32_t NumberOfBlocks, uint32_t Timeout)

  20. {

  21. printf("------------------- Start Read SD card block data ------------------\r\n");

  22. if(HAL_SD_ReadBlocks(hsd, pData, BlockAdd, NumberOfBlocks, Timeout) == HAL_OK)

  23. {

  24. while(HAL_SD_GetCardState(hsd) != HAL_SD_CARD_TRANSFER)

  25. {

  26. }

  27. printf("\r\nRead Block Success!\r\n");

  28. }

  29. else

  30. {

  31. printf("\r\nRead Block Failed!\r\n");

  32. }

  33. }

主函数里代码如下:

  1. /* USER CODE BEGIN 2 */

  2. show_sdcard_info();

  3. //擦除5个扇区

  4. erase_sdcard(&hsd, 0, 5);

  5. memset(Buffer_Tx, 0x55, sizeof(Buffer_Tx));

  6. write_sdcard(&hsd, Buffer_Tx, 1, 2, 10);

  7. read_sdcard(&hsd, Buffer_Rx, 1, 2, 10);

  8. //查看读取到的数据

  9. for(i = 0; i < sizeof(Buffer_Rx)/sizeof(Buffer_Rx[0]); i++)

  10. {

  11. printf("0x%02x:%02x ", i, Buffer_Rx[i]);

  12. }

  13. /* USER CODE END 2 */

3、DMA方式读写

  • 这里需要注意:SDIO DMA每次由读数据变为写数据或者由写数据变为读数据时,都需要重新初始化DMA,是为了更改数据传输的方向。

  • (这里特别坑,参考别的博主的代码发现不能用,经过自己亲自修改后,代码更改了很多次才发现这个问题,以下代码验证通过可直接使用)

  1. //非阻塞式DMA读

  2. HAL_StatusTypeDef SDIO_ReadBlocks_DMA(SD_HandleTypeDef *hsd, uint8_t *pData, uint32_t BlockAdd, uint32_t NumberOfBlocks)

  3. {

  4. HAL_StatusTypeDef Return_Status;

  5. HAL_SD_CardStateTypeDef SD_Card_Status;

  6. do{ SD_Card_Status = HAL_SD_GetCardState(hsd);}while(SD_Card_Status != HAL_SD_CARD_TRANSFER );

  7. HAL_DMA_DeInit(&hdma_sdio);

  8. hdma_sdio.Instance = DMA2_Channel4;

  9. hdma_sdio.Init.Direction = DMA_PERIPH_TO_MEMORY;

  10. hdma_sdio.Init.PeriphInc = DMA_PINC_DISABLE;

  11. hdma_sdio.Init.MemInc = DMA_MINC_ENABLE;

  12. hdma_sdio.Init.PeriphDataAlignment = DMA_PDATAALIGN_WORD;

  13. hdma_sdio.Init.MemDataAlignment = DMA_MDATAALIGN_WORD;

  14. hdma_sdio.Init.Mode = DMA_NORMAL;

  15. hdma_sdio.Init.Priority = DMA_PRIORITY_LOW;

  16. if (HAL_DMA_Init(&hdma_sdio) != HAL_OK)

  17. {

  18. Error_Handler();

  19. }

  20. __HAL_LINKDMA(hsd,hdmarx,hdma_sdio);

  21. MX_SDIO_SD_Init();

  22. Return_Status = HAL_SD_ReadBlocks_DMA(hsd, pData, BlockAdd, NumberOfBlocks);

  23. return Return_Status;

  24. }

  25. //非阻塞式DMA写

  26. HAL_StatusTypeDef SDIO_WriteBlocks_DMA(SD_HandleTypeDef *hsd, uint8_t *pData, uint32_t BlockAdd, uint32_t NumberOfBlocks)

  27. {

  28. HAL_StatusTypeDef Return_Status;

  29. HAL_SD_CardStateTypeDef SD_Card_Status;

  30. do{ SD_Card_Status = HAL_SD_GetCardState(hsd);}while(SD_Card_Status != HAL_SD_CARD_TRANSFER );

  31. HAL_DMA_DeInit(&hdma_sdio);

  32. hdma_sdio.Instance = DMA2_Channel4;

  33. hdma_sdio.Init.Direction = DMA_PERIPH_TO_MEMORY;

  34. hdma_sdio.Init.PeriphInc = DMA_PINC_DISABLE;

  35. hdma_sdio.Init.MemInc = DMA_MINC_ENABLE;

  36. hdma_sdio.Init.PeriphDataAlignment = DMA_PDATAALIGN_WORD;

  37. hdma_sdio.Init.MemDataAlignment = DMA_MDATAALIGN_WORD;

  38. hdma_sdio.Init.Mode = DMA_NORMAL;

  39. hdma_sdio.Init.Priority = DMA_PRIORITY_LOW;

  40. if (HAL_DMA_Init(&hdma_sdio) != HAL_OK)

  41. {

  42. Error_Handler();

  43. }

  44. __HAL_LINKDMA(hsd,hdmarx,hdma_sdio);

  45. MX_SDIO_SD_Init();

  46. Return_Status = HAL_SD_WriteBlocks_DMA(hsd,pData, BlockAdd, NumberOfBlocks);

  47. return Return_Status;

  48. }

主函数里代码如下:

  1. /* USER CODE BEGIN 2 */

  2. HAL_StatusTypeDef Return_Status;

  3. memset(Buffer_Tx_DMA, 0x55, sizeof(Buffer_Tx_DMA));

  4. Return_Status = SDIO_WriteBlocks_DMA(&hsd,Buffer_Tx_DMA, 0, 1);

  5. printf("write status :%d\r\n",Return_Status);

  6. Return_Status = SDIO_ReadBlocks_DMA(&hsd,Buffer_Rx_DMA, 0, 2);

  7. printf("read status :%d\r\n",Return_Status);

  8. //查看读取到的数据

  9. for(i = 0; i < sizeof(Buffer_Rx_DMA)/sizeof(Buffer_Rx_DMA[0]); i++)

  10. {

  11. printf("0x%02x:%02x ", i, Buffer_Rx_DMA[i]);

  12. }

  13. /* USER CODE END 2 */

** 六、结果分析**

1、输入的函数参数是扇区编号,而不是实际偏移地址。

SD卡的扇区大小是512byte ,所以每个扇区的偏移地址是0x200

这里的参数要传入SD卡扇区的编号,而不是地址,进入原函数我们可以看到官方内部已经帮我们做好了地址偏移。

2、测试结果

相关推荐
智商偏低3 小时前
单片机之helloworld
单片机·嵌入式硬件
青牛科技-Allen5 小时前
GC3910S:一款高性能双通道直流电机驱动芯片
stm32·单片机·嵌入式硬件·机器人·医疗器械·水泵、
白鱼不小白7 小时前
stm32 USART串口协议与外设(程序)——江协教程踩坑经验分享
stm32·单片机·嵌入式硬件
S,D7 小时前
MCU引脚的漏电流、灌电流、拉电流区别是什么
驱动开发·stm32·单片机·嵌入式硬件·mcu·物联网·硬件工程
芯岭技术10 小时前
PY32F002A单片机 低成本控制器解决方案,提供多种封装
单片机·嵌入式硬件
youmdt11 小时前
Arduino IDE ESP8266连接0.96寸SSD1306 IIC单色屏显示北京时间
单片机·嵌入式硬件
嘿·嘘11 小时前
第七章 STM32内部FLASH读写
stm32·单片机·嵌入式硬件
Meraki.Zhang11 小时前
【STM32实践篇】:I2C驱动编写
stm32·单片机·iic·驱动·i2c
几个几个n13 小时前
STM32-第二节-GPIO输入(按键,传感器)
单片机·嵌入式硬件
Despacito0o17 小时前
ESP32-s3摄像头驱动开发实战:从零搭建实时图像显示系统
人工智能·驱动开发·嵌入式硬件·音视频·嵌入式实时数据库