参考:
1.正点原子
背景:
SD 卡的使用,通过直接操作读写扇区不方便。在电脑上我们的资料常以文件的形式保存,通过文件名我们可以快速对自己的文件数据等进行分类。对于 SD 卡这种容量可以达到非常大的存储介质,按扇区去管理数据已经变得不方便,我们希望单片机也可以像电脑一样方便地用文件的形式去管理,在需要做数据采集的场合也会更加便利。第二种方式管理操作SD卡,使用 FATFS 这个软件工具,利用它在 STM32 上实现类似电脑上的文件管理功能,方便管理 SD 卡上的数据。

51.1 FATFS 简介

FATFS 是一个完全免费开源的 FAT/exFAT 文件系统模块,专门为小型的嵌入式系统而设计。它完全用标准 C 语言(ANSI C C89)编写,所以具有良好的硬件平台独立性,只需做简单的修改就可以移植到 8051、PIC、AVR、ARM、Z80、RX 等系列单片机上。它支持 FATl2、FATl6 和FAT32,支持多个存储媒介;有独立的缓冲区,可以对多个文件进行读/写,并特别对 8 位单片机和 16 位单片机做了优化。FATFS 的特点有:
⚫ Windows/dos 系统兼容的 FAT/exFAT 文件系统
⚫ 独立于硬件平台,方便跨硬件平台移植
⚫ 代码量少、效率高
⚫ 多种配置选项
 支持多卷(物理驱动器或分区,最多 10 个卷)
 多个 ANSI/OEM 代码页包括 DBCS
 支持长文件名、ANSI/OEM 或 Unicode
 支持 RTOS
 支持多种扇区大小
 只读、最小化的 API 和 I/O 缓冲区等
 新版的 exFAT 文件系统,突破了原来 FAT32 对容量管理 32Gb 的上限,可支持更巨大的存储FATFS 的这些特点,加上免费、开源的原则,使得FATFS 应用非常广泛。FATFS 模块的层次结构如图51.1.1 所示:
在这里插入图片描述

最顶层是应用层,使用者无需理会FATFS的内部结构和复杂的FAT协议,只需要调用FATFS模块提供给用户的一系列应用接口函数,如 f_open,f_read,f_write 和 f_close 等,就可以像在PC 上读/写文件那样简单。
中间层 FATFS 模块,实现了 FAT 文件读/写协议。FATFS 模块提供的是 ff.c 和 ff.h。除非有必要,使用者一般不用修改,使用时将头文件直接包含进去即可。需要我们编写移植代码的是 FATFS 模块提供的底层接口,它包括存储媒介读/写接口(diskI/O)和供给文件创建修改时间的实时时钟。
FATFS 的源码及英文详述,大家可以在:http://elm-chan.org/fsw/ff/00index_e.html 这个网站下载到,教程目前使用的版本为 R0.14b。本章我们就使用最新版本的 FATFS 作为介绍,下载最新版本的 FATFS 软件包,解压后可以得到两个文件夹:doc 和 src。doc 里面主要是对 FATFS 的介绍,而 src 里面才是我们需要的源码。source 文件夹详情表,如表 51.1.1 所示:
在这里插入图片描述

