(经验分享)STM32H7的SDMMC总线应用之SD卡移植FatFs文件系统
88.2 SD卡硬件接口设计STM32H7驱动SD卡设计如下:
关于这个原理图,要了解到以下几个知识:
大家自己设计推荐也接上拉电阻。
这里采用SDMMC的4线方式。
88.3 SD卡基础知识
这里将SD卡相关的基础知识为大家做个普及。
88.3.1 SD卡分类
根据不同容量做的区分,主要包括Full SD,miniSD和microSD。
88.3.2 SD卡容量及其使用的文件系统
容量小于2GB(SD卡)使用FAT12或者FAT16,容量在2GB和32GB之间(SDHC卡)使用FAT32,容量大于32GB小于2TB(SDXC卡)使用exFAT。
88.3.3 SD卡总线速度和速度等级
SD卡速度:
SD卡速度等级:
88.4 各种存储卡区别
市面上的卡种类非常多,容易把人搞糊涂,这里将这些卡种类为大家做个区分:
88.4.1 SD卡,miniSD卡,TF卡,MircoSD卡
TF卡是MicroSD卡的另一种叫法,无需做区分。SD卡,miniSD卡,MircoSD卡其实是一种卡,区别是引脚使用上。
3fce2ad94bbc173bef248d0225d019cc.png (63.1 KB, 下载次数: 0)下载附件3 天前 上传
88.4.2 SDIO卡
SDIO卡就是使用SDIO外设来接SD卡。
而为什么叫SDIO,根据wiki百科说明,其实就是SD卡接口规范的扩展,带了输入输出功能,这个接口不仅可以接SD卡,还可以接其它外设,如条形码读卡器,WiFi,蓝牙,调制解调器等。
对于STM32的SDIO来说,他就是指STM32的一个外设接口,不仅能够来接SD卡,还可以接其它外设。
88.4.3 MMC卡,eMMC
截止2018年,市场上已经没有设备内置MMC卡槽,但eMMC得到了广泛应用。
88.4.4 CF卡
CF卡是早期最成功的存储卡格式之一,像MMC/SD卡都是后来才推出的。CF卡仍然很受欢迎卡之一,并得到许多专业设备和高端消费类设备的支持。截至2017年,佳能和尼康都将CompactFlash用于其旗舰数码相机。佳能还选择了CompactFlash作为其专业高清无带摄像机的记录介质。
基础规格:
实际效果:
88.4.5 总体区别
88.5 关于SD卡内部是否自带擦写均衡
4c171fc75703c1cb96f4a9dc14f9a4fc.png (137.97 KB, 下载次数: 0)下载附件3 天前 上传
88.6 FatFs文件系统简介
FatFs是用于小型嵌入式系统的通用FAT / exFAT文件系统模块。FatFs是按照ANSI C(C89)编写的并且与磁盘I / O层完全分开。因此,它独立于平台。它可以并入资源有限的小型MCU中,例如8051,PIC,AVR,ARM,Z80,RX等。此处还提供了适用于小型MCU的Petit FatFs模块。
特征:
DOS / Windows兼容的FAT / exFAT文件系统。
平台无关,容易移植。
程序代码和工作区的占用空间非常小。
支持以下各种配置选项:
ANSI / OEM或Unicode中的长文件名。
exFAT文件系统,64位LBA和GPT可存储大量数据。
RTOS的线程安全。
多个卷(物理驱动器和分区)。
可变扇区大小。
多个代码页,包括DBCS。
只读,可选API,I / O缓冲区等
88.4.5 总体区别
88.5 关于SD卡内部是否自带擦写均衡
4c171fc75703c1cb96f4a9dc14f9a4fc.png (137.97 KB, 下载次数: 0)下载附件3 天前 上传
88.6 FatFs文件系统简介
FatFs是用于小型嵌入式系统的通用FAT / exFAT文件系统模块。FatFs是按照ANSI C(C89)编写的并且与磁盘I / O层完全分开。因此,它独立于平台。它可以并入资源有限的小型MCU中,例如8051,PIC,AVR,ARM,Z80,RX等。此处还提供了适用于小型MCU的Petit FatFs模块。
特征:
DOS / Windows兼容的FAT / exFAT文件系统。
平台无关,容易移植。
程序代码和工作区的占用空间非常小。
支持以下各种配置选项:
ANSI / OEM或Unicode中的长文件名。
exFAT文件系统,64位LBA和GPT可存储大量数据。
RTOS的线程安全。
多个卷(物理驱动器和分区)。
可变扇区大小。
多个代码页,包括DBCS。
只读,可选API,I / O缓冲区等
88.7 FatFs移植步骤
这里将FatFs的移植步骤为大家做个说明。
FatFs各个文件的依赖关系:
驱动一个磁盘或者多个磁盘的框图:
88.7.1 第1步,了解整体设计框架
为了方便大家移植,需要大家先对移植好的工程有个整体认识:
88.7.2 第2步,添加FatFs和SDMMC驱动到工程
本教程前面章节配套的例子都可以作为模板使用,在模板的基础上需要添加FatFs文件,SDMMC驱动文件和SD卡驱动文件,大家可以直接从本章教程提供的例子里面复制。
SD卡驱动文件bsp_sdio_sd.c和bsp_sdio_sd.h添加到自己的工程里面,路径不限。
配套例子是放在\User\bsp\src和\User\bsp\inc文件。
SDMMMC驱动文件stm32h7xx_hal_sd.c和stm32h7xx_ll_sdmmc.c
这个是STM32H7的HAL库自带的。
FatFs相关源文件。
大家可以将所有相关文件都复制到自己的工程里面,配套例子是放在\Libraries\FatFs。
88.7.3 第3步,添加工程路径
当前需要添加的两个FatFs路径,大家根据自己添加的源文件位置,添加相关路径即可:
88.7.4 第4步,配置GPIO和时钟
根据大家使用SDMMC1或者SDMMC2配置相应时钟和GPIO,当前V7板子是用的SDMMC1:
[*]__weak void BSP_SD_MspInit(SD_HandleTypeDef *hsd, void *Params)
[*]{
[*]GPIO_InitTypeDef gpio_init_structure;
[*]
[*]/* Enable SDIO clock */
[*]__HAL_RCC_SDMMC1_CLK_ENABLE();
[*]
[*]/* Enable GPIOs clock */
[*]__HAL_RCC_GPIOB_CLK_ENABLE();
[*]__HAL_RCC_GPIOC_CLK_ENABLE();
[*]__HAL_RCC_GPIOD_CLK_ENABLE();
[*]
[*]gpio_init_structure.Mode = GPIO_MODE_AF_PP;
[*]gpio_init_structure.Pull = GPIO_NOPULL;
[*]gpio_init_structure.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
[*]
[*]/* D0(PC8), D1(PC9), D2(PC10), D3(PC11), CK(PC12), CMD(PD2) */
[*]/* Common GPIO configuration */
[*]gpio_init_structure.Alternate = GPIO_AF12_SDIO1;
[*]
[*]/* GPIOC configuration */
[*]gpio_init_structure.Pin = GPIO_PIN_8 | GPIO_PIN_9 | GPIO_PIN_10 | GPIO_PIN_11 | GPIO_PIN_12;
[*]HAL_GPIO_Init(GPIOC, &gpio_init_structure);
[*]
[*]/* GPIOD configuration */
[*]gpio_init_structure.Pin = GPIO_PIN_2;
[*]HAL_GPIO_Init(GPIOD, &gpio_init_structure);
[*]
[*]__HAL_RCC_SDMMC1_FORCE_RESET();
[*]__HAL_RCC_SDMMC1_RELEASE_RESET();
[*]
[*]/* NVIC configuration for SDIO interrupts */
[*]HAL_NVIC_SetPriority(SDMMC1_IRQn, 5, 0);
[*]HAL_NVIC_EnableIRQ(SDMMC1_IRQn);
[*]}
复制代码
88.7.5 第5步,MPU配置
为了方便大家移植测试,我们这里直接关闭AXI SRAM的读Cache和写Cache(这样就跟使用STM32F1或者STM32F4系列里面的SRAM一样)。此配置是在bsp.c文件的MPU_Config函数里面实现:
[*]/*
[*]*********************************************************************************************************
[*]* 函 数 名: MPU_Config
[*]* 功能说明: 配置MPU
[*]* 形 参: 无
[*]* 返 回 值: 无
[*]*********************************************************************************************************
[*]*/
[*]static void MPU_Config( void )
[*]{
[*] MPU_Region_InitTypeDef MPU_InitStruct;
[*]
[*] /* 禁止 MPU */
[*] HAL_MPU_Disable();
[*]
[*]#if 0
[*] /* 配置AXI SRAM的MPU属性为Write back, Read allocate,Write allocate */
[*] MPU_InitStruct.Enable = MPU_REGION_ENABLE;
[*] MPU_InitStruct.BaseAddress = 0x24000000;
[*] MPU_InitStruct.Size = MPU_REGION_SIZE_512KB;
[*] MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
[*] MPU_InitStruct.IsBufferable = MPU_ACCESS_BUFFERABLE;
[*] MPU_InitStruct.IsCacheable = MPU_ACCESS_CACHEABLE;
[*] MPU_InitStruct.IsShareable = MPU_ACCESS_NOT_SHAREABLE;
[*] MPU_InitStruct.Number = MPU_REGION_NUMBER0;
[*] MPU_InitStruct.TypeExtField = MPU_TEX_LEVEL1;
[*] MPU_InitStruct.SubRegionDisable = 0x00;
[*] MPU_InitStruct.DisableExec = MPU_INSTRUCTION_ACCESS_ENABLE;
[*]
[*] HAL_MPU_ConfigRegion(&MPU_InitStruct);
[*]
[*]#else
[*] /* 当前是采用下面的配置 */
[*] /* 配置AXI SRAM的MPU属性为NORMAL, NO Read allocate,NO Write allocate */
[*] MPU_InitStruct.Enable = MPU_REGION_ENABLE;
[*] MPU_InitStruct.BaseAddress = 0x24000000;
[*] MPU_InitStruct.Size = MPU_REGION_SIZE_512KB;
[*] MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
[*] MPU_InitStruct.IsBufferable = MPU_ACCESS_NOT_BUFFERABLE;
[*] MPU_InitStruct.IsCacheable = MPU_ACCESS_NOT_CACHEABLE;
[*] MPU_InitStruct.IsShareable = MPU_ACCESS_NOT_SHAREABLE;
[*] MPU_InitStruct.Number = MPU_REGION_NUMBER0;
[*] MPU_InitStruct.TypeExtField = MPU_TEX_LEVEL1;
[*] MPU_InitStruct.SubRegionDisable = 0x00;
[*] MPU_InitStruct.DisableExec = MPU_INSTRUCTION_ACCESS_ENABLE;
[*]
[*] HAL_MPU_ConfigRegion(&MPU_InitStruct);
[*]#endif
[*] /* 配置FMC扩展IO的MPU属性为Device或者Strongly Ordered */
[*] MPU_InitStruct.Enable = MPU_REGION_ENABLE;
[*] MPU_InitStruct.BaseAddress = 0x60000000;
[*] MPU_InitStruct.Size = ARM_MPU_REGION_SIZE_64KB;
[*] MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
[*] MPU_InitStruct.IsBufferable = MPU_ACCESS_BUFFERABLE;
[*] MPU_InitStruct.IsCacheable = MPU_ACCESS_NOT_CACHEABLE;
[*] MPU_InitStruct.IsShareable = MPU_ACCESS_NOT_SHAREABLE;
[*] MPU_InitStruct.Number = MPU_REGION_NUMBER1;
[*] MPU_InitStruct.TypeExtField = MPU_TEX_LEVEL0;
[*] MPU_InitStruct.SubRegionDisable = 0x00;
[*] MPU_InitStruct.DisableExec = MPU_INSTRUCTION_ACCESS_ENABLE;
[*]
[*] HAL_MPU_ConfigRegion(&MPU_InitStruct);
[*]
[*] /*使能 MPU */
[*] HAL_MPU_Enable(MPU_PRIVILEGED_DEFAULT);
[*]}
复制代码
88.7.6 第6步,FatFs的配置文件ffconf.h设置
移植阶段,这个里面的配置可以不用动,无需任何修改。需要注意的是我们这里开启了长文件名支持,对应的宏定义:
[*]#define _USE_LFN 3
复制代码
88.7.7 第7步,添加应用代码
这里将FatFs大部分操作函数都做了应用,专门整理到了文件demo_sd_fatfs.c。通过串口命令进行操作,大家可以直接将这个文件添加到自己的工程里面。
另外注意,如果自己的工程里面没有移植我们其它的驱动,可以直接调用FatFs的测试函数,比如浏览SD根目录文件,可以直接调用函数ViewRootDir。
88.8 FatFs应用代码测试
这里将FatFs大部分函数都做了测试。注意,所有用到的函数在FatFs官网都有详细说明。
88.8.1 注册SD卡驱动
注册SD卡功能是ST简单封装的一个函数,方便用户实现FatFs驱动多个磁盘。
代码如下:
[*]char DiskPath; /* SD卡逻辑驱动路径,比盘符0,就是"0:/" */
[*]/* 注册SD卡驱动 */
[*]FATFS_LinkDriver(&SD_Driver, DiskPath);
复制代码
88.8.2 SD卡文件浏览
SD卡根目录的文件浏览代码实现如下:
[*]/*
[*]*********************************************************************************************************
[*]* 函 数 名: ViewRootDir
[*]* 功能说明: 显示SD卡根目录下的文件名
[*]* 形 参:无
[*]* 返 回 值: 无
[*]*********************************************************************************************************
[*]*/
[*]extern SD_HandleTypeDef uSdHandle;
[*]static void ViewRootDir(void)
[*]{
[*] FRESULT result;
[*] uint32_t cnt = 0;
[*] FILINFO fno;
[*]
[*] /* 挂载文件系统 */
[*] result = f_mount(&fs, DiskPath, 0); /* Mount a logical drive */
[*] if (result != FR_OK)
[*] {
[*] printf("挂载文件系统失败 (%s)\r\n", FR_Table);
[*] }
[*]
[*] /* 打开根文件夹 */
[*] result = f_opendir(&DirInf, DiskPath); /* 如果不带参数,则从当前目录开始 */
[*] if (result != FR_OK)
[*] {
[*] printf("打开根目录失败(%s)\r\n", FR_Table);
[*] return;
[*] }
[*]
[*] printf("属性 |文件大小 | 短文件名 | 长文件名\r\n");
[*] for (cnt = 0; ;cnt++)
[*] {
[*] result = f_readdir(&DirInf, &FileInf); /* 读取目录项,索引会自动下移 */
[*] if (result != FR_OK || FileInf.fname == 0)
[*] {
[*] break;
[*] }
[*]
[*] if (FileInf.fname == '.')
[*] {
[*] continue;
[*] }
[*]
[*] /* 判断是文件还是子目录 */
[*] if (FileInf.fattrib & AM_DIR)
[*] {
[*] printf("(0x%02d)目录", FileInf.fattrib);
[*] }
[*] else
[*] {
[*] printf("(0x%02d)文件", FileInf.fattrib);
[*] }
[*]
[*] f_stat(FileInf.fname, &fno);
[*]
[*] /* 打印文件大小, 最大4G */
[*] printf(" %10d", (int)fno.fsize);
[*]
[*]
[*] printf("%s\r\n", (char *)FileInf.fname); /* 长文件名 */
[*] }
[*]
[*] /* 打印卡速度信息 */
[*] if(uSdHandle.SdCard.CardSpeed == CARD_NORMAL_SPEED)
[*] {
[*] printf("Normal Speed Card <12.5MB/S, MAX Clock < 25MHz, Spec Version 1.01\r\n");
[*] }
[*] else if (uSdHandle.SdCard.CardSpeed == CARD_HIGH_SPEED)
[*] {
[*] printf("High Speed Card <25MB/s, MAX Clock < 50MHz, Spec Version 2.00\r\n");
[*] }
[*] else if (uSdHandle.SdCard.CardSpeed == CARD_ULTRA_HIGH_SPEED)
[*] {
[*] printf("UHS-I SD Card <50MB/S for SDR50, DDR50 Cards, MAX Clock < 50MHz OR 100MHz\r\n");
[*] printf("UHS-I SD Card <104MB/S for SDR104, MAX Clock < 108MHz, Spec version 3.01\r\n");
[*] }
[*]
[*]
[*] /* 卸载文件系统 */
[*] f_mount(NULL, DiskPath, 0);
[*]}
复制代码
f_mount可以上电后仅调用一次,我们这里是为了测试方式,使用前挂载,使用完毕后卸载。
代码里面加入了SD卡速度信息打印功能,方便大家了解自己的卡类型。通过查询全局结构体变量uSdHandle来实现。
文件浏览通过函数f_readdir实现。
88.8.3 SD卡创建txt文件并写入数据
代码实现如下:
[*]/*
[*]*********************************************************************************************************
[*]* 函 数 名: CreateNewFile
[*]* 功能说明: 在SD卡创建一个新文件,文件内容填写“<atarget="_blank">www.armfly.com</a>”
[*]* 形 参:无
[*]* 返 回 值: 无
[*]*********************************************************************************************************
[*]*/
[*]static void CreateNewFile(void)
[*]{
[*] FRESULT result;
[*] uint32_t bw;
[*] char path;
[*]
[*]
[*] /* 挂载文件系统 */
[*] result = f_mount(&fs, DiskPath, 0); /* Mount a logical drive */
[*] if (result != FR_OK)
[*] {
[*] printf("挂载文件系统失败 (%s)\r\n", FR_Table);
[*] }
[*]
[*] /* 打开文件 */
[*] sprintf(path, "%sarmfly.txt", DiskPath);
[*] result = f_open(&file, path, FA_CREATE_ALWAYS | FA_WRITE);
[*] if (result == FR_OK)
[*] {
[*] printf("armfly.txt 文件打开成功\r\n");
[*] }
[*] else
[*] {
[*] printf("armfly.txt 文件打开失败(%s)\r\n", FR_Table);
[*] }
[*]
[*] /* 写一串数据 */
[*] result = f_write(&file, FsWriteBuf, strlen(FsWriteBuf), &bw);
[*] if (result == FR_OK)
[*] {
[*] printf("armfly.txt 文件写入成功\r\n");
[*] }
[*] else
[*] {
[*] printf("armfly.txt 文件写入失败(%s)\r\n", FR_Table);
[*] }
[*]
[*] /* 关闭文件*/
[*] f_close(&file);
[*]
[*] /* 卸载文件系统 */
[*] f_mount(NULL, DiskPath, 0);
[*]}
复制代码
f_mount可以上电后仅调用一次,我们这里是为了测试方式,使用前挂载,使用完毕后卸载。
函数f_open用来创建并打开文件。
函数f_write用来写入数据。
函数f_close用来关闭文件,注意调用完函数f_write后,内容还没有实际写入到SD卡中,调用了f_close后,数据才真正的写入到SD卡。当然也可以调用函数f_sync,内容也会实际的写入。
88.8.4 SD卡文件读取
代码实现如下:
[*]/*
[*]*********************************************************************************************************
[*]* 函 数 名: ReadFileData
[*]* 功能说明: 读取文件armfly.txt前128个字符,并打印到串口
[*]* 形 参:无
[*]* 返 回 值: 无
[*]*********************************************************************************************************
[*]*/
[*]static void ReadFileData(void)
[*]{
[*] FRESULT result;
[*] uint32_t bw;
[*] char path;
[*]
[*]
[*] /* 挂载文件系统 */
[*] result = f_mount(&fs, DiskPath, 0); /* Mount a logical drive */
[*] if (result != FR_OK)
[*] {
[*] printf("挂载文件系统失败 (%s)\r\n", FR_Table);
[*] }
[*]
[*] /* 打开文件 */
[*] sprintf(path, "%sarmfly.txt", DiskPath);
[*] result = f_open(&file, path, FA_OPEN_EXISTING | FA_READ);
[*] if (result !=FR_OK)
[*] {
[*] printf("Don't Find File : armfly.txt\r\n");
[*] return;
[*] }
[*]
[*] /* 读取文件 */
[*] result = f_read(&file, FsReadBuf, sizeof(FsReadBuf), &bw);
[*] if (bw > 0)
[*] {
[*] FsReadBuf = 0;
[*] printf("\r\narmfly.txt 文件内容 : \r\n%s\r\n", FsReadBuf);
[*] }
[*] else
[*] {
[*] printf("\r\narmfly.txt 文件内容 : \r\n");
[*] }
[*]
[*] /* 关闭文件*/
[*] f_close(&file);
[*]
[*] /* 卸载文件系统 */
[*] f_mount(NULL, DiskPath, 0);
[*]}
复制代码
f_mount可以上电后仅调用一次,我们这里是为了测试方式,使用前挂载,使用完毕后卸载。
函数f_open用来打开文件。
函数f_read用来读取文件中的内容。
函数f_close用来关闭打开的文件。
88.8.5 SD卡创建文件夹
代码实现如下:
[*]/*
[*]*********************************************************************************************************
[*]* 函 数 名: CreateDir
[*]* 功能说明: 在SD卡根目录创建Dir1和Dir2目录,在Dir1目录下创建子目录Dir1_1
[*]* 形 参:无
[*]* 返 回 值: 无
[*]*********************************************************************************************************
[*]*/
[*]static void CreateDir(void)
[*]{
[*] FRESULT result;
[*] char path;
[*]
[*]
[*] /* 挂载文件系统 */
[*] result = f_mount(&fs, DiskPath, 0); /* Mount a logical drive */
[*] if (result != FR_OK)
[*] {
[*] printf("挂载文件系统失败 (%s)\r\n", FR_Table);
[*] }
[*]
[*] /* 创建目录/Dir1 */
[*] sprintf(path, "%sDir1", DiskPath);
[*] result = f_mkdir(path);
[*] if (result == FR_OK)
[*] {
[*] printf("f_mkdir Dir1 Ok\r\n");
[*] }
[*] else if (result == FR_EXIST)
[*] {
[*] printf("Dir1 目录已经存在(%d)\r\n", result);
[*] }
[*] else
[*] {
[*] printf("f_mkdir Dir1 失败 (%s)\r\n", FR_Table);
[*] return;
[*] }
[*]
[*] /* 创建目录/Dir2 */
[*] sprintf(path, "%sDir2", DiskPath);
[*] result = f_mkdir(path);
[*] if (result == FR_OK)
[*] {
[*] printf("f_mkdir Dir2 Ok\r\n");
[*] }
[*] else if (result == FR_EXIST)
[*] {
[*] printf("Dir2 目录已经存在(%d)\r\n", result);
[*] }
[*] else
[*] {
[*] printf("f_mkdir Dir2 失败 (%s)\r\n", FR_Table);
[*] return;
[*] }
[*]
[*] /* 创建子目录 /Dir1/Dir1_1 注意:创建子目录Dir1_1时,必须先创建好Dir1 */
[*] sprintf(path, "%sDir1/Dir1_1", DiskPath);
[*] result = f_mkdir(path); /* */
[*] if (result == FR_OK)
[*] {
[*] printf("f_mkdir Dir1_1 成功\r\n");
[*] }
[*] else if (result == FR_EXIST)
[*] {
[*] printf("Dir1_1 目录已经存在 (%d)\r\n", result);
[*] }
[*] else
[*] {
[*] printf("f_mkdir Dir1_1 失败 (%s)\r\n", FR_Table);
[*] return;
[*] }
[*]
[*] /* 卸载文件系统 */
[*] f_mount(NULL, DiskPath, 0);
[*]}
复制代码
f_mount可以上电后仅调用一次,我们这里是为了测试方式,使用前挂载,使用完毕后卸载。
创建目录通过函数f_mkdir。
88.8.6 SD卡文件和文件夹删除
代码实现如下:
[*]/*
[*]*********************************************************************************************************
[*]* 函 数 名: DeleteDirFile
[*]* 功能说明: 删除SD卡根目录下的 armfly.txt 文件和 Dir1,Dir2 目录
[*]* 形 参:无
[*]* 返 回 值: 无
[*]*********************************************************************************************************
[*]*/
[*]static void DeleteDirFile(void)
[*]{
[*] FRESULT result;
[*] uint8_t i;
[*] char path;
[*]
[*] /* 挂载文件系统 */
[*] result = f_mount(&fs, DiskPath, 0); /* Mount a logical drive */
[*] if (result != FR_OK)
[*] {
[*] printf("挂载文件系统失败 (%s)\r\n", FR_Table);
[*] }
[*]
[*] /* 删除目录/Dir1 【因为还存在目录非空(存在子目录),所以这次删除会失败】*/
[*] sprintf(path, "%sDir1", DiskPath);
[*] result = f_unlink(path);
[*] if (result == FR_OK)
[*] {
[*] printf("删除目录Dir1成功\r\n");
[*] }
[*] else if (result == FR_NO_FILE)
[*] {
[*] printf("没有发现文件或目录 :%s\r\n", "/Dir1");
[*] }
[*] else
[*] {
[*] printf("删除Dir1失败(错误代码 = %d) 文件只读或目录非空\r\n", result);
[*] }
[*]
[*] /* 先删除目录/Dir1/Dir1_1 */
[*] sprintf(path, "%sDir1/Dir1_1", DiskPath);
[*] result = f_unlink(path);
[*] if (result == FR_OK)
[*] {
[*] printf("删除子目录/Dir1/Dir1_1成功\r\n");
[*] }
[*] else if ((result == FR_NO_FILE) || (result == FR_NO_PATH))
[*] {
[*] printf("没有发现文件或目录 :%s\r\n", "/Dir1/Dir1_1");
[*] }
[*] else
[*] {
[*] printf("删除子目录/Dir1/Dir1_1失败(错误代码 = %d) 文件只读或目录非空\r\n", result);
[*] }
[*]
[*] /* 先删除目录/Dir1 */
[*] sprintf(path, "%sDir1", DiskPath);
[*] result = f_unlink(path);
[*] if (result == FR_OK)
[*] {
[*] printf("删除目录Dir1成功\r\n");
[*] }
[*] else if (result == FR_NO_FILE)
[*] {
[*] printf("没有发现文件或目录 :%s\r\n", "/Dir1");
[*] }
[*] else
[*] {
[*] printf("删除Dir1失败(错误代码 = %d) 文件只读或目录非空\r\n", result);
[*] }
[*]
[*] /* 删除目录/Dir2 */
[*] sprintf(path, "%sDir2", DiskPath);
[*] result = f_unlink(path);
[*] if (result == FR_OK)
[*] {
[*] printf("删除目录 Dir2 成功\r\n");
[*] }
[*] else if (result == FR_NO_FILE)
[*] {
[*] printf("没有发现文件或目录 :%s\r\n", "/Dir2");
[*] }
[*] else
[*] {
[*] printf("删除Dir2 失败(错误代码 = %d) 文件只读或目录非空\r\n", result);
[*] }
[*]
[*] /* 删除文件 armfly.txt */
[*] sprintf(path, "%sarmfly.txt", DiskPath);
[*] result = f_unlink(path);
[*] if (result == FR_OK)
[*] {
[*] printf("删除文件 armfly.txt 成功\r\n");
[*] }
[*] else if (result == FR_NO_FILE)
[*] {
[*] printf("没有发现文件或目录 :%s\r\n", "armfly.txt");
[*] }
[*] else
[*] {
[*] printf("删除armfly.txt失败(错误代码 = %d) 文件只读或目录非空\r\n", result);
[*] }
[*]
[*] /* 删除文件 speed1.txt */
[*] for (i = 0; i < 20; i++)
[*] {
[*] sprintf(path, "%sSpeed%02d.txt", DiskPath, i);/* 每写1次,序号递增 */
[*] result = f_unlink(path);
[*] if (result == FR_OK)
[*] {
[*] printf("删除文件%s成功\r\n", path);
[*] }
[*] else if (result == FR_NO_FILE)
[*] {
[*] printf("没有发现文件:%s\r\n", path);
[*] }
[*] else
[*] {
[*] printf("删除%s文件失败(错误代码 = %d) 文件只读或目录非空\r\n", path, result);
[*] }
[*] }
[*]
[*] /* 卸载文件系统 */
[*] f_mount(NULL, DiskPath, 0);
[*]}
复制代码
f_mount可以上电后仅调用一次,我们这里是为了测试方式,使用前挂载,使用完毕后卸载。
文件夹和文件的删除都是通过函数f_unlink实现,这里注意一点,删除文件夹时,只有文件夹中的内容为空时,才可以删除文件夹。
88.8.7 SD卡读写速度测试
代码实现如下,主要是方便大家测试SD卡的读写性能。
[*]/*
[*]*********************************************************************************************************
[*]* 函 数 名: WriteFileTest
[*]* 功能说明: 测试文件读写速度
[*]* 形 参:无
[*]* 返 回 值: 无
[*]*********************************************************************************************************
[*]*/
[*]static void WriteFileTest(void)
[*]{
[*] FRESULT result;
[*] char path;
[*] uint32_t bw;
[*] uint32_t i,k;
[*] uint32_t runtime1,runtime2,timelen;
[*] uint8_t err = 0;
[*] static uint8_t s_ucTestSn = 0;
[*]
[*]
[*] for (i = 0; i < sizeof(g_TestBuf); i++)
[*] {
[*] g_TestBuf<i> = (i / 512) + '0';
[*] </i>}
[*]
[*] /* 挂载文件系统 */
[*] result = f_mount(&fs, DiskPath, 0); /* Mount a logical drive */
[*] if (result != FR_OK)
[*] {
[*] printf("挂载文件系统失败 (%s)\r\n", FR_Table);
[*] }
[*]
[*] /* 打开文件 */
[*] sprintf(path, "%sSpeed%02d.txt", DiskPath, s_ucTestSn++); /* 每写1次,序号递增 */
[*] result = f_open(&file, path, FA_CREATE_ALWAYS | FA_WRITE);
[*]
[*] /* 写一串数据 */
[*] printf("开始写文件%s %dKB ...\r\n", path, TEST_FILE_LEN / 1024);
[*]
[*] runtime1 = bsp_GetRunTime(); /* 读取系统运行时间 */
[*] for (i = 0; i < TEST_FILE_LEN / BUF_SIZE; i++)
[*] {
[*] result = f_write(&file, g_TestBuf, sizeof(g_TestBuf), &bw);
[*] if (result == FR_OK)
[*] {
[*] if (((i + 1) % 8) == 0)
[*] {
[*] printf(".");
[*] }
[*] }
[*] else
[*] {
[*] err = 1;
[*] printf("%s文件写失败\r\n", path);
[*] break;
[*] }
[*] }
[*] runtime2 = bsp_GetRunTime(); /* 读取系统运行时间 */
[*]
[*] if (err == 0)
[*] {
[*] timelen = (runtime2 - runtime1);
[*] printf("\r\n写耗时 : %dms 平均写速度 : %dB/S (%dKB/S)\r\n",
[*] timelen,
[*] (TEST_FILE_LEN * 1000) / timelen,
[*] ((TEST_FILE_LEN / 1024) * 1000) / timelen);
[*] }
[*]
[*] f_close(&file); /* 关闭文件*/
[*]
[*]
[*] /* 开始读文件测试 */
[*] result = f_open(&file, path, FA_OPEN_EXISTING | FA_READ);
[*] if (result !=FR_OK)
[*] {
[*] printf("没有找到文件: %s\r\n", path);
[*] return;
[*] }
[*]
[*] printf("开始读文件 %dKB ...\r\n", TEST_FILE_LEN / 1024);
[*]
[*] runtime1 = bsp_GetRunTime(); /* 读取系统运行时间 */
[*] for (i = 0; i < TEST_FILE_LEN / BUF_SIZE; i++)
[*] {
[*] result = f_read(&file, g_TestBuf, sizeof(g_TestBuf), &bw);
[*] if (result == FR_OK)
[*] {
[*] if (((i + 1) % 8) == 0)
[*] {
[*] printf(".");
[*] }
[*]
[*] /* 比较写入的数据是否正确,此语句会导致读卡速度结果降低到 3.5MBytes/S */
[*] for (k = 0; k < sizeof(g_TestBuf); k++)
[*] {
[*] if (g_TestBuf != (k / 512) + '0')
[*] {
[*] err = 1;
[*] printf("Speed1.txt 文件读成功,但是数据出错\r\n");
[*] break;
[*] }
[*] }
[*] if (err == 1)
[*] {
[*] break;
[*] }
[*] }
[*] else
[*] {
[*] err = 1;
[*] printf("Speed1.txt 文件读失败\r\n");
[*] break;
[*] }
[*] }
[*]
[*] runtime2 = bsp_GetRunTime(); /* 读取系统运行时间 */
[*]
[*] if (err == 0)
[*] {
[*] timelen = (runtime2 - runtime1);
[*] printf("\r\n读耗时 : %dms 平均读速度 : %dB/S (%dKB/S)\r\n", timelen,
[*] (TEST_FILE_LEN * 1000) / timelen, ((TEST_FILE_LEN / 1024) * 1000) / timelen);
[*] }
[*]
[*] /* 关闭文件*/
[*] f_close(&file);
[*]
[*] /* 卸载文件系统 */
[*] f_mount(NULL, DiskPath, 0);
[*]}
复制代码
f_mount可以上电后仅调用一次,我们这里是为了测试方式,使用前挂载,使用完毕后卸载。
为了实现更高性能的测试,大家可以加大宏定义
#define BUF_SIZE (4*1024) /* 每次读写SD卡的最大数据长度 */
设置的缓冲大小,比如设置为64KB进行测试。
88.9 FatFs移植接口文件diskio.c说明
这里将FatFs的底层接口文件diskio.c的实现为大家简单做个说明。
88.9.1 磁盘状态函数disk_status
代码如下:
[*]/**
[*]* @briefGets Disk Status
[*]* @parampdrv: Physical drive number (0..)
[*]* @retval DSTATUS: Operation status
[*]*/
[*]DSTATUS disk_status (
[*] BYTE pdrv /* Physical drive number to identify the drive */
[*])
[*]{
[*]DSTATUS stat;
[*]
[*]stat = disk.drv->disk_status(disk.lun);
[*]return stat;
[*]}
[*]实际对应的函数在文件sd_diskio_dma.c
[*]
[*]/**
[*]* @briefGets Disk Status
[*]* @paramlun : not used
[*]* @retval DSTATUS: Operation status
[*]*/
[*]DSTATUS SD_status(BYTE lun)
[*]{
[*]return SD_CheckStatus(lun);
[*]}
[*]
[*]static DSTATUS SD_CheckStatus(BYTE lun)
[*]{
[*]Stat = STA_NOINIT;
[*]
[*]if(BSP_SD_GetCardState() == MSD_OK)
[*]{
[*] Stat &= ~STA_NOINIT;
[*]}
[*]
[*]return Stat;
[*]}
复制代码
88.9.2 磁盘初始化函数disk_initialize
代码如下:
[*]/**
[*]* @briefInitializes a Drive
[*]* @parampdrv: Physical drive number (0..)
[*]* @retval DSTATUS: Operation status
[*]*/
[*]DSTATUS disk_initialize (
[*] BYTE pdrv /* Physical drive nmuber to identify the drive */
[*])
[*]{
[*]DSTATUS stat = RES_OK;
[*]
[*]if(disk.is_initialized == 0)
[*]{
[*] disk.is_initialized = 1;
[*] stat = disk.drv->disk_initialize(disk.lun);
[*]}
[*]return stat;
[*]}
[*]实际对应的函数在文件sd_diskio_dma.c:
[*]
[*]/**
[*]* @briefInitializes a Drive
[*]* @paramlun : not used
[*]* @retval DSTATUS: Operation status
[*]*/
[*]DSTATUS SD_initialize(BYTE lun)
[*]{
[*]#if !defined(DISABLE_SD_INIT)
[*]
[*]if(BSP_SD_Init() == MSD_OK)
[*]{
[*] Stat = SD_CheckStatus(lun);
[*]}
[*]
[*]#else
[*]Stat = SD_CheckStatus(lun);
[*]#endif
[*]return Stat;
[*]}
复制代码
88.9.3 磁盘读函数disk_read
代码如下:
[*]/**
[*]* @briefReads Sector(s)
[*]* @parampdrv: Physical drive number (0..)
[*]* @param*buff: Data buffer to store read data
[*]* @paramsector: Sector address (LBA)
[*]* @paramcount: Number of sectors to read (1..128)
[*]* @retval DRESULT: Operation result
[*]*/
[*]DRESULT disk_read (
[*] BYTE pdrv, /* Physical drive nmuber to identify the drive */
[*] BYTE *buff, /* Data buffer to store read data */
[*] DWORD sector, /* Sector address in LBA */
[*] UINT count /* Number of sectors to read */
[*])
[*]{
[*]DRESULT res;
[*]
[*]res = disk.drv->disk_read(disk.lun, buff, sector, count);
[*]return res;
[*]}
复制代码
实际对应的函数在文件sd_diskio_dma.c:
下面代码中最关键的处理是形参buff的4字节对齐问题(SDMMC自带的IDMA需要4字节对齐),如果buff地址是4字节对齐的,不做处理,如果不是对齐,通过复制到一个4字节对齐的缓冲里面做DMA传递。这个是理解下面代码的关键。
[*]/**
[*]* @briefReads Sector(s)
[*]* @paramlun : not used
[*]* @param*buff: Data buffer to store read data
[*]* @paramsector: Sector address (LBA)
[*]* @paramcount: Number of sectors to read (1..128)
[*]* @retval DRESULT: Operation result
[*]*/
[*]DRESULT SD_read(BYTE lun, BYTE *buff, DWORD sector, UINT count)
[*]{
[*] DRESULT res = RES_ERROR;
[*] uint32_t timeout;
[*] ReadStatus = 0;
[*]
[*] if (!((uint32_t)buff & 0x3))
[*] {
[*] if(BSP_SD_ReadBlocks_DMA((uint32_t*)buff,
[*] (uint32_t) (sector),
[*] count) == MSD_OK)
[*] {
[*] /* Wait that the reading process is completed or a timeout occurs */
[*] timeout = HAL_GetTick();
[*] while((ReadStatus == 0) && ((HAL_GetTick() - timeout) < SD_TIMEOUT))
[*] {
[*] }
[*]
[*] /* incase of a timeout return error */
[*] if (ReadStatus == 0)
[*] {
[*] res = RES_ERROR;
[*] }
[*] else
[*] {
[*] ReadStatus = 0;
[*] timeout = HAL_GetTick();
[*]
[*] while((HAL_GetTick() - timeout) < SD_TIMEOUT)
[*] {
[*] if (BSP_SD_GetCardState() == SD_TRANSFER_OK)
[*] {
[*] res = RES_OK;
[*]
[*] #if (ENABLE_SD_DMA_CACHE_MAINTENANCE_READ == 1)
[*] SCB_CleanInvalidateDCache();
[*] #endif
[*] break;
[*] }
[*] }
[*] }
[*] }
[*] }
[*] else
[*] {
[*] uint8_t ret;
[*] int i;
[*]
[*] for (i = 0; i < count; i++)
[*] {
[*]
[*] ret = BSP_SD_ReadBlocks_DMA((uint32_t*)scratch, (uint32_t)sector++, 1);
[*]
[*] if(ret == MSD_OK)
[*] {
[*] /* Wait that the reading process is completed or a timeout occurs */
[*] timeout = HAL_GetTick();
[*] while((ReadStatus == 0) && ((HAL_GetTick() - timeout) < SD_TIMEOUT))
[*] {
[*]
[*] }
[*] /* incase of a timeout return error */
[*] if (ReadStatus == 0)
[*] {
[*] break;
[*] }
[*] else
[*] {
[*] ReadStatus = 0;
[*] timeout = HAL_GetTick();
[*]
[*] while((HAL_GetTick() - timeout) < SD_TIMEOUT)
[*] {
[*] if (BSP_SD_GetCardState() == SD_TRANSFER_OK)
[*] {
[*] #if (ENABLE_SD_DMA_CACHE_MAINTENANCE_READ == 1)
[*] SCB_CleanInvalidateDCache();
[*] #endif
[*]
[*] memcpy(buff, scratch, BLOCKSIZE);
[*] buff += BLOCKSIZE;
[*]
[*] break;
[*] }
[*] }
[*] }
[*] }
[*] else
[*] {
[*] break;
[*] }
[*] }
[*] if ((i == count) && (ret == MSD_OK))
[*] {
[*] res = RES_OK;
[*] }
[*] }
[*] return res;
[*]}
复制代码
88.9.4 磁盘写函数disk_write
代码如下:
[*]/**
[*]* @briefWrites Sector(s)
[*]* @parampdrv: Physical drive number (0..)
[*]* @param*buff: Data to be written
[*]* @paramsector: Sector address (LBA)
[*]* @paramcount: Number of sectors to write (1..128)
[*]* @retval DRESULT: Operation result
[*]*/
[*]DRESULT disk_write (
[*] BYTE pdrv, /* Physical drive nmuber to identify the drive */
[*] const BYTE *buff, /* Data to be written */
[*] DWORD sector, /* Sector address in LBA */
[*] UINT count /* Number of sectors to write */
[*])
[*]{
[*]DRESULT res;
[*]
[*]res = disk.drv->disk_write(disk.lun, buff, sector, count);
[*]return res;
[*]}
复制代码
实际对应的函数在文件sd_diskio_dma.c:
下面代码中最关键的处理是形参buff的4字节对齐问题(SDMMC自带的IDMA需要4字节对齐),如果buff地址是4字节对齐的,不做处理,如果不是对齐,通过复制到一个4字节对齐的缓冲里面做DMA传递。这个是理解下面代码的关键。
[*]/**
[*]* @briefWrites Sector(s)
[*]* @paramlun : not used
[*]* @param*buff: Data to be written
[*]* @paramsector: Sector address (LBA)
[*]* @paramcount: Number of sectors to write (1..128)
[*]* @retval DRESULT: Operation result
[*]*/
[*]#if _USE_WRITE == 1
[*]DRESULT SD_write(BYTE lun, const BYTE *buff, DWORD sector, UINT count)
[*]{
[*] DRESULT res = RES_ERROR;
[*] uint32_t timeout;
[*] WriteStatus = 0;
[*]
[*]#if (ENABLE_SD_DMA_CACHE_MAINTENANCE_WRITE == 1)
[*] SCB_CleanInvalidateDCache();
[*]#endif
[*]
[*] if (!((uint32_t)buff & 0x3))
[*] {
[*] if(BSP_SD_WriteBlocks_DMA((uint32_t*)buff,
[*] (uint32_t)(sector),
[*] count) == MSD_OK)
[*] {
[*] /* Wait that writing process is completed or a timeout occurs */
[*] timeout = HAL_GetTick();
[*] while((WriteStatus == 0) && ((HAL_GetTick() - timeout) < SD_TIMEOUT))
[*] {
[*] }
[*]
[*] /* incase of a timeout return error */
[*] if (WriteStatus == 0)
[*] {
[*] res = RES_ERROR;
[*] }
[*] else
[*] {
[*] WriteStatus = 0;
[*] timeout = HAL_GetTick();
[*]
[*] while((HAL_GetTick() - timeout) < SD_TIMEOUT)
[*] {
[*] if (BSP_SD_GetCardState() == SD_TRANSFER_OK)
[*] {
[*] res = RES_OK;
[*] break;
[*] }
[*] }
[*] }
[*] }
[*] }
[*] else
[*] {
[*] int i;
[*] uint8_t ret;
[*]
[*] for (i = 0; i < count; i++)
[*] {
[*] WriteStatus = 0;
[*]
[*] memcpy((void *)scratch, (void *)buff, BLOCKSIZE);
[*] buff += BLOCKSIZE;
[*]
[*] ret = BSP_SD_WriteBlocks_DMA((uint32_t*)scratch, (uint32_t)sector++, 1);
[*] if(ret == MSD_OK)
[*] {
[*] /* Wait that writing process is completed or a timeout occurs */
[*]
[*] timeout = HAL_GetTick();
[*] while((WriteStatus == 0) && ((HAL_GetTick() - timeout) < SD_TIMEOUT))
[*] {
[*] }
[*]
[*] /* incase of a timeout return error */
[*] if (WriteStatus == 0)
[*] {
[*] break;
[*] }
[*] else
[*] {
[*] WriteStatus = 0;
[*] timeout = HAL_GetTick();
[*]
[*] while((HAL_GetTick() - timeout) < SD_TIMEOUT)
[*] {
[*] if (BSP_SD_GetCardState() == SD_TRANSFER_OK)
[*] {
[*] break;
[*] }
[*] }
[*] }
[*] }
[*] else
[*] {
[*] break;
[*] }
[*] }
[*]
[*] if ((i == count) && (ret == MSD_OK))
[*] {
[*] res = RES_OK;
[*] }
[*] }
[*]
[*] return res;
[*]}
复制代码
88.9.5 磁盘I/O控制函数disk_ioctl
代码如下:
[*]/**
[*]* @briefI/O control operation
[*]* @parampdrv: Physical drive number (0..)
[*]* @paramcmd: Control code
[*]* @param*buff: Buffer to send/receive control data
[*]* @retval DRESULT: Operation result
[*]*/
[*]#if _USE_IOCTL == 1
[*]DRESULT disk_ioctl (
[*] BYTE pdrv, /* Physical drive nmuber (0..) */
[*] BYTE cmd, /* Control code */
[*] void *buff /* Buffer to send/receive control data */
[*])
[*]{
[*]DRESULT res;
[*]
[*]res = disk.drv->disk_ioctl(disk.lun, cmd, buff);
[*]return res;
[*]}
[*]#endif /* _USE_IOCTL == 1 */
复制代码
实际对应的函数在文件sd_diskio_dma.c
特别注意,如果大家要调用FatFs的API格式化SD卡,此函数比较重要。下面几个cmd一定实现:
[*]/**
[*]* @briefI/O control operation
[*]* @paramlun : not used
[*]* @paramcmd: Control code
[*]* @param*buff: Buffer to send/receive control data
[*]* @retval DRESULT: Operation result
[*]*/
[*]#if _USE_IOCTL == 1
[*]DRESULT SD_ioctl(BYTE lun, BYTE cmd, void *buff)
[*]{
[*]DRESULT res = RES_ERROR;
[*]BSP_SD_CardInfo CardInfo;
[*]
[*]if (Stat & STA_NOINIT) return RES_NOTRDY;
[*]
[*]switch (cmd)
[*]{
[*]/* Make sure that no pending write process */
[*]case CTRL_SYNC :
[*] res = RES_OK;
[*] break;
[*]
[*]/* Get number of sectors on the disk (DWORD) */
[*]case GET_SECTOR_COUNT :
[*] BSP_SD_GetCardInfo(&CardInfo);
[*] *(DWORD*)buff = CardInfo.LogBlockNbr;
[*] res = RES_OK;
[*] break;
[*]
[*]/* Get R/W sector size (WORD) */
[*]case GET_SECTOR_SIZE :
[*] BSP_SD_GetCardInfo(&CardInfo);
[*] *(WORD*)buff = CardInfo.LogBlockSize;
[*] res = RES_OK;
[*] break;
[*]
[*]/* Get erase block size in unit of sector (DWORD) */
[*]case GET_BLOCK_SIZE :
[*] BSP_SD_GetCardInfo(&CardInfo);
[*] *(DWORD*)buff = CardInfo.LogBlockSize / SD_DEFAULT_BLOCK_SIZE;
[*] res = RES_OK;
[*] break;
[*]
[*]default:
[*] res = RES_PARERR;
[*]}
[*]
[*]return res;
[*]}
[*]#endif /* _USE_IOCTL == 1 */
复制代码
88.9.6 RTC时间获取函数get_fattime
我们这里未使用这个函数,此函数的作用是用户创建文件时,可以将创建文件时间设置为此函数的获取值
[*]/**
[*]* @briefGets Time from RTC
[*]* @paramNone
[*]* @retval Time in DWORD
[*]*/
[*]__weak DWORD get_fattime (void)
[*]{
[*]return 0;
[*]}
复制代码
本帖最后由 一刀一级 于 2021-12-21 16:56 编辑
88.10 SDMMC自带IDMA的4字节对齐问题(重要)
由于本章教程配套例子使用了SDMMC自带的IDMA,所以也专门做了4字节对齐处理。处理思路就是底层的读写函数里面如果地址是4字节对齐的,不做处理,如果不是对齐,通过复制到一个4字节对齐的缓冲里面做DMA传递。
其实有个更简单,性能也最高的解决办法,核心思想如下(ffconf.h文件里面设置的扇区大小基本都是512字节):
当要写入和读取的数据小于扇区大小时,会直接使用FATFS结构体里面的数组win做DMA写操作到,正好1个扇区大小。由于数组win的地址是4字节对齐的,所以无需做处理。
当要写入和读取的数据大于等于扇区大小时,扇区整数倍的地方将直接使用用户提供的收发缓冲区发送,而不足一个扇区的地方将使用FATFS结构体里面的数组。这种情况下用户要做的就是直接定义个4字节对齐的读写缓冲区即可。
针对本章教程配套的例子,我们直接做了32字节对齐,同时也方便了Cache处理:
[*]ALIGN_32BYTES(char FsReadBuf);
[*]ALIGN_32BYTES(char FsWriteBuf) = {"FatFS Write Demo \r\n \r\n"};
[*]ALIGN_32BYTES(uint8_t g_TestBuf);
复制代码
88.11 实验例程设计框架
通过程序设计框架,让大家先对配套例程有一个全面的认识,然后再理解细节,本次实验例程的设计框架如下:
第1阶段,上电启动阶段:
这部分在第14章进行了详细说明。
第2阶段,进入main函数:
第1步,硬件初始化,主要是MPU,Cache,HAL库,系统时钟,滴答定时器,LED和串口。
第2步,FatFs应用程序设计部分。
88.12 实验例程说明(MDK)
配套例子:
V7-025_FatFS文件系统例子(SD卡 V1.1)
实验目的:
学习SD卡的FatFS移植实现。
实验内容:
上电启动了一个软件定时器,每100ms翻转一次LED2。
V7开发板的SD卡接口是用的SDMMC1,而这个接口仅支持AXI SRAM区访问,其它SRAM和TCP均不支持。
实验操作:
测试前务必将SD卡插入到开发板左上角的卡座中。
支持以下6个功能,用户通过电脑端串口软件发送数字1-6给开发板即可
printf("1 - 显示根目录下的文件列表\r\n");
printf("2 - 创建一个新文件armfly.txt\r\n");
printf("3 - 读armfly.txt文件的内容\r\n");
printf("4 - 创建目录\r\n");
printf("5 - 删除文件和目录\r\n");
printf("6 - 读写文件速度测试\r\n");
上电后串口打印的信息:
波特率 115200,数据位 8,奇偶校验位无,停止位 1
程序设计:
系统栈大小分配:
RAM空间用的AXI SRAM:
硬件外设初始化
硬件外设的初始化是在 bsp.c 文件实现:
[*]/*
[*]*********************************************************************************************************
[*]* 函 数 名: bsp_Init
[*]* 功能说明: 初始化所有的硬件设备。该函数配置CPU寄存器和外设的寄存器并初始化一些全局变量。只需要调用一次
[*]* 形 参:无
[*]* 返 回 值: 无
[*]*********************************************************************************************************
[*]*/
[*]void bsp_Init(void)
[*]{
[*] /* 配置MPU */
[*] MPU_Config();
[*]
[*] /* 使能L1 Cache */
[*] CPU_CACHE_Enable();
[*]
[*] /*
[*] STM32H7xx HAL 库初始化,此时系统用的还是H7自带的64MHz,HSI时钟:
[*] - 调用函数HAL_InitTick,初始化滴答时钟中断1ms。
[*] - 设置NVIV优先级分组为4。
[*] */
[*] HAL_Init();
[*]
[*] /*
[*] 配置系统时钟到400MHz
[*] - 切换使用HSE。
[*] - 此函数会更新全局变量SystemCoreClock,并重新配置HAL_InitTick。
[*] */
[*] SystemClock_Config();
[*]
[*] /*
[*] Event Recorder:
[*] - 可用于代码执行时间测量,MDK5.25及其以上版本才支持,IAR不支持。
[*] - 默认不开启,如果要使能此选项,务必看V7开发板用户手册第xx章
[*] */
[*]#if Enable_EventRecorder == 1
[*] /* 初始化EventRecorder并开启 */
[*] EventRecorderInitialize(EventRecordAll, 1U);
[*] EventRecorderStart();
[*]#endif
[*]
[*] bsp_InitKey(); /* 按键初始化,要放在滴答定时器之前,因为按钮检测是通过滴答定时器扫描 */
[*] bsp_InitTimer(); /* 初始化滴答定时器 */
[*] bsp_InitUart(); /* 初始化串口 */
[*] bsp_InitExtIO(); /* 初始化FMC总线74HC574扩展IO. 必须在 bsp_InitLed()前执行 */
[*] bsp_InitLed(); /* 初始化LED */
[*]}
复制代码
MPU配置和Cache配置:
数据Cache和指令Cache都开启。配置了AXI SRAM区和FMC的扩展IO区。
[*]/*
[*]*********************************************************************************************************
[*]* 函 数 名: MPU_Config
[*]* 功能说明: 配置MPU
[*]* 形 参: 无
[*]* 返 回 值: 无
[*]*********************************************************************************************************
[*]*/
[*]static void MPU_Config( void )
[*]{
[*] MPU_Region_InitTypeDef MPU_InitStruct;
[*]
[*] /* 禁止 MPU */
[*] HAL_MPU_Disable();
[*]
[*]#if 0
[*] /* 配置AXI SRAM的MPU属性为Write back, Read allocate,Write allocate */
[*] MPU_InitStruct.Enable = MPU_REGION_ENABLE;
[*] MPU_InitStruct.BaseAddress = 0x24000000;
[*] MPU_InitStruct.Size = MPU_REGION_SIZE_512KB;
[*] MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
[*] MPU_InitStruct.IsBufferable = MPU_ACCESS_BUFFERABLE;
[*] MPU_InitStruct.IsCacheable = MPU_ACCESS_CACHEABLE;
[*] MPU_InitStruct.IsShareable = MPU_ACCESS_NOT_SHAREABLE;
[*] MPU_InitStruct.Number = MPU_REGION_NUMBER0;
[*] MPU_InitStruct.TypeExtField = MPU_TEX_LEVEL1;
[*] MPU_InitStruct.SubRegionDisable = 0x00;
[*] MPU_InitStruct.DisableExec = MPU_INSTRUCTION_ACCESS_ENABLE;
[*]
[*] HAL_MPU_ConfigRegion(&MPU_InitStruct);
[*]
[*]#else
[*] /* 当前是采用下面的配置 */
[*] /* 配置AXI SRAM的MPU属性为NORMAL, NO Read allocate,NO Write allocate */
[*] MPU_InitStruct.Enable = MPU_REGION_ENABLE;
[*] MPU_InitStruct.BaseAddress = 0x24000000;
[*] MPU_InitStruct.Size = MPU_REGION_SIZE_512KB;
[*] MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
[*] MPU_InitStruct.IsBufferable = MPU_ACCESS_NOT_BUFFERABLE;
[*] MPU_InitStruct.IsCacheable = MPU_ACCESS_NOT_CACHEABLE;
[*] MPU_InitStruct.IsShareable = MPU_ACCESS_NOT_SHAREABLE;
[*] MPU_InitStruct.Number = MPU_REGION_NUMBER0;
[*] MPU_InitStruct.TypeExtField = MPU_TEX_LEVEL1;
[*] MPU_InitStruct.SubRegionDisable = 0x00;
[*] MPU_InitStruct.DisableExec = MPU_INSTRUCTION_ACCESS_ENABLE;
[*]
[*] HAL_MPU_ConfigRegion(&MPU_InitStruct);
[*]#endif
[*] /* 配置FMC扩展IO的MPU属性为Device或者Strongly Ordered */
[*] MPU_InitStruct.Enable = MPU_REGION_ENABLE;
[*] MPU_InitStruct.BaseAddress = 0x60000000;
[*] MPU_InitStruct.Size = ARM_MPU_REGION_SIZE_64KB;
[*] MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
[*] MPU_InitStruct.IsBufferable = MPU_ACCESS_BUFFERABLE;
[*] MPU_InitStruct.IsCacheable = MPU_ACCESS_NOT_CACHEABLE;
[*] MPU_InitStruct.IsShareable = MPU_ACCESS_NOT_SHAREABLE;
[*] MPU_InitStruct.Number = MPU_REGION_NUMBER1;
[*] MPU_InitStruct.TypeExtField = MPU_TEX_LEVEL0;
[*] MPU_InitStruct.SubRegionDisable = 0x00;
[*] MPU_InitStruct.DisableExec = MPU_INSTRUCTION_ACCESS_ENABLE;
[*]
[*] HAL_MPU_ConfigRegion(&MPU_InitStruct);
[*]
[*] /*使能 MPU */
[*] HAL_MPU_Enable(MPU_PRIVILEGED_DEFAULT);
[*]}
[*]
[*]/*
[*]*********************************************************************************************************
[*]* 函 数 名: CPU_CACHE_Enable
[*]* 功能说明: 使能L1 Cache
[*]* 形 参: 无
[*]* 返 回 值: 无
[*]*********************************************************************************************************
[*]*/
[*]static void CPU_CACHE_Enable(void)
[*]{
[*] /* 使能 I-Cache */
[*] SCB_EnableICache();
[*]
[*] /* 使能 D-Cache */
[*] SCB_EnableDCache();
[*]}
复制代码
主功能:
主程序实现如下操作:
上电启动了一个软件定时器,每100ms翻转一次LED2。
支持以下6个功能,用户通过电脑端串口软件发送数字给开发板即可:
1 - 显示根目录下的文件列表
2 - 创建一个新文件armfly.txt
3 - 读armfly.txt文件的内容
4 - 创建目录
5 - 删除文件和目录
6 - 读写文件速度测试
[*]/*
[*]*********************************************************************************************************
[*]* 函 数 名: main
[*]* 功能说明: c程序入口
[*]* 形 参: 无
[*]* 返 回 值: 错误代码(无需处理)
[*]*********************************************************************************************************
[*]*/
[*]int main(void)
[*]{
[*] bsp_Init(); /* 硬件初始化 */
[*]
[*] Printf**(); /* 打印例程名称和版本等信息 */
[*]
[*] DemoFatFS(); /* SD卡测试 */
[*]}
[*]
[*]/*
[*]*********************************************************************************************************
[*]* 函 数 名: DemoFatFS
[*]* 功能说明: FatFS文件系统演示主程序
[*]* 形 参: 无
[*]* 返 回 值: 无
[*]*********************************************************************************************************
[*]*/
[*]void DemoFatFS(void)
[*]{
[*] uint8_t cmd;
[*]
[*] /* 打印命令列表,用户可以通过串口操作指令 */
[*] DispMenu();
[*]
[*] /* 注册SD卡驱动 */
[*] FATFS_LinkDriver(&SD_Driver, DiskPath);
[*]
[*] bsp_StartAutoTimer(0, 500); /* 启动1个500ms的自动重装的定时器 */
[*]
[*] while (1)
[*] {
[*]
[*] /* 判断定时器超时时间 */
[*] if (bsp_CheckTimer(0))
[*] {
[*] /* 每隔500ms 进来一次 */
[*] bsp_LedToggle(2);
[*] }
[*]
[*] if (comGetChar(COM1, &cmd)) /* 从串口读入一个字符(非阻塞方式) */
[*] {
[*] printf("\r\n");
[*] switch (cmd)
[*] {
[*] case '1':
[*] printf("【1 - ViewRootDir】\r\n");
[*] ViewRootDir(); /* 显示SD卡根目录下的文件名 */
[*] break;
[*]
[*] case '2':
[*] printf("【2 - CreateNewFile】\r\n");
[*] CreateNewFile(); /* 创建一个新文件,写入一个字符串 */
[*] break;
[*]
[*] case '3':
[*] printf("【3 - ReadFileData】\r\n");
[*] ReadFileData(); /* 读取根目录下armfly.txt的内容 */
[*] break;
[*]
[*] case '4':
[*] printf("【4 - CreateDir】\r\n");
[*] CreateDir(); /* 创建目录 */
[*] break;
[*]
[*] case '5':
[*] printf("【5 - DeleteDirFile】\r\n");
[*] DeleteDirFile(); /* 删除目录和文件 */
[*] break;
[*]
[*] case '6':
[*] printf("【6 - TestSpeed】\r\n");
[*] WriteFileTest(); /* 速度测试 */
[*] break;
[*]
[*] default:
[*] DispMenu();
[*] break;
[*] }
[*] }
[*] }
[*]}
复制代码
88.13 实验例程说明(IAR)
配套例子:
V7-025_FatFS文件系统例子(SD卡 V1.1)
实验目的:
学习SD卡的FatFS移植实现。
实验内容:
上电启动了一个软件定时器,每100ms翻转一次LED2。
V7开发板的SD卡接口是用的SDMMC1,而这个接口仅支持AXI SRAM区访问,其它SRAM和TCP均不支持。
实验操作:
测试前务必将SD卡插入到开发板左上角的卡座中。
支持以下6个功能,用户通过电脑端串口软件发送数字1-6给开发板即可
printf("1 - 显示根目录下的文件列表\r\n");
printf("2 - 创建一个新文件armfly.txt\r\n");
printf("3 - 读armfly.txt文件的内容\r\n");
printf("4 - 创建目录\r\n");
printf("5 - 删除文件和目录\r\n");
printf("6 - 读写文件速度测试\r\n");
上电后串口打印的信息:
波特率 115200,数据位 8,奇偶校验位无,停止位 1
程序设计:
系统栈大小分配:
RAM空间用的AXI SRAM:
硬件外设初始化
硬件外设的初始化是在 bsp.c 文件实现:
[*]/*
[*]*********************************************************************************************************
[*]* 函 数 名: bsp_Init
[*]* 功能说明: 初始化所有的硬件设备。该函数配置CPU寄存器和外设的寄存器并初始化一些全局变量。只需要调用一次
[*]* 形 参:无
[*]* 返 回 值: 无
[*]*********************************************************************************************************
[*]*/
[*]void bsp_Init(void)
[*]{
[*] /* 配置MPU */
[*] MPU_Config();
[*]
[*] /* 使能L1 Cache */
[*] CPU_CACHE_Enable();
[*]
[*] /*
[*] STM32H7xx HAL 库初始化,此时系统用的还是H7自带的64MHz,HSI时钟:
[*] - 调用函数HAL_InitTick,初始化滴答时钟中断1ms。
[*] - 设置NVIV优先级分组为4。
[*] */
[*] HAL_Init();
[*]
[*] /*
[*] 配置系统时钟到400MHz
[*] - 切换使用HSE。
[*] - 此函数会更新全局变量SystemCoreClock,并重新配置HAL_InitTick。
[*] */
[*] SystemClock_Config();
[*]
[*] /*
[*] Event Recorder:
[*] - 可用于代码执行时间测量,MDK5.25及其以上版本才支持,IAR不支持。
[*] - 默认不开启,如果要使能此选项,务必看V7开发板用户手册第xx章
[*] */
[*]#if Enable_EventRecorder == 1
[*] /* 初始化EventRecorder并开启 */
[*] EventRecorderInitialize(EventRecordAll, 1U);
[*] EventRecorderStart();
[*]#endif
[*]
[*] bsp_InitKey(); /* 按键初始化,要放在滴答定时器之前,因为按钮检测是通过滴答定时器扫描 */
[*] bsp_InitTimer(); /* 初始化滴答定时器 */
[*] bsp_InitUart(); /* 初始化串口 */
[*] bsp_InitExtIO(); /* 初始化FMC总线74HC574扩展IO. 必须在 bsp_InitLed()前执行 */
[*] bsp_InitLed(); /* 初始化LED */
[*]}
复制代码
MPU配置和Cache配置:
数据Cache和指令Cache都开启。配置了AXI SRAM区和FMC的扩展IO区。
[*]/*
[*]*********************************************************************************************************
[*]* 函 数 名: MPU_Config
[*]* 功能说明: 配置MPU
[*]* 形 参: 无
[*]* 返 回 值: 无
[*]*********************************************************************************************************
[*]*/
[*]static void MPU_Config( void )
[*]{
[*] MPU_Region_InitTypeDef MPU_InitStruct;
[*]
[*] /* 禁止 MPU */
[*] HAL_MPU_Disable();
[*]
[*]#if 0
[*] /* 配置AXI SRAM的MPU属性为Write back, Read allocate,Write allocate */
[*] MPU_InitStruct.Enable = MPU_REGION_ENABLE;
[*] MPU_InitStruct.BaseAddress = 0x24000000;
[*] MPU_InitStruct.Size = MPU_REGION_SIZE_512KB;
[*] MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
[*] MPU_InitStruct.IsBufferable = MPU_ACCESS_BUFFERABLE;
[*] MPU_InitStruct.IsCacheable = MPU_ACCESS_CACHEABLE;
[*] MPU_InitStruct.IsShareable = MPU_ACCESS_NOT_SHAREABLE;
[*] MPU_InitStruct.Number = MPU_REGION_NUMBER0;
[*] MPU_InitStruct.TypeExtField = MPU_TEX_LEVEL1;
[*] MPU_InitStruct.SubRegionDisable = 0x00;
[*] MPU_InitStruct.DisableExec = MPU_INSTRUCTION_ACCESS_ENABLE;
[*]
[*] HAL_MPU_ConfigRegion(&MPU_InitStruct);
[*]
[*]#else
[*] /* 当前是采用下面的配置 */
[*] /* 配置AXI SRAM的MPU属性为NORMAL, NO Read allocate,NO Write allocate */
[*] MPU_InitStruct.Enable = MPU_REGION_ENABLE;
[*] MPU_InitStruct.BaseAddress = 0x24000000;
[*] MPU_InitStruct.Size = MPU_REGION_SIZE_512KB;
[*] MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
[*] MPU_InitStruct.IsBufferable = MPU_ACCESS_NOT_BUFFERABLE;
[*] MPU_InitStruct.IsCacheable = MPU_ACCESS_NOT_CACHEABLE;
[*] MPU_InitStruct.IsShareable = MPU_ACCESS_NOT_SHAREABLE;
[*] MPU_InitStruct.Number = MPU_REGION_NUMBER0;
[*] MPU_InitStruct.TypeExtField = MPU_TEX_LEVEL1;
[*] MPU_InitStruct.SubRegionDisable = 0x00;
[*] MPU_InitStruct.DisableExec = MPU_INSTRUCTION_ACCESS_ENABLE;
[*]
[*] HAL_MPU_ConfigRegion(&MPU_InitStruct);
[*]#endif
[*] /* 配置FMC扩展IO的MPU属性为Device或者Strongly Ordered */
[*] MPU_InitStruct.Enable = MPU_REGION_ENABLE;
[*] MPU_InitStruct.BaseAddress = 0x60000000;
[*] MPU_InitStruct.Size = ARM_MPU_REGION_SIZE_64KB;
[*] MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
[*] MPU_InitStruct.IsBufferable = MPU_ACCESS_BUFFERABLE;
[*] MPU_InitStruct.IsCacheable = MPU_ACCESS_NOT_CACHEABLE;
[*] MPU_InitStruct.IsShareable = MPU_ACCESS_NOT_SHAREABLE;
[*] MPU_InitStruct.Number = MPU_REGION_NUMBER1;
[*] MPU_InitStruct.TypeExtField = MPU_TEX_LEVEL0;
[*] MPU_InitStruct.SubRegionDisable = 0x00;
[*] MPU_InitStruct.DisableExec = MPU_INSTRUCTION_ACCESS_ENABLE;
[*]
[*] HAL_MPU_ConfigRegion(&MPU_InitStruct);
[*]
[*] /*使能 MPU */
[*] HAL_MPU_Enable(MPU_PRIVILEGED_DEFAULT);
[*]}
[*]
[*]/*
[*]*********************************************************************************************************
[*]* 函 数 名: CPU_CACHE_Enable
[*]* 功能说明: 使能L1 Cache
[*]* 形 参: 无
[*]* 返 回 值: 无
[*]*********************************************************************************************************
[*]*/
[*]static void CPU_CACHE_Enable(void)
[*]{
[*] /* 使能 I-Cache */
[*] SCB_EnableICache();
[*]
[*] /* 使能 D-Cache */
[*] SCB_EnableDCache();
[*]}
复制代码
主功能:
主程序实现如下操作:
上电启动了一个软件定时器,每100ms翻转一次LED2。
支持以下6个功能,用户通过电脑端串口软件发送数字给开发板即可:
1 - 显示根目录下的文件列表
2 - 创建一个新文件armfly.txt
3 - 读armfly.txt文件的内容
4 - 创建目录
5 - 删除文件和目录
6 - 读写文件速度测试
[*]/*
[*]*********************************************************************************************************
[*]* 函 数 名: main
[*]* 功能说明: c程序入口
[*]* 形 参: 无
[*]* 返 回 值: 错误代码(无需处理)
[*]*********************************************************************************************************
[*]*/
[*]int main(void)
[*]{
[*] bsp_Init(); /* 硬件初始化 */
[*]
[*] Printf**(); /* 打印例程名称和版本等信息 */
[*]
[*] DemoFatFS(); /* SD卡测试 */
[*]}
[*]
[*]/*
[*]*********************************************************************************************************
[*]* 函 数 名: DemoFatFS
[*]* 功能说明: FatFS文件系统演示主程序
[*]* 形 参: 无
[*]* 返 回 值: 无
[*]*********************************************************************************************************
[*]*/
[*]void DemoFatFS(void)
[*]{
[*] uint8_t cmd;
[*]
[*] /* 打印命令列表,用户可以通过串口操作指令 */
[*] DispMenu();
[*]
[*] /* 注册SD卡驱动 */
[*] FATFS_LinkDriver(&SD_Driver, DiskPath);
[*]
[*] bsp_StartAutoTimer(0, 500); /* 启动1个500ms的自动重装的定时器 */
[*]
[*] while (1)
[*] {
[*]
[*] /* 判断定时器超时时间 */
[*] if (bsp_CheckTimer(0))
[*] {
[*] /* 每隔500ms 进来一次 */
[*] bsp_LedToggle(2);
[*] }
[*]
[*] if (comGetChar(COM1, &cmd)) /* 从串口读入一个字符(非阻塞方式) */
[*] {
[*] printf("\r\n");
[*] switch (cmd)
[*] {
[*] case '1':
[*] printf("【1 - ViewRootDir】\r\n");
[*] ViewRootDir(); /* 显示SD卡根目录下的文件名 */
[*] break;
[*]
[*] case '2':
[*] printf("【2 - CreateNewFile】\r\n");
[*] CreateNewFile(); /* 创建一个新文件,写入一个字符串 */
[*] break;
[*]
[*] case '3':
[*] printf("【3 - ReadFileData】\r\n");
[*] ReadFileData(); /* 读取根目录下armfly.txt的内容 */
[*] break;
[*]
[*] case '4':
[*] printf("【4 - CreateDir】\r\n");
[*] CreateDir(); /* 创建目录 */
[*] break;
[*]
[*] case '5':
[*] printf("【5 - DeleteDirFile】\r\n");
[*] DeleteDirFile(); /* 删除目录和文件 */
[*] break;
[*]
[*] case '6':
[*] printf("【6 - TestSpeed】\r\n");
[*] WriteFileTest(); /* 速度测试 */
[*] break;
[*]
[*] default:
[*] DispMenu();
[*] break;
[*] }
[*] }
[*] }
[*]}
复制代码
88.14 总结
本章节就为大家讲解这么多,需要大家实现操作一遍来熟练掌握FatFs的移植,然后FatFs相关的知识点可以到FatFs官网查看,资料非常详细。
一般都是用来SD卡,SD I/O 卡,MMC卡进行通讯。
移植FATFS文件系统的具体步骤 现在想移植FATFS文件系统 SD卡在fatfs系统中如何处理? SD卡在fatfs系统中如何处理? 没有教程吗 sdio写入sd卡速度快还是spi速度快. 在UCOSII中移植FATFS 如何更改应用储存路径? FatFS是一种开源的文件系统格式,移植方便,容易使用 FatFs文件系统在SD卡驱动上的移植