1)实验平台:正点原子MiniPro H750开发板
2)平台购买地址:https://detail.tmall.com/item.htm?id=677017430560
3)全套实验源码+手册+视频下载地址:http://www.openedv.com/thread-336836-1-1.html
4)对正点原子STM32感兴趣的同学可以加群讨论:879133275

第四十六章 FATFS实验

上一章,我们学习了SD卡的使用,并实现了简单的读写扇区功能。在电脑上我们的资料常以文件的形式保存,通过文件名我们可以快速对自己的文件数据等进行分类。对于SD卡这种容量可以达到非常大的存储介质,按扇区去管理数据已经变得不方便,我们希望单片机也可以像电脑一样方便地用文件的形式去管理,在需要做数据采集的场合也会更加便利。
本章,我们将介绍FATFS这个软件工具,利用它在STM32上实现类似电脑上的文件管理功能,方便管理SD卡上的数据,并设计例程在SD卡上生成文件并对文件实现读写操作。
本章分为如下几个部分:
46.1 FATFS简介
46.2 硬件设计
46.3 程序设计
46.4 下载验证

46.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模块的层次结构如图46.1.1所示:

图46.1.1 FATFS层次结构图
最顶层是应用层,使用者无需理会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.14。本章我们就使用最新版本的FATFS作为介绍,下载最新版本的FATFS软件包,解压后可以得到两个文件夹:doc和src。doc里面主要是对FATFS的介绍,而src里面才是我们需要的源码。source文件夹详情表,如表46.1.1.1所示:

表46.1.1 FATFS的源码文件介绍
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个接口函数,如下图所示:

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

表46.1.2 disk_initialize函数介绍
第二个函数是disk_status函数,该函数介绍如表46.1.3所示:

表46.1.3 disk_status函数介绍
第三个函数是disk_read函数,该函数介绍如表46.1.4所示:

表46.1.4 disk_read函数介绍
第四个函数是disk_write函数,该函数介绍如表46.1.5所示:
函数名称 disk_write

表46.1.5 disk_write函数介绍
第五个函数是disk_ioctl函数,该函数介绍如表46.1.6所示:

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

  1. 例程功能
    本章实验功能简介:开机的时候先初始化SD卡,初始化成功之后,注册两个磁盘:一个给SD卡用,一个给SPI FLASH用,之所以把SPI FLASH也当成磁盘来用,一方面是为了演示大容量的SPI Flash也可以用FATFS管理,说明FATFS的灵活性;另一方面可以展示FATFS方式比原来直接按地址管理数据便利性,使板载SPI Flash的使用更具灵活性。挂载成功后获取SD卡的容量和剩余空间,并显示在LCD模块上,最后定义USMART输入指令进行各项测试。本实验通过LED0指示程序运行状态。
  2. 硬件资源
    1)RGB灯
    RED :LED0 - PB4
    2)正点原子2.8/3.5/4.3/7寸TFTLCD模块(仅限MCU屏,16位8080并口驱动)
    3)串口1 (PA9/PA10连接在板载USB转串口芯片CH340上面)
    4)SD卡,通过SDMMC1(SDMMC_D0D4(PC8PC11),SDMMC_SCK(PC12),
    SDMMC_CMD(PD2))连接
    5)norflash(QSPI FLASH芯片,连接在QSPI上)
    这几个外设原理图我们在之前的章节已经介绍过了,这里就不重复介绍了,不清楚的话可以查看本文之前章节的描述或对光盘资料提供的开发板原理图。
    46.3 程序设计
    FATFS的驱动为一个硬件独立的组件,因此我们把FATFS的移植代码放到《Middlewares》文件夹下。
    本章,我们在《SD卡实验》的基础上进行拓展。在《Middlewares》下新建一个 FATFS 的文件夹,然后将 FATFS R0.14 程序包解压到该文件夹下。同时,我们在 FATFS 文件夹里面新建一个exfuns的文件夹,用于存放我们针对FATFS做的一些扩展代码(这个我们后面再讲)。操作结果如图 46.3.1 所示:

图46.3.1 FATFS 文件夹子目录
46.3.1 程序流程图

图46.3.1.1 SD读写实验程序流程图
46.3.2 程序解析

  1. FATFS驱动代码
    这里我们只讲解核心代码,详细的源码请大家参考光盘本实验对应源码。
    diskio.c/.h为我们提供了规定好的底层驱动接口的返回值。这个函数需要使用到我们的硬件接口,所以需要把使用到的硬件驱动的头文件包进来。
#include "./MALLOC/malloc.h"
#include "./FATFS/source/diskio.h"
#include "./BSP/SDMMC/sdmmc_sdcard.h"
#include "./BSP/NORFLASH/norflash_ex.h"

按照46.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       /* 外部qspi flash,卷标为1 */
/*** SPB滑屏占用大小:2049600=1.955MB≈501个扇区(以4K字节一个扇区计算)* 字库大小:6302984=6.011MB≈1539个扇区* 对于25Q128* 0X90000000 是基地址,为了满足综合实验,以基地址为偏移:* 主 代码区:0~4096*199,占200个扇区,800KB* SPB 数据区:200*4096~700*4096,占501个扇区,2004KB* 字库数据区:701*4096~2239*4096,占1539个扇区,6156KB* 文件系统区:2240*4096~4095*4096,占1856个扇区,7424KB*/
#define SPI_FLASH_SECTOR_SIZE   512
#define SPI_FLASH_SECTOR_COUNT  7424 * 2  /* 25Q128,最后7424KB字节给FATFS占用 */
#define SPI_FLASH_BLOCK_SIZE    8           /* 每个BLOCK有8个扇区 */
#define SPI_FLASH_FATFS_BASE    2240*4096 /* FATFS在外部FLASH的

起始地址 */
另外,diskio.c里面的函数,直接决定了磁盘编号(盘符/卷标)所对应的具体设备,比如,以上代码中,我们就通过switch来判断,到底要操作SD卡,还是SPI FLASH,然后,分别执行对应设备的相关操作。以此实现磁盘编号和磁盘的关联。

  1. disk_initialize函数
    要使用一个外设首先要对它进行初始化,所以先看sd卡的初始化函数,其声明如下:
    DSTATUS disk_initialize ( BYTE pdrv)
    函数描述:
    初始化指定编号的磁盘,磁盘所指定的存储区。使用每个磁盘前进行初始化,那在代码中直接根据编号调用硬件的初始化接口即可,这样也能保证代码的扩展性,硬件的顺序可以根据自己的喜好定义。
    函数形参:
    形参1是FATFS管理的磁盘编号pdrv : 磁盘编号0~9,我们配置FF_VOLUMES为2来支持两个磁盘,因此可选值为0和1。
    代码实现如下:
/*** @brief       初始化磁盘* @param       pdrv : 磁盘编号0~9* @retval      无*/
DSTATUS disk_initialize (BYTE pdrv       /* Physical drive nmuber to identify the drive */
)
{uint8_t res = 0;switch (pdrv){case SD_CARD:               /* SD卡 */res = sd_init();      /* SD卡初始化 */break;case EX_FLASH:            /* 外部flash *//* 没有初始化函数!不能调用:norflash_init!* 因为在sys.c里面,开启内存映射的时候,已经初始化QSPI接口了*//* 读取FLASH ID, 通过读取ID, 将g_norflash_addrw修改,以支持25Q256 * 如果调用了norflash_init, 则不需要再读取ID了*/norflash_ex_read_id(); 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才是正常的状态,其它情况发生的话就需要结合硬件进行分析了。
  1. disk_status函数
    要使用一个外设首先要对它进行初始化,所以先看sdio的初始化函数,其声明如下:
    DSTATUS disk_status (BYTE pdrv)
    函数描述:
    可以随时查询对应的SD卡的状态。我们硬件初始化后就
    函数形参:
    形参1是FATFS管理的磁盘编号pdrv : 磁盘编号0~9,我们配置FF_VOLUMES为2来支持两个磁盘,因此可选值为0和1。
    为了简单测试,所以我们这里没有加入硬件状态的判断,代码也不贴出来了。
    函数返回值:
    直接返回RES_OK。
  2. 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为本次要读取的数据量,最长为unsigned int,读到的数量为字节数。
    代码实现如下,同样要根据我们定义的设备标号,在swich-case中添加对应硬件的驱动:
/*** @brief       读扇区* @param       pdrv   : 磁盘编号0~9* @param       buff   : 数据接收缓冲首地址* @param       sector : 扇区地址* @param       count  : 需要读取的扇区数* @retval      无*/
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_ex_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为本次要读取的数据量,最长为unsigned int,读到的数量为字节数。
代码实现如下,同样要根据我们定义的设备标号,在swich-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中已经定义好了),为方便,我们先只实现几个标准的应用接口,关于iSDIO/MMC的一些扩展命令我们再根据需要进行支持。
/
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的实现来理解这几个参数。
命令 说明
CTRL_SYNC 确保设备已完成挂起的写入过程。如果磁盘I/O层或存储设备具有回写缓存,则必须立即将缓存数据提交到介质。如果对介质的每个写入操作都正常完成,则此命令无任何动作。
GET_SECTOR_COUNT 返回对应标号的硬盘的可用扇区数。此命令由f_mkfs\f_fdisk函数用于确定要创建的卷/分区的大小。使用时需要设置FF_USE_MKFS为1。
GET_SECTOR_SIZE 将用于读/写函数的扇区大小检索到buff指向的WORD变量中。有效扇区大小为512、1024、2048和4096。只有在FF_MAX_SS>FF_MIN_SS执行此命令时。当FF_MAX_SS=FF_MIN_SS时,将永远不会使用此命令,并且读/写函数必须仅在FF_MAX_SSbytes/扇区中工作。
GET_BLOCK_SIZE 将扇区单位中闪存介质的块大小以DWORD指针存到buff中。允许的值为1到32768。如果擦除块大小未知或非闪存介质,则返回1。此命令仅由f_mkfs函数使用,并尝试对齐擦除块边界上的数据区域。使用时需要设置FF_USE_MKFS为1。
形参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中有其定义,编写读函数时已经介绍了,注意要把返回值转成这个枚举类型的参数。
以上实现了我们46.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++){fs[i] = (FATFS *)mymalloc(SRAMIN, sizeof(FATFS));/*为磁盘i工作区申请内存 */if (!fs[i])break;}#if USE_FATTESTER == 1  /* 如果使能了文件系统测试 */res = mf_init();     /* 初始化文件系统测试(申请内存) */
#endifif (i == FF_VOLUMES && res == 0){return 0;   /* 申请有一个失败,即失败. */}else {return 1;}
}
其它的函数我们暂时没有使用到,这里先不介绍了,大家使用时查阅源码即可。
2. mian.c代码
在main.c就比较简单了,按照我们的流程图的思路编写即可,在成功初始化后,我们通过LCD显示文件操作的结果。
最后,我们编写的main函数如下:
int main(void)
{uint32_t total, free;uint8_t t = 0;uint8_t res = 0;sys_cache_enable();                       /* 打开L1-Cache */HAL_Init();                                 /* 初始化HAL库 */sys_stm32_clock_init(240, 2, 2, 4);    /* 设置时钟, 480Mhz */delay_init(480);                          /* 延时初始化 */usart_init(115200);                      /* 串口初始化为115200 */usmart_dev.init(240);                     /* 初始化USMART */mpu_memory_protection();                 /* 保护相关存储区域 */led_init();                               /* 初始化LED */lcd_init();                             /* 初始化LCD */key_init();                             /* 初始化按键 */my_mem_init(SRAMIN);                     /* 初始化内部内存池(AXI) */my_mem_init(SRAM12);                     /* 初始化SRAM12内存池(SRAM1+SRAM2) */my_mem_init(SRAM4);                     /* 初始化SRAM4内存池(SRAM4) */my_mem_init(SRAMDTCM);                  /* 初始化DTCM内存池(DTCM) */my_mem_init(SRAMITCM);                /* 初始化ITCM内存池(ITCM) */lcd_show_string(30, 50, 200, 16, 16, "STM32", RED);lcd_show_string(30, 70, 200, 16, 16, "FATFS TEST", RED);lcd_show_string(30, 90, 200, 16, 16, "ATOM@ALIENTEK", RED);lcd_show_string(30, 110, 200, 16, 16, "Use USMART for test", RED);while (sd_init())   /* 检测不到SD卡 */{lcd_show_string(30, 150, 200, 16, 16, "SD Card Error!", RED);delay_ms(500);lcd_show_string(30, 150, 200, 16, 16, "Please Check! ", RED);delay_ms(500);LED0_TOGGLE();  /* LED0闪烁 */}exfuns_init();                      /* 为fatfs相关变量申请内存 */f_mount(fs[0], "0:", 1);          /* 挂载SD卡 */res = f_mount(fs[1], "1:", 1);    /* 挂载FLASH. */if (res == 0X0D)   /* FLASH磁盘,FAT文件系统错误,重新格式化FLASH */
{/* 格式化FLASH */lcd_show_string(30, 150, 200, 16, 16, "Flash Disk Formatting...", RED);
/* 格式化FLASH,1:,盘符;0,使用默认格式化参数 */res = f_mkfs("1:", 0, 0, FF_MAX_SS);   if (res == 0){/* 设置Flash磁盘的名字为:ALIENTEK */f_setlabel((const TCHAR *)"1:ALIENTEK");lcd_show_string(30, 150, 200, 16, 16, "Flash Disk Format Finish",
RED); /* 格式化完成 */}else lcd_show_string(30, 150, 200, 16, 16, "Flash Disk Format Error ",
RED);    /* 格式化失败 */delay_ms(1000);}lcd_fill(30, 150, 240, 150 + 16, WHITE);     /* 清除显示 */while (exfuns_get_free("0", &total, &free)) /* 得到SD卡的总容量和剩余容量 */{lcd_show_string(30, 150, 200, 16, 16, "SD Card Fatfs Error!", RED);delay_ms(200);lcd_fill(30, 150, 240, 150 + 16, WHITE);/* 清除显示 */delay_ms(200);LED0_TOGGLE();  /* LED0闪烁 */}lcd_show_string(30, 150, 200, 16, 16, "FATFS OK!", BLUE);lcd_show_string(30, 170, 200, 16, 16, "SD Total Size:     MB", BLUE);
lcd_show_string(30, 190, 200, 16, 16, "SD Free Size:     MB", BLUE);
/* 显示SD卡总容量 MB */
lcd_show_num(30 + 8 * 14, 170, total >> 10, 5, 16, BLUE);
/* 显示SD卡剩余容量 MB */lcd_show_num(30 + 8 * 14, 190, free >> 10, 5, 16, BLUE);while (1){t++;delay_ms(200);LED0_TOGGLE();  /* LED0闪烁 */}
}