FATFS 模块在移植的时候,我们一般只需要修改 2 个文件,即 ffconf.h 和 diskio.c。FATFS模块的所有配置项都是存放在 ffconf.h 里面,我们可以通过配置里面的一些选项,来满足自己的需求。接下来我们介绍几个重要的配置选项。
1) FF_FS_TINY。这个选项在 R0.07 版本中开始出现,之前的版本都是以独立的 C 文件出现(FATFS 和 TinyFATFS),有了这个选项之后,两者整合在一起了,使用起来更方便。我们使用 FATFS,所以把这个选项定义为 0 即可。
2) FF _FS_READONLY。这个用来配置是不是只读,本章我们需要读写都用,所以这里设置为 0 即可。
3) FF _USE_STRFUNC。这个用来设置是否支持字符串类操作,比如 f_putc,f_puts 等,本章我们需要用到,故设置这里为 1。
4) FF _USE_MKFS。用来定时是否使能格式化,本章需要用到,所以设置这里为 1。
5) FF _USE_FASTSEEK。这个用来使能快速定位,我们设置为 1,使能快速定位。
6) FF _USE_LABEL。这个用来设置是否支持磁盘盘符(磁盘名字)读取与设置。我们设置为 1,使能,就可以通过相关函数读取或者设置磁盘的名字了。
7) FF _CODE_PAGE。这个用于设置语言类型,包括很多选项(见 FATFS 官网说明),我们这里设置为 936,即简体中文(GBK 码,同一个文件夹下的 ffunicode.c 根据这个宏选择对应的语言设置)。
8) FF_USE_LFN。该选项用于设置是否支持长文件名(还需要_CODE_PAGE 支持),取值范围为 03。0,表示不支持长文件名,13 是支持长文件名,但是存储地方不一样,我们选择使用 3,通过 ff_memalloc 函数来动态分配长文件名的存储区域。
9) FF_VOLUMES。用于设置 FATFS 支持的逻辑设备数目,我们设置为 2,即支持 2 个设备。
10)FF_MAX_SS。扇区缓冲的最大值,一般设置为 512。
11)FF_FS_EXFAT。新版本增加的功能,使用 exFAT 文件系统,用于支持超过 32Gb 的超大存储。它们使用的是 exFAT 文件系统,使用它时必须要根据设置 FF_USE_LFN 这个参数的值以决定 exFATs 系统使用的内存来自堆栈还是静态数组。
其他配置项,我们这里就不一一介绍了,FATFS 的说明文档里面有很详细的介绍,大家自己阅读 http://elm-chan.org/fsw/ff/doc/config.html 即可。

下面我们来讲讲 FATFS 的移植,FATFS 的移植主要分为 3 步:
① 数据类型:在 integer.h 里面去定义好数据的类型。这里需要了解你用的编译器的数据类型,并根据编译器定义好数据类型。
② 配置:通过 ffconf.h 配置 FATFS 的相关功能,以满足你的需要。
③ 函数编写:打开 diskio.c,进行底层驱动编写,需要编写 5 个接口函数,如下图所示:
在这里插入图片描述

通过以下三步,我们即可完成对 FATFS 的移植。
第一步,我们使用的是 MDK5.34 编译器,数据类型和 integer.h 里面定义的一致,所以此步,我们不需要做任何改动。
第二步,关于 ffconf.h 里面的相关配置,我们在前面已经有介绍(之前介绍的 11 个配置),我们将对应配置修改为我们介绍时候的值即可,其他的配置用默认配置。
第三步,因为 FATFS 模块完全与磁盘 I/O 层分开,因此需要下面的函数来实现底层物理磁盘的读写与获取当前时间。底层磁盘 I/O 模块并不是 FATFS 的一部分,并且必须由用户提供。这些函数一般有 5 个,在 diskio.c 里面。
首先是 disk_initialize 函数,该函数介绍如表 51.1.2 所示:
在这里插入图片描述

第二个函数是 disk_status 函数,该函数介绍如表 51.1.3 所示:
在这里插入图片描述
在这里插入图片描述

第三个函数是 disk_read 函数,该函数介绍如表 51.1.4 所示:
在这里插入图片描述

第四个函数是 disk_write 函数,该函数介绍如表 51.1.5 所示:
在这里插入图片描述

第五个函数是 disk_ioctl 函数,该函数介绍如表 51.1.6 所示:
在这里插入图片描述
在这里插入图片描述

以上五个函数,我们将在软件设计部分一一实现。通过以上 3 个步骤,我们就完成了对FATFS 的移植,就可以在我们的代码里面使用 FATFS 了。
FATFS 提供了很多 API 函数,这些函数 FATFS 的自带介绍文件里面都有详细的介绍(包括参考代码),我们这里就不多说了。这里需要注意的是,在使用 FATFS的时候,必须先通过f_mount函数注册一个工作区,才能开始后续 API 的使用,关于 FATFS 的介绍,我们就介绍到这里。大家可以通过 FATFS 自带的介绍文件进一步了解和熟悉 FATFS 的使用。

