你的浏览器版本过低,可能导致网站不能正常访问!
为了你能正常使用网站功能,请使用这些浏览器。

【经验分享】STM32H7的SDMMC总线应用之SD卡移植FatFs文件系统

[复制链接]
STMCU小助手 发布时间:2021-11-6 23:41
88.1 初学者重要提示
1、  SDMMC的相关知识点可以看第87章。

2、  操作SD卡是以扇区(512字节)为单位进行操作。

3、  SD卡联盟强烈强烈建议使用此软件来格式化SD/SDHC/SDXC卡,而不要使用各个操作系统随附的格式化工具。通常,操作系统附带的格式化工具可以格式化包括SD/SDHC/SDXC卡在内的各种存储介质,但是可能无法针对SD/SDHC/SDXC卡进行优化,并且可能导致性能降低。

4、 STM32H7的SDMMC也支持eMMC:

5、  支持128GB的大容量SD卡,需要大家使能FatFS的exFAT即可。

6、  FatFs的挂载函数f_mount可以上电后仅调用一次,本章配套例子为了测试方式,使用前挂载,使用完毕后卸载。

88.2 SD卡硬件接口设计
STM32H7驱动SD卡设计如下:

f9de6ca407a3e3d5c8d7e0c58a07bbf0.png


关于这个原理图,要了解到以下几个知识:

  大家自己设计推荐也接上拉电阻。
  这里采用SDMMC的4线方式。
88.3 SD卡基础知识
这里将SD卡相关的基础知识为大家做个普及。

88.3.1 SD卡分类
根据不同容量做的区分,主要包括Full SD,miniSD和microSD。

88e145c519b05bcafc12769f81df4dda.png


88.3.2 SD卡容量及其使用的文件系统
容量小于2GB(SD卡)使用FAT12或者FAT16,容量在2GB和32GB之间(SDHC卡)使用FAT32,容量大于32GB小于2TB(SDXC卡)使用exFAT。

70958713d18b7d564f10529af08024b2.png


88.3.3 SD卡总线速度和速度等级
SD卡速度:

78db713dea29b15c2522fa9ebfe40300.png


SD卡速度等级:

6fedab7e885b7953d8207dfda203f7f1.png


88.4 各种存储卡区别
市面上的卡种类非常多,容易把人搞糊涂,这里将这些卡种类为大家做个区分:

88.4.1 SD卡,miniSD卡,TF卡,MircoSD卡
TF卡是MicroSD卡的另一种叫法,无需做区分。SD卡,miniSD卡,MircoSD卡其实是一种卡,区别是引脚使用上。

5a2beb00f362d55e9eaa977991b173b5.png


7b1bd7c48fb94b253cf761008580441a.png


3fce2ad94bbc173bef248d0225d019cc.png


b992722aadfe2f02a1ad471509905dd3.png


88.4.2 SDIO卡
SDIO卡就是使用SDIO外设来接SD卡。

而为什么叫SDIO,根据wiki百科说明,其实就是SD卡接口规范的扩展,带了输入输出功能,这个接口不仅可以接SD卡,还可以接其它外设,如条形码读卡器,WiFi,蓝牙,调制解调器等。

对于STM32的SDIO来说,他就是指STM32的一个外设接口,不仅能够来接SD卡,还可以接其它外设。

a1ed06eacbb686fc342bbb6ef015fae1.png


88.4.3 MMC卡,eMMC
截止2018年,市场上已经没有设备内置MMC卡槽,但eMMC得到了广泛应用。

88.4.4 CF卡
CF卡是早期最成功的存储卡格式之一,像MMC/SD卡都是后来才推出的。CF卡仍然很受欢迎卡之一,并得到许多专业设备和高端消费类设备的支持。截至2017年,佳能和尼康都将CompactFlash用于其旗舰数码相机。佳能还选择了CompactFlash作为其专业高清无带摄像机的记录介质。

基础规格:

24e8bd6dbfb6f27f6dd40b4d9985a73f.png


实际效果:

8320a136f7ab18f04294c33d507cb83c.png


67b7260edb63383caa197a218da700a9.png


88.4.5 总体区别

40b434d39f7d5e29ac2a225049304bc1.png



88.5 关于SD卡内部是否自带擦写均衡
根据网上搜的一个闪迪的规格书,里面说是带擦写均衡的:

4c171fc75703c1cb96f4a9dc14f9a4fc.png


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缓冲区等

4739625032dd615e9cf5295f169d22b5.png c9d240ff5c3314203e15a7f2c2398cb6.png af909cc0071025486c5552087de940aa.png



FatFS的官网的资料介绍非常详细,大家哪里不清楚的都可以找到说明,如果打算使用FatFS,建议把官方的这些资料了解下:

88.7 FatFs移植步骤
这里将FatFs的移植步骤为大家做个说明。

FatFs各个文件的依赖关系:

807228c98df7c11e0a8a69d06cbd72f9.png


驱动一个磁盘或者多个磁盘的框图:

c8dec97b59a457f8897c09372b6342c9.png


88.7.1 第1步,了解整体设计框架
为了方便大家移植,需要大家先对移植好的工程有个整体认识:

d43a8092fed32131742af8aa4423400d.png


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路径,大家根据自己添加的源文件位置,添加相关路径即可:

aa5aedecadc53be8f5ea15ca9ed13274.png


88.7.4 第4步,配置GPIO和时钟
根据大家使用SDMMC1或者SDMMC2配置相应时钟和GPIO,当前V7板子是用的SDMMC1:

  1. __weak void BSP_SD_MspInit(SD_HandleTypeDef *hsd, void *Params)
  2. {
  3.   GPIO_InitTypeDef gpio_init_structure;

  4.   /* Enable SDIO clock */
  5.   __HAL_RCC_SDMMC1_CLK_ENABLE();

  6.   /* Enable GPIOs clock */
  7.   __HAL_RCC_GPIOB_CLK_ENABLE();
  8.   __HAL_RCC_GPIOC_CLK_ENABLE();
  9.   __HAL_RCC_GPIOD_CLK_ENABLE();

  10.   gpio_init_structure.Mode      = GPIO_MODE_AF_PP;
  11.   gpio_init_structure.Pull      = GPIO_NOPULL;
  12.   gpio_init_structure.Speed     = GPIO_SPEED_FREQ_VERY_HIGH;

  13.   /* D0(PC8), D1(PC9), D2(PC10), D3(PC11), CK(PC12), CMD(PD2) */
  14.   /* Common GPIO configuration */
  15.   gpio_init_structure.Alternate = GPIO_AF12_SDIO1;

  16.   /* GPIOC configuration */
  17.   gpio_init_structure.Pin = GPIO_PIN_8 | GPIO_PIN_9 | GPIO_PIN_10 | GPIO_PIN_11 | GPIO_PIN_12;
  18.   HAL_GPIO_Init(GPIOC, &gpio_init_structure);

  19.   /* GPIOD configuration */
  20.   gpio_init_structure.Pin = GPIO_PIN_2;
  21.   HAL_GPIO_Init(GPIOD, &gpio_init_structure);

  22.   __HAL_RCC_SDMMC1_FORCE_RESET();
  23.   __HAL_RCC_SDMMC1_RELEASE_RESET();

  24.   /* NVIC configuration for SDIO interrupts */
  25.   HAL_NVIC_SetPriority(SDMMC1_IRQn, 5, 0);
  26.   HAL_NVIC_EnableIRQ(SDMMC1_IRQn);
  27. }
复制代码