main函数初始化了LED和LCD用于显示效果,初始化按键和ADC用于辅助显示ADC。在usmart_config.c里面的usmart_nametab数组添加如下内容:
/* 函数名列表初始化(用户自己添加)

  • 用户直接在这里输入要执行的函数名及其查找串
 */
struct _m_usmart_nametab usmart_nametab[] =
{#if USMART_USE_WRFUNS == 1      /* 如果使能了读写操作 */(void *)read_addr, "uint32_t read_addr(uint32_t addr)",(void *)write_addr, "void write_addr(uint32_t addr,uint32_t val)",
#endif(void *)delay_ms, "void delay_ms(uint16_t nms)",(void *)delay_us, "void delay_us(uint32_t nus)",(void *)mf_mount, "uint8_t mf_mount(uint8_t* path,uint8_t mt)",(void *)mf_open, "uint8_t mf_open(uint8_t*path,uint8_t mode)",(void *)mf_close, "uint8_t mf_close(void)",(void *)mf_read, "uint8_t mf_read(uint16_t len)",(void *)mf_write, "uint8_t mf_write(uint8_t*dat,uint16_t len)",(void *)mf_opendir, "uint8_t mf_opendir(uint8_t* path)",(void *)mf_closedir, "uint8_t mf_closedir(void)",(void *)mf_readdir, "uint8_t mf_readdir(void)",(void *)mf_scan_files, "uint8_t mf_scan_files(uint8_t * path)",(void *)mf_showfree, "uint32_t mf_showfree(uint8_t *path)",(void *)mf_lseek, "uint8_t mf_lseek(uint32_t offset)",(void *)mf_tell, "uint32_t mf_tell(void)",(void *)mf_size, "uint32_t mf_size(void)",(void *)mf_mkdir, "uint8_t mf_mkdir(uint8_t*path)",(void *)mf_fmkfs, "uint8_t mf_fmkfs(uint8_t* path,uint8_t opt,uint16_t au)",(void *)mf_unlink, "uint8_t mf_unlink(uint8_t *path)",(void *)mf_rename, "uint8_t mf_rename(uint8_t *oldname,uint8_t* newname)",(void *)mf_getlabel, "void mf_getlabel(uint8_t *path)",(void *)mf_setlabel, "void mf_setlabel(uint8_t *path)",(void *)mf_gets, "void mf_gets(uint16_t size)",(void *)mf_putc, "uint8_t mf_putc(uint8_t c)",(void *)mf_puts, "uint8_t mf_puts(uint8_t *str)",
};

