【STM32H7教程】第88章 STM32H7的SDMMC总线应用之SD卡移植FatFs文件系统

时间:2024-03-08 12:01:20

完整教程下载地址:http://www.armbbs.cn/forum.php?mod=viewthread&tid=86980

第88章       STM32H7的SDMMC总线应用之SD卡移植FatFs文件系统

本章节为大家讲解SD卡的FatFs文件系统移植。

88.1 初学者重要提示

88.2 SD卡硬件接口设计

88.3 SD卡基础知识

88.4 各种存储卡区别

88.5 关于SD卡内部是否自带擦写均衡

88.6 FatFs文件系统简介

88.7 FatFs移植步骤

88.8 FatFs应用代码测试

88.9 FatFs移植接口文件diskio.c说明

88.10 SDMMMC自带IDMA的4字节对齐问题(重要)

88.11 实验例程设计框架

88.12 实验例程说明(MDK)

88.13 实验例程说明(IAR)

88.14 总结

 

 

88.1 初学者重要提示

1、  SDMMC的相关知识点可以看第87章。

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

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

软件下载:http://www.armbbs.cn/forum.php?mod=viewthread&tid=96181

4、 STM32H7的SDMMC也支持eMMC:

  •  【普及贴】各个厂家eMMC读写速度,镁光,东芝,三星,ISSI和旺宏

http://www.armbbs.cn/forum.php?mod=viewthread&tid=95954

  •   H7的8线SDIO DMA驱动eMMC的裸机性能,读43MB/S,写18.8MB/S

http://www.armbbs.cn/forum.php?mod=viewthread&tid=95953

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

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

7、  FAT文件系统基础:

http://elm-chan.org/fsw/ff/00index_e.html

8、  exFAT文件系统基础:

http://elm-chan.org/fsw/ff/00index_e.html

9、  FatFS API列表:

http://elm-chan.org/fsw/ff/00index_e.html

10、  FatFS 应用笔记(含移植说明)

http://elm-chan.org/fsw/ff/doc/appnote.html

11、  FatFS中ffconf.h文件中各种配置说明:

http://elm-chan.org/fsw/ff/doc/config.html

12、  FatFs的RTOS版本移植:

  •   基于STM32H7的uCOS-III + FatFS + emWin + ST USB的综合模板下载

http://www.armbbs.cn/forum.php?mod=viewthread&tid=100125

  •   基于STM32H7的FreeRTOS+ FatFS + emWin + ST USB的综合模板下载

http://www.armbbs.cn/forum.php?mod=viewthread&tid=100127

  •   基于STM32H7的uCOS-II + FatFS + emWin + ST USB的综合模板下载

http://www.armbbs.cn/forum.php?mod=viewthread&tid=100126

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卡其实是一种卡,区别是引脚使用上。

88.4.2 SDIO卡

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

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

https://en.wikipedia.org/wiki/SD_card#SDIO_cards

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

 

88.4.3 MMC卡,eMMC

截止2018年,市场上已经没有设备内置MMC卡槽,但eMMC得到了广泛应用https://en.wikipedia.org/wiki/MultiMediaCard

88.4.4 CF卡

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

基础规格:

 

实际效果:

 

 

88.4.5 总体区别

来自Wiki:https://en.wikipedia.org/wiki/SD_card#Micro

 

88.5 关于SD卡内部是否自带擦写均衡

根据网上搜的一个闪迪的规格书,里面说是带擦写均衡的:

http://www.armbbs.cn/forum.php?mod=viewthread&tid=102891

 

88.6 FatFs文件系统简介

FatFs是用于小型嵌入式系统的通用FAT / exFAT文件系统模块。FatFs是按照ANSI C(C89)编写的并且与磁盘I / O层完全分开。因此,它独立于平台。它可以并入资源有限的小型MCU中,例如8051,PIC,AVR,ARM,Z80,RX等。此处还提供了适用于小型MCU的Petit FatFs模块。

特征:

  1.   DOS / Windows兼容的FAT / exFAT文件系统。
  2.   平台无关,容易移植。
  3.   程序代码和工作区的占用空间非常小。
  4.   支持以下各种配置选项:
  •  ANSI / OEM或Unicode中的长文件名。
  •  exFAT文件系统,64位LBA和GPT可存储大量数据。
  • RTOS的线程安全。
  •  多个卷(物理驱动器和分区)。
  •  可变扇区大小。
  •  多个代码页,包括DBCS。
  •   只读,可选API,I / O缓冲区等

 

 

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

  •   FAT文件系统基础:

http://elm-chan.org/fsw/ff/00index_e.html

  •   exFAT文件系统基础:

http://elm-chan.org/fsw/ff/00index_e.html

  •   FatFS API列表:

http://elm-chan.org/fsw/ff/00index_e.html

  •   FatFS 应用笔记(含移植说明)

http://elm-chan.org/fsw/ff/doc/appnote.html

  •   FatFS中ffconf.h文件中各种配置说明:

http://elm-chan.org/fsw/ff/doc/config.html

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[4]; /* 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]);
    }

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

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

        if (FileInf.fname[0] == \'.\')
        {
            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卡创建一个新文件,文件内容填写“www.armfly.com”
*    形    参:无
*    返 回 值: 无
*********************************************************************************************************
*/
static void CreateNewFile(void)
{
    FRESULT result;
    uint32_t bw;
    char path[32];


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

    /* 打开文件 */
    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]);
    }

    /* 写一串数据 */
    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[result]);
    }

    /* 关闭文件*/
    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[64];

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

    /* 打开文件 */
    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[bw] = 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[64]; 

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

    /* 创建目录/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[result]);
        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[result]);
        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[result]);
        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[64]; 
    
     /* 挂载文件系统 */
    result = f_mount(&fs, DiskPath, 0);            /* Mount a logical drive */
    if (result != FR_OK)
    {
        printf("挂载文件系统失败 (%s)\r\n", FR_Table[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);
    }

    /* 先删除目录/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[64]; 
    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\';
    }

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

    /* 打开文件 */
    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] != (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

代码如下:

/**
  * @brief  Gets Disk Status
  * @param  pdrv: 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[pdrv]->disk_status(disk.lun[pdrv]);
  return stat;
}

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

/**
  * @brief  Gets Disk Status
  * @param  lun : 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

代码如下:

/**
  * @brief  Initializes a Drive
  * @param  pdrv: 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[pdrv] == 0)
  {
    disk.is_initialized[pdrv] = 1;
    stat = disk.drv[pdrv]->disk_initialize(disk.lun[pdrv]);
  }
  return stat;
}

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

/**
  * @brief  Initializes a Drive
  * @param  lun : 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

代码如下:

/**
  * @brief  Reads Sector(s)
  * @param  pdrv: Physical drive number (0..)
  * @param  *buff: Data buffer to store read data
  * @param  sector: Sector address (LBA)
  * @param  count: 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[pdrv]->disk_read(disk.lun[pdrv], buff, sector, count);
  return res;
}

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

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

/**
  * @brief  Reads Sector(s)
  * @param  lun : not used
  * @param  *buff: Data buffer to store read data
  * @param  sector: Sector address (LBA)
  * @param  count: 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

代码如下:

/**
  * @brief  Writes Sector(s)
  * @param  pdrv: Physical drive number (0..)
  * @param  *buff: Data to be written
  * @param  sector: Sector address (LBA)
  * @param  count: 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[pdrv]->disk_write(disk.lun[pdrv], buff, sector, count);
  return res;
}

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

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

/**
  * @brief  Writes Sector(s)
  * @param  lun : not used
  * @param  *buff: Data to be written
  * @param  sector: Sector address (LBA)
  * @param  count: 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

代码如下:

/**
  * @brief  I/O control operation
  * @param  pdrv: Physical drive number (0..)
  * @param  cmd: 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[pdrv]->disk_ioctl(disk.lun[pdrv], cmd, buff);
  return res;
}
#endif /* _USE_IOCTL == 1 */

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

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

/**
  * @brief  I/O control operation
  * @param  lun : not used
  * @param  cmd: 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

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

/**
  * @brief  Gets Time from RTC
  * @param  None
  * @retval Time in DWORD
  */
__weak DWORD get_fattime (void)
{
  return 0;
}

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 www.armfly.com \r\n"};
ALIGN_32BYTES(uint8_t g_TestBuf[BUF_SIZE]);

88.11          实验例程设计框架

通过程序设计框架,让大家先对配套例程有一个全面的认识,然后再理解细节,本次实验例程的设计框架如下:

 第1阶段,上电启动阶段:

  • 这部分在第14章进行了详细说明。

  第2阶段,进入main函数:

  • 第1步,硬件初始化,主要是MPU,Cache,HAL库,系统时钟,滴答定时器,LED和串口。
  • 第2步,FatFs应用程序设计部分。

88.12          实验例程说明(MDK)

配套例子:

V7-025_FatFS文件系统例子(SD卡 V1.1)

实验目的:

  1. 学习SD卡的FatFS移植实现。

实验内容:

  1. 上电启动了一个软件定时器,每100ms翻转一次LED2。
  2. V7开发板的SD卡接口是用的SDMMC1,而这个接口仅支持AXI SRAM区访问,其它SRAM和TCP均不支持。

实验操作:

  1. 测试前务必将SD卡插入到开发板左上角的卡座中。
  2. 支持以下6个功能,用户通过电脑端串口软件发送数字1-6给开发板即可
  3. printf("1 - 显示根目录下的文件列表\r\n");
  4. printf("2 - 创建一个新文件armfly.txt\r\n");
  5. printf("3 - 读armfly.txt文件的内容\r\n");
  6. printf("4 - 创建目录\r\n");
  7. printf("5 - 删除文件和目录\r\n");
  8. 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();        /* 硬件初始化 */
    
    PrintfLogo();    /* 打印例程名称和版本等信息 */

    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)

实验目的:

  1. 学习SD卡的FatFS移植实现。

实验内容:

  1. 上电启动了一个软件定时器,每100ms翻转一次LED2。
  2. V7开发板的SD卡接口是用的SDMMC1,而这个接口仅支持AXI SRAM区访问,其它SRAM和TCP均不支持。

实验操作:

  1. 测试前务必将SD卡插入到开发板左上角的卡座中。
  2. 支持以下6个功能,用户通过电脑端串口软件发送数字1-6给开发板即可
  3. printf("1 - 显示根目录下的文件列表\r\n");
  4. printf("2 - 创建一个新文件armfly.txt\r\n");
  5. printf("3 - 读armfly.txt文件的内容\r\n");
  6. printf("4 - 创建目录\r\n");
  7. printf("5 - 删除文件和目录\r\n");
  8. 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();        /* 硬件初始化 */
    
    PrintfLogo();    /* 打印例程名称和版本等信息 */

    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官网查看,资料非常详细。