stm32 CubeMx 实现SD卡/sd nand FATFS读写测试

发布时间:2023-09-22  

1. 前言

SD卡/SD nand是嵌入式开发中常为使用的大容量存储设备,SD nand虽然当前价格比SD卡高,但胜在价格、封装以及稳定性上有优势,实际操作和SD卡没什么区别。


关于 SD卡/SDnand 的驱动,有了CubeMx之后其实基本上都自动生成了对应的驱动了,基本上把驱动配置一下之后,自己写一些应用就可以完成基本的读写了,同时关于FATFS文件系统,也可以直接采用CubeMx配置,也不用自己移植,因此使用STM32开发这些还是比较爽的!不过使用过程中也有一些坑,自动生成的驱动有时候也还是有一些bug,因此还是需要大家对对应驱动有一定的了解。


本文将主要分享关于使用 CubeMx 配置 stm32 的工程,通过SDIO总线完成 SD卡/SD nand 的读写,并配置FATFS,采用文件操作实现对 SD卡/SD nand 的读写操作;此外还将分享博主在调试过程中遇到的一些问题,比如CubeMx自动生成的驱动存在的bug等,以及分享关于驱动部分的代码分析!


2. 环境介绍

2.1 软硬件说明

硬件环境:

主控:stm32f103vet6

SD nand: CSNP1GCR01-AOW【样品CS创世SD NAND由深圳市雷龙发展有限公司免费提供的,感兴趣的可到雷龙官网申请】

软件环境:

CubeMx版本:Version 6.6.1

注意:当前最新版本 V6.8.0,生成的工程配置存在bug,具体细节在后文描述

2.2 外设原理图

SD卡槽原理图部分如下:

image.php?url=YD_cnt_77_01Mx085OLIn5





image.php?url=YD_cnt_77_01Mx08XWjQKw





3. 工程搭建

3.1 CubeMx 配置

  1. 1.选择芯片,ACCESS TO MCU SELECTOR


image.php?url=YD_cnt_77_01Mx08XJhnc8






  1. 2.搜索对应的芯片型号,在对应列表下方选择对应芯片



  2. 3.配置时钟方案,采用外部高速时钟,无源晶振方案


image.php?url=YD_cnt_77_01Mx08VaagmX






  1. 4.配置调试器,由于我采用SWD调试接口,因此选择 Serial Wrie 串行总线


image.php?url=YD_cnt_77_01Mx08UssGAQ






  1. 5.配置SDIO外设,由于我们所使用的SD nand支持4线传输,因此此处选择4线宽度;如果你所使用的SD nand或SD卡不支持4线传输,此处应选择1线宽度;支持4线宽度的SD卡肯定可以使用1线宽度,因此如果你实在不知道你的SD卡支持几线宽度,你可以直接选择1线宽度!4线和1线宽度的差别也就在于速度上相差了4倍!

  2. (注意这里暂时不需要对SDIO的参数进行配置,后面我们再回来配置!)


image.php?url=YD_cnt_77_01Mx08Trm7ST






  1. 6.完成时钟树配置:

  • 配置外部晶振频率

  • 调整时钟选择,SYSCLK由PLL产生,PLL由外部时钟倍频产生

  • 配置SDIO外设时钟,注意此处SDIO外设比较特殊,有两个时钟!具体原因见后文!

image.php?url=YD_cnt_77_01Mx08T1NKIR





  1. 7.

  2. 修改SDIO参数配置,主要是修改SDIOCLK的分频

  • 由于我们上述配置的SDIO时钟为 72M,而SD卡支持的通讯速率在0MHz至25MHz之间,因此我们需要分频,配置 SDIO Clock divider bypass 为 Disable

  • 此处设置 SDIOCLK clock divide factor CLKDIV分频系数为 8,这个受限于具体的SD卡支持的最大速度。如果设置值较小,可能由于SDIO_CK速度过高,SD卡/SDnand不支持,导致通讯失败,因此建议先将此值设大点(或查看SD卡/SDnand手册,或先设一个较大值,软件完成SD信息读取后再配置)

  • 注意这个配置的时钟是用于SD读写通讯时候的时钟,而不是SD卡信息识别过程时的速度!