46.4 下载验证
将程序下载到开发板后,我们使用的是16Gb标有“SDHC”标志的micorSD卡,可以看到LCD显示如图46.4.1所示:

图46.4.1程序运行效果图
打开串口调试助手,我们就可以串口调用前面添加的各种FATFS测试函数了,比如我们输入mf_scan_files(“0:”),即可扫描SD卡根目录的所有文件,如图46.4.2所示:

图46.4.2扫描SD卡根目录所有文件
其他函数的测试,用类似的办法即可实现。注意这里0代表SD卡,1代表SPI FLASH。另外,提醒大家,mf_unlink函数,在删除文件夹的时候,必须保证文件夹是空的,才可以正常删除,否则不能删除。

【正点原子STM32连载】第四十六章 FATFS实验 摘自【正点原子】MiniPro STM32H750 开发指南_V1.1相关推荐

  1. 【正点原子STM32连载】 第二十九章 低功耗实验 摘自【正点原子】MiniPro STM32H750 开发指南_V1.1

    1)实验平台:正点原子MiniPro H750开发板 2)平台购买地址:https://detail.tmall.com/item.htm?id=677017430560 3)全套实验源码+手册+视频 ...

  2. 【正点原子STM32连载】 第二十六章 USMART调试组件实验 摘自【正点原子】MiniPro STM32H750 开发指南_V1.1

    1)实验平台:正点原子MiniPro H750开发板 2)平台购买地址:https://detail.tmall.com/item.htm?id=677017430560 3)全套实验源码+手册+视频 ...

  3. 【正点原子MP157连载】第十九章 OLED实验-摘自【正点原子】STM32MP1 M4裸机CubeIDE开发指南

    1)实验平台:正点原子STM32MP157开发板 2)购买链接:https://item.taobao.com/item.htm?&id=629270721801 3)全套实验源码+手册+视频 ...

  4. 【正点原子STM32连载】 第三十一章 ADC实验 摘自【正点原子】MiniPro STM32H750 开发指南_V1.1

    1)实验平台:正点原子MiniPro H750开发板 2)平台购买地址:https://detail.tmall.com/item.htm?id=677017430560 3)全套实验源码+手册+视频 ...

  5. pdfstamper生成pdf无法显示汉字_正点原子STM32F4/F7水星开发板资料连载第四十六章 汉字显示实验...

    1)实验平台:正点原子水星 STM32F4/F7 开发板 2)摘自<STM32F7 开发指南(HAL 库版)>关注官方微信号公众号,获取更多资料:正点原子 3)全套实验源码+手册+视频下载 ...

  6. 光盘显示0字节可用_正点原子STM32F4/F7水星开发板资料连载第四十六章 汉字显示实验

    1)实验平台:正点原子水星 STM32F4/F7 开发板 2)摘自<STM32F7 开发指南(HAL 库版)>关注官方微信号公众号,获取更多资料:正点原子 3)全套实验源码+手册+视频下载 ...

  7. 【正点原子STM32连载】第六十六章 综合测试实验摘自【正点原子】STM32F103 战舰开发指南V1.2

    1)实验平台:正点原子stm32f103战舰开发板V4 2)平台购买地址:https://detail.tmall.com/item.htm?id=609294757420 3)全套实验源码+手册+视 ...

  8. 【正点原子STM32连载】第四十五章 SD卡实验 摘自【正点原子】MiniPro STM32H750 开发指南_V1.1

    1)实验平台:正点原子MiniPro H750开发板 2)平台购买地址:https://detail.tmall.com/item.htm?id=677017430560 3)全套实验源码+手册+视频 ...

  9. 【正点原子STM32连载】第四十二章 FLASH模拟EEPROM实验 摘自【正点原子】MiniPro STM32H750 开发指南_V1.1

    1)实验平台:正点原子MiniPro H750开发板 2)平台购买地址:https://detail.tmall.com/item.htm?id=677017430560 3)全套实验源码+手册+视频 ...

最新文章

  1. 返回Json数据浏览器带上<pre></pre>标签解决方法
  2. 网易二面:Kafka为什么吞吐量大、速度快?
  3. Silverlight Telerik控件学习:数据录入、数据验证
  4. python opencv2_Python + OpenCV2 系列:2 - 图片操作
  5. asp.net 中RadioButtonList的选项改变事件处理(采用jquery操作)
  6. 教育|一位女博士五年的艰难毕业历程
  7. 奥飞娱乐:贝肯熊和镇魂街盲盒产品计划于2021年下半年上市
  8. hbase数据读取优化_read读取优化_HBase最佳实践_HBase开发指南_云数据库 HBase - 阿里云...
  9. commons-pool2-2.3 jar包_Maven项目中引入net.sf.json.JSONObject依赖jar包
  10. 查找算法之一 顺序查找
  11. http实时推送技术
  12. 尼奥智能陪伴机器人如何绑定设备_巴巴腾 智能陪护儿童机器人A3,为儿童专业定制的小伙伴...
  13. python 字典写入excel_Python向excel中写入数据的方法
  14. java学习是网上java学习视频好,还是报班比较好
  15. 2017年广东省乡级行政区划数据介绍
  16. 51单片机学习历程---单片机入门
  17. 计算机鼠标左右键作用,鼠标的左右键的用途
  18. SEO原创和伪原创文章的一些技巧
  19. npm 和 yarn 缓存清理
  20. 一剂拯救“国足”的终极药方:全面 AI 化

热门文章

  1. 平阴县实验学校计算机老师,人物 | 平阴县实验学校邢珍:认认真真做事,踏踏实实做人...
  2. workbench 左侧导航栏消失解决方案
  3. Liunx服务器安装SVN
  4. leetcode:904. 水果成篮
  5. OpenCL编程初探
  6. TCP/IP-单播是如何实现的
  7. 您为什么要加入CSDN个人空间
  8. matlab构造传递函数
  9. Android 平台代号、版本、API 级别和 NDK 版本对照表
  10. 2021-06-22第十章主讲