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

2024-01-18 17:26:43 雷龙发展

目录

  完整项目源代码下载地址: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,不够用。

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

 SD NAND可兼容主流STM32单片机芯片型号

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

SD NAND FLASH与普通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引脚图

SD NAND,贴片式TF卡,贴片式SD卡,北京君正,nor flash,存储,芯片,主控,小尺寸emmc,大容量SLC Nand,语音芯片,语音识别,语音控制,语音模块,离线语音

  2、芯片外观及封装:

SD NAND,贴片式TF卡,贴片式SD卡,北京君正,nor flash,存储,芯片,主控,小尺寸emmc,大容量SLC Nand,语音芯片,语音识别,语音控制,语音模块,离线语音SD NAND,贴片式TF卡,贴片式SD卡,北京君正,nor flash,存储,芯片,主控,小尺寸emmc,大容量SLC Nand,语音芯片,语音识别,语音控制,语音模块,离线语音

  3、硬件电路原理图

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

SD NAND硬件电路原理图

  4、测试用转接板实物图

SD NAND测试用转接板实物图

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

SD NAND搭载stm32F1精英版实验

  运行中的实物图:

SD NAND测试应用中

  四、 CubeMX配置STM32具体步骤

  1、时钟和系统配置

时钟和系统配置图

时钟和系统配置图

  2、配置SDIO

SD Nand配置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

    SD NAND配置SDIO的参考系数图

  ### 注意:刚开始我做的过程中,参考了下面的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 传输模式。

    SD NAND配置DMA (可选)

  4、设置串口

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

SD NAND设置串口信息

  五、代码编写

  1、公共代码

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

  1. #include <stdio.h>

  2. #include <string.h>

  3.  

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

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

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

  7.  

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

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

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

  11. uint32_t i;

  12.  

  13. extern DMA_HandleTypeDef hdma_sdio;

  14.  

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

  16. {

  17.  /*

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

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

  20. */

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

  22.     return 1;

  23. }

  24.  

  25.  

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

  27. void show_sdcard_info(void)

  28. {

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

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

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

  32. {      

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

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

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

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

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

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

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

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

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

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

  43. HAL_SD_CardCIDTypeDef sdcard_cid;

  44. HAL_SD_GetCardCID(&hsd,&sdcard_cid);

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

  46. }

  47. else

  48. {

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

  50. }

  51. }

  52.  

  53. /* 擦除SD卡块 */

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

  55. {

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

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

  58.   {

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

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

  61.     {

  62.     }

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

  64.   }

  65.   else

  66.   {

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

  68.   }

  69. }



  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. /* 读取操作之后的数据 */

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

  21. {

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

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

  24.   {

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

  26.     {

  27.     }

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

  29.   }

  30.   else

  31.   {

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

  33.   }

  34. }


  主函数里代码如下:

  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.  

  8. HAL_DMA_DeInit(&hdma_sdio);

  9. hdma_sdio.Instance = DMA2_Channel4;

  10. hdma_sdio.Init.Direction = DMA_PERIPH_TO_MEMORY;

  11. hdma_sdio.Init.PeriphInc = DMA_PINC_DISABLE;

  12. hdma_sdio.Init.MemInc = DMA_MINC_ENABLE;

  13. hdma_sdio.Init.PeriphDataAlignment = DMA_PDATAALIGN_WORD;

  14. hdma_sdio.Init.MemDataAlignment = DMA_MDATAALIGN_WORD;

  15. hdma_sdio.Init.Mode = DMA_NORMAL;

  16. hdma_sdio.Init.Priority = DMA_PRIORITY_LOW;

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

  18. {

  19. Error_Handler();

  20. }

  21. __HAL_LINKDMA(hsd,hdmarx,hdma_sdio);

  22.  

  23. MX_SDIO_SD_Init();

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

  25. return Return_Status;

  26. }

  27.  

  28. //非阻塞式DMA写

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

  30. {

  31. HAL_StatusTypeDef Return_Status;

  32. HAL_SD_CardStateTypeDef SD_Card_Status;

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

  34.  

  35. HAL_DMA_DeInit(&hdma_sdio);

  36. hdma_sdio.Instance = DMA2_Channel4;

  37. hdma_sdio.Init.Direction = DMA_PERIPH_TO_MEMORY;

  38. hdma_sdio.Init.PeriphInc = DMA_PINC_DISABLE;

  39. hdma_sdio.Init.MemInc = DMA_MINC_ENABLE;

  40. hdma_sdio.Init.PeriphDataAlignment = DMA_PDATAALIGN_WORD;

  41. hdma_sdio.Init.MemDataAlignment = DMA_MDATAALIGN_WORD;

  42. hdma_sdio.Init.Mode = DMA_NORMAL;

  43. hdma_sdio.Init.Priority = DMA_PRIORITY_LOW;

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

  45. {

  46. Error_Handler();

  47. }

  48. __HAL_LINKDMA(hsd,hdmarx,hdma_sdio);

  49.  

  50. MX_SDIO_SD_Init();

  51.  

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

  53. return Return_Status;

  54. }


  主函数里代码如下:

  1. /* USER CODE BEGIN 2 */

  2.  

  3. HAL_StatusTypeDef Return_Status;

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

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

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

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

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

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

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

  11. {

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

  13. }

  14.  

  15. /* USER CODE END 2 */


  六、结果分析

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

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

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

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

  2、测试结果

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

服务电话&邮箱

Tel:13691982107(优先)17727831243

QQ:1302648372 | line@longsto.com

对外联系邮箱:info@longsto.com

投诉:ceo@longsto.com |13923450403

地址:深圳市龙华区民治街道展滔科技大厦B座1907室

首页
产品
资讯
联系