芯片芯语 发表于 2023-6-2 17:59

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

文章目录stm32 CubeMx 实现SD卡/SD nand FATFS读写测试1. 前言2. 环境介绍2.1 软硬件说明2.2 外设原理图3. 工程搭建3.1 CubeMx 配置3.2 SDIO时钟配置说明3.2 读写测试3.2.1 添加读写测试代码3.3 FATFS文件操作3.3.1 修改读写测试代码3.4 配置问题记录3.4.1 CubeMx生成代码bug3.4.2 SD插入检测引脚配置4. 结束语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 软硬件说明硬件环境:主控:stm32f103vet6SD nand: CSNP1GCR01-AOW【样品CS创世SD NAND由深圳市雷龙发展有限公司免费提供的,感兴趣的可到雷龙官网申请】软件环境:CubeMx版本:Version 6.6.1注意:当前最新版本 V6.8.0,生成的工程配置存在bug,具体细节在后文描述2.2 外设原理图SD卡槽原理图部分如下:https://i1.go2yd.com/image.php?url=YD_cnt_77_01Mx085OLIn5
编辑


https://i1.go2yd.com/image.php?url=YD_cnt_77_01Mx08XWjQKw
编辑


3. 工程搭建3.1 CubeMx 配置
[*]1.选择芯片,ACCESS TO MCU SELECTOR
[*]
https://i1.go2yd.com/image.php?url=YD_cnt_77_01Mx08XJhnc8
编辑



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

[*]
[*]3.配置时钟方案,采用外部高速时钟,无源晶振方案
[*]
https://i1.go2yd.com/image.php?url=YD_cnt_77_01Mx08VaagmX
编辑



[*]
[*]4.配置调试器,由于我采用SWD调试接口,因此选择 Serial Wrie 串行总线
[*]
https://i1.go2yd.com/image.php?url=YD_cnt_77_01Mx08UssGAQ
编辑



[*]
[*]5.配置SDIO外设,由于我们所使用的SD nand支持4线传输,因此此处选择4线宽度;如果你所使用的SD nand或SD卡不支持4线传输,此处应选择1线宽度;支持4线宽度的SD卡肯定可以使用1线宽度,因此如果你实在不知道你的SD卡支持几线宽度,你可以直接选择1线宽度!4线和1线宽度的差别也就在于速度上相差了4倍!
[*](注意这里暂时不需要对SDIO的参数进行配置,后面我们再回来配置!)
[*]
https://i1.go2yd.com/image.php?url=YD_cnt_77_01Mx08Trm7ST
编辑



[*]
[*]6.完成时钟树配置:

[*]配置外部晶振频率
[*]调整时钟选择,SYSCLK由PLL产生,PLL由外部时钟倍频产生
[*]配置SDIO外设时钟,注意此处SDIO外设比较特殊,有两个时钟!具体原因见后文!
https://i1.go2yd.com/image.php?url=YD_cnt_77_01Mx08T1NKIR
编辑



[*]7.
[*]修改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卡信息识别过程时的速度!
https://i1.go2yd.com/image.php?url=YD_cnt_77_01Mx08Ri0q0N
编辑



[*]
https://i1.go2yd.com/image.php?url=YD_cnt_77_01Mx08RnA1Z4
编辑



[*]
8.勾选 FATFS 配置,选择 SD Cardhttps://i1.go2yd.com/image.php?url=YD_cnt_77_01Mx08R9Am7n
编辑


9.配置SD卡检测引脚,有以下两种方案
[*]方案一:选择一个输入IO,作为触发引脚
https://i1.go2yd.com/image.php?url=YD_cnt_77_01Mx08PfCgh3
编辑



[*]方案二:不配置输入IO,最后生成代码的时候无视警报即可,生成的代码会自动取消输入检测判断
https://i1.go2yd.com/image.php?url=YD_cnt_77_01Mx08P3dHpR
编辑


10.配置调试串口,用来打印信息,此处我选择USART1,大家可根据自己硬件环境自行选择https://i1.go2yd.com/image.php?url=YD_cnt_77_01Mx08OFrG9X
编辑


11.配置工程信息
[*]配置工程名
[*]选择工程路径
[*]配置应用程序结构,我习惯选择 Basic 结构
[*]选择IDE工具及版本
[*]修改堆栈大小,适当改大一点,怕不够用
https://i1.go2yd.com/image.php?url=YD_cnt_77_01Mx08NTJ89o
编辑


12.勾选将外设初始化放置在独立的.c和.h文件,这样每个外设的初始化是独立的,方便阅读移植!https://i1.go2yd.com/image.php?url=YD_cnt_77_01Mx08LoQemv
编辑


13.生成代码https://i1.go2yd.com/image.php?url=YD_cnt_77_01Mx08LBLxXn
编辑


3.2 SDIO时钟配置说明在上述CubeMx时钟配置中,外设的时钟一般都是只有一路过去,但是在此处我们会发现SDIO的时钟在时钟树中有两个!没弄清楚还会以为这是CubeMx出现bug了!https://i1.go2yd.com/image.php?url=YD_cnt_77_01Mx08KVSMts
编辑


其实这是SDIO外设的特殊点,我们查看数据手册上的时钟树,便可以发现,实际上是真的有两路时钟,分别是:1)SDIOCLK;2)至SDIO的AHB接口;https://i1.go2yd.com/image.php?url=YD_cnt_77_01Mx08JPVVjB
编辑


之后,我们看到数据手册的SDIO章节,我们可以看到SDIO外设分为:1)AHB总线接口 和 2)SDIO适配器两大块,且使用不同的时钟,这也就是我们在时钟树配置中可以看到有两路时钟配置的原因了!从下图我们可以知道,SDIO外设不同于其他外设,其外设模块部分与中断、DMA是分开的,并采用不同的时钟!https://i1.go2yd.com/image.php?url=YD_cnt_77_01Mx08IWsorx
编辑