88.7.5 第5步,MPU配置
为了方便大家移植测试,我们这里直接关闭AXI SRAM的读Cache和写Cache(这样就跟使用STM32F1或者STM32F4系列里面的SRAM一样)。此配置是在bsp.c文件的MPU_Config函数里面实现:

  1. /*
  2. *********************************************************************************************************
  3. *    函 数 名: MPU_Config
  4. *    功能说明: 配置MPU
  5. *    形    参: 无
  6. *    返 回 值: 无
  7. *********************************************************************************************************
  8. */
  9. static void MPU_Config( void )
  10. {
  11.     MPU_Region_InitTypeDef MPU_InitStruct;

  12.     /* 禁止 MPU */
  13.     HAL_MPU_Disable();

  14. #if 0
  15.        /* 配置AXI SRAM的MPU属性为Write back, Read allocate,Write allocate */
  16.     MPU_InitStruct.Enable           = MPU_REGION_ENABLE;
  17.     MPU_InitStruct.BaseAddress      = 0x24000000;
  18.     MPU_InitStruct.Size             = MPU_REGION_SIZE_512KB;
  19.     MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
  20.     MPU_InitStruct.IsBufferable     = MPU_ACCESS_BUFFERABLE;
  21.     MPU_InitStruct.IsCacheable      = MPU_ACCESS_CACHEABLE;
  22.     MPU_InitStruct.IsShareable      = MPU_ACCESS_NOT_SHAREABLE;
  23.     MPU_InitStruct.Number           = MPU_REGION_NUMBER0;
  24.     MPU_InitStruct.TypeExtField     = MPU_TEX_LEVEL1;
  25.     MPU_InitStruct.SubRegionDisable = 0x00;
  26.     MPU_InitStruct.DisableExec      = MPU_INSTRUCTION_ACCESS_ENABLE;

  27.     HAL_MPU_ConfigRegion(&MPU_InitStruct);

  28. #else
  29.      /* 当前是采用下面的配置 */
  30.     /* 配置AXI SRAM的MPU属性为NORMAL, NO Read allocate,NO Write allocate */
  31.     MPU_InitStruct.Enable           = MPU_REGION_ENABLE;
  32.     MPU_InitStruct.BaseAddress      = 0x24000000;
  33.     MPU_InitStruct.Size             = MPU_REGION_SIZE_512KB;
  34.     MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
  35.     MPU_InitStruct.IsBufferable     = MPU_ACCESS_NOT_BUFFERABLE;
  36.     MPU_InitStruct.IsCacheable      = MPU_ACCESS_NOT_CACHEABLE;
  37.     MPU_InitStruct.IsShareable      = MPU_ACCESS_NOT_SHAREABLE;
  38.     MPU_InitStruct.Number           = MPU_REGION_NUMBER0;
  39.     MPU_InitStruct.TypeExtField     = MPU_TEX_LEVEL1;
  40.     MPU_InitStruct.SubRegionDisable = 0x00;
  41.     MPU_InitStruct.DisableExec      = MPU_INSTRUCTION_ACCESS_ENABLE;

  42.     HAL_MPU_ConfigRegion(&MPU_InitStruct);
  43. #endif
  44.     /* 配置FMC扩展IO的MPU属性为Device或者Strongly Ordered */
  45.     MPU_InitStruct.Enable           = MPU_REGION_ENABLE;
  46.     MPU_InitStruct.BaseAddress      = 0x60000000;
  47.     MPU_InitStruct.Size             = ARM_MPU_REGION_SIZE_64KB;   
  48.     MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
  49.     MPU_InitStruct.IsBufferable     = MPU_ACCESS_BUFFERABLE;
  50.     MPU_InitStruct.IsCacheable      = MPU_ACCESS_NOT_CACHEABLE;
  51.     MPU_InitStruct.IsShareable      = MPU_ACCESS_NOT_SHAREABLE;
  52.     MPU_InitStruct.Number           = MPU_REGION_NUMBER1;
  53.     MPU_InitStruct.TypeExtField     = MPU_TEX_LEVEL0;
  54.     MPU_InitStruct.SubRegionDisable = 0x00;
  55.     MPU_InitStruct.DisableExec      = MPU_INSTRUCTION_ACCESS_ENABLE;

  56.     HAL_MPU_ConfigRegion(&MPU_InitStruct);

  57.     /*使能 MPU */
  58.     HAL_MPU_Enable(MPU_PRIVILEGED_DEFAULT);
  59. }
复制代码

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驱动多个磁盘。

代码如下:

  1. char DiskPath[4]; /* SD卡逻辑驱动路径,比盘符0,就是"0:/" */
  2. /* 注册SD卡驱动 */
  3. FATFS_LinkDriver(&SD_Driver, DiskPath);
  4. 88.8.2 SD卡文件浏览
  5. SD卡根目录的文件浏览代码实现如下:

  6. /*
  7. *********************************************************************************************************
  8. *    函 数 名: ViewRootDir
  9. *    功能说明: 显示SD卡根目录下的文件名
  10. *    形    参:无
  11. *    返 回 值: 无
  12. *********************************************************************************************************
  13. */
  14. extern SD_HandleTypeDef uSdHandle;
  15. static void ViewRootDir(void)
  16. {
  17.     FRESULT result;
  18.     uint32_t cnt = 0;
  19.     FILINFO fno;

  20.      /* 挂载文件系统 */
  21.     result = f_mount(&fs, DiskPath, 0);    /* Mount a logical drive */
  22.     if (result != FR_OK)
  23.     {
  24.         printf("挂载文件系统失败 (%s)\r\n", FR_Table[result]);
  25.     }

  26.     /* 打开根文件夹 */
  27.     result = f_opendir(&DirInf, DiskPath); /* 如果不带参数,则从当前目录开始 */
  28.     if (result != FR_OK)
  29.     {
  30.         printf("打开根目录失败  (%s)\r\n", FR_Table[result]);
  31.         return;
  32.     }

  33.     printf("属性        |  文件大小 | 短文件名 | 长文件名\r\n");
  34.     for (cnt = 0; ;cnt++)
  35.     {
  36.         result = f_readdir(&DirInf, &FileInf);         /* 读取目录项,索引会自动下移 */
  37.         if (result != FR_OK || FileInf.fname[0] == 0)
  38.         {
  39.             break;
  40.         }

  41.         if (FileInf.fname[0] == '.')
  42.         {
  43.             continue;
  44.         }

  45.         /* 判断是文件还是子目录 */
  46.         if (FileInf.fattrib & AM_DIR)
  47.         {
  48.             printf("(0x%02d)目录  ", FileInf.fattrib);
  49.         }
  50.         else
  51.         {
  52.             printf("(0x%02d)文件  ", FileInf.fattrib);
  53.         }

  54.         f_stat(FileInf.fname, &fno);

  55.         /* 打印文件大小, 最大4G */
  56.         printf(" %10d", (int)fno.fsize);


  57.         printf("  %s\r\n", (char *)FileInf.fname);    /* 长文件名 */
  58.     }

  59.     /* 打印卡速度信息 */
  60.     if(uSdHandle.SdCard.CardSpeed == CARD_NORMAL_SPEED)
  61.     {
  62.         printf("Normal Speed Card <12.5MB/S, MAX Clock < 25MHz, Spec Version 1.01\r\n");           
  63.     }
  64.     else if (uSdHandle.SdCard.CardSpeed == CARD_HIGH_SPEED)
  65.     {
  66.         printf("High Speed Card <25MB/s, MAX Clock < 50MHz, Spec Version 2.00\r\n");            
  67.     }
  68.     else if (uSdHandle.SdCard.CardSpeed == CARD_ULTRA_HIGH_SPEED)
  69.     {
  70.         printf("UHS-I SD Card <50MB/S for SDR50, DDR50 Cards, MAX Clock < 50MHz OR 100MHz\r\n");
  71.         printf("UHS-I SD Card <104MB/S for SDR104, MAX Clock < 108MHz, Spec version 3.01\r\n");   
  72.     }   


  73.     /* 卸载文件系统 */
  74.      f_mount(NULL, DiskPath, 0);
  75. }
复制代码

  f_mount可以上电后仅调用一次,我们这里是为了测试方式,使用前挂载,使用完毕后卸载。
代码里面加入了SD卡速度信息打印功能,方便大家了解自己的卡类型。通过查询全局结构体变量uSdHandle来实现。
  文件浏览通过函数f_readdir实现。
88.8.3 SD卡创建txt文件并写入数据
代码实现如下:

  1. /*
  2. *********************************************************************************************************
  3. *    函 数 名: CreateNewFile
  4. *    功能说明: 在SD卡创建一个新文件,文件内容填写“<a href="http://www.armfly.com" target="_blank">www.armfly.com</a>”
  5. *    形    参:无
  6. *    返 回 值: 无
  7. *********************************************************************************************************
  8. */
  9. static void CreateNewFile(void)
  10. {
  11.     FRESULT result;
  12.     uint32_t bw;
  13.     char path[32];


  14.      /* 挂载文件系统 */
  15.     result = f_mount(&fs, DiskPath, 0);            /* Mount a logical drive */
  16.     if (result != FR_OK)
  17.     {
  18.         printf("挂载文件系统失败 (%s)\r\n", FR_Table[result]);
  19.     }

  20.     /* 打开文件 */
  21.     sprintf(path, "%sarmfly.txt", DiskPath);
  22.     result = f_open(&file, path, FA_CREATE_ALWAYS | FA_WRITE);
  23.     if (result == FR_OK)
  24.     {
  25.         printf("armfly.txt 文件打开成功\r\n");
  26.     }
  27.     else
  28.     {
  29.         printf("armfly.txt 文件打开失败  (%s)\r\n", FR_Table[result]);
  30.     }

  31.     /* 写一串数据 */
  32.     result = f_write(&file, FsWriteBuf, strlen(FsWriteBuf), &bw);
  33.     if (result == FR_OK)
  34.     {
  35.         printf("armfly.txt 文件写入成功\r\n");
  36.     }
  37.     else
  38.     {
  39.         printf("armfly.txt 文件写入失败  (%s)\r\n", FR_Table[result]);
  40.     }

  41.     /* 关闭文件*/
  42.     f_close(&file);

  43.     /* 卸载文件系统 */
  44.     f_mount(NULL, DiskPath, 0);
  45. }
复制代码

  f_mount可以上电后仅调用一次,我们这里是为了测试方式,使用前挂载,使用完毕后卸载。
  函数f_open用来创建并打开文件。
  函数f_write用来写入数据。
  函数f_close用来关闭文件,注意调用完函数f_write后,内容还没有实际写入到SD卡中,调用了f_close后,数据才真正的写入到SD卡。当然也可以调用函数f_sync,内容也会实际的写入。
