网站建设怎样做好,个人性质的网站,上弘科技网站建设,wordpress 分类名称文章目录
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 配置问题记…文章目录
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生成代码bug
3.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 软硬件说明
硬件环境 主控stm32f103vet6
SD nand CSNPGCR01-AOW
软件环境 CubeMx版本Version 6.6.1
注意当前最新版本 V6.8.0生成的工程配置存在bug具体细节在后文描述
2.2 外设原理图
SD卡槽原理图部分如下 3. 工程搭建
3.1 CubeMx 配置 1.选择芯片ACCESS TO MCU SELECTOR 2.搜索对应的芯片型号在对应列表下方选择对应芯片 3.配置时钟方案采用外部高速时钟无源晶振方案 4.配置调试器由于我采用SWD调试接口因此选择 Serial Wrie 串行总线 5.配置SDIO外设由于我们所使用的SD nand支持4线传输因此此处选择4线宽度如果你所使用的SD nand或SD卡不支持4线传输此处应选择1线宽度支持4线宽度的SD卡肯定可以使用1线宽度因此如果你实在不知道你的SD卡支持几线宽度你可以直接选择1线宽度4线和1线宽度的差别也就在于速度上相差了4倍 (注意这里暂时不需要对SDIO的参数进行配置后面我们再回来配置) 6.完成时钟树配置 配置外部晶振频率 调整时钟选择SYSCLK由PLL产生PLL由外部时钟倍频产生 配置SDIO外设时钟注意此处SDIO外设比较特殊有两个时钟具体原因见后文 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卡信息识别过程时的速度
8.勾选 FATFS 配置选择 SD Card 9.配置SD卡检测引脚有以下两种方案 方案一选择一个输入IO作为触发引脚 方案二不配置输入IO最后生成代码的时候无视警报即可生成的代码会自动取消输入检测判断 10.配置调试串口用来打印信息此处我选择USART1大家可根据自己硬件环境自行选择 11.配置工程信息 配置工程名 选择工程路径 配置应用程序结构我习惯选择 Basic 结构 选择IDE工具及版本 修改堆栈大小适当改大一点怕不够用 12.勾选将外设初始化放置在独立的.c和.h文件这样每个外设的初始化是独立的方便阅读移植 13.生成代码 3.2 SDIO时钟配置说明
在上述CubeMx时钟配置中外设的时钟一般都是只有一路过去但是在此处我们会发现SDIO的时钟在时钟树中有两个没弄清楚还会以为这是CubeMx出现bug了 其实这是SDIO外设的特殊点我们查看数据手册上的时钟树便可以发现实际上是真的有两路时钟分别是1SDIOCLK2至SDIO的AHB接口 之后我们看到数据手册的SDIO章节我们可以看到SDIO外设分为1AHB总线接口 和 2SDIO适配器两大块且使用不同的时钟这也就是我们在时钟树配置中可以看到有两路时钟配置的原因了 从下图我们可以知道SDIO外设不同于其他外设其外设模块部分与中断、DMA是分开的并采用不同的时钟 关于AHB总线接口及SDIO适配器更多细节大家可自行阅读参考手册部分章节内容此处不做赘述。 此外关于时钟配置有一个特别需要注意的也就是SDIO_CK时钟信号。SDIO_CK时钟也就是我们SDIO外设与SD卡/SD nand通讯的CLK时钟从上图我们可知SDIO_CK时钟来自SDIO适配器也就是来自SDIOCLK对应CubeMX时钟配置中的 3.2 读写测试
3.2.1 添加读写测试代码 1.使能 MicroLIB 微库否则调用 printf 函数会卡住 2.修改编码规则为 UTF-8这是由于我们CubeMx中配置的FATFS的编码格式为 UTF-8导致如果不修改为 UTF-8 则部分中文会乱码 //TODO:确认是由FATFS配置导致 3.添加 printf 重映射 位置可根据自行决定 #include stdio.h 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)(CardCap20)); //卡容量 } 5.添加初始化及读写测试代码注意此处我们没有直接使用FATFS的读写接口我们先测试生成的SD驱动函数接口 int main(void) { /* USER CODE BEGIN 1 */ BYTE send_buf[512]; 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.修改烧录器配置配置为烧录后自动运行 7.下载测试这里由于我们采用UTF-8编码所以使用的串口上位机也需要支持UTF-8解析我们这里使用Mobaxterm上位机测试结果如下: 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 stdio.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)(CardCap20)); //卡容量 } 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[512]; 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[1024] {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:/ 测试结果如下 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 stdio.h #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)(CardCap20)); //卡容釿 } 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[1024] {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 低电平有效 如果硬件上没有此插入检测引脚则可以在CubeMx内不进行配置只是在生成代码的时候会提示警报而已可以不用关心生成的代码项会自动屏蔽插入检测 4. 结束语 以上便是本文的全部内容了欢迎大家评论区留言讨论 使用CubeMx虽然能帮助我们快速生成驱动但是对于SD卡/SD nand的驱动流程我们还是需要有清晰的认识推荐阅读 SD Nand 与 SD卡 SDIO模式应用流程