关于AHB总线接口及SDIO适配器更多细节,大家可自行阅读参考手册部分章节内容,此处不做赘述。此外,关于时钟配置有一个特别需要注意的,也就是SDIO_CK时钟信号。SDIO_CK时钟,也就是我们SDIO外设与SD卡/SD nand通讯的CLK时钟,从上图我们可知,SDIO_CK时钟来自SDIO适配器,也就是来自SDIOCLK,对应CubeMX时钟配置中的:https://i1.go2yd.com/image.php?url=YD_cnt_77_01Mx08HKsQvu
编辑


https://i1.go2yd.com/image.php?url=YD_cnt_77_01Mx08H2hi40
编辑


3.2 读写测试3.2.1 添加读写测试代码
[*]1.使能 MicroLIB 微库,否则调用 printf 函数会卡住
https://i1.go2yd.com/image.php?url=YD_cnt_77_01Mx08G9BZVO
编辑


2.修改编码规则为 UTF-8,这是由于我们CubeMx中配置的FATFS的编码格式为 UTF-8导致,如果不修改为 UTF-8 则部分中文会乱码! //TODO:确认是由FATFS配置导致https://i1.go2yd.com/image.php?url=YD_cnt_77_01Mx08FO8oOv
编辑


https://i1.go2yd.com/image.php?url=YD_cnt_77_01Mx08EYFRKS
编辑


3.添加 printf 重映射 (位置可根据自行决定)
[*]#include
[*]int fputc(int ch, FILE *f)
[*]{
[*]HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1,0xffff);
[*]return (ch);
[*]}
4.添加 sdcard 信息打印函数,查看卡片信息
[*]HAL_SD_CardInfoTypeDef SDCardInfo;
[*]void printf_sdcard_info(void)
[*]{
[*]uint64_t CardCap; //SD卡容量
[*]HAL_SD_CardCIDTypeDef SDCard_CID;
[*]
[*]HAL_SD_GetCardCID(&hsd,&SDCard_CID); //获取CID
[*]HAL_SD_GetCardInfo(&hsd,&SDCardInfo); //获取SD卡信息
[*]CardCap=(uint64_t)(SDCardInfo.LogBlockNbr)*(uint64_t)(SDCardInfo.LogBlockSize); //计算SD卡容量
[*]switch(SDCardInfo.CardType)
[*]{
[*]case CARD_SDSC:
[*]{
[*]if(SDCardInfo.CardVersion == CARD_V1_X)
[*]printf("Card Type:SDSC V1\r\n");
[*]else if(SDCardInfo.CardVersion == CARD_V2_X)
[*]printf("Card Type:SDSC V2\r\n");
[*]}
[*]break;
[*]case CARD_SDHC_SDXC:printf("Card Type:SDHC\r\n");break;
[*]default:break;
[*]}
[*]
[*]printf("Card ManufacturerID: %d \r\n",SDCard_CID.ManufacturerID); //制造商ID
[*]printf("CardVersion: %d \r\n",(uint32_t)(SDCardInfo.CardVersion)); //卡版本号
[*]printf("Class: %d \r\n",(uint32_t)(SDCardInfo.Class)); //SD卡类别
[*]printf("Card RCA(RelCardAdd):%d \r\n",SDCardInfo.RelCardAdd); //卡相对地址
[*]printf("Card BlockNbr: %d \r\n",SDCardInfo.BlockNbr); //块数量
[*]printf("Card BlockSize: %d \r\n",SDCardInfo.BlockSize); //块大小
[*]printf("LogBlockNbr: %d \r\n",(uint32_t)(SDCardInfo.LogBlockNbr)); //逻辑块数量
[*]printf("LogBlockSize: %d \r\n",(uint32_t)(SDCardInfo.LogBlockSize)); //逻辑块大小
[*]printf("Card Capacity: %d MB\r\n",(uint32_t)(CardCap>>20)); //卡容量
[*]}
5.添加初始化及读写测试代码,注意此处我们没有直接使用FATFS的读写接口,我们先测试生成的SD驱动函数接口
[*]int main(void)
[*]{
[*]/* USER CODE BEGIN 1 */
[*]BYTE send_buf;
[*]DRESULT ret;
[*]/* USER CODE END 1 */
[*]
[*]/* ...省略若干自动生成代码... */
[*]
[*]/* USER CODE BEGIN 2 */
[*]
[*]SD_Driver.disk_initialize(0);
[*]printf_sdcard_info();
[*]
[*]printf("\r\n\r\n********** 英文读写测试 **********\r\n");
[*]
[*]ret = SD_Driver.disk_write(0,
[*](BYTE *)"Life is too short to spend time with people who suck the happiness out of you. \
[*]If someone wants you in their life, they’ll make room for you. You shouldn’t have to fight for a spot. Never, ever\
[*]insist yourself to someone who continuously overlooks your worth. And remember, it’s not the people that stand by \
[*]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\
[*]friends",20,2);
[*]printf("sd write result:%d\r\n", ret);
[*]ret = SD_Driver.disk_read(0, send_buf, 20, 2);
[*]printf("sd reak result:%d\r\n", ret);
[*]printf("sd read content:\r\n%s\r\n", send_buf);
[*]
[*]printf("\r\n\r\n********** 中文读写测试 **********\r\n");
[*]
[*]ret = SD_Driver.disk_write(0,
[*](BYTE *)"开发者社区的明天需要大家一同开源共创,期待下一次你的分享,让我们一同携手共进,推动人类科技的发展!!!\r\n\
[*]创作不易,转载请注明出处~\r\n\
[*]更多文章敬请关注:爱出名的狗腿子\r\n", 22, 2);
[*]printf("sd write result:%d\r\n", ret);
[*]ret = SD_Driver.disk_read(0, send_buf, 22, 2);
[*]printf("sd reak result:%d\r\n", ret);
[*]printf("sd read content:\r\n%s\r\n", send_buf);
[*]/* USER CODE END 2 */
[*]while (1)
[*]{
[*]/* USER CODE END WHILE */
[*]
[*]/* USER CODE BEGIN 3 */
[*]}
[*]/* USER CODE END 3 */
[*]}
6.修改烧录器配置,配置为烧录后自动运行https://i1.go2yd.com/image.php?url=YD_cnt_77_01Mx08M2ko1H
编辑