image.php?url=YD_cnt_77_01Mx08Ri0q0N






image.php?url=YD_cnt_77_01Mx08RnA1Z4


编辑




8.勾选 FATFS 配置,选择 SD Card

image.php?url=YD_cnt_77_01Mx08R9Am7n


编辑



9.配置SD卡检测引脚,有以下两种方案

  • 方案一:选择一个输入IO,作为触发引脚

image.php?url=YD_cnt_77_01Mx08PfCgh3


编辑



  • 方案二:不配置输入IO,最后生成代码的时候无视警报即可,生成的代码会自动取消输入检测判断

image.php?url=YD_cnt_77_01Mx08P3dHpR


编辑



10.配置调试串口,用来打印信息,此处我选择USART1,大家可根据自己硬件环境自行选择

image.php?url=YD_cnt_77_01Mx08OFrG9X


编辑



11.配置工程信息

  • 配置工程名

  • 选择工程路径

  • 配置应用程序结构,我习惯选择 Basic 结构

  • 选择IDE工具及版本

  • 修改堆栈大小,适当改大一点,怕不够用

image.php?url=YD_cnt_77_01Mx08NTJ89o


编辑



12.勾选将外设初始化放置在独立的.c和.h文件,这样每个外设的初始化是独立的,方便阅读移植!

image.php?url=YD_cnt_77_01Mx08LoQemv


编辑



13.生成代码

image.php?url=YD_cnt_77_01Mx08LBLxXn


编辑



3.2 SDIO时钟配置说明

在上述CubeMx时钟配置中,外设的时钟一般都是只有一路过去,但是在此处我们会发现SDIO的时钟在时钟树中有两个!没弄清楚还会以为这是CubeMx出现bug了!

image.php?url=YD_cnt_77_01Mx08KVSMts


编辑



其实这是SDIO外设的特殊点,我们查看数据手册上的时钟树,便可以发现,实际上是真的有两路时钟,分别是:1)SDIOCLK;2)至SDIO的AHB接口;

image.php?url=YD_cnt_77_01Mx08JPVVjB




之后,我们看到数据手册的SDIO章节,我们可以看到SDIO外设分为:1)AHB总线接口 和 2)SDIO适配器两大块,且使用不同的时钟,这也就是我们在时钟树配置中可以看到有两路时钟配置的原因了!

从下图我们可以知道,SDIO外设不同于其他外设,其外设模块部分与中断、DMA是分开的,并采用不同的时钟!

image.php?url=YD_cnt_77_01Mx08IWsorx





关于AHB总线接口及SDIO适配器更多细节,大家可自行阅读参考手册部分章节内容,此处不做赘述。

此外,关于时钟配置有一个特别需要注意的,也就是SDIO_CK时钟信号。SDIO_CK时钟,也就是我们SDIO外设与SD卡/SD nand通讯的CLK时钟,从上图我们可知,SDIO_CK时钟来自SDIO适配器,也就是来自SDIOCLK,对应CubeMX时钟配置中的:

image.php?url=YD_cnt_77_01Mx08HKsQvu





image.php?url=YD_cnt_77_01Mx08H2hi40





3.2 读写测试

3.2.1 添加读写测试代码

  1. 1.使能 MicroLIB 微库,否则调用 printf 函数会卡住

image.php?url=YD_cnt_77_01Mx08G9BZVO





2.修改编码规则为 UTF-8,这是由于我们CubeMx中配置的FATFS的编码格式为 UTF-8导致,如果不修改为 UTF-8 则部分中文会乱码! //TODO:确认是由FATFS配置导致

image.php?url=YD_cnt_77_01Mx08FO8oOv





image.php?url=YD_cnt_77_01Mx08EYFRKS





3.添加 printf 重映射 (位置可根据自行决定)

  1. #include

  2. int fputc(int ch, FILE *f)

  3. {

  4. HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1,0xffff);

  5. return (ch);

  6. }