88.8.4 SD卡文件读取
代码实现如下:

  1. /*
  2. *********************************************************************************************************
  3. *    函 数 名: ReadFileData
  4. *    功能说明: 读取文件armfly.txt前128个字符,并打印到串口
  5. *    形    参:无
  6. *    返 回 值: 无
  7. *********************************************************************************************************
  8. */
  9. static void ReadFileData(void)
  10. {
  11.     FRESULT result;
  12.     uint32_t bw;
  13.     char path[64];


  14.      /* 挂载文件系统 */
  15.     result = f_mount(&fs, DiskPath, 0);            /* Mount a logical drive */
  16.     if (result != FR_OK)
  17.     {
  18.         printf("挂载文件系统失败 (%s)\r\n", FR_Table[result]);
  19.     }

  20.     /* 打开文件 */
  21.     sprintf(path, "%sarmfly.txt", DiskPath);
  22.     result = f_open(&file, path, FA_OPEN_EXISTING | FA_READ);
  23.     if (result !=  FR_OK)
  24.     {
  25.         printf("Don't Find File : armfly.txt\r\n");
  26.         return;
  27.     }

  28.     /* 读取文件 */
  29.     result = f_read(&file, FsReadBuf, sizeof(FsReadBuf), &bw);
  30.     if (bw > 0)
  31.     {
  32.         FsReadBuf[bw] = 0;
  33.         printf("\r\narmfly.txt 文件内容 : \r\n%s\r\n", FsReadBuf);
  34.     }
  35.     else
  36.     {
  37.         printf("\r\narmfly.txt 文件内容 : \r\n");
  38.     }

  39.     /* 关闭文件*/
  40.     f_close(&file);

  41.     /* 卸载文件系统 */
  42.     f_mount(NULL, DiskPath, 0);
  43. }
复制代码

f_mount可以上电后仅调用一次,我们这里是为了测试方式,使用前挂载,使用完毕后卸载。
  函数f_open用来打开文件。
  函数f_read用来读取文件中的内容。
  函数f_close用来关闭打开的文件。
88.8.5 SD卡创建文件夹
代码实现如下:

  1. /*
  2. *********************************************************************************************************
  3. *    函 数 名: CreateDir
  4. *    功能说明: 在SD卡根目录创建Dir1和Dir2目录,在Dir1目录下创建子目录Dir1_1
  5. *    形    参:无
  6. *    返 回 值: 无
  7. *********************************************************************************************************
  8. */
  9. static void CreateDir(void)
  10. {
  11.     FRESULT result;
  12.     char path[64];


  13.      /* 挂载文件系统 */
  14.     result = f_mount(&fs, DiskPath, 0);            /* Mount a logical drive */
  15.     if (result != FR_OK)
  16.     {
  17.         printf("挂载文件系统失败 (%s)\r\n", FR_Table[result]);
  18.     }

  19.     /* 创建目录/Dir1 */
  20.     sprintf(path, "%sDir1", DiskPath);
  21.     result = f_mkdir(path);
  22.     if (result == FR_OK)
  23.     {
  24.         printf("f_mkdir Dir1 Ok\r\n");
  25.     }
  26.     else if (result == FR_EXIST)
  27.     {
  28.         printf("Dir1 目录已经存在(%d)\r\n", result);
  29.     }
  30.     else
  31.     {
  32.         printf("f_mkdir Dir1 失败 (%s)\r\n", FR_Table[result]);
  33.         return;
  34.     }

  35.     /* 创建目录/Dir2 */
  36.     sprintf(path, "%sDir2", DiskPath);
  37.     result = f_mkdir(path);
  38.     if (result == FR_OK)
  39.     {
  40.         printf("f_mkdir Dir2 Ok\r\n");
  41.     }
  42.     else if (result == FR_EXIST)
  43.     {
  44.         printf("Dir2 目录已经存在(%d)\r\n", result);
  45.     }
  46.     else
  47.     {
  48.         printf("f_mkdir Dir2 失败 (%s)\r\n", FR_Table[result]);
  49.         return;
  50.     }

  51.     /* 创建子目录 /Dir1/Dir1_1       注意:创建子目录Dir1_1时,必须先创建好Dir1 */
  52.     sprintf(path, "%sDir1/Dir1_1", DiskPath);
  53.     result = f_mkdir(path); /* */
  54.     if (result == FR_OK)
  55.     {
  56.         printf("f_mkdir Dir1_1 成功\r\n");
  57.     }
  58.     else if (result == FR_EXIST)
  59.     {
  60.         printf("Dir1_1 目录已经存在 (%d)\r\n", result);
  61.     }
  62.     else
  63.     {
  64.         printf("f_mkdir Dir1_1 失败 (%s)\r\n", FR_Table[result]);
  65.         return;
  66.     }

  67.     /* 卸载文件系统 */
  68.     f_mount(NULL, DiskPath, 0);
  69. }
复制代码

  f_mount可以上电后仅调用一次,我们这里是为了测试方式,使用前挂载,使用完毕后卸载。
  创建目录通过函数f_mkdir。
88.8.6 SD卡文件和文件夹删除
代码实现如下:

  1. /*
  2. *********************************************************************************************************
  3. *    函 数 名: DeleteDirFile
  4. *    功能说明: 删除SD卡根目录下的 armfly.txt 文件和 Dir1,Dir2 目录
  5. *    形    参:无
  6. *    返 回 值: 无
  7. *********************************************************************************************************
  8. */
  9. static void DeleteDirFile(void)
  10. {
  11.     FRESULT result;
  12.     uint8_t i;
  13.     char path[64];

  14.      /* 挂载文件系统 */
  15.     result = f_mount(&fs, DiskPath, 0);            /* Mount a logical drive */
  16.     if (result != FR_OK)
  17.     {
  18.         printf("挂载文件系统失败 (%s)\r\n", FR_Table[result]);
  19.     }

  20.     /* 删除目录/Dir1 【因为还存在目录非空(存在子目录),所以这次删除会失败】*/
  21.     sprintf(path, "%sDir1", DiskPath);
  22.     result = f_unlink(path);
  23.     if (result == FR_OK)
  24.     {
  25.         printf("删除目录Dir1成功\r\n");
  26.     }
  27.     else if (result == FR_NO_FILE)
  28.     {
  29.         printf("没有发现文件或目录 :%s\r\n", "/Dir1");
  30.     }
  31.     else
  32.     {
  33.         printf("删除Dir1失败(错误代码 = %d) 文件只读或目录非空\r\n", result);
  34.     }

  35.     /* 先删除目录/Dir1/Dir1_1 */
  36.     sprintf(path, "%sDir1/Dir1_1", DiskPath);
  37.     result = f_unlink(path);
  38.     if (result == FR_OK)
  39.     {
  40.         printf("删除子目录/Dir1/Dir1_1成功\r\n");
  41.     }
  42.     else if ((result == FR_NO_FILE) || (result == FR_NO_PATH))
  43.     {
  44.         printf("没有发现文件或目录 :%s\r\n", "/Dir1/Dir1_1");
  45.     }
  46.     else
  47.     {
  48.         printf("删除子目录/Dir1/Dir1_1失败(错误代码 = %d) 文件只读或目录非空\r\n", result);
  49.     }

  50.     /* 先删除目录/Dir1 */
  51.     sprintf(path, "%sDir1", DiskPath);
  52.     result = f_unlink(path);
  53.     if (result == FR_OK)
  54.     {
  55.         printf("删除目录Dir1成功\r\n");
  56.     }
  57.     else if (result == FR_NO_FILE)
  58.     {
  59.         printf("没有发现文件或目录 :%s\r\n", "/Dir1");
  60.     }
  61.     else
  62.     {
  63.         printf("删除Dir1失败(错误代码 = %d) 文件只读或目录非空\r\n", result);
  64.     }

  65.     /* 删除目录/Dir2 */
  66.     sprintf(path, "%sDir2", DiskPath);
  67.     result = f_unlink(path);
  68.     if (result == FR_OK)
  69.     {
  70.         printf("删除目录 Dir2 成功\r\n");
  71.     }
  72.     else if (result == FR_NO_FILE)
  73.     {
  74.         printf("没有发现文件或目录 :%s\r\n", "/Dir2");
  75.     }
  76.     else
  77.     {
  78.         printf("删除Dir2 失败(错误代码 = %d) 文件只读或目录非空\r\n", result);
  79.     }

  80.     /* 删除文件 armfly.txt */
  81.     sprintf(path, "%sarmfly.txt", DiskPath);
  82.     result = f_unlink(path);
  83.     if (result == FR_OK)
  84.     {
  85.         printf("删除文件 armfly.txt 成功\r\n");
  86.     }
  87.     else if (result == FR_NO_FILE)
  88.     {
  89.         printf("没有发现文件或目录 :%s\r\n", "armfly.txt");
  90.     }
  91.     else
  92.     {
  93.         printf("删除armfly.txt失败(错误代码 = %d) 文件只读或目录非空\r\n", result);
  94.     }

  95.     /* 删除文件 speed1.txt */
  96.     for (i = 0; i < 20; i++)
  97.     {
  98.         sprintf(path, "%sSpeed%02d.txt", DiskPath, i);/* 每写1次,序号递增 */   
  99.         result = f_unlink(path);
  100.         if (result == FR_OK)
  101.         {
  102.             printf("删除文件%s成功\r\n", path);
  103.         }
  104.         else if (result == FR_NO_FILE)
  105.         {
  106.             printf("没有发现文件:%s\r\n", path);
  107.         }
  108.         else
  109.         {
  110.             printf("删除%s文件失败(错误代码 = %d) 文件只读或目录非空\r\n", path, result);
  111.         }
  112.     }

  113.     /* 卸载文件系统 */
  114.     f_mount(NULL, DiskPath, 0);
  115. }