7.下载测试,这里由于我们采用UTF-8编码,所以使用的串口上位机也需要支持UTF-8解析,我们这里使用Mobaxterm上位机,测试结果如下:https://i1.go2yd.com/image.php?url=YD_cnt_77_01Mx08DUiQpY
编辑


8.main.c 文件全部代码如下,供大家参考:
[*]/* USER CODE BEGIN Header */
[*]/**
[*]******************************************************************************
[*]* @file : main.c
[*]* @brief : Main program body
[*]******************************************************************************
[*]* @attention
[*]*
[*]* Copyright (c) 2023 STMicroelectronics.
[*]* All rights reserved.
[*]*
[*]* This software is licensed under terms that can be found in the LICENSE file
[*]* in the root directory of this software component.
[*]* If no LICENSE file comes with this software, it is provided AS-IS.
[*]*
[*]******************************************************************************
[*]*/
[*]/* USER CODE END Header */
[*]/* Includes ------------------------------------------------------------------*/
[*]#include "main.h"
[*]#include "fatfs.h"
[*]#include "sdio.h"
[*]#include "usart.h"
[*]#include "gpio.h"
[*]
[*]/* Private includes ----------------------------------------------------------*/
[*]/* USER CODE BEGIN Includes */
[*]
[*]#include
[*]/* USER CODE END Includes */
[*]
[*]/* Private typedef -----------------------------------------------------------*/
[*]/* USER CODE BEGIN PTD */
[*]
[*]/* USER CODE END PTD */
[*]
[*]/* Private define ------------------------------------------------------------*/
[*]/* USER CODE BEGIN PD */
[*]/* USER CODE END PD */
[*]
[*]/* Private macro -------------------------------------------------------------*/
[*]/* USER CODE BEGIN PM */
[*]
[*]/* USER CODE END PM */
[*]
[*]/* Private variables ---------------------------------------------------------*/
[*]
[*]/* USER CODE BEGIN PV */
[*]
[*]/* USER CODE END PV */
[*]
[*]/* Private function prototypes -----------------------------------------------*/
[*]void SystemClock_Config(void);
[*]/* USER CODE BEGIN PFP */
[*]
[*]/* USER CODE END PFP */
[*]
[*]/* Private user code ---------------------------------------------------------*/
[*]/* USER CODE BEGIN 0 */
[*]
[*]HAL_SD_CardInfoTypeDef SDCardInfo;
[*]void printf_sdcard_info(void)
[*]{
[*]uint64_t CardCap; //SD卡容量
[*]HAL_SD_CardCIDTypeDef SDCard_CID;
[*]
[*]HAL_SD_GetCardCID(&hsd,&SDCard_CID); //获取CID
[*]HAL_SD_GetCardInfo(&hsd,&SDCardInfo); //获取SD卡信息
[*]CardCap=(uint64_t)(SDCardInfo.LogBlockNbr)*(uint64_t)(SDCardInfo.LogBlockSize); //计算SD卡容量
[*]switch(SDCardInfo.CardType)
[*]{
[*]case CARD_SDSC:
[*]{
[*]if(SDCardInfo.CardVersion == CARD_V1_X)
[*]printf("Card Type:SDSC V1\r\n");
[*]else if(SDCardInfo.CardVersion == CARD_V2_X)
[*]printf("Card Type:SDSC V2\r\n");
[*]}
[*]break;
[*]case CARD_SDHC_SDXC:printf("Card Type:SDHC\r\n");break;
[*]default:break;
[*]}
[*]
[*]printf("Card ManufacturerID: %d \r\n",SDCard_CID.ManufacturerID); //制造商ID
[*]printf("CardVersion: %d \r\n",(uint32_t)(SDCardInfo.CardVersion)); //卡版本号
[*]printf("Class: %d \r\n",(uint32_t)(SDCardInfo.Class)); //SD卡类别
[*]printf("Card RCA(RelCardAdd):%d \r\n",SDCardInfo.RelCardAdd); //卡相对地址
[*]printf("Card BlockNbr: %d \r\n",SDCardInfo.BlockNbr); //块数量
[*]printf("Card BlockSize: %d \r\n",SDCardInfo.BlockSize); //块大小
[*]printf("LogBlockNbr: %d \r\n",(uint32_t)(SDCardInfo.LogBlockNbr)); //逻辑块数量
[*]printf("LogBlockSize: %d \r\n",(uint32_t)(SDCardInfo.LogBlockSize)); //逻辑块大小
[*]printf("Card Capacity: %d MB\r\n",(uint32_t)(CardCap>>20)); //卡容量
[*]}
[*]
[*]int fputc(int ch, FILE *f)
[*]{
[*]HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1,0xffff);
[*]return (ch);
[*]}
[*]/* USER CODE END 0 */
[*]
[*]/**
[*]* @brief The application entry point.
[*]* @retval int
[*]*/
[*]int main(void)
[*]{
[*]/* USER CODE BEGIN 1 */
[*]BYTE send_buf;
[*]DRESULT ret;
[*]/* USER CODE END 1 */
[*]
[*]/* MCU Configuration--------------------------------------------------------*/
[*]
[*]/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
[*]HAL_Init();
[*]
[*]/* USER CODE BEGIN Init */
[*]
[*]/* USER CODE END Init */
[*]
[*]/* Configure the system clock */
[*]SystemClock_Config();
[*]
[*]/* USER CODE BEGIN SysInit */
[*]
[*]/* USER CODE END SysInit */
[*]
[*]/* Initialize all configured peripherals */
[*]MX_GPIO_Init();
[*]MX_SDIO_SD_Init();
[*]MX_USART1_UART_Init();
[*]MX_FATFS_Init();
[*]/* USER CODE BEGIN 2 */
[*]
[*]SD_Driver.disk_initialize(0);
[*]printf_sdcard_info();
[*]
[*]printf("\r\n\r\n********** 英文读写测试 **********\r\n");
[*]
[*]ret = SD_Driver.disk_write(0,
[*](BYTE *)"Life is too short to spend time with people who suck the happiness out of you. \
[*]If someone wants you in their life, they’ll make room for you. You shouldn’t have to fight for a spot. Never, ever\
[*]insist yourself to someone who continuously overlooks your worth. And remember, it’s not the people that stand by \
[*]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\
[*]friends",20,2);
[*]printf("sd write result:%d\r\n", ret);
[*]ret = SD_Driver.disk_read(0, send_buf, 20, 2);
[*]printf("sd reak result:%d\r\n", ret);
[*]printf("sd read content:\r\n%s\r\n", send_buf);
[*]
[*]printf("\r\n\r\n********** 中文读写测试 **********\r\n");
[*]
[*]ret = SD_Driver.disk_write(0,
[*](BYTE *)"开发者社区的明天需要大家一同开源共创,期待下一次你的分享,让我们一同携手共进,推动人类科技的发展!!!\r\n\
[*]创作不易,转载请注明出处~\r\n\
[*]更多文章敬请关注:爱出名的狗腿子\r\n", 22, 2);
[*]printf("sd write result:%d\r\n", ret);
[*]ret = SD_Driver.disk_read(0, send_buf, 22, 2);
[*]printf("sd reak result:%d\r\n", ret);
[*]printf("sd read content:\r\n%s\r\n", send_buf);
[*]
[*]/* USER CODE END 2 */
[*]
[*]/* Infinite loop */
[*]/* USER CODE BEGIN WHILE */
[*]while (1)
[*]{
[*]/* USER CODE END WHILE */
[*]
[*]/* USER CODE BEGIN 3 */
[*]}
[*]/* USER CODE END 3 */
[*]}
[*]
[*]/**
[*]* @brief System Clock Configuration
[*]* @retval None
[*]*/
[*]void SystemClock_Config(void)
[*]{
[*]RCC_OscInitTypeDef RCC_OscInitStruct = {0};
[*]RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
[*]
[*]/** Initializes the RCC Oscillators according to the specified parameters
[*]* in the RCC_OscInitTypeDef structure.
[*]*/
[*]RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
[*]RCC_OscInitStruct.HSEState = RCC_HSE_ON;
[*]RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1;
[*]RCC_OscInitStruct.HSIState = RCC_HSI_ON;
[*]RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
[*]RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
[*]RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9;
[*]if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
[*]{
[*]Error_Handler();
[*]}
[*]
[*]/** Initializes the CPU, AHB and APB buses clocks
[*]*/
[*]RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
[*]|RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
[*]RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
[*]RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
[*]RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;
[*]RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;
[*]
[*]if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK)
[*]{
[*]Error_Handler();
[*]}
[*]}
[*]
[*]/* USER CODE BEGIN 4 */
[*]
[*]/* USER CODE END 4 */
[*]
[*]/**
[*]* @brief This function is executed in case of error occurrence.
[*]* @retval None
[*]*/
[*]void Error_Handler(void)
[*]{
[*]/* USER CODE BEGIN Error_Handler_Debug */
[*]/* User can add his own implementation to report the HAL error return state */
[*]__disable_irq();
[*]while (1)
[*]{
[*]}
[*]/* USER CODE END Error_Handler_Debug */
[*]}
[*]
[*]#ifdef USE_FULL_ASSERT
[*]/**
[*]* @brief Reports the name of the source file and the source line number
[*]* where the assert_param error has occurred.
[*]* @param file: pointer to the source file name
[*]* @param line: assert_param error line source number
[*]* @retval None
[*]*/
[*]void assert_failed(uint8_t *file, uint32_t line)
[*]{
[*]/* USER CODE BEGIN 6 */
[*]/* User can add his own implementation to report the file name and line number,
[*]ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
[*]/* USER CODE END 6 */
[*]}
[*]#endif /* USE_FULL_ASSERT */
3.3 FATFS文件操作移植了FATFS,当然也就可以只用通用的文件系统操作函数完成文件的读写,通用的文件系统操作API 在 ff.c 文件内,声明在 ff.h 文件内,主要使用的API接口如下:
[*]FRESULT f_open (FIL* fp, const TCHAR* path, BYTE mode); /* Open or create a file */
[*]FRESULT f_close (FIL* fp); /* Close an open file object */
[*]FRESULT f_read (FIL* fp, void* buff, UINT btr, UINT* br); /* Read data from a file */
[*]FRESULT f_write (FIL* fp, const void* buff, UINT btw, UINT* bw); /* Write data to a file */
[*]FRESULT f_forward (FIL* fp, UINT(*func)(const BYTE*,UINT), UINT btf, UINT* bf); /* Forward data to the stream */
[*]FRESULT f_lseek (FIL* fp, DWORD ofs); /* Move file pointer of a file object */
[*]FRESULT f_truncate (FIL* fp); /* Truncate file */
[*]FRESULT f_sync (FIL* fp); /* Flush cached data of a writing file */
[*]FRESULT f_opendir (DIR* dp, const TCHAR* path); /* Open a directory */
[*]FRESULT f_closedir (DIR* dp); /* Close an open directory */
[*]FRESULT f_readdir (DIR* dp, FILINFO* fno); /* Read a directory item */
[*]FRESULT f_findfirst (DIR* dp, FILINFO* fno, const TCHAR* path, const TCHAR* pattern); /* Find first file */
[*]FRESULT f_findnext (DIR* dp, FILINFO* fno); /* Find next file */
[*]FRESULT f_mkdir (const TCHAR* path); /* Create a sub directory */
[*]FRESULT f_unlink (const TCHAR* path); /* Delete an existing file or directory */
[*]FRESULT f_rename (const TCHAR* path_old, const TCHAR* path_new); /* Rename/Move a file or directory */
[*]FRESULT f_stat (const TCHAR* path, FILINFO* fno); /* Get file status */
[*]FRESULT f_chmod (const TCHAR* path, BYTE attr, BYTE mask); /* Change attribute of the file/dir */
[*]FRESULT f_utime (const TCHAR* path, const FILINFO* fno); /* Change times-tamp of the file/dir */
[*]FRESULT f_chdir (const TCHAR* path); /* Change current directory */
[*]FRESULT f_chdrive (const TCHAR* path); /* Change current drive */
[*]FRESULT f_getcwd (TCHAR* buff, UINT len); /* Get current directory */
[*]FRESULT f_getfree (const TCHAR* path, DWORD* nclst, FATFS** fatfs); /* Get number of free clusters on the drive */
[*]FRESULT f_getlabel (const TCHAR* path, TCHAR* label, DWORD* vsn); /* Get volume label */
[*]FRESULT f_setlabel (const TCHAR* label); /* Set volume label */
[*]FRESULT f_mount (FATFS* fs, const TCHAR* path, BYTE opt); /* Mount/Unmount a logical drive */
[*]FRESULT f_mkfs (const TCHAR* path, BYTE sfd, UINT au); /* Create a file system on the volume */
[*]FRESULT f_fdisk (BYTE pdrv, const DWORD szt[], void* work); /* Divide a physical drive into some partitions */
[*]int f_putc (TCHAR c, FIL* fp); /* Put a character to the file */
[*]int f_puts (const TCHAR* str, FIL* cp); /* Put a string to the file */
[*]int f_printf (FIL* fp, const TCHAR* str, ...); /* Put a formatted string to the file */
[*]TCHAR* f_gets (TCHAR* buff, int len, FIL* fp); /* Get a string from the file */
关于API的使用此处不做过多赘述,大家可以自行上官网查阅 FATFS官网,或者网上搜索,或直接看下述示例亦可。3.3.1 修改读写测试代码修改3.2.1章节所使用的读写测试代码,此处我们直接使用FATFS文件系统的读写函数接口,修改主函数如下,注意需要包含fatfs.h头文件!
[*]#include "fatfs.h"
[*]int main()
[*]{
[*]/* USER CODE BEGIN 1 */
[*]#define USERPath "0:/"
[*]
[*]BYTE write_buf[] = "\r\n\r\n\
[*]hello world!\r\n\
[*]开发者社区的明天需要大家一同开源共创,期待下一次你的分享,让我们一同携手共进,推动人类科技的发展!!!\r\n\
[*]创作不易,转载请注明出处~\r\n\
[*]更多文章敬请关注:爱出名的狗腿子\r\n\r\n\
[*]";
[*]BYTE read_buf = {0};
[*]UINT num;
[*]FRESULT ret;
[*]/* USER CODE END 1 */
[*]
[*]/* ... 省略初始化代码... */
[*]
[*]/* USER CODE BEGIN 2 */
[*]
[*]/* 挂载文件系统,挂载的时候会完成对应硬件设备(SD卡/SDnand)初始化 */
[*]ret = f_mount(&SDFatFS, USERPath, 1);
[*]if (ret != FR_OK) {
[*]printf("f_mount error!\r\n");
[*]goto mount_error;
[*]} else if(ret == FR_NO_FILESYSTEM) { /* 检测是否存在文件系统,如果没有则进行格式化 */
[*]printf("未检测到FATFS文件系统,执行格式化...\r\n");
[*]ret = f_mkfs(USERPath, 0, 0);
[*]if(ret == FR_OK) {
[*]printf("格式化成功!\r\n");
[*]f_mount(NULL, USERPath, 1); /* 先取消挂载,后重新挂载 */
[*]ret = f_mount(&SDFatFS, USERPath, 1);
[*]} else {
[*]printf("格式化失败!\r\n");
[*]goto mount_error;
[*]}
[*]} else {
[*]printf("f_mount success!\r\n");
[*]}
[*]
[*]/* 读写测试 */
[*]printf("\r\n ========== write test ==========\r\n");
[*]ret = f_open(&SDFile, "hello.txt", FA_CREATE_ALWAYS | FA_WRITE);
[*]if(ret == FR_OK) {
[*]printf("open file sucess!\r\n");
[*]ret = f_write(&SDFile, write_buf, sizeof(write_buf), &num);
[*]if(ret == FR_OK) {
[*]printf("write \"%s\" success!\r\nwrite len:%d\r\n", write_buf, num);
[*]} else {
[*]printf("write error! ret:%d \r\n", ret);
[*]goto rw_error;
[*]}
[*]f_close(&SDFile);
[*]} else {
[*]printf("open file error!\r\n");
[*]goto rw_error;
[*]}
[*]
[*]printf("\r\n ========== read test ==========\r\n");
[*]ret = f_open(&SDFile, "hello.txt",FA_OPEN_EXISTING | FA_READ);
[*]if(ret == FR_OK) {
[*]printf("open file sucess!\r\n");
[*]ret = f_read(&SDFile, read_buf, sizeof(read_buf), &num);
[*]if(ret == FR_OK) {
[*]printf("read data:\"%s\"!\r\nread len:%d\r\n", read_buf, num);
[*]} else {
[*]printf("read error! ret:%d \r\n", ret);
[*]goto rw_error;
[*]}
[*]} else {
[*]printf("open file error!\r\n");
[*]goto rw_error;
[*]}
[*]
[*]rw_error:
[*]f_close(&SDFile);
[*]
[*]mount_error:
[*]f_mount(NULL, USERPath, 1);
[*]/* USER CODE END 2 */
[*]while (1) {
[*]
[*]}
[*]}
#define USERPath "0:/" 表示挂载的位置,这是由于FATFS初始化的时候链接的根目录为 0:/ ,所以挂载的文件系统需要在此目录下,当然也可以是此目录下的路径,如0:/hello,但不能是其他目录,如 1:/https://i1.go2yd.com/image.php?url=YD_cnt_77_01Mx08CWe7Ji
编辑