51.2 硬件设计

1. 例程功能
本章实验功能简介:开机的时候先初始化 SD 卡,初始化成功之后,注册两个磁盘:一个给 SD 卡用,一个给 SPI FLASH 用,之所以把 SPI FLASH 也当成磁盘来用,一方面是为了演示大容量的 SPI Flash 也可以用 FATFS 管理,说明 FATFS 的灵活性;另一方面可以展示 FATFS 方式比原来直接按地址管理数据便利性,使板载 SPI Flash 的使用更具灵活性。挂载成功后获取SD 卡的容量和剩余空间,并显示在 LCD 模块上,最后定义 USMART 输入指令进行各项测试。
这个原来是正点原子的目标,目前学习快速搭建,使用的是STM32CubeMX,由于STM32CubeMX非常方便但是屏蔽了很多细节,所以还是按照正点原子的步骤学习,但是时间使用的时候还是直接使用STM32CubeMX实现。
2. 硬件资源
1)串口 1 (PA9/PA10 连接在板载 USB 转串口芯片 CH340 上面)
2)micro SD 卡
3)NOR FLASH

51.3 程序设计

FATFS 的驱动为一个硬件独立的组件,因此我们把 FATFS 的移植代码放到“Middlewares”文件夹下。
本章,我们在“SD 卡实验”的基础上进行拓展。在“Middlewares”下新建一个 FATFS 的文件夹,然后将 FATFS R0.14b 程序包解压到该文件夹下。同时,我们在 FATFS 文件夹里面新建一个 exfuns 的文件夹,用于存放我们针对 FATFS 做的一些扩展代码(这个我们后面再讲)。
操作结果如图 51.3.1 所示:
在这里插入图片描述

51.3.2 程序解析

1. FATFS 驱动代码
diskio.c/.h 为我们提供了规定好的底层驱动接口的返回值。这个函数需要使用到我们的硬件接口,所以需要把使用到的硬件驱动的头文件包进来。

#include "./MALLOC/malloc.h"
#include "./FATFS/source/diskio.h"
#include "./BSP/SDIO/sdio_sdcard.h"
#include "./BSP/NORFLASH/norflash_ex.h"

按照 51.1 的描述,接下来我们来对这几个接口进行补充实现。本章,我们用 FATFS 管理了2 个磁盘:SD 卡和 SPI FLASH,我们设置 SD_CARD 为 0,EX_FLASH 位为 1,对应到disk_read/disk_write 函数里面。SD 卡比较好说,但是 SPI FLASH,因为其扇区是 4K 字节大小,我们为了方便设计,强制将其扇区定义为 512 字节,这样带来的好处就是设计使用相对简单,坏处就是擦除次数大增,所以不要随便往 SPI FLASH 里面写数据,非必要最好别写,如果频繁写的话,很容易将 SPI FLASH 写坏。

#define SD_CARD 0 /* SD 卡,卷标为 0 */
#define EX_FLASH 1 /* 外部 spi flash,卷标为 1 */
/**
* 对于 25Q128 FLASH 芯片, 我们规定前 12M 给 FATFS 使用, 12M 以后
* 紧跟字库, 3 个字库 + UNIGBK.BIN, 总大小 3.09M, 共占用 15.09M
* 15.09M 以后的存储空间大家可以随便使用. 
*/
#define SPI_FLASH_SECTOR_SIZE 512 /* 扇区大小 */
#define SPI_FLASH_SECTOR_COUNT 12 * 1024 * 2 /* 扇区数目 */
#define SPI_FLASH_BLOCK_SIZE 8 /* 每个 BLOCK 有 8 个扇区 */
#define SPI_FLASH_FATFS_BASE 0 /* FATFS 在外部 FLASH 起始地址为 0 */