复制代码

  f_mount可以上电后仅调用一次,我们这里是为了测试方式,使用前挂载,使用完毕后卸载。
  文件夹和文件的删除都是通过函数f_unlink实现,这里注意一点,删除文件夹时,只有文件夹中的内容为空时,才可以删除文件夹。
88.8.7 SD卡读写速度测试
代码实现如下,主要是方便大家测试SD卡的读写性能。

  1. /*
  2. *********************************************************************************************************
  3. *    函 数 名: WriteFileTest
  4. *    功能说明: 测试文件读写速度
  5. *    形    参:无
  6. *    返 回 值: 无
  7. *********************************************************************************************************
  8. */
  9. static void WriteFileTest(void)
  10. {
  11.     FRESULT result;
  12.     char path[64];
  13.     uint32_t bw;
  14.     uint32_t i,k;
  15.     uint32_t runtime1,runtime2,timelen;
  16.     uint8_t err = 0;
  17.     static uint8_t s_ucTestSn = 0;


  18.     for (i = 0; i < sizeof(g_TestBuf); i++)
  19.     {
  20.         g_TestBuf<i> = (i / 512) + '0';
  21.     </i>}

  22.       /* 挂载文件系统 */
  23.     result = f_mount(&fs, DiskPath, 0);            /* Mount a logical drive */
  24.     if (result != FR_OK)
  25.     {
  26.         printf("挂载文件系统失败 (%s)\r\n", FR_Table[result]);
  27.     }

  28.     /* 打开文件 */
  29.     sprintf(path, "%sSpeed%02d.txt", DiskPath, s_ucTestSn++); /* 每写1次,序号递增 */   
  30.     result = f_open(&file, path, FA_CREATE_ALWAYS | FA_WRITE);

  31.     /* 写一串数据 */
  32.     printf("开始写文件%s %dKB ...\r\n", path, TEST_FILE_LEN / 1024);

  33.     runtime1 = bsp_GetRunTime();    /* 读取系统运行时间 */
  34.     for (i = 0; i < TEST_FILE_LEN / BUF_SIZE; i++)
  35.     {
  36.         result = f_write(&file, g_TestBuf, sizeof(g_TestBuf), &bw);
  37.         if (result == FR_OK)
  38.         {
  39.             if (((i + 1) % 8) == 0)
  40.             {
  41.                 printf(".");
  42.             }
  43.         }
  44.         else
  45.         {
  46.             err = 1;
  47.             printf("%s文件写失败\r\n", path);
  48.             break;
  49.         }
  50.     }
  51.     runtime2 = bsp_GetRunTime();    /* 读取系统运行时间 */

  52.     if (err == 0)
  53.     {
  54.         timelen = (runtime2 - runtime1);
  55.         printf("\r\n  写耗时 : %dms   平均写速度 : %dB/S (%dKB/S)\r\n",
  56.             timelen,
  57.             (TEST_FILE_LEN * 1000) / timelen,
  58.             ((TEST_FILE_LEN / 1024) * 1000) / timelen);
  59.     }

  60.     f_close(&file);        /* 关闭文件*/


  61.     /* 开始读文件测试 */
  62.     result = f_open(&file, path, FA_OPEN_EXISTING | FA_READ);
  63.     if (result !=  FR_OK)
  64.     {
  65.         printf("没有找到文件: %s\r\n", path);
  66.         return;
  67.     }

  68.     printf("开始读文件 %dKB ...\r\n", TEST_FILE_LEN / 1024);

  69.     runtime1 = bsp_GetRunTime();    /* 读取系统运行时间 */
  70.     for (i = 0; i < TEST_FILE_LEN / BUF_SIZE; i++)
  71.     {
  72.         result = f_read(&file, g_TestBuf, sizeof(g_TestBuf), &bw);
  73.         if (result == FR_OK)
  74.         {
  75.             if (((i + 1) % 8) == 0)
  76.             {
  77.                 printf(".");
  78.             }

  79.             /* 比较写入的数据是否正确,此语句会导致读卡速度结果降低到 3.5MBytes/S */
  80.             for (k = 0; k < sizeof(g_TestBuf); k++)
  81.             {
  82.                 if (g_TestBuf[k] != (k / 512) + '0')
  83.                 {
  84.                       err = 1;
  85.                     printf("Speed1.txt 文件读成功,但是数据出错\r\n");
  86.                     break;
  87.                 }
  88.             }
  89.             if (err == 1)
  90.             {
  91.                 break;
  92.             }
  93.         }
  94.         else
  95.         {
  96.             err = 1;
  97.             printf("Speed1.txt 文件读失败\r\n");
  98.             break;
  99.         }
  100.     }

  101.     runtime2 = bsp_GetRunTime();    /* 读取系统运行时间 */

  102.     if (err == 0)
  103.     {
  104.         timelen = (runtime2 - runtime1);
  105.         printf("\r\n  读耗时 : %dms   平均读速度 : %dB/S (%dKB/S)\r\n", timelen,
  106.             (TEST_FILE_LEN * 1000) / timelen, ((TEST_FILE_LEN / 1024) * 1000) / timelen);
  107.     }

  108.     /* 关闭文件*/
  109.     f_close(&file);

  110.     /* 卸载文件系统 */
  111.     f_mount(NULL, DiskPath, 0);
  112. }
复制代码

  f_mount可以上电后仅调用一次,我们这里是为了测试方式,使用前挂载,使用完毕后卸载。
  为了实现更高性能的测试,大家可以加大宏定义
#define BUF_SIZE                           (4*1024)              /* 每次读写SD卡的最大数据长度 */

设置的缓冲大小,比如设置为64KB进行测试。

88.9 FatFs移植接口文件diskio.c说明
这里将FatFs的底层接口文件diskio.c的实现为大家简单做个说明。

88.9.1 磁盘状态函数disk_status
代码如下:

  1. /**
  2.   * @brief  Gets Disk Status
  3.   * @param  pdrv: Physical drive number (0..)
  4.   * @retval DSTATUS: Operation status
  5.   */
  6. DSTATUS disk_status (
  7.     BYTE pdrv        /* Physical drive number to identify the drive */
  8. )
  9. {
  10.   DSTATUS stat;

  11.   stat = disk.drv[pdrv]->disk_status(disk.lun[pdrv]);
  12.   return stat;
  13. }
  14. 实际对应的函数在文件sd_diskio_dma.c

  15. /**
  16.   * @brief  Gets Disk Status
  17.   * @param  lun : not used
  18.   * @retval DSTATUS: Operation status
  19.   */
  20. DSTATUS SD_status(BYTE lun)
  21. {
  22.   return SD_CheckStatus(lun);
  23. }

  24. static DSTATUS SD_CheckStatus(BYTE lun)
  25. {
  26.   Stat = STA_NOINIT;

  27.   if(BSP_SD_GetCardState() == MSD_OK)
  28.   {
  29.     Stat &= ~STA_NOINIT;
  30.   }

  31.   return Stat;
  32. }
复制代码

88.9.2 磁盘初始化函数disk_initialize
代码如下:

  1. /**
  2.   * @brief  Initializes a Drive
  3.   * @param  pdrv: Physical drive number (0..)
  4.   * @retval DSTATUS: Operation status
  5.   */
  6. DSTATUS disk_initialize (
  7.     BYTE pdrv                /* Physical drive nmuber to identify the drive */
  8. )
  9. {
  10.   DSTATUS stat = RES_OK;

  11.   if(disk.is_initialized[pdrv] == 0)
  12.   {
  13.     disk.is_initialized[pdrv] = 1;
  14.     stat = disk.drv[pdrv]->disk_initialize(disk.lun[pdrv]);
  15.   }
  16.   return stat;
  17. }
复制代码

实际对应的函数在文件sd_diskio_dma.c:

  1. /**
  2.   * @brief  Initializes a Drive
  3.   * @param  lun : not used
  4.   * @retval DSTATUS: Operation status
  5.   */
  6. DSTATUS SD_initialize(BYTE lun)
  7. {
  8. #if !defined(DISABLE_SD_INIT)

  9.   if(BSP_SD_Init() == MSD_OK)
  10.   {
  11.     Stat = SD_CheckStatus(lun);
  12.   }

  13. #else
  14.   Stat = SD_CheckStatus(lun);
  15. #endif
  16.   return Stat;
  17. }