测试结果如下:https://i1.go2yd.com/image.php?url=YD_cnt_77_01Mx08BWSRPJ
编辑


main.c完整内容如下:
[*]/* USER CODE BEGIN Header */
[*]/**
[*]******************************************************************************
[*]* @file : main.c
[*]* @brief : Main program body
[*]******************************************************************************
[*]* @attention
[*]*
[*]* Copyright (c) 2023 STMicroelectronics.
[*]* All rights reserved.
[*]*
[*]* This software is licensed under terms that can be found in the LICENSE file
[*]* in the root directory of this software component.
[*]* If no LICENSE file comes with this software, it is provided AS-IS.
[*]*
[*]******************************************************************************
[*]*/
[*]/* USER CODE END Header */
[*]/* Includes ------------------------------------------------------------------*/
[*]#include "main.h"
[*]#include "fatfs.h"
[*]#include "sdio.h"
[*]#include "usart.h"
[*]#include "gpio.h"
[*]
[*]/* Private includes ----------------------------------------------------------*/
[*]/* USER CODE BEGIN Includes */
[*]
[*]#include
[*]#include "fatfs.h"
[*]/* USER CODE END Includes */
[*]
[*]/* Private typedef -----------------------------------------------------------*/
[*]/* USER CODE BEGIN PTD */
[*]
[*]/* USER CODE END PTD */
[*]
[*]/* Private define ------------------------------------------------------------*/
[*]/* USER CODE BEGIN PD */
[*]/* USER CODE END PD */
[*]
[*]/* Private macro -------------------------------------------------------------*/
[*]/* USER CODE BEGIN PM */
[*]
[*]/* USER CODE END PM */
[*]
[*]/* Private variables ---------------------------------------------------------*/
[*]
[*]/* USER CODE BEGIN PV */
[*]
[*]/* USER CODE END PV */
[*]
[*]/* Private function prototypes -----------------------------------------------*/
[*]void SystemClock_Config(void);
[*]/* USER CODE BEGIN PFP */
[*]
[*]/* USER CODE END PFP */
[*]
[*]/* Private user code ---------------------------------------------------------*/
[*]/* USER CODE BEGIN 0 */
[*]
[*]HAL_SD_CardInfoTypeDef SDCardInfo;
[*]void printf_sdcard_info(void)
[*]{
[*]uint64_t CardCap; //SD卡容釿
[*]HAL_SD_CardCIDTypeDef SDCard_CID;
[*]
[*]HAL_SD_GetCardCID(&hsd,&SDCard_CID); //获取CID
[*]HAL_SD_GetCardInfo(&hsd,&SDCardInfo); //获取SD卡信恿
[*]CardCap=(uint64_t)(SDCardInfo.LogBlockNbr)*(uint64_t)(SDCardInfo.LogBlockSize); //计算SD卡容釿
[*]switch(SDCardInfo.CardType)
[*]{
[*]case CARD_SDSC:
[*]{
[*]if(SDCardInfo.CardVersion == CARD_V1_X)
[*]printf("Card Type:SDSC V1\r\n");
[*]else if(SDCardInfo.CardVersion == CARD_V2_X)
[*]printf("Card Type:SDSC V2\r\n");
[*]}
[*]break;
[*]case CARD_SDHC_SDXC:printf("Card Type:SDHC\r\n");break;
[*]default:break;
[*]}
[*]
[*]printf("Card ManufacturerID: %d \r\n",SDCard_CID.ManufacturerID); //制鿠商ID
[*]printf("CardVersion: %d \r\n",(uint32_t)(SDCardInfo.CardVersion)); //卡版本号
[*]printf("Class: %d \r\n",(uint32_t)(SDCardInfo.Class)); //SD卡类刿
[*]printf("Card RCA(RelCardAdd):%d \r\n",SDCardInfo.RelCardAdd); //卡相对地坿
[*]printf("Card BlockNbr: %d \r\n",SDCardInfo.BlockNbr); //块数釿
[*]printf("Card BlockSize: %d \r\n",SDCardInfo.BlockSize); //块大尿
[*]printf("LogBlockNbr: %d \r\n",(uint32_t)(SDCardInfo.LogBlockNbr)); //逻辑块数釿
[*]printf("LogBlockSize: %d \r\n",(uint32_t)(SDCardInfo.LogBlockSize)); //逻辑块大尿
[*]printf("Card Capacity: %d MB\r\n",(uint32_t)(CardCap>>20)); //卡容釿
[*]}
[*]
[*]int fputc(int ch, FILE *f)
[*]{
[*]HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1,0xffff);
[*]return (ch);
[*]}
[*]
[*]/* USER CODE END 0 */
[*]
[*]/**
[*]* @brief The application entry point.
[*]* @retval int
[*]*/
[*]int main(void)
[*]{
[*]/* USER CODE BEGIN 1 */
[*]#define USERPath "0:/"
[*]
[*]BYTE write_buf[] = "\r\n\r\n\
[*]hello world!\r\n\
[*]开发者社区的明天需要大家一同开源共创,期待下一次你的分享,让我们一同携手共进,推动人类科技的发展!!!\r\n\
[*]创作不易,转载请注明出处~\r\n\
[*]更多文章敬请关注:爱出名的狗腿子\r\n\r\n\
[*]";
[*]BYTE read_buf = {0};
[*]UINT num;
[*]FRESULT ret;
[*]/* USER CODE END 1 */
[*]
[*]/* MCU Configuration--------------------------------------------------------*/
[*]
[*]/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
[*]HAL_Init();
[*]
[*]/* USER CODE BEGIN Init */
[*]
[*]/* USER CODE END Init */
[*]
[*]/* Configure the system clock */
[*]SystemClock_Config();
[*]
[*]/* USER CODE BEGIN SysInit */
[*]
[*]/* USER CODE END SysInit */
[*]
[*]/* Initialize all configured peripherals */
[*]MX_GPIO_Init();
[*]MX_SDIO_SD_Init();
[*]MX_USART1_UART_Init();
[*]MX_FATFS_Init();
[*]/* USER CODE BEGIN 2 */
[*]
[*]/* 挂载文件系统,挂载的时候会完成对应硬件设备(SD卡/SDnand)初始化 */
[*]ret = f_mount(&SDFatFS, USERPath, 1);
[*]if (ret != FR_OK) {
[*]printf("f_mount error!\r\n");
[*]goto mount_error;
[*]} else if(ret == FR_NO_FILESYSTEM) { /* 检测是否存在文件系统,如果没有则进行格式化 */
[*]printf("未检测到FATFS文件系统,执行格式化...\r\n");
[*]ret = f_mkfs(USERPath, 0, 0);
[*]if(ret == FR_OK) {
[*]printf("格式化成功!\r\n");
[*]f_mount(NULL, USERPath, 1); /* 先取消挂载,后重新挂载 */
[*]ret = f_mount(&SDFatFS, USERPath, 1);
[*]} else {
[*]printf("格式化失败!\r\n");
[*]goto mount_error;
[*]}
[*]} else {
[*]printf("f_mount success!\r\n");
[*]}
[*]
[*]/* 读写测试 */
[*]printf("\r\n ========== write test ==========\r\n");
[*]ret = f_open(&SDFile, "hello.txt", FA_CREATE_ALWAYS | FA_WRITE);
[*]if(ret == FR_OK) {
[*]printf("open file sucess!\r\n");
[*]ret = f_write(&SDFile, write_buf, sizeof(write_buf), &num);
[*]if(ret == FR_OK) {
[*]printf("write \"%s\" success!\r\nwrite len:%d\r\n", write_buf, num);
[*]} else {
[*]printf("write error! ret:%d \r\n", ret);
[*]goto rw_error;
[*]}
[*]f_close(&SDFile);
[*]} else {
[*]printf("open file error!\r\n");
[*]goto rw_error;
[*]}
[*]
[*]printf("\r\n ========== read test ==========\r\n");
[*]ret = f_open(&SDFile, "hello.txt",FA_OPEN_EXISTING | FA_READ);
[*]if(ret == FR_OK) {
[*]printf("open file sucess!\r\n");
[*]ret = f_read(&SDFile, read_buf, sizeof(read_buf), &num);
[*]if(ret == FR_OK) {
[*]printf("read data:\"%s\"!\r\nread len:%d\r\n", read_buf, num);
[*]} else {
[*]printf("read error! ret:%d \r\n", ret);
[*]goto rw_error;
[*]}
[*]} else {
[*]printf("open file error!\r\n");
[*]goto rw_error;
[*]}
[*]
[*]rw_error:
[*]f_close(&SDFile);
[*]
[*]mount_error:
[*]f_mount(NULL, USERPath, 1);
[*]/* USER CODE END 2 */
[*]
[*]/* Infinite loop */
[*]/* USER CODE BEGIN WHILE */
[*]while (1)
[*]{
[*]/* USER CODE END WHILE */
[*]
[*]/* USER CODE BEGIN 3 */
[*]}
[*]/* USER CODE END 3 */
[*]}
[*]
[*]/**
[*]* @brief System Clock Configuration
[*]* @retval None
[*]*/
[*]void SystemClock_Config(void)
[*]{
[*]RCC_OscInitTypeDef RCC_OscInitStruct = {0};
[*]RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
[*]
[*]/** Initializes the RCC Oscillators according to the specified parameters
[*]* in the RCC_OscInitTypeDef structure.
[*]*/
[*]RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
[*]RCC_OscInitStruct.HSEState = RCC_HSE_ON;
[*]RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1;
[*]RCC_OscInitStruct.HSIState = RCC_HSI_ON;
[*]RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
[*]RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
[*]RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9;
[*]if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
[*]{
[*]Error_Handler();
[*]}
[*]
[*]/** Initializes the CPU, AHB and APB buses clocks
[*]*/
[*]RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
[*]|RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
[*]RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
[*]RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
[*]RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;
[*]RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;
[*]
[*]if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK)
[*]{
[*]Error_Handler();
[*]}
[*]}
[*]
[*]/* USER CODE BEGIN 4 */
[*]
[*]/* USER CODE END 4 */
[*]
[*]/**
[*]* @brief This function is executed in case of error occurrence.
[*]* @retval None
[*]*/
[*]void Error_Handler(void)
[*]{
[*]/* USER CODE BEGIN Error_Handler_Debug */
[*]/* User can add his own implementation to report the HAL error return state */
[*]__disable_irq();
[*]while (1)
[*]{
[*]}
[*]/* USER CODE END Error_Handler_Debug */
[*]}
[*]
[*]#ifdef USE_FULL_ASSERT
[*]/**
[*]* @brief Reports the name of the source file and the source line number
[*]* where the assert_param error has occurred.
[*]* @param file: pointer to the source file name
[*]* @param line: assert_param error line source number
[*]* @retval None
[*]*/
[*]void assert_failed(uint8_t *file, uint32_t line)
[*]{
[*]/* USER CODE BEGIN 6 */
[*]/* User can add his own implementation to report the file name and line number,
[*]ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
[*]/* USER CODE END 6 */
[*]}
[*]#endif /* USE_FULL_ASSERT */
3.4 配置问题记录3.4.1 CubeMx生成代码bug测试发现,使用CubeMx当前最新版本:V6.8.0版本,生成代码会存在以下问题:
[*]SD卡/SDnand 卡片信息读取成功,但是读写测试失败
经过仔细分析代码后发现,出现的问题在 MX_SDIO_SD_Init() 此初始化函数内的配置项错误导致,具体分析如下:
[*]我们在CubeMx里面配置的时候选择的是4线宽度模式 SD 4bit Wide bus
[*]v6.8.0版本CubeMx生成的 MX_SDIO_SD_Init() SD初始化函数内,hsd.Init.BusWide = SDIO_BUS_WIDE_4B;
[*]看上去没有什么问题,配置4线模式,对应的初始化项也使用4线模式,但是不然,我们继续分析 MX_SDIO_SD_Init() 此初始配置的调用
[*]MX_SDIO_SD_Init() 此函数在main函数内初始化的时候调用,此函数只配置了 hsd 结构体,并未配置给SDIO硬件寄存器
[*]之后调用 SD_Driver.disk_initialize(0); 函数的时候才真正开始进行SDIO外设配置
BSP_SD_Init()  ->HAL_SD_Init()    ->HAL_SD_InitCard()在 HAL_SD_InitCard() 函数内使用Init结构体配置SDIO外设,总线宽度1bit,时钟速度<400k,以进**片的初始化识别。      -> SD_InitCard()        -> SDIO_Init(hsd->Instance, hsd->Init)      -> SDMMC_CmdBlockLength(hsd->Instance, BLOCKSIZE)· 在 SD_InitCard() 函数内实现SD卡的初始化识别,之后调用 SDIO_Init() 将 MX_SDIO_SD_Init() 内对 hsd 的配置配置给SDIO外设,此处的作用主要是提升SDIO外设时钟速率为我们配置的速率;· v6.8.0版本的代码此时hsd.Init.BusWide = SDIO_BUS_WIDE_4B; ,因此v6.8.0版本代码后续SDIO外设使用4线通讯;· 之后调用 SDMMC_CmdBlockLength() 设置块大小,由于SDIO外设已切换到4线模式,而SD卡/SDnand此时仍然处于1线模式,因此配置会出错   -> HAL_SD_ConfigWideBusOperation(&hsd, SDIO_BUS_WIDE_4B)根据前面获取到的SD卡SCR寄存器值,判断是否支持4线模式,如果支持则发送配置命令通知SD卡/SDnand进入4线模式,之后修改SDIO外设总线宽度为4线模式6.通过以上分析可知,MX_SDIO_SD_Init() 函数内对 hsd.Init.BusWide = SDIO_BUS_WIDE_4B; 的配置会导致对SD卡块大小的配置失败,从而导致后续读写时失败,报错为块大小设置失败!7.综上,针对当前最新版本 V6.8.0 版本CubeMx的处理方法是:手动修改此 hsd.Init.BusWide 配置为 SDIO_BUS_WIDE_1B 或更换低版本CubeMx,本人更换V6.6.1版本后无此bug。3.4.2 SD插入检测引脚配置使用CubeMx配置FATFS 选择 SD Card 之后,有一个配置参数,用来配置SD Card的输入检测引脚。如果我们在硬件上有设计SD卡的卡槽插入检测引脚插入连接到了MCU的IO,则可配置对应IO为输入模式,并设置对应IO为输入检测引脚,比如,我们设置PD12为输入检测引脚,则配置如下:对应代码如下,输入检测 IO 低电平有效!https://i1.go2yd.com/image.php?url=YD_cnt_77_01Mx088b2Vk2
编辑


https://i1.go2yd.com/image.php?url=YD_cnt_77_01Mx08Ds3I5r
编辑


如果硬件上,没有此插入检测引脚,则可以在CubeMx内不进行配置,只是在生成代码的时候会提示警报而已,可以不用关心,生成的代码项会自动屏蔽插入检测!https://i1.go2yd.com/image.php?url=YD_cnt_77_01Mx08AGoLIZ
编辑


4. 结束语
[*]以上便是本文的全部内容了,欢迎大家评论区留言讨论!

[*]使用CubeMx虽然能帮助我们快速生成驱动,但是对于SD卡/SD nand的驱动流程,我们还是需要有清晰的认识,推荐阅读: SD Nand 与 SD卡 SDIO模式应用流程
————————————————【本文转载自CSDN,作者: 爱出名的狗腿子】
页: [1]
查看完整版本: stm32 CubeMx 实现SD卡/sd nand FATFS读写测试