另外,diskio.c 里面的函数,直接决定了磁盘编号(盘符/卷标)所对应的具体设备,比如,以上代码中,我们就通过 switch 来判断,到底要操作 SD 卡,还是 SPI FLASH,然后,分别执行对应设备的相关操作。以此实现磁盘编号和磁盘的关联。
1. disk_initialize 函数
要使用 FATFS 管理,首先要对磁盘进行初始化,所以先看磁盘的初始化函数,其声明如下:
DSTATUS disk_initialize ( BYTE pdrv)
⚫ 函数描述:
初始化指定编号的磁盘,磁盘所指定的存储区。使用每个磁盘前进行初始化,那在代码中直接根据编号调用硬件的初始化接口即可,这样也能保证代码的扩展性,硬件的顺序可以根据自己的喜好定义。
⚫ 函数形参:
形参 1 是 FATFS 管理的磁盘编号 pdrv : 磁盘编号 0~9,我们配置 FF_VOLUMES 为 2 来支持两个磁盘,因此可选值为 0 和 1。
代码实现如下:

/**
* @brief 初始化磁盘
* @param pdrv : 磁盘编号 0~9
* @retval 执行结果(参考 FATFS,DSTATUS 的定义)
*/
/* Physical drive nmuber to identify the drive */
DSTATUS disk_initialize (BYTE pdrv )
{
    uint8_t res = 0;
    switch (pdrv)
    {
        case SD_CARD: /* SD 卡 */
            res = sd_init(); /* SD 卡初始化 */
            break;
        case EX_FLASH: /* 外部 flash */
            norflash_init(); /* 外部 flash 初始化 */ 
            break;
        default:
            res = 1;
    }
    
    if (res)
    {
        return STA_NOINIT;
    }
    else
    {
        return 0; /* 初始化成功*/
    }
}

⚫ 函数返回值:
DSTATUS 枚举类型的值,FATFS 规定了自己的返回值来管理各接口函数的操作结果,方便后续函数的操作和判断,我们看看它的定义:

/* Status of Disk Functions */
typedef BYTE DSTATUS;/* Disk Status Bits (DSTATUS) */
#define STA_NOINIT 0x01 /* Drive not initialized */
#define STA_NODISK 0x02 /* No medium in the drive */
#define STA_PROTECT 0x04 /* Write protected */

定义时也写出了各个参数的含义,根据 ff.c 中的调用实例可知操作返回0 才是正常的状态,其它情况发生的话就需要结合硬件进行分析了。

2. disk_status 函数
便于我们知道当前磁盘驱动器的状态,所以 FATFS 也有 disk_status 函数提供给我们使用,其声明如下:
DSTATUS disk_status (BYTE pdrv)
⚫ 函数描述:
返回当前磁盘驱动器的状态。
⚫ 函数形参:
形参 1 是 FATFS 管理的磁盘编号 pdrv : 磁盘编号 0~9,我们配置 FF_VOLUMES 为 2 来支持两个磁盘,因此可选值为 0 和 1。
⚫ 函数返回值:
直接返回 RES_OK。