复制代码

88.9.3 磁盘读函数disk_read
代码如下:

  1. /**
  2.   * @brief  Reads Sector(s)
  3.   * @param  pdrv: Physical drive number (0..)
  4.   * @param  *buff: Data buffer to store read data
  5.   * @param  sector: Sector address (LBA)
  6.   * @param  count: Number of sectors to read (1..128)
  7.   * @retval DRESULT: Operation result
  8.   */
  9. DRESULT disk_read (
  10.     BYTE pdrv,        /* Physical drive nmuber to identify the drive */
  11.     BYTE *buff,        /* Data buffer to store read data */
  12.     DWORD sector,            /* Sector address in LBA */
  13.     UINT count        /* Number of sectors to read */
  14. )
  15. {
  16.   DRESULT res;

  17.   res = disk.drv[pdrv]->disk_read(disk.lun[pdrv], buff, sector, count);
  18.   return res;
  19. }
复制代码

实际对应的函数在文件sd_diskio_dma.c:

下面代码中最关键的处理是形参buff的4字节对齐问题(SDMMC自带的IDMA需要4字节对齐),如果buff地址是4字节对齐的,不做处理,如果不是对齐,通过复制到一个4字节对齐的缓冲里面做DMA传递。这个是理解下面代码的关键。

  1. /**
  2.   * @brief  Reads Sector(s)
  3.   * @param  lun : not used
  4.   * @param  *buff: Data buffer to store read data
  5.   * @param  sector: Sector address (LBA)
  6.   * @param  count: Number of sectors to read (1..128)
  7.   * @retval DRESULT: Operation result
  8.   */
  9. DRESULT SD_read(BYTE lun, BYTE *buff, DWORD sector, UINT count)
  10. {
  11.     DRESULT res = RES_ERROR;
  12.     uint32_t timeout;
  13.     ReadStatus = 0;

  14.     if (!((uint32_t)buff & 0x3))
  15.     {
  16.         if(BSP_SD_ReadBlocks_DMA((uint32_t*)buff,
  17.                                 (uint32_t) (sector),
  18.                                 count) == MSD_OK)
  19.         {
  20.             /* Wait that the reading process is completed or a timeout occurs */
  21.             timeout = HAL_GetTick();
  22.             while((ReadStatus == 0) && ((HAL_GetTick() - timeout) < SD_TIMEOUT))
  23.             {
  24.             }

  25.             /* incase of a timeout return error */
  26.             if (ReadStatus == 0)
  27.             {
  28.                 res = RES_ERROR;
  29.             }
  30.             else
  31.             {
  32.                 ReadStatus = 0;
  33.                 timeout = HAL_GetTick();

  34.                 while((HAL_GetTick() - timeout) < SD_TIMEOUT)
  35.                 {
  36.                     if (BSP_SD_GetCardState() == SD_TRANSFER_OK)
  37.                     {
  38.                         res = RES_OK;

  39.                         #if (ENABLE_SD_DMA_CACHE_MAINTENANCE_READ == 1)
  40.                            SCB_CleanInvalidateDCache();
  41.                         #endif
  42.                         break;
  43.                     }
  44.                 }
  45.             }
  46.         }
  47.     }
  48.     else
  49.     {
  50.         uint8_t ret;
  51.         int i;

  52.         for (i = 0; i < count; i++)
  53.         {

  54.             ret = BSP_SD_ReadBlocks_DMA((uint32_t*)scratch, (uint32_t)sector++, 1);

  55.             if(ret == MSD_OK)
  56.             {
  57.                 /* Wait that the reading process is completed or a timeout occurs */
  58.                 timeout = HAL_GetTick();
  59.                 while((ReadStatus == 0) && ((HAL_GetTick() - timeout) < SD_TIMEOUT))
  60.                 {

  61.                 }
  62.                 /* incase of a timeout return error */
  63.                 if (ReadStatus == 0)
  64.                 {
  65.                     break;
  66.                 }
  67.                 else
  68.                 {
  69.                     ReadStatus = 0;
  70.                     timeout = HAL_GetTick();

  71.                     while((HAL_GetTick() - timeout) < SD_TIMEOUT)
  72.                     {
  73.                         if (BSP_SD_GetCardState() == SD_TRANSFER_OK)
  74.                         {
  75.                             #if (ENABLE_SD_DMA_CACHE_MAINTENANCE_READ == 1)
  76.                                 SCB_CleanInvalidateDCache();
  77.                             #endif

  78.                             memcpy(buff, scratch, BLOCKSIZE);
  79.                             buff += BLOCKSIZE;

  80.                             break;
  81.                         }
  82.                     }
  83.                 }
  84.             }
  85.             else
  86.             {
  87.                 break;
  88.             }
  89.         }
  90.         if ((i == count) && (ret == MSD_OK))
  91.         {
  92.            res = RES_OK;      
  93.         }
  94.     }
  95.     return res;
  96. }
复制代码

88.9.4 磁盘写函数disk_write
代码如下:

  1. /**
  2.   * @brief  Writes Sector(s)
  3.   * @param  pdrv: Physical drive number (0..)
  4.   * @param  *buff: Data to be written
  5.   * @param  sector: Sector address (LBA)
  6.   * @param  count: Number of sectors to write (1..128)
  7.   * @retval DRESULT: Operation result
  8.   */
  9. DRESULT disk_write (
  10.     BYTE pdrv,        /* Physical drive nmuber to identify the drive */
  11.     const BYTE *buff,    /* Data to be written */
  12.     DWORD sector,        /* Sector address in LBA */
  13.     UINT count            /* Number of sectors to write */
  14. )
  15. {
  16.   DRESULT res;

  17.   res = disk.drv[pdrv]->disk_write(disk.lun[pdrv], buff, sector, count);
  18.   return res;
  19. }
复制代码

实际对应的函数在文件sd_diskio_dma.c:

下面代码中最关键的处理是形参buff的4字节对齐问题(SDMMC自带的IDMA需要4字节对齐),如果buff地址是4字节对齐的,不做处理,如果不是对齐,通过复制到一个4字节对齐的缓冲里面做DMA传递。这个是理解下面代码的关键。

  1. /**
  2.   * @brief  Writes Sector(s)
  3.   * @param  lun : not used
  4.   * @param  *buff: Data to be written
  5.   * @param  sector: Sector address (LBA)
  6.   * @param  count: Number of sectors to write (1..128)
  7.   * @retval DRESULT: Operation result
  8.   */
  9. #if _USE_WRITE == 1
  10. DRESULT SD_write(BYTE lun, const BYTE *buff, DWORD sector, UINT count)
  11. {
  12.     DRESULT res = RES_ERROR;
  13.     uint32_t timeout;
  14.     WriteStatus = 0;

  15. #if (ENABLE_SD_DMA_CACHE_MAINTENANCE_WRITE == 1)
  16.    SCB_CleanInvalidateDCache();
  17. #endif

  18.     if (!((uint32_t)buff & 0x3))
  19.     {
  20.         if(BSP_SD_WriteBlocks_DMA((uint32_t*)buff,
  21.                                 (uint32_t)(sector),
  22.                                 count) == MSD_OK)
  23.         {
  24.             /* Wait that writing process is completed or a timeout occurs */
  25.             timeout = HAL_GetTick();
  26.             while((WriteStatus == 0) && ((HAL_GetTick() - timeout) < SD_TIMEOUT))
  27.             {
  28.             }

  29.             /* incase of a timeout return error */
  30.             if (WriteStatus == 0)
  31.             {
  32.                 res = RES_ERROR;
  33.             }
  34.             else
  35.             {
  36.                 WriteStatus = 0;
  37.                 timeout = HAL_GetTick();

  38.                 while((HAL_GetTick() - timeout) < SD_TIMEOUT)
  39.                 {
  40.                     if (BSP_SD_GetCardState() == SD_TRANSFER_OK)
  41.                     {
  42.                         res = RES_OK;
  43.                         break;
  44.                     }
  45.                 }
  46.             }
  47.         }
  48.     }
  49.     else
  50.     {
  51.         int i;
  52.         uint8_t ret;

  53.         for (i = 0; i < count; i++)
  54.         {
  55.             WriteStatus = 0;

  56.             memcpy((void *)scratch, (void *)buff, BLOCKSIZE);
  57.             buff += BLOCKSIZE;

  58.             ret = BSP_SD_WriteBlocks_DMA((uint32_t*)scratch, (uint32_t)sector++, 1);
  59.             if(ret == MSD_OK)
  60.             {
  61.                 /* Wait that writing process is completed or a timeout occurs */

  62.                 timeout = HAL_GetTick();
  63.                 while((WriteStatus == 0) && ((HAL_GetTick() - timeout) < SD_TIMEOUT))
  64.                 {
  65.                 }

  66.                 /* incase of a timeout return error */
  67.                 if (WriteStatus == 0)
  68.                 {
  69.                     break;
  70.                 }
  71.                 else
  72.                 {
  73.                     WriteStatus = 0;
  74.                     timeout = HAL_GetTick();

  75.                     while((HAL_GetTick() - timeout) < SD_TIMEOUT)
  76.                     {
  77.                         if (BSP_SD_GetCardState() == SD_TRANSFER_OK)
  78.                         {
  79.                             break;
  80.                         }
  81.                     }
  82.                 }
  83.             }
  84.             else
  85.             {
  86.                 break;
  87.             }
  88.         }

  89.         if ((i == count) && (ret == MSD_OK))
  90.         {
  91.             res = RES_OK;           
  92.         }
  93.     }

  94.     return res;
  95. }
复制代码

88.9.5 磁盘I/O控制函数disk_ioctl
代码如下:

  1. /**
  2.   * @brief  I/O control operation
  3.   * @param  pdrv: Physical drive number (0..)
  4.   * @param  cmd: Control code
  5.   * @param  *buff: Buffer to send/receive control data
  6.   * @retval DRESULT: Operation result
  7.   */
  8. #if _USE_IOCTL == 1
  9. DRESULT disk_ioctl (
  10.     BYTE pdrv,        /* Physical drive nmuber (0..) */
  11.     BYTE cmd,        /* Control code */
  12.     void *buff        /* Buffer to send/receive control data */
  13. )
  14. {
  15.   DRESULT res;

  16.   res = disk.drv[pdrv]->disk_ioctl(disk.lun[pdrv], cmd, buff);
  17.   return res;
  18. }
  19. #endif /* _USE_IOCTL == 1 */
复制代码

实际对应的函数在文件sd_diskio_dma.c

特别注意,如果大家要调用FatFs的API格式化SD卡,此函数比较重要。下面几个cmd一定实现:

  1. /**
  2.   * @brief  I/O control operation
  3.   * @param  lun : not used
  4.   * @param  cmd: Control code
  5.   * @param  *buff: Buffer to send/receive control data
  6.   * @retval DRESULT: Operation result
  7.   */
  8. #if _USE_IOCTL == 1
  9. DRESULT SD_ioctl(BYTE lun, BYTE cmd, void *buff)
  10. {
  11.   DRESULT res = RES_ERROR;
  12.   BSP_SD_CardInfo CardInfo;

  13.   if (Stat & STA_NOINIT) return RES_NOTRDY;

  14.   switch (cmd)
  15.   {
  16.   /* Make sure that no pending write process */
  17.   case CTRL_SYNC :
  18.     res = RES_OK;
  19.     break;

  20.   /* Get number of sectors on the disk (DWORD) */
  21.   case GET_SECTOR_COUNT :
  22.     BSP_SD_GetCardInfo(&CardInfo);
  23.     *(DWORD*)buff = CardInfo.LogBlockNbr;
  24.     res = RES_OK;
  25.     break;

  26.   /* Get R/W sector size (WORD) */
  27.   case GET_SECTOR_SIZE :
  28.     BSP_SD_GetCardInfo(&CardInfo);
  29.     *(WORD*)buff = CardInfo.LogBlockSize;
  30.     res = RES_OK;
  31.     break;

  32.   /* Get erase block size in unit of sector (DWORD) */
  33.   case GET_BLOCK_SIZE :
  34.     BSP_SD_GetCardInfo(&CardInfo);
  35.     *(DWORD*)buff = CardInfo.LogBlockSize / SD_DEFAULT_BLOCK_SIZE;
  36.     res = RES_OK;
  37.     break;

  38.   default:
  39.     res = RES_PARERR;
  40.   }

  41.   return res;
  42. }
  43. #endif /* _USE_IOCTL == 1 */
复制代码

88.9.6 RTC时间获取函数get_fattime
我们这里未使用这个函数,此函数的作用是用户创建文件时,可以将创建文件时间设置为此函数的获取值

  1. /**
  2.   * @brief  Gets Time from RTC
  3.   * @param  None
  4.   * @retval Time in DWORD
  5.   */
  6. __weak DWORD get_fattime (void)
  7. {
  8.   return 0;
  9. }
复制代码

88.10          SDMMC自带IDMA的4字节对齐问题(重要)
由于本章教程配套例子使用了SDMMC自带的IDMA,所以也专门做了4字节对齐处理。处理思路就是底层的读写函数里面如果地址是4字节对齐的,不做处理,如果不是对齐,通过复制到一个4字节对齐的缓冲里面做DMA传递。

其实有个更简单,性能也最高的解决办法,核心思想如下(ffconf.h文件里面设置的扇区大小基本都是512字节):

  当要写入和读取的数据小于扇区大小时,会直接使用FATFS结构体里面的数组win[_MAX_SS]做DMA写操作到,正好1个扇区大小。由于数组win[_MAX_SS]的地址是4字节对齐的,所以无需做处理。
当要写入和读取的数据大于等于扇区大小时,扇区整数倍的地方将直接使用用户提供的收发缓冲区发送,而不足一个扇区的地方将使用FATFS结构体里面的数组。这种情况下用户要做的就是直接定义个4字节对齐的读写缓冲区即可。

针对本章教程配套的例子,我们直接做了32字节对齐,同时也方便了Cache处理:

ALIGN_32BYTES(char FsReadBuf[1024]);
ALIGN_32BYTES(char FsWriteBuf[1024]) = {"FatFS Write Demo \r\n\r\n"};
ALIGN_32BYTES(uint8_t g_TestBuf[BUF_SIZE]);
88.11          实验例程设计框架
通过程序设计框架,让大家先对配套例程有一个全面的认识,然后再理解细节,本次实验例程的设计框架如下:

221606064730a272474202e70a1b9b04.png


第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

95ad3e3dfdd1f6c6afafe42330279e30.png


程序设计:

  系统栈大小分配:

1a0c8f57a9e4ff6b4affd69de6a3605f.png


  RAM空间用的AXI SRAM:

f2a96373dda469bc2b8d37b20d93559a.png


  硬件外设初始化

硬件外设的初始化是在 bsp.c 文件实现:

  1. /*
  2. *********************************************************************************************************
  3. *    函 数 名: bsp_Init
  4. *    功能说明: 初始化所有的硬件设备。该函数配置CPU寄存器和外设的寄存器并初始化一些全局变量。只需要调用一次
  5. *    形    参:无
  6. *    返 回 值: 无
  7. *********************************************************************************************************
  8. */
  9. void bsp_Init(void)
  10. {
  11.     /* 配置MPU */
  12.     MPU_Config();

  13.     /* 使能L1 Cache */
  14.     CPU_CACHE_Enable();

  15.     /*
  16.        STM32H7xx HAL 库初始化,此时系统用的还是H7自带的64MHz,HSI时钟:
  17.        - 调用函数HAL_InitTick,初始化滴答时钟中断1ms。
  18.        - 设置NVIV优先级分组为4。
  19.      */
  20.     HAL_Init();

  21.     /*
  22.        配置系统时钟到400MHz
  23.        - 切换使用HSE。
  24.        - 此函数会更新全局变量SystemCoreClock,并重新配置HAL_InitTick。
  25.     */
  26.     SystemClock_Config();

  27.     /*
  28.        Event Recorder:
  29.        - 可用于代码执行时间测量,MDK5.25及其以上版本才支持,IAR不支持。
  30.        - 默认不开启,如果要使能此选项,务必看V7开发板用户手册第xx章
  31.     */   
  32. #if Enable_EventRecorder == 1  
  33.     /* 初始化EventRecorder并开启 */
  34.     EventRecorderInitialize(EventRecordAll, 1U);
  35.     EventRecorderStart();
  36. #endif

  37.     bsp_InitKey();        /* 按键初始化,要放在滴答定时器之前,因为按钮检测是通过滴答定时器扫描 */
  38.     bsp_InitTimer();      /* 初始化滴答定时器 */
  39.     bsp_InitUart();    /* 初始化串口 */
  40.     bsp_InitExtIO();    /* 初始化FMC总线74HC574扩展IO. 必须在 bsp_InitLed()前执行 */   
  41.     bsp_InitLed();        /* 初始化LED */   
  42. }
复制代码

MPU配置和Cache配置:

数据Cache和指令Cache都开启。配置了AXI SRAM区和FMC的扩展IO区。

  1. /*
  2. *********************************************************************************************************
  3. *    函 数 名: MPU_Config
  4. *    功能说明: 配置MPU
  5. *    形    参: 无
  6. *    返 回 值: 无
  7. *********************************************************************************************************
  8. */
  9. static void MPU_Config( void )
  10. {
  11.     MPU_Region_InitTypeDef MPU_InitStruct;

  12.     /* 禁止 MPU */
  13.     HAL_MPU_Disable();

  14. #if 0
  15.        /* 配置AXI SRAM的MPU属性为Write back, Read allocate,Write allocate */
  16.     MPU_InitStruct.Enable           = MPU_REGION_ENABLE;
  17.     MPU_InitStruct.BaseAddress      = 0x24000000;
  18.     MPU_InitStruct.Size             = MPU_REGION_SIZE_512KB;
  19.     MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
  20.     MPU_InitStruct.IsBufferable     = MPU_ACCESS_BUFFERABLE;
  21.     MPU_InitStruct.IsCacheable      = MPU_ACCESS_CACHEABLE;
  22.     MPU_InitStruct.IsShareable      = MPU_ACCESS_NOT_SHAREABLE;
  23.     MPU_InitStruct.Number           = MPU_REGION_NUMBER0;
  24.     MPU_InitStruct.TypeExtField     = MPU_TEX_LEVEL1;
  25.     MPU_InitStruct.SubRegionDisable = 0x00;
  26.     MPU_InitStruct.DisableExec      = MPU_INSTRUCTION_ACCESS_ENABLE;

  27.     HAL_MPU_ConfigRegion(&MPU_InitStruct);

  28. #else
  29.      /* 当前是采用下面的配置 */
  30.     /* 配置AXI SRAM的MPU属性为NORMAL, NO Read allocate,NO Write allocate */
  31.     MPU_InitStruct.Enable           = MPU_REGION_ENABLE;
  32.     MPU_InitStruct.BaseAddress      = 0x24000000;
  33.     MPU_InitStruct.Size             = MPU_REGION_SIZE_512KB;
  34.     MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
  35.     MPU_InitStruct.IsBufferable     = MPU_ACCESS_NOT_BUFFERABLE;
  36.     MPU_InitStruct.IsCacheable      = MPU_ACCESS_NOT_CACHEABLE;
  37.     MPU_InitStruct.IsShareable      = MPU_ACCESS_NOT_SHAREABLE;
  38.     MPU_InitStruct.Number           = MPU_REGION_NUMBER0;
  39.     MPU_InitStruct.TypeExtField     = MPU_TEX_LEVEL1;
  40.     MPU_InitStruct.SubRegionDisable = 0x00;
  41.     MPU_InitStruct.DisableExec      = MPU_INSTRUCTION_ACCESS_ENABLE;

  42.     HAL_MPU_ConfigRegion(&MPU_InitStruct);
  43. #endif
  44.     /* 配置FMC扩展IO的MPU属性为Device或者Strongly Ordered */
  45.     MPU_InitStruct.Enable           = MPU_REGION_ENABLE;
  46.     MPU_InitStruct.BaseAddress      = 0x60000000;
  47.     MPU_InitStruct.Size             = ARM_MPU_REGION_SIZE_64KB;   
  48.     MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
  49.     MPU_InitStruct.IsBufferable     = MPU_ACCESS_BUFFERABLE;
  50.     MPU_InitStruct.IsCacheable      = MPU_ACCESS_NOT_CACHEABLE;
  51.     MPU_InitStruct.IsShareable      = MPU_ACCESS_NOT_SHAREABLE;
  52.     MPU_InitStruct.Number           = MPU_REGION_NUMBER1;
  53.     MPU_InitStruct.TypeExtField     = MPU_TEX_LEVEL0;
  54.     MPU_InitStruct.SubRegionDisable = 0x00;
  55.     MPU_InitStruct.DisableExec      = MPU_INSTRUCTION_ACCESS_ENABLE;

  56.     HAL_MPU_ConfigRegion(&MPU_InitStruct);

  57.     /*使能 MPU */
  58.     HAL_MPU_Enable(MPU_PRIVILEGED_DEFAULT);
  59. }

  60. /*
  61. *********************************************************************************************************
  62. *    函 数 名: CPU_CACHE_Enable
  63. *    功能说明: 使能L1 Cache
  64. *    形    参: 无
  65. *    返 回 值: 无
  66. *********************************************************************************************************
  67. */
  68. static void CPU_CACHE_Enable(void)
  69. {
  70.     /* 使能 I-Cache */
  71.     SCB_EnableICache();

  72.     /* 使能 D-Cache */
  73.     SCB_EnableDCache();
  74. }
复制代码

  主功能:

主程序实现如下操作:

  上电启动了一个软件定时器,每100ms翻转一次LED2。
   支持以下6个功能,用户通过电脑端串口软件发送数字给开发板即可:
  1 - 显示根目录下的文件列表
  2 - 创建一个新文件armfly.txt
  3 - 读armfly.txt文件的内容
  4 - 创建目录
  5 - 删除文件和目录
  6 - 读写文件速度测试
  1. /*
  2. *********************************************************************************************************
  3. *    函 数 名: main
  4. *    功能说明: c程序入口
  5. *    形    参: 无
  6. *    返 回 值: 错误代码(无需处理)
  7. *********************************************************************************************************
  8. */
  9. int main(void)
  10. {
  11.     bsp_Init();        /* 硬件初始化 */

  12.     PrintfLogo();    /* 打印例程名称和版本等信息 */

  13.     DemoFatFS();    /* SD卡测试 */
  14. }

  15. /*
  16. *********************************************************************************************************
  17. *    函 数 名: DemoFatFS
  18. *    功能说明: FatFS文件系统演示主程序
  19. *    形    参: 无
  20. *    返 回 值: 无
  21. *********************************************************************************************************
  22. */
  23. void DemoFatFS(void)
  24. {
  25.     uint8_t cmd;

  26.     /* 打印命令列表,用户可以通过串口操作指令 */
  27.     DispMenu();

  28.     /* 注册SD卡驱动 */
  29.     FATFS_LinkDriver(&SD_Driver, DiskPath);

  30.     bsp_StartAutoTimer(0, 500);    /* 启动1个500ms的自动重装的定时器 */

  31.     while (1)
  32.     {

  33.         /* 判断定时器超时时间 */
  34.         if (bsp_CheckTimer(0))   
  35.         {            
  36.             /* 每隔500ms 进来一次 */  
  37.             bsp_LedToggle(2);
  38.         }

  39.         if (comGetChar(COM1, &cmd))    /* 从串口读入一个字符(非阻塞方式) */
  40.         {
  41.             printf("\r\n");
  42.             switch (cmd)
  43.             {
  44.                 case '1':
  45.                     printf("【1 - ViewRootDir】\r\n");
  46.                     ViewRootDir();        /* 显示SD卡根目录下的文件名 */
  47.                     break;

  48.                 case '2':
  49.                     printf("【2 - CreateNewFile】\r\n");
  50.                     CreateNewFile();    /* 创建一个新文件,写入一个字符串 */
  51.                     break;

  52.                 case '3':
  53.                     printf("【3 - ReadFileData】\r\n");
  54.                     ReadFileData();        /* 读取根目录下armfly.txt的内容 */
  55.                     break;

  56.                 case '4':
  57.                     printf("【4 - CreateDir】\r\n");
  58.                     CreateDir();        /* 创建目录 */
  59.                     break;

  60.                 case '5':
  61.                     printf("【5 - DeleteDirFile】\r\n");
  62.                     DeleteDirFile();    /* 删除目录和文件 */
  63.                     break;

  64.                 case '6':
  65.                     printf("【6 - TestSpeed】\r\n");
  66.                     WriteFileTest();    /* 速度测试 */
  67.                     break;

  68.                 default:
  69.                     DispMenu();
  70.                     break;
  71.             }
  72.         }
  73.     }
  74. }
复制代码

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

f1a1b991806ba8fd0c6928d35304421e.png


程序设计:

  系统栈大小分配:

b66763016b3ce70d84b5242f9d1356a2.png


  RAM空间用的AXI SRAM:

a244719bdb0d1df61f937894faa25808.png


  硬件外设初始化

硬件外设的初始化是在 bsp.c 文件实现:

  1. /*
  2. *********************************************************************************************************
  3. *    函 数 名: bsp_Init
  4. *    功能说明: 初始化所有的硬件设备。该函数配置CPU寄存器和外设的寄存器并初始化一些全局变量。只需要调用一次
  5. *    形    参:无
  6. *    返 回 值: 无
  7. *********************************************************************************************************
  8. */
  9. void bsp_Init(void)
  10. {
  11.     /* 配置MPU */
  12.     MPU_Config();

  13.     /* 使能L1 Cache */
  14.     CPU_CACHE_Enable();

  15.     /*
  16.        STM32H7xx HAL 库初始化,此时系统用的还是H7自带的64MHz,HSI时钟:
  17.        - 调用函数HAL_InitTick,初始化滴答时钟中断1ms。
  18.        - 设置NVIV优先级分组为4。
  19.      */
  20.     HAL_Init();

  21.     /*
  22.        配置系统时钟到400MHz
  23.        - 切换使用HSE。
  24.        - 此函数会更新全局变量SystemCoreClock,并重新配置HAL_InitTick。
  25.     */
  26.     SystemClock_Config();

  27.     /*
  28.        Event Recorder:
  29.        - 可用于代码执行时间测量,MDK5.25及其以上版本才支持,IAR不支持。
  30.        - 默认不开启,如果要使能此选项,务必看V7开发板用户手册第xx章
  31.     */   
  32. #if Enable_EventRecorder == 1  
  33.     /* 初始化EventRecorder并开启 */
  34.     EventRecorderInitialize(EventRecordAll, 1U);
  35.     EventRecorderStart();
  36. #endif

  37.     bsp_InitKey();        /* 按键初始化,要放在滴答定时器之前,因为按钮检测是通过滴答定时器扫描 */
  38.     bsp_InitTimer();      /* 初始化滴答定时器 */
  39.     bsp_InitUart();    /* 初始化串口 */
  40.     bsp_InitExtIO();    /* 初始化FMC总线74HC574扩展IO. 必须在 bsp_InitLed()前执行 */   
  41.     bsp_InitLed();        /* 初始化LED */   
  42. }
复制代码

  MPU配置和Cache配置:

数据Cache和指令Cache都开启。配置了AXI SRAM区和FMC的扩展IO区。

  1. /*
  2. *********************************************************************************************************
  3. *    函 数 名: MPU_Config
  4. *    功能说明: 配置MPU
  5. *    形    参: 无
  6. *    返 回 值: 无
  7. *********************************************************************************************************
  8. */
  9. static void MPU_Config( void )
  10. {
  11.     MPU_Region_InitTypeDef MPU_InitStruct;

  12.     /* 禁止 MPU */
  13.     HAL_MPU_Disable();

  14. #if 0
  15.        /* 配置AXI SRAM的MPU属性为Write back, Read allocate,Write allocate */
  16.     MPU_InitStruct.Enable           = MPU_REGION_ENABLE;
  17.     MPU_InitStruct.BaseAddress      = 0x24000000;
  18.     MPU_InitStruct.Size             = MPU_REGION_SIZE_512KB;
  19.     MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
  20.     MPU_InitStruct.IsBufferable     = MPU_ACCESS_BUFFERABLE;
  21.     MPU_InitStruct.IsCacheable      = MPU_ACCESS_CACHEABLE;
  22.     MPU_InitStruct.IsShareable      = MPU_ACCESS_NOT_SHAREABLE;
  23.     MPU_InitStruct.Number           = MPU_REGION_NUMBER0;
  24.     MPU_InitStruct.TypeExtField     = MPU_TEX_LEVEL1;
  25.     MPU_InitStruct.SubRegionDisable = 0x00;
  26.     MPU_InitStruct.DisableExec      = MPU_INSTRUCTION_ACCESS_ENABLE;

  27.     HAL_MPU_ConfigRegion(&MPU_InitStruct);

  28. #else
  29.      /* 当前是采用下面的配置 */
  30.     /* 配置AXI SRAM的MPU属性为NORMAL, NO Read allocate,NO Write allocate */
  31.     MPU_InitStruct.Enable           = MPU_REGION_ENABLE;
  32.     MPU_InitStruct.BaseAddress      = 0x24000000;
  33.     MPU_InitStruct.Size             = MPU_REGION_SIZE_512KB;
  34.     MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
  35.     MPU_InitStruct.IsBufferable     = MPU_ACCESS_NOT_BUFFERABLE;
  36.     MPU_InitStruct.IsCacheable      = MPU_ACCESS_NOT_CACHEABLE;
  37.     MPU_InitStruct.IsShareable      = MPU_ACCESS_NOT_SHAREABLE;
  38.     MPU_InitStruct.Number           = MPU_REGION_NUMBER0;
  39.     MPU_InitStruct.TypeExtField     = MPU_TEX_LEVEL1;
  40.     MPU_InitStruct.SubRegionDisable = 0x00;
  41.     MPU_InitStruct.DisableExec      = MPU_INSTRUCTION_ACCESS_ENABLE;

  42.     HAL_MPU_ConfigRegion(&MPU_InitStruct);
  43. #endif
  44.     /* 配置FMC扩展IO的MPU属性为Device或者Strongly Ordered */
  45.     MPU_InitStruct.Enable           = MPU_REGION_ENABLE;
  46.     MPU_InitStruct.BaseAddress      = 0x60000000;
  47.     MPU_InitStruct.Size             = ARM_MPU_REGION_SIZE_64KB;   
  48.     MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
  49.     MPU_InitStruct.IsBufferable     = MPU_ACCESS_BUFFERABLE;
  50.     MPU_InitStruct.IsCacheable      = MPU_ACCESS_NOT_CACHEABLE;
  51.     MPU_InitStruct.IsShareable      = MPU_ACCESS_NOT_SHAREABLE;
  52.     MPU_InitStruct.Number           = MPU_REGION_NUMBER1;
  53.     MPU_InitStruct.TypeExtField     = MPU_TEX_LEVEL0;
  54.     MPU_InitStruct.SubRegionDisable = 0x00;
  55.     MPU_InitStruct.DisableExec      = MPU_INSTRUCTION_ACCESS_ENABLE;

  56.     HAL_MPU_ConfigRegion(&MPU_InitStruct);

  57.     /*使能 MPU */
  58.     HAL_MPU_Enable(MPU_PRIVILEGED_DEFAULT);
  59. }

  60. /*
  61. *********************************************************************************************************
  62. *    函 数 名: CPU_CACHE_Enable
  63. *    功能说明: 使能L1 Cache
  64. *    形    参: 无
  65. *    返 回 值: 无
  66. *********************************************************************************************************
  67. */
  68. static void CPU_CACHE_Enable(void)
  69. {
  70.     /* 使能 I-Cache */
  71.     SCB_EnableICache();

  72.     /* 使能 D-Cache */
  73.     SCB_EnableDCache();
  74. }
复制代码

  主功能:

主程序实现如下操作:

  上电启动了一个软件定时器,每100ms翻转一次LED2。
   支持以下6个功能,用户通过电脑端串口软件发送数字给开发板即可:
1 - 显示根目录下的文件列表
  2 - 创建一个新文件armfly.txt
3 - 读armfly.txt文件的内容
4 - 创建目录
  5 - 删除文件和目录
  6 - 读写文件速度测试
  1. /*
  2. *********************************************************************************************************
  3. *    函 数 名: main
  4. *    功能说明: c程序入口
  5. *    形    参: 无
  6. *    返 回 值: 错误代码(无需处理)
  7. *********************************************************************************************************
  8. */
  9. int main(void)
  10. {
  11.     bsp_Init();        /* 硬件初始化 */

  12.     PrintfLogo();    /* 打印例程名称和版本等信息 */

  13.     DemoFatFS();    /* SD卡测试 */
  14. }

  15. /*
  16. *********************************************************************************************************
  17. *    函 数 名: DemoFatFS
  18. *    功能说明: FatFS文件系统演示主程序
  19. *    形    参: 无
  20. *    返 回 值: 无
  21. *********************************************************************************************************
  22. */
  23. void DemoFatFS(void)
  24. {
  25.     uint8_t cmd;

  26.     /* 打印命令列表,用户可以通过串口操作指令 */
  27.     DispMenu();

  28.     /* 注册SD卡驱动 */
  29.     FATFS_LinkDriver(&SD_Driver, DiskPath);

  30.     bsp_StartAutoTimer(0, 500);    /* 启动1个500ms的自动重装的定时器 */

  31.     while (1)
  32.     {

  33.         /* 判断定时器超时时间 */
  34.         if (bsp_CheckTimer(0))   
  35.         {            
  36.             /* 每隔500ms 进来一次 */  
  37.             bsp_LedToggle(2);
  38.         }

  39.         if (comGetChar(COM1, &cmd))    /* 从串口读入一个字符(非阻塞方式) */
  40.         {
  41.             printf("\r\n");
  42.             switch (cmd)
  43.             {
  44.                 case '1':
  45.                     printf("【1 - ViewRootDir】\r\n");
  46.                     ViewRootDir();        /* 显示SD卡根目录下的文件名 */
  47.                     break;

  48.                 case '2':
  49.                     printf("【2 - CreateNewFile】\r\n");
  50.                     CreateNewFile();    /* 创建一个新文件,写入一个字符串 */
  51.                     break;

  52.                 case '3':
  53.                     printf("【3 - ReadFileData】\r\n");
  54.                     ReadFileData();        /* 读取根目录下armfly.txt的内容 */
  55.                     break;

  56.                 case '4':
  57.                     printf("【4 - CreateDir】\r\n");
  58.                     CreateDir();        /* 创建目录 */
  59.                     break;

  60.                 case '5':
  61.                     printf("【5 - DeleteDirFile】\r\n");
  62.                     DeleteDirFile();    /* 删除目录和文件 */
  63.                     break;

  64.                 case '6':
  65.                     printf("【6 - TestSpeed】\r\n");
  66.                     WriteFileTest();    /* 速度测试 */
  67.                     break;

  68.                 default:
  69.                     DispMenu();
  70.                     break;
  71.             }
  72.         }
  73.     }
  74. }
复制代码

88.14   总结
本章节就为大家讲解这么多,需要大家实现操作一遍来熟练掌握FatFs的移植,然后FatFs相关的知识点可以到FatFs官网查看,资料非常详细。


f9de6ca407a3e3d5c8d7e0c58a07bbf0.png
f1a1b991806ba8fd0c6928d35304421e.png
f2a96373dda469bc2b8d37b20d93559a.png
收藏 评论0 发布时间:2021-11-6 23:41

举报

0个回答

所属标签

关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
STM32N6 AI生态系统
STM32MCU,MPU高性能GUI
ST ACEPACK电源模块
意法半导体生物传感器
STM32Cube扩展软件包
关注我们
st-img 微信公众号
st-img 手机版