4.添加 sdcard 信息打印函数,查看卡片信息

  1. HAL_SD_CardInfoTypeDef SDCardInfo;

  2. void printf_sdcard_info(void)

  3. {

  4. uint64_t CardCap; //SD卡容量

  5. HAL_SD_CardCIDTypeDef SDCard_CID;


  6. HAL_SD_GetCardCID(&hsd,&SDCard_CID); //获取CID

  7. HAL_SD_GetCardInfo(&hsd,&SDCardInfo); //获取SD卡信息

  8. CardCap=(uint64_t)(SDCardInfo.LogBlockNbr)*(uint64_t)(SDCardInfo.LogBlockSize); //计算SD卡容量

  9. switch(SDCardInfo.CardType)

  10. {

  11. case CARD_SDSC:

  12. {

  13. if(SDCardInfo.CardVersion == CARD_V1_X)

  14. printf("Card Type:SDSC V1rn");

  15. else if(SDCardInfo.CardVersion == CARD_V2_X)

  16. printf("Card Type:SDSC V2rn");

  17. }

  18. break;

  19. case CARD_SDHC_SDXC:printf("Card Type:SDHCrn");break;

  20. default:break;

  21. }


  22. printf("Card ManufacturerID: %d rn",SDCard_CID.ManufacturerID); //制造商ID

  23. printf("CardVersion: %d rn",(uint32_t)(SDCardInfo.CardVersion)); //卡版本号

  24. printf("Class: %d rn",(uint32_t)(SDCardInfo.Class)); //SD卡类别

  25. printf("Card RCA(RelCardAdd):%d rn",SDCardInfo.RelCardAdd); //卡相对地址

  26. printf("Card BlockNbr: %d rn",SDCardInfo.BlockNbr); //块数量

  27. printf("Card BlockSize: %d rn",SDCardInfo.BlockSize); //块大小

  28. printf("LogBlockNbr: %d rn",(uint32_t)(SDCardInfo.LogBlockNbr)); //逻辑块数量

  29. printf("LogBlockSize: %d rn",(uint32_t)(SDCardInfo.LogBlockSize)); //逻辑块大小

  30. printf("Card Capacity: %d MBrn",(uint32_t)(CardCap>>20)); //卡容量

  31. }

5.添加初始化及读写测试代码,注意此处我们没有直接使用FATFS的读写接口,我们先测试生成的SD驱动函数接口

  1. int main(void)

  2. {

  3. /* USER CODE BEGIN 1 */

  4. BYTE send_buf[512];

  5. DRESULT ret;

  6. /* USER CODE END 1 */


  7. /* ...省略若干自动生成代码... */


  8. /* USER CODE BEGIN 2 */


  9. SD_Driver.disk_initialize(0);

  10. printf_sdcard_info();


  11. printf("rnrn********** 英文读写测试 **********rn");


  12. ret = SD_Driver.disk_write(0,

  13. (BYTE *)"Life is too short to spend time with people who suck the happiness out of you.

  14. If someone wants you in their life, they’ll make room for you. You shouldn’t have to fight for a spot. Never, ever

  15. insist yourself to someone who continuously overlooks your worth. And remember, it’s not the people that stand by

  16. your side when you’re at your best, but the ones who stand beside you when you’re at your worst that are your true

  17. friends",20,2);

  18. printf("sd write result:%drn", ret);

  19. ret = SD_Driver.disk_read(0, send_buf, 20, 2);

  20. printf("sd reak result:%drn", ret);

  21. printf("sd read content:rn%srn", send_buf);


  22. printf("rnrn********** 中文读写测试 **********rn");


  23. ret = SD_Driver.disk_write(0,

  24. (BYTE *)"开发者社区的明天需要大家一同开源共创,期待下一次你的分享,让我们一同携手共进,推动人类科技的发展!!!rn

  25. 创作不易,转载请注明出处~rn

  26. 更多文章敬请关注:爱出名的狗腿子rn", 22, 2);

  27. printf("sd write result:%drn", ret);

  28. ret = SD_Driver.disk_read(0, send_buf, 22, 2);

  29. printf("sd reak result:%drn", ret);

  30. printf("sd read content:rn%srn", send_buf);

  31. /* USER CODE END 2 */

  32. while (1)

  33. {

  34. /* USER CODE END WHILE */


  35. /* USER CODE BEGIN 3 */

  36. }

  37. /* USER CODE END 3 */

  38. }

6.修改烧录器配置,配置为烧录后自动运行

image.php?url=YD_cnt_77_01Mx08M2ko1H





7.下载测试,这里由于我们采用UTF-8编码,所以使用的串口上位机也需要支持UTF-8解析,我们这里使用Mobaxterm上位机,测试结果如下:

image.php?url=YD_cnt_77_01Mx08DUiQpY




8.main.c 文件全部代码如下,供大家参考:

  1. /* USER CODE BEGIN Header */

  2. /**

  3. ******************************************************************************

  4. * @file : main.c

  5. * @brief : Main program body

  6. ******************************************************************************

  7. * @attention

  8. *

  9. * Copyright (c) 2023 STMicroelectronics.

  10. * All rights reserved.

  11. *

  12. * This software is licensed under terms that can be found in the LICENSE file

  13. * in the root directory of this software component.

  14. * If no LICENSE file comes with this software, it is provided AS-IS.

  15. *

  16. ******************************************************************************

  17. */

  18. /* USER CODE END Header */

  19. /* Includes ------------------------------------------------------------------*/

  20. #include "main.h"

  21. #include "fatfs.h"

  22. #include "sdio.h"

  23. #include "usart.h"

  24. #include "gpio.h"


  25. /* Private includes ----------------------------------------------------------*/

  26. /* USER CODE BEGIN Includes */


  27. #include

  28. /* USER CODE END Includes */


  29. /* Private typedef -----------------------------------------------------------*/

  30. /* USER CODE BEGIN PTD */


  31. /* USER CODE END PTD */


  32. /* Private define ------------------------------------------------------------*/

  33. /* USER CODE BEGIN PD */

  34. /* USER CODE END PD */


  35. /* Private macro -------------------------------------------------------------*/

  36. /* USER CODE BEGIN PM */


  37. /* USER CODE END PM */


  38. /* Private variables ---------------------------------------------------------*/


  39. /* USER CODE BEGIN PV */


  40. /* USER CODE END PV */


  41. /* Private function prototypes -----------------------------------------------*/

  42. void SystemClock_Config(void);

  43. /* USER CODE BEGIN PFP */


  44. /* USER CODE END PFP */


  45. /* Private user code ---------------------------------------------------------*/

  46. /* USER CODE BEGIN 0 */


  47. HAL_SD_CardInfoTypeDef SDCardInfo;

  48. void printf_sdcard_info(void)

  49. {

  50. uint64_t CardCap; //SD卡容量

  51. HAL_SD_CardCIDTypeDef SDCard_CID;


  52. HAL_SD_GetCardCID(&hsd,&SDCard_CID); //获取CID

  53. HAL_SD_GetCardInfo(&hsd,&SDCardInfo); //获取SD卡信息

  54. CardCap=(uint64_t)(SDCardInfo.LogBlockNbr)*(uint64_t)(SDCardInfo.LogBlockSize); //计算SD卡容量

  55. switch(SDCardInfo.CardType)

  56. {

  57. case CARD_SDSC:

  58. {

  59. if(SDCardInfo.CardVersion == CARD_V1_X)

  60. printf("Card Type:SDSC V1rn");

  61. else if(SDCardInfo.CardVersion == CARD_V2_X)

  62. printf("Card Type:SDSC V2rn");

  63. }

  64. break;

  65. case CARD_SDHC_SDXC:printf("Card Type:SDHCrn");break;

  66. default:break;

  67. }


  68. printf("Card ManufacturerID: %d rn",SDCard_CID.ManufacturerID); //制造商ID

  69. printf("CardVersion: %d rn",(uint32_t)(SDCardInfo.CardVersion)); //卡版本号

  70. printf("Class: %d rn",(uint32_t)(SDCardInfo.Class)); //SD卡类别

  71. printf("Card RCA(RelCardAdd):%d rn",SDCardInfo.RelCardAdd); //卡相对地址

  72. printf("Card BlockNbr: %d rn",SDCardInfo.BlockNbr); //块数量

  73. printf("Card BlockSize: %d rn",SDCardInfo.BlockSize); //块大小

  74. printf("LogBlockNbr: %d rn",(uint32_t)(SDCardInfo.LogBlockNbr)); //逻辑块数量

  75. printf("LogBlockSize: %d rn",(uint32_t)(SDCardInfo.LogBlockSize)); //逻辑块大小

  76. printf("Card Capacity: %d MBrn",(uint32_t)(CardCap>>20)); //卡容量

  77. }


  78. int fputc(int ch, FILE *f)

  79. {

  80. HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1,0xffff);

  81. return (ch);

  82. }

  83. /* USER CODE END 0 */


  84. /**

  85. * @brief The application entry point.

  86. * @retval int

  87. */

  88. int main(void)

  89. {

  90. /* USER CODE BEGIN 1 */

  91. BYTE send_buf[512];

  92. DRESULT ret;

  93. /* USER CODE END 1 */


  94. /* MCU Configuration--------------------------------------------------------*/


  95. /* Reset of all peripherals, Initializes the Flash interface and the Systick. */

  96. HAL_Init();


  97. /* USER CODE BEGIN Init */


  98. /* USER CODE END Init */


  99. /* Configure the system clock */

  100. SystemClock_Config();


  101. /* USER CODE BEGIN SysInit */


  102. /* USER CODE END SysInit */


  103. /* Initialize all configured peripherals */

  104. MX_GPIO_Init();

  105. MX_SDIO_SD_Init();

  106. MX_USART1_UART_Init();

  107. MX_FATFS_Init();

  108. /* USER CODE BEGIN 2 */


  109. SD_Driver.disk_initialize(0);

  110. printf_sdcard_info();


  111. printf("rnrn********** 英文读写测试 **********rn");


  112. ret = SD_Driver.disk_write(0,

  113. (BYTE *)"Life is too short to spend time with people who suck the happiness out of you.

  114. If someone wants you in their life, they’ll make room for you. You shouldn’t have to fight for a spot. Never, ever

  115. insist yourself to someone who continuously overlooks your worth. And remember, it’s not the people that stand by

  116. your side when you’re at your best, but the ones who stand beside you when you’re at your worst that are your true

  117. friends",20,2);

  118. printf("sd write result:%drn", ret);

  119. ret = SD_Driver.disk_read(0, send_buf, 20, 2);

  120. printf("sd reak result:%drn", ret);

  121. printf("sd read content:rn%srn", send_buf);


  122. printf("rnrn********** 中文读写测试 **********rn");

文章来源于:电子工程世界    原文链接
本站所有转载文章系出于传递更多信息之目的,且明确注明来源,不希望被转载的媒体或个人可与我们联系,我们将立即进行删除处理。

我们与500+贴片厂合作,完美满足客户的定制需求。为品牌提供定制化的推广方案、专属产品特色页,多渠道推广,SEM/SEO精准营销以及与公众号的联合推广...详细>>

利用葫芦芯平台的卓越技术服务和新产品推广能力,原厂代理能轻松打入消费物联网(IOT)、信息与通信(ICT)、汽车及新能源汽车、工业自动化及工业物联网、装备及功率电子...详细>>

充分利用其强大的电子元器件采购流量,创新性地为这些物料提供了一个全新的窗口。我们的高效数字营销技术,不仅可以助你轻松识别与连接到需求方,更能够极大地提高“闲置物料”的处理能力,通过葫芦芯平台...详细>>

我们的目标很明确:构建一个全方位的半导体产业生态系统。成为一家全球领先的半导体互联网生态公司。目前,我们已成功打造了智能汽车、智能家居、大健康医疗、机器人和材料等五大生态领域。更为重要的是...详细>>

我们深知加工与定制类服务商的价值和重要性,因此,我们倾力为您提供最顶尖的营销资源。在我们的平台上,您可以直接接触到100万的研发工程师和采购工程师,以及10万的活跃客户群体...详细>>

凭借我们强大的专业流量和尖端的互联网数字营销技术,我们承诺为原厂提供免费的产品资料推广服务。无论是最新的资讯、技术动态还是创新产品,都可以通过我们的平台迅速传达给目标客户...详细>>

我们不止于将线索转化为潜在客户。葫芦芯平台致力于形成业务闭环,从引流、宣传到最终销售,全程跟进,确保每一个potential lead都得到妥善处理,从而大幅提高转化率。不仅如此...详细>>