3. disk_read 函数
disk_read 实现直接从硬件接口读取数据,这个为接口 FATFS 的其它读操作接口函数调用,其声明如下:
DRESULT disk_read (BYTE pdrv, BYTE buff, DWORD sector, UINT count)
⚫ 函数描述:
从磁盘驱动器上读取扇区数据。
⚫ 函数形参:
形参 1:是 FATFS 管理的磁盘编号 pdrv : 磁盘编号 0~9,我们配置 FF_VOLUMES 为 2 来支持两个磁盘,因此可选值为 0 和 1。
形参 2:buff 指向要保存数据的内存区域指针,为字节类型。
形参 3:sector 为实际物理操作时要访问的扇区地址。
形参 4:count 为本次要读取的数据量,读到的数量为count
512。
代码实现如下,同样要根据我们定义的设备标号,在 swich-case 中添加对应硬件的驱动:

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 */
)
{
    uint8_t res = 0;
    if (!count)
        return RES_PARERR; 
    /* count 不能等于 0,否则返回参数错误 */
    switch (pdrv)
    {
        case SD_CARD: /* SD 卡 */
            res = sd_read_disk(buff, sector, count);
            while (res) /* 读出错 */
            {
                if (res != 2)
                    sd_init(); /* 重新初始化 SD 卡 */
                res = sd_read_disk(buff, sector, count);
                //printf("sd rd error:%d\r\n", res);
            }
            break;
        case EX_FLASH: /* 外部 flash */
            for (; count > 0; count--)
            {
                norflash_read(buff, SPI_FLASH_FATFS_BASE + sector *SPI_FLASH_SECTOR_SIZE, SPI_FLASH_SECTOR_SIZE);
                sector++;
                buff += SPI_FLASH_SECTOR_SIZE;
            }
            res = 0;
            break;
        default:
            res = 1;
    }
     
    /* 处理返回值,将返回值转成 ff.c 的返回值 */
    if (res == 0x00)
    {
        return RES_OK;
    }
    else
    {
        return RES_ERROR;
    }
}

⚫ 函数返回值:
DRESULT 为枚举类型,diskio.h 中有其定义,根据返回值的含义确认操作结果即可。在这里也把该结果展示出来,如下所示:

/* Results of Disk Functions */
typedef enum
{
    RES_OK = 0, /* 0: 操作成功 */
    RES_ERROR, /* 1: 读/写错误 */
    RES_WRPRT, /* 2: 写保护状态 */
    RES_NOTRDY, /* 3: 设备忙 */
    RES_PARERR /* 4: 其它情形 */
} DRESULT;

根据返回值的含义确认操作结果即可。

4. disk_write 函数
disk_write 实现直接从硬件接口读取数据,这个接口为FATFS的其它写操作接口函数调用,其声明如下:
DRESULT disk_write ( BYTE pdrv, const BYTE buff, DWORD sector, UINT count)
⚫ 函数描述:
向磁盘驱动器上写入扇区数据。
⚫ 函数形参:
形参 1 是 FATFS 管理的磁盘编号 pdrv : 磁盘编号 0~9,我们配置 FF_VOLUMES 为 2 来支持两个磁盘,因此可选值为 0 和 1。
形参 2:buff 指向要保存数据的内存区域指针,为字节类型。
形参 3:sector 为实际物理操作时要访问的扇区地址。
形参 4:count 为本次要写入的数据量,写入的数量为count
512字节(count表示的是扇区的倍数)。
代码实现如下,同样要根据我们定义的设备标号,在 switch-case 中添加对应硬件的驱动:

/**
* @brief 写扇区
* @param pdrv 
: 磁盘编号 0~9
* @param buff 
: 发送数据缓存区首地址
* @param sector : 扇区地址
* @param count : 需要写入的扇区数
* @retval 无
*/
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 */
)
{
    uint8_t res = 0;
    if (!count)
        return RES_PARERR; /* count 不能等于 0,否则返回参数错误 */
    switch (pdrv)
    {
        case SD_CARD: /* SD 卡 */
            res = sd_write_disk((uint8_t *)buff, sector, count);
            while (res) /* 写出错 */
            {
                sd_init(); /* 重新初始化 SD 卡 */
                res = sd_write_disk((uint8_t *)buff, sector, count);
                //printf("sd wr error:%d\r\n", res);
            }
            break;
        case EX_FLASH: /* 外部 flash */
            for (; count > 0; count--)
            {
                norflash_write((uint8_t *)buff, SPI_FLASH_FATFS_BASE + sector *SPI_FLASH_SECTOR_SIZE, SPI_FLASH_SECTOR_SIZE);
                sector++;
                buff += SPI_FLASH_SECTOR_SIZE;
            }
            res = 0;
            break;
        default:
            res = 1;
    }
    /* 处理返回值,将返回值转成 ff.c 的返回值 */
    if (res == 0x00)
    {
        return RES_OK;
    }
    else
    {
        return RES_ERROR;
    }
}

⚫ 函数返回值:
DRESULT 为枚举类型,diskio.h 中有其定义,编写读函数时已经介绍了,注意要把返回值转成这个枚举类型的参数。

5. disk_ioctl 函数
disk_ioctl 实现一些控制命令,这个接口为 FATFS 提供一些硬件操作信息,其声明如下:
DRESULT disk_ioctl (BYTE pdrv, BYTE cmd, void *buff)
⚫ 函数描述:
初始化指定编号的磁盘,磁盘所指定的存储区。
⚫ 函数形参:
形参 1 是 FATFS 管理的磁盘编号 pdrv : 磁盘编号 0~9,我们配置 FF_VOLUMES 为 2 来支持两个磁盘,因此可选值为 0 和 1。
形参 2:cmd 是 FATFS 定义好的一些宏,用于访问硬盘设备的一些状态。我们实现几个简单的操作接口,用于获取磁盘容量这些基础信息(diskio.h 中已经定义好了),为方便,我们先只实现几个标准的应用接口,关于 SDIO 的一些扩展命令我们再根据需要进行支持。

/* Command code for disk_ioctrl fucntion */
/* Generic command (Used by FatFs) */
#define CTRL_SYNC 0 /* 完成挂起的写入过程(当 FF_FS_READONLY == 0) */
#define GET_SECTOR_COUNT 1 /* 获取磁盘扇区数(当 FF_USE_MKFS == 1) */
#define GET_SECTOR_SIZE 2  /*获取磁盘存储空间大小 (当 FF_MAX_SS != FF_MIN_SS) */
#define GET_BLOCK_SIZE 3   /* 每个扇区块的大小 (当 FF_USE_MKFS == 1) */

下面是从 http://elm-chan.org/fsw/ff/doc/dioctl.html 得到的参数实现效果,我们也可以参考原有的 disk_ioctl 的实现来理解这几个参数。

形参 3 :buff 为 void 形指针,根据命令的格式需要,把对应的值转成对应形式传给它。
参考原有的 disk_ioctl 的实现,我们的函数实现如下。

/**
* @brief 获取其他控制参数
* @param pdrv : 磁盘编号 0~9
* @param ctrl : 控制代码
* @param buff : 发送/接收缓冲区指针
* @retval 无
*/
DRESULT disk_ioctl (
BYTE pdrv, /* Physical drive nmuber (0..) */
BYTE cmd, /* Control code */
void *buff /* Buffer to send/receive control data */
)
{
    DRESULT res;
    if (pdrv == SD_CARD) /* SD 卡 */
    {
        switch (cmd)
        {
            case CTRL_SYNC: 
                res = RES_OK; 
                break;
            case GET_SECTOR_SIZE:
                *(DWORD *)buff = 512;
                res = RES_OK;
                break;
            case GET_BLOCK_SIZE:
                *(WORD *)buff = g_sd_card_info_handle.LogBlockSize;
                res = RES_OK;
                break;
            case GET_SECTOR_COUNT:
                *(DWORD *)buff = g_sd_card_info_handle.LogBlockNbr;
                res = RES_OK;
                break;
            default:
                res = RES_PARERR;
                break;
        }
    }
    else if (pdrv == EX_FLASH) /* 外部 FLASH */
    {
        switch (cmd)
        {
        case CTRL_SYNC:
            res = RES_OK;
            break;
        case GET_SECTOR_SIZE:
            *(WORD *)buff = SPI_FLASH_SECTOR_SIZE;
            res = RES_OK;
            break;
        case GET_BLOCK_SIZE:
            *(WORD *)buff = SPI_FLASH_BLOCK_SIZE;
            res = RES_OK;
            break;
        case GET_SECTOR_COUNT:
            *(DWORD *)buff = SPI_FLASH_SECTOR_COUNT;
            res = RES_OK;
            break;
        default:
            res = RES_PARERR;
            break;
            }
    }
    else
    {
        res = RES_ERROR; /* 其他的不支持 */
    }
    return res;
}

⚫ 函数返回值:
DRESULT 为枚举类型,diskio.h 中有其定义,编写读函数时已经介绍了,注意要把返回值转成这个枚举类型的参数。
以上实现了我们 51.1 节提到的 5 个函数,ff.c 中需要实现 get_fattime (void),同时因为在ffconf.h 里面设置对长文件名的支持为方法 3,所以必须在 ffsystem.c 中实现 get_fattime、ff_memalloc 和 ff_memfree 这三个函数。这部分比较简单,直接参考我们修改后的 ffsystem.c 的源码。
至此,我们已经可以直接使用 FATFS 下的 ff.c 下的 f_mount 的接口挂载磁盘,然后使用类似标准 C 的文件操作函数,就可以实现文件操作。但 f_mount 还需要一些文件操作的内存,为了方便操作,我们在 FATFS 文件夹下还新建了一个 exfuns 的文件夹,该文件夹用于保存一些针对 FATFS 的扩展代码,如刚才提到的 FATFS 相关函数的内存申请方法等。
本章,我们编写了 4 个文件,分别是:exfuns.c、exfuns.h、fattester.c 和 fattester.h。其中 exfuns.c主要定义了一些全局变量,方便 FATFS 的使用,同时实现了磁盘容量获取等函数。而 fattester.c文件则主要是为了测试 FATFS 用,因为 FATFS 的很多函数无法直接通过 USMART 调用,所以我们在 fattester.c 里面对这些函数进行了一次再封装,使得可以通过 USMART 调用。
这几个文件的代码,大家可以直接使用本例程源码,我们将 exfuns.c/.h 和 fattester.c/.h 加入FATFS 组下的 exfuns 文件下,直接使用即可。

6. exfuns_init 函数
我们在使用文件操作前,需要用 f_mount 函数挂载磁盘,我们在挂载 SD 卡前需要一些文件系统的内存,为了方便管理,我们定义一个全局的 fs[FF_VOLUMES]指针,定成数组是我们要管理多个磁盘,而 f_mount 也需要一个 FATFS 类型的指针,定义如下:
/* 逻辑磁盘工作区(在调用任何 FATFS 相关函数之前,必须先给 fs 申请内存) */
FATFS *fs[FF_VOLUMES];
接下来只要用内存管理部分的知识来实现对 fs 指针的内存申请即可。

/**
* @brief 为 exfuns 申请内存
* @param 无
* @retval 0, 成功; 1, 失败.
*/
uint8_t exfuns_init(void)
{
    uint8_t i;
    uint8_t res = 0;
     
    for (i = 0; i < FF_VOLUMES; i++)
    { /*为磁盘 i 工作区申请内存 */
        fs[i] = (FATFS *)
            mymalloc(SRAMIN, sizeof(FATFS));
        if (!fs[i])
            break;
    }
    
    #if USE_FATTESTER == 1 /* 如果使能了文件系统测试 */ 
    res = mf_init(); /* 初始化文件系统测试(申请内存) */
    #endif
     
    if (i == FF_VOLUMES && res == 0)
    {
        return 0; /* 申请有一个失败,即失败. */
    }
    else
    {
        return 1;
    }
}

其它的函数我们暂时没有使用到,这里先不介绍了,大家使用时查阅源码即可。

51.4 STM32CubeMX

在这里插入图片描述
在这里插入图片描述

51.5 下载验证

在代码编译成功之后,我们通过下载代码,串口打印:
在这里插入图片描述

取出sd卡,插入电脑,看见正常生成的文件和写入的数据
在这里插入图片描述

51.6 源码

git clone git@gitee.com:xiaoliangliangcong/stm32.git
STM32F407ZGT6/19.FATFS

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