25. 基于FLASH的FatFs文件系统移植¶
25.1. 文件系统¶
即使读者可能不了解文件系统,读者也一定对“文件”这个概念十分熟悉。数据在PC上是以文件的形式储存在磁盘中的, 这些数据的形式一般为ASCII码或二进制形式。在上一章我们已经写好了串行Flash芯片的驱动函数,我们可以非常方便的在串行Flash芯片上读写数据。 如需要记录文字“embedfire-野火 www.embedfire.com”,可以把这些文字转化成相应编码的字符串,存储在数组中,然后调用串行 FLASH 的写函数, 把数组内容写入到串行Flash芯片的指定地址上,在需要的时候从该地址把数据读取出来,再对读出来的数据以相应编码的格式进行解读。
但是,这样直接存储数据会带来极大的不便,如难以记录有效数据的位置,难以确定存储介质的剩余空间,以及应以何种格式来解读数据。 就如同一个巨大的图书馆无人管理,杂乱无章地存放着各种书籍,难以查找所需的文档。想象一下图书馆的采购人员购书后,把书籍往馆内一扔,拍拍屁股走人, 当有人来借阅某本书的时候,就不得不一本本地查找。这样直接存储数据的方式对于小容量的存储介质如EEPROM还可以接受, 但对于大容量Flash芯片或者SD卡之类的大容量设备,我们需要一种高效的方式来管理它的存储内容。
这些管理方式即为文件系统,它是为了存储和管理数据,而在存储介质建立的一种组织结构,这些结构包括操作系统引导区、目录和文件。 常见的windows下的文件系统格式包括FAT32、NTFS、exFAT。在使用文件系统前,要先对存储介质进行格式化。格式化先擦除原来内容, 在存储介质上新建一个文件分配表和目录 。这样,文件系统就可以记录数据存放的物理地址、剩余空间。
使用文件系统时, 数据都以文件的形式存储。写入新文件时,先在目录中创建一个文件索引,它指示了文件存放的物理地址, 再把数据存储到该地址中。当需要读取数据时,可以从目录中找到该文件的索引,进而在相应的地址中读取出数据。 具体还涉及到逻辑地址、簇大小、不连续存储等一系列辅助结构或处理过程。
文件系统的存在使我们在存取数据时,不再是简单地向某物理地址直接读写,而是要遵循它的读写格式。如经过逻辑转换, 一个完整的文件可能被分开成多段存储到不连续的物理地址,使用目录或链表的方式来获知下一段的位置。
上一章的串行Flash芯片驱动只完成了向物理地址写入数据的工作,而根据文件系统格式的逻辑转换部分则需要额外的代码来完成。 实质上,这个逻辑转换部分可以理解为当我们需要写入一段数据时,由它来求解向什么物理地址写入数据、以什么格式写入及写入一些原始数据以外的信息(如目录)。 这个逻辑转换部分代码我们也习惯称之为文件系统。
25.2. FatFs文件系统介绍¶
上面提到的逻辑转换部分代码(文件系统)即为本章的要点,文件系统庞大而复杂,它需要根据应用的文件系统格式而编写, 而且一般与驱动层分离开来,很方便移植,所以工程应用中一般是移植现成的文件系统源码。
FatFs是面向小型嵌入式系统的一种通用的FAT文件系统。它完全是由ANSI C语言编写并且完全独立于底层的I/O介质。 因此它可以很容易地不加修改地移植到其他的处理器当中,如8051、PIC、AVR、SH、Z80、H8、ARM等。 FatFs支持FAT12、FAT16、FAT32等格式, 所以我们利用前面写好的串行Flash芯片驱动,把FatFs文件系统代码移植到工程之中, 就可以利用文件系统的各种函数,对串行Flash芯片以“文件”格式进行读写操作了。
- FatFs文件系统的源码可以从FatFs官网下载:
FatFs 源码下载连接:http://elm-chan.org/fsw/ff/archives.html
25.2.1. FatFs 特性¶
DOS / Windows兼容的FAT / exFAT文件系统。
与平台无关,易于移植。
程序代码和工作区的占用空间非常小。
支持以下各种配置选项:
ANSI / OEM或Unicode中的长文件名。
exFAT文件系统,64位LBA和GPT可存储大量数据。
RTOS的线程。
多个卷(物理驱动器和分区,最多10个卷)。
可变扇区大小。
多个代码页,包括DBCS。
只读,可选API,I / O缓冲区等…
25.3. 源码下载¶
FatFs 和 Petit FatFs: Petit FatFs 是用于小型8位微控制器的 FatFs 模块的子集。它是按照ANSI C编写的,并且与磁盘I/O层完全分开。 即使RAM大小小于扇区大小,也可以将其合并到内存有限的微型微控制器中。 我们这里选择完整版的 FatFs。
版本选目前最新的2022年11月06日更新的版本,也就是 FatFs 0.15 版本。
25.3.1. 源码结构¶
在移植FatFs文件系统到开发板之前,我们先要到FatFs的官网获取源码, 官网有对FatFs做详细的介绍,有兴趣可以了解。 解压之后可看到里面有 documents 和 source 这两个文件夹和 LICENSE.txt 文件, 见图 FatFs文件目录 。 documents 文件夹里面是一些使用帮助文档; source 才是FatFs文件系统的源码。 而 LICENSE.txt 则是使用FatFs所需遵循的许可证。
FatFs 0.15 版本的源码结构如下:
25.3.1.1. FatFs帮助文档¶
打开 documents 文件夹,可看到如图 documents文件夹的文件目录 :
documents 这个文件夹下面存放的是 FatFs 模块文档:
其中 doc 文件夹里面是编译好的html文档,讲的是FatFs里面各个函数的使用方法, 这些函数都是封装得非常好的函数,利用这些函数我们就可以操作串行Flash芯片。 有关具体的函数我们在用到的时候再讲解。
res文件夹包含doc文件夹下文件需要用到的图片,还有四个名为app.c文件,内容都是FatFs具体应用例程。
00index_e.html 相当于FatFs的主页。
updates.txt 记录了各个版本的更新。
至于另外一个文件,可以不管。
25.3.1.2. FATFS源码¶
打开 src 文件夹,可看到如图 source文件夹的文件目录 :
source 这个文件夹下面存放的是 FatFs 源码:
diskio.c/.h: IO层的实现。
ff.c/.h: FatFs核心文件,文件管理的实现方法。该文件独立于底层介质操作文件的函数,利用这些函数实现文件的读写。
ffconf.h: 这个头文件包含了对FatFs功能配置的宏定义,通过修改这些宏定义就可以裁剪FatFs的功能。
ffsystem.c: 操作系统相关。
ffunicode.c: unicode编码相关。FF_USE_LFN != 0 时必须包含此文件。
00history.txt: 介绍了FatFs的版本更新情况。
00readme.txt: 说明了当前目录下各个文件的功能。
25.3.2. 长文件名¶
FatFs模块支持FAT文件系统的长文件名(LFN)扩展名。 默认情况下,禁用LFN。 如果要启用LFN,要将 FF_USE_LFN 设置为1、2或3,并将 ffunicode.c 文件添加到项目中。 LFN功能需要一定的工作缓冲区。 缓冲区大小可由 FF_MAX_LFN 根据可用内存配置。 LFN的长度最多可以为255个字符,因此 FF_MAX_LFN 也应设置为255。
25.3.3. FatFs限制¶
文件系统类型:FAT、FAT32(rev0.0) 和 exFAT(rev1.0)。
打开的文件数量:无限制。(取决于可用内存)
卷数:最多 10 个。
扇区大小:512、1024、2048 和 4096 字节。
最小卷大小:128 个扇区。
最大卷大小:32 位 LBA 中的 2^32 - 1 扇区,在带有 exFAT 的 64 位 LBA 中几乎不受限制。
最大文件大小:FAT 卷上为2^32 - 1 字节,exFAT 卷上几乎不受限制。
群集大小:FAT 卷上最多 128 个扇区,exFAT 卷上最多 16 MB。
25.3.4. FatFs的已知问题¶
在网址:http://elm-chan.org/fsw/ff/patches.html 记录着有关 Fatfs 最新版本的已知问题以及问题的解决方法或补丁。
网址上会公布已发现并解决的BUG,以补丁形式发布。 以R0.14b版本为例,目前该版本有2个补丁。
有需要的话可以按照官方提供的方法对源码进行修改, 由于我们这里使用的都是一些比较基础的功能,这些功能是没有问题的,所以就不进行修改了。
25.4. FatFs文件系统移植实验¶
25.4.1. 硬件设计及FSP¶
FatFs属于软件组件,不需要附带其他硬件电路。我们使用串行Flash芯片作为物理存储设备,其硬件电路在上一章已经做了分析,这里就直接使用。
25.4.2. FatFs 移植步骤概述¶
- 基本步骤:
实现底层驱动接口
修改配置文件
移植FatFs之前我们先通过FatFs的程序结构图了解FatFs在程序中的关系网络,见图 FatFs程序结构图 。
用户应用程序需要由用户编写,想实现什么功能就编写什么的程序,一般我们只用到f_mount()、f_open()、f_write()、f_read()就可以实现文件的读写操作。
FatFs组件是FatFs的主体,文件都在源码src文件夹中, 其中ff.c、ff.h、ffsystem.c 以及 ffunicode.c 4个文件我们不需要改动, 只需要修改 ffconf.h 和 diskio.c/.h 3个文件。
底层设备输入输出要求实现存储设备的读写操作函数、存储设备信息获取函数等等。我们使用串行Flash芯片作为物理设备, 在上一章节已经编写好了串行Flash芯片的驱动程序,这里我们就直接使用。
25.4.2.1. 实现底层驱动接口¶
FatFs文件系统与底层介质的驱动分离开来,对底层介质的操作都要交给用户去实现,它仅仅是提供了一个函数接口而已。 表 FatFs移植需要用户支持函数 为FatFs移植时用户必须支持的函数。 通过表 FatFs移植需要用户支持函数 我们可以清晰知道很多函数是在一定条件下才需要添加的, 只有前三个函数是必须添加的。我们完全可以根据实际需求选择所需用到的函数。
前三个函数是实现读文件最基本需求。接下来三个函数是实现创建文件、修改文件需要的。为实现格式化功能, 需要在disk_ioctl添加两个获取物理设备信息选项。我们一般只要实现前面六个函数就可以了,已经足够满足大部分功能。
为支持简体中文长文件名称需要添加 ff_uni2oem、ff_oem2uni 和 ff_wtoupper 函数, 实际这三个已经在 ffunicode.c 文件中实现,我们只要直接把 ffunicode.c 文件添加到工程中就可以。
底层设备驱动函数是存放在diskio.c文件,我们的目的就是把diskio.c中的函数接口与串行Flash芯片驱动连接起来。 总共有五个函数,分别为设备状态获取(disk_status)、设备初始化(disk_initialize)、扇区读取(disk_read)、 扇区写入(disk_write)、其他控制(disk_ioctl)。
接下来,我们对每个函数结合串行Flash芯片驱动做详细讲解。
25.4.2.2. 修改配置文件¶
ffconf.h 文件是 FatFs 的配置文件。
下面是 ffconf.h 文件中,需要修改的部分,只把需要修改的部分放出来:
更加详细的部分参考:http://elm-chan.org/fsw/ff/doc/config.html#use_mkfs
/********************/
/* 下面是经过修改部分 */
/********************/
#define FF_USE_MKFS 1 //此选项切换是否启用 f_mkfs() 函数,用于格式化 Flash、SD卡等
/* This option switches f_mkfs() function. (0:Disable or 1:Enable) */
#define FF_CODE_PAGE 936 //此选项指定使用的OEM代码页
/* This option specifies the OEM code page to be used on the target system.
/ Incorrect code page setting can cause a file open failure.
/
/ 437 - U.S.
/ 720 - Arabic
/ 737 - Greek
/ 771 - KBL
/ 775 - Baltic
/ 850 - Latin 1
/ 852 - Latin 2
/ 855 - Cyrillic
/ 857 - Turkish
/ 860 - Portuguese
/ 861 - Icelandic
/ 862 - Hebrew
/ 863 - Canadian French
/ 864 - Arabic
/ 865 - Nordic
/ 866 - Russian
/ 869 - Greek 2
/ 932 - Japanese (DBCS)
/ 936 - Simplified Chinese (DBCS)
/ 949 - Korean (DBCS)
/ 950 - Traditional Chinese (DBCS)
/ 0 - Include all code pages above and configured by f_setcp()
*/
#define FF_USE_LFN 2 //此选项切换对长文件名的支持
#define FF_MAX_LFN 255 //设置长文件名的最长长度
/* The FF_USE_LFN switches the support for LFN (long file name).
/
/ 0: Disable LFN. FF_MAX_LFN has no effect.
/ 1: Enable LFN with static working buffer on the BSS. Always NOT thread-safe.
/ 2: Enable LFN with dynamic working buffer on the STACK.
/ 3: Enable LFN with dynamic working buffer on the HEAP.
#define FF_LFN_UNICODE 2 //此选项设置是否启用 Unicode 字符编码
/* This option switches the character encoding on the API when LFN is enabled.
/
/ 0: ANSI/OEM in current CP (TCHAR = char)
/ 1: Unicode in UTF-16 (TCHAR = WCHAR)
/ 2: Unicode in UTF-8 (TCHAR = char)
/ 3: Unicode in UTF-32 (TCHAR = DWORD)
/
/ Also behavior of string I/O functions will be affected by this option.
/ When LFN is not enabled, this option has no effect. */
#define FF_VOLUMES 2 //要使用的卷(逻辑驱动器)的数量。范围(1-10)
/* Number of volumes (logical drives) to be used. (1-10) */
#define FF_MIN_SS 512
#define FF_MAX_SS 4096 //这组选项配置支持的扇区大小范围
/* This set of options configures the range of sector size to be supported. (512,
/ 1024, 2048 or 4096) Always set both 512 for most systems, generic memory card and
/ harddisk, but a larger value may be required for on-board flash memory and some
/ type of optical media. When FF_MAX_SS is larger than FF_MIN_SS, FatFs is configured
/ for variable sector size mode and disk_ioctl() function needs to implement
/ GET_SECTOR_SIZE command. */
/* 若 FF_MIN_SS != FF_MAX_SS。则需要在 disk_ioctl() 中指定所需操作的设备的扇区大小
* 在 case GET_SECTOR_SIZE 项中指定 */
#define FF_FS_NORTC 1 //设置为1关闭时间戳 启用时间戳功能需要RTC
#define FF_NORTC_MON 1
#define FF_NORTC_MDAY 1
#define FF_NORTC_YEAR 2022
/* The option FF_FS_NORTC switches timestamp feature. If the system does not have
/ an RTC or valid timestamp is not needed, set FF_FS_NORTC = 1 to disable the
/ timestamp feature. Every object modified by FatFs will have a fixed timestamp
/ defined by FF_NORTC_MON, FF_NORTC_MDAY and FF_NORTC_YEAR in local time.
/ To enable timestamp function (FF_FS_NORTC = 0), get_fattime() function need to be
/ added to the project to read current time form real-time clock. FF_NORTC_MON,
/ FF_NORTC_MDAY and FF_NORTC_YEAR have no effect.
/ These options have no effect in read-only configuration (FF_FS_READONLY = 1). */
25.4.3. 路径名格式说明¶
在 FatFs 的 API 函数中,经常能看到 “const TCHAR* path” 这样的函数参数,比如:
FRESULT f_mount ( FATFS* fs, /* 指向要注册的文件系统对象的指针(NULL:卸载) */ const TCHAR* path, /* 要装载/卸载的逻辑驱动器号 */ BYTE opt /* 装载选项:0=不装载(延迟装载),1=立即装载 */ ) FRESULT f_open ( FIL* fp, /* 指向空白文件对象的指针 */ const TCHAR* path, /* 指向文件名的指针 */ BYTE mode /* 访问模式和打开模式标志 */ )
这就要求我们要知道 path 这个参数的可输入值是什么,也就是我们要了解FatFs的文件命名规范。
25.4.3.1. 文件命名格式¶
FatFs模块上的路径名格式与DOS/Windows的文件名规范类似,如下所示:
[drive#:][/]"directory 目录"/"file 文件"
FatFs 模块支持长文件名(LFN) 和 8.3 格式的文件名 (SFN)。长文件名(LFN) 可在 FF_USE_LFN >= 1 时使用。 子目录用 \ 或 / 分隔,方式与DOS/Windows API相同。 重复的分隔符和终止分隔符将被忽略,例如:“//animal///cat/”。 唯一不同的是,FatFs中指定逻辑驱动器(FAT卷)的标题驱动器前缀是数字(0-9)+冒号,而在DOS/Windows中是字母(A-Z)+冒号。 逻辑驱动器编号是指定要访问的卷的标识符。 如果省略驱动器前缀,则假定逻辑驱动器编号为默认驱动器。
注解
LFN 和 SFN
8.3 命名规则,又称短文件名(Short File Name,SFN)是一种限制文件名长度的方法,这在DOS和Windows 95及Windows NT 3.51以前的Microsoft Windows版本中,在FAT文件系统中的常用方法。
长文件名,(Long file name,LFN)也指长文件名支持。在旧版本的DOS操作系统下,因为文件名称有8.3格式的限制,凡文件主档名超过8字节或扩展名超过3字节的文件名,都被称为“长文件名”,在Windows下正常的文件名置换于DOS(或“命令提示字符”)环境下则可能无法完整显示,如“Program files”资料夹可能会显示成其对应的8.3文件名“PROGRA~1”。
25.4.3.2. FF_FS_RPATH¶
在默认配置 (FF_FS_RPATH==0) 中, 是没有当前目录的概念的。 卷上的每个对象始终以从根目录开始的完整路径名指定。 不可以使用点目录名称(“.”,“..”)。标题分隔符被忽略, 它可以存在或省略。默认驱动器固定为驱动器0。
启用相对路径功能时(FF_FS_RPATH>=1),如果存在分隔符, 则从根目录跟随指定的路径。如果没有分隔符,则从默认驱动器的当前目录开始。 路径名也允许使用点目录名。 当前目录由f_chdir函数设置,默认驱动器为f_chdrive函数设置的当前驱动器。
此外,驱动器前缀可以采用预定义的任意字符串。 当选项FF_STR_VOLUME_ID == 1 时,也可以将任意字符串卷 ID 用作驱动器前缀。 例如 “flash:file1.txt”、“ram:temp.dat” 或 “sd:” 。 如果 srting 与任何卷 ID 都不匹配,则该函数将失败并返回 FR_INVALID_DRIVE。
当FF_STR_VOLUME_ID == 2 时,可以使用 Unix 样式的驱动器前缀。 例如 “/flash/file1.txt”、“/ram/temp.dat” 或 “/sd” 。
#define FF_STR_VOLUME_ID 0 //FF_STR_VOLUME_ID开关支持任意字符串形式的卷ID。 #define FF_VOLUME_STRS "RAM","NAND","CF","SD","SD2","USB","USB2","USB3" /* FF_STR_VOLUME_ID switches support for volume ID in arbitrary strings. / When FF_STR_VOLUME_ID is set to 1 or 2, arbitrary strings can be used as drive / number in the path name. FF_VOLUME_STRS defines the volume ID strings for each / logical drives. Number of items must not be less than FF_VOLUMES. Valid / characters for the volume ID strings are A-Z, a-z and 0-9, however, they are / compared in case-insensitive. If FF_STR_VOLUME_ID >= 1 and FF_VOLUME_STRS is / not defined, a user defined volume string table needs to be defined as: / / const char* VolumeStr[FF_VOLUMES] = {"ram","flash","sd","usb",... */
FF_STR_VOLUME_ID: 此选项切换对字符串卷 ID 的支持。
数值 |
描述 |
示例 |
---|---|---|
0 |
只能使用数字 ID 中的 DOS/Windows 样式驱动器前缀。 |
1:/filename |
1 |
还可以使用字符串 ID 中的 DOS/Windows 样式驱动器前缀。 |
flash:/filename |
2 |
也可以使用字符串ID中的Unix样式驱动器前缀。 |
/flash/filename |
FF_VOLUME_STRS: 此选项定义每个逻辑驱动器的卷 ID 字符串。项目数量不得少于FF_VOLUMES。 卷 ID 字符串的有效字符是 A-Z、a-z 和 0-9,不区分大小写。 如果FF_STR_VOLUME_ID == 0,则此选项不起作用。 如果 FF_STR_VOLUME_ID >= 1 并且未定义此选项, 则需要定义用户定义的卷字符串表,如下所示。不应动态修改该表。
/* 0: ~ 3: 的用户定义卷ID字符串: */ const char* VolumeStr[FF_VOLUMES] = {"ram","flash","sd","usb"};
这里我们并没有用到任意字符串形式的卷ID,就没有开启 FF_STR_VOLUME_ID 宏。
25.4.4. 接口函数¶
主要的接口文件都在 diskio.c 文件中了,我们只需要根据函数所提供的输入参数和返回参数, 来实现函数的功能,提供给FatFs调用就好了。
FatFs文件系统与存储设备的连接函数在diskio.c文件中,主要有5个函数需要我们编写的。
宏定义和存储设备状态获取函数
/*-----------------------------------------------------------------------*/
/* Get Drive Status */
/*-----------------------------------------------------------------------*/
DSTATUS disk_status (
BYTE pdrv /* Physical drive nmuber to identify the drive */
)
{
DSTATUS stat;
switch (pdrv) {
case DEV_FLASH :
QSPI_Flash_WaitForWriteEnd(); //等待Flash芯片内部操作完成
stat = RES_OK;
return stat;
}
return STA_NOINIT;
}
存储设备初始化函数
/*-----------------------------------------------------------------------*/
/* Inidialize a Drive */
/*-----------------------------------------------------------------------*/
DSTATUS disk_initialize (
BYTE pdrv /* Physical drive nmuber to identify the drive */
)
{
DSTATUS stat;
switch (pdrv) {
case DEV_FLASH :
QSPI_Flash_Init();
stat = RES_OK;
return stat;
}
return STA_NOINIT;
}
存储设备数据读取函数
/*-----------------------------------------------------------------------*/
/* Read Sector(s) */
/*-----------------------------------------------------------------------*/
DRESULT disk_read (
BYTE pdrv, /* Physical drive nmuber to identify the drive */
BYTE *buff, /* Data buffer to store read data */
LBA_t sector, /* Start sector in LBA */
UINT count /* Number of sectors to read */
)
{
DRESULT res;
switch (pdrv) {
case DEV_FLASH :
// translate the arguments here
QSPI_Flash_BufferRead(buff, sector<<12, count<<12); //1 sector == 4096 bytes
res = RES_OK;
return res;
}
return RES_PARERR;
}
存储设备数据写入函数
DRESULT disk_write (
BYTE pdrv, /* Physical drive nmuber to identify the drive */
const BYTE *buff, /* Data to be written */
LBA_t sector, /* Start sector in LBA */
UINT count /* Number of sectors to write */
)
{
DRESULT res;
uint32_t write_addr;
switch (pdrv) {
case DEV_FLASH :
write_addr = sector << 12;
QSPI_Flash_SectorErase(write_addr);
QSPI_Flash_BufferWrite(buff, write_addr, count<<12);
res = RES_OK;
return res;
}
return RES_PARERR;
}
IO控制函数
DRESULT disk_ioctl (
BYTE pdrv, /* Physical drive nmuber (0..) */
BYTE cmd, /* Control code */
void *buff /* Buffer to send/receive control data */
)
{
DRESULT res;
switch (pdrv) {
case DEV_FLASH :
switch (cmd) {
case GET_SECTOR_COUNT: /* 扇区数量:1024*4096/1024/1024 = 4(MB) */
*(DWORD *)buff = 1024;
break;
case GET_SECTOR_SIZE: /* 扇区大小 */
*(WORD *)buff = 4096;
break;
case GET_BLOCK_SIZE: /* 同时擦除扇区个数 */
*(DWORD *)buff = 1;
break;
}
res = RES_OK;
return res;
}
return RES_PARERR;
}
get_fattime函数
DWORD get_fattime(void) {
/* 返回当前时间戳 */
return (DWORD)(2022 - 80) << 25 | /* Year */
(DWORD)(1 + 1) << 21 | /* Month */
(DWORD)1 << 16 | /* Mday */
(DWORD)1 << 11 | /* Hour */
(DWORD)1 << 5 | /* Min */
(DWORD)1 >> 1; /* Sec */
}
由于之前在配置文件关闭了 RTC 和时间戳功能,因此其实不需要实现这个返回当前时间戳函数。
由 FF_FS_NORTC 决定是否启用,FF_FS_NORTC设置为1以禁用时间戳功能。
25.4.5. FatFs 基本 API 函数说明¶
更多关于 FatFs 接口函数的说明请看官方的说明:http://elm-chan.org/fsw/ff/00index_e.html
25.4.5.1. f_mount¶
f_mount 函数原型如下:
FRESULT f_mount (
FATFS* fs, /* [IN] Filesystem object */
const TCHAR* path, /* [IN] Logical drive number */
BYTE opt /* [IN] Initialization option */
);
FatFs 需要每个逻辑驱动器(FAT 卷)的工作区域(文件系统对象)。 在执行任何文件/目录操作之前,需要使用逻辑驱动器的f_mount函数注册文件系统对象。 完成此过程后,文件/目录 API 函数已准备好工作。 某些卷管理功能(f_mkfs、f_fdisk和f_setcp)不需要文件系统对象。
f_mount函数将文件系统对象注册/注销到 FatFs 模块,如下所示:
确定由 path 指定的逻辑驱动器。
清除并注销卷的已注册工作区(如果存在)。
如果 fs 不为 NULL,则清除新工作区并将其注册到卷中。
如果指定了强制装入,则对卷执行卷装入过程。
opt:安装选项。0:现在不挂载(要在第一次访问卷时挂载),1:强制挂载卷以检查它是否准备好工作。
25.4.5.2. f_mkfs¶
f_mkfs函数原型如下:
FRESULT f_mkfs (
const TCHAR* path, /* [IN] Logical drive number */
const MKFS_PARM* opt,/* [IN] Format options */
void* work, /* [-] Working buffer */
UINT len /* [IN] Size of working buffer */
);
簇(cluster): 由于扇区的空间比较小且数目众多,在寻址时比较困难, 所以操作系统就将多个的扇区组合在一起,形成一个更大的单位, 再对这个单位进行整体的操作。也可以理解为文件的磁盘空间分配单位。 当簇的大小为 32768 字节时,大小为 100 字节的文件将占用 32768 字节的磁盘空间。 随着簇大小的增加,磁盘使用的空间效率会变得很低,但与此同时,读/写效率也会提高。 因此,簇的大小是空间效率和读/写效率之间的权衡。
opt:若为NULL,则启用默认参数
static const MKFS_PARM defopt = {FM_ANY, 0, 0, 0, 0}; /* Default parameter */
由 work 来指向用于格式化过程的工作缓冲区的指针。
由 len 来确定工作缓冲区的大小(以字节为单位)。至少需要FF_MAX_SS。
提示
当 FF_FS_READONLY == 0 且 FF_USE_MKFS == 1 时可用。
25.4.5.3. f_setlabel¶
f_setlabel 函数原型如下:
FRESULT f_setlabel (
const TCHAR* label /* [IN] Volume label to be set */
);
当字符串具有驱动器前缀时,卷标将设置为驱动器前缀指定的卷。 Unix 样式的卷 ID 不能用于指定卷。 如果未指定驱动器号,则卷标将设置为默认驱动器。 如果给定卷标的长度为零,则将删除卷上的卷标。卷标的格式如下所示:
在 FAT 卷上转换 OEM 代码页时最多 11 个字节。 在 exFAT 卷中最多 11 个字符。 FAT 卷允许的字符数为:SFN 允许的字符不包括点。低写字符向上转换。 exFAT 卷允许的字符为:LFN 允许的字符包括点。将保留小写字符。 空格可以嵌入卷标中的任何位置。尾随空格在 FAT 音量处被截断。
提示
当FF_FS_READONLY == 0 且FF_USE_LABEL == 1 时可用。
25.4.5.4. f_open¶
f_open 函数原型如下:
FRESULT f_open (
FIL* fp, /* [OUT] Pointer to the file object structure */
const TCHAR* path, /* [IN] File name */
BYTE mode /* [IN] Mode flags */
);
打开一个文件并创建一个文件对象。 完成对文件的访问后应该使用f_close函数将其关闭。 如果在关机、移出介质或重新装入之前对文件进行了任何更改且未调用f_close函数将其关闭,则文件可能会 collapsed (损坏)。
可选 mode 选项:
提示
当FF_FS_READONLY == 1 时,只有FA_READ和FA_OPEN_EXISTING可用于模式标志。
25.4.5.5. f_close¶
f_close 函数原型如下:
FRESULT f_close (
FIL* fp /* [IN] Pointer to the file object */
);
关闭打开的文件对象。如果文件已更改,则文件的缓存信息将写回卷。 函数成功后,文件对象不再有效,可以丢弃。
请注意,如果文件对象处于只读模式且未启用FF_FS_LOCK, 则也可以在不执行f_close函数的情况下丢弃该文件对象。 但是,不建议这样做。
25.4.5.6. f_write¶
f_write 函数原型如下:
FRESULT f_write (
FIL* fp, /* [IN] Pointer to the file object structure */
const void* buff, /* [IN] Pointer to the data to be written */
UINT btw, /* [IN] Number of bytes to write */
UINT* bw /* [OUT] Pointer to the variable to return number of bytes written */
);
该函数开始在读/写指针指向的文件偏移处将数据写入文件。 读/写指针随着写入的字节数而前进。 功能成功后,应检查 *bw 以检测磁盘已满。 如果 *bw < btw,则表示卷在写入操作期间已满。 该函数在卷已满或接近满时可能需要一段时间。
提示
FF_FS_READONLY == 0 时可用。
25.4.5.7. f_read¶
f_read 函数原型如下:
FRESULT f_read (
FIL* fp, /* [IN] File object */
void* buff, /* [OUT] Buffer to store read data */
UINT btr, /* [IN] Number of bytes to read */
UINT* br /* [OUT] Number of bytes read */
);
该函数开始在读/写指针指向的文件偏移处从文件中读取数据。 读/写指针随着读取的字节数而前进。 函数成功后,应检查 *br 以检测文件的末尾。 如果 *br < btr,则表示在读取操作期间读/写指针命中文件。
25.4.6. 软件设计¶
与FatFs文件系统使用相关的变量定义如下:
FATFS fs; /* FatFs文件系统对象 */
FIL fnew; /* 文件对象 */
UINT fnum; /* 文件成功读写数量 */
FRESULT res_flash; /* 文件操作结果 */
BYTE fileReadBuffer[1024]; /* 读缓冲区 */
BYTE fileWriteBuffer[] = /* 写缓冲区 */
"感谢您选用野火启明瑞萨RA开发板 [FatFs读写测试文件.txt]";
BYTE work[FF_MAX_SS]; /* Work area (larger is better for processing time) */
FATFS是在ff.h文件定义的一个结构体类型,针对的对象是物理设备,包含了物理设备的物理编号、扇区大小等等信息, 一般我们都需要为每个物理设备定义一个FATFS变量。
FIL也是在ff.h文件定义的一个结构体类型,针对的对象是文件系统内具体的文件,包含了文件很多基本属性,比如文件大小、 路径、当前读写地址等等。如果需要在同一时间打开多个文件进行读写,才需要定义多个FIL变量,不然一般定义一个FIL变量即可。
FRESULT是也在ff.h文件定义的一个枚举类型,作为FatFs函数的返回值类型,主要管理FatFs运行中出现的错误。 总共有19种错误类型,包括物理设备读写错误、找不到文件、没有挂载工作空间等等错误。这在实际编程中非常重要, 当有错误出现是我们要停止文件读写,通过返回值我们可以快速定位到错误发生的可能地点。如果运行没有错误才返回FR_OK。
fnum是个32位无符号整形变量,用来记录实际读取或者写入数据的数组。
fileReadBuffer和fileWriteBuffer分别对应读取和写入数据缓存区,都是8位无符号整形数组。
hal_entry 入口函数
/* 用户头文件包含 */
#include "led/bsp_led.h"
#include "debug_uart/bsp_debug_uart.h"
//FatFs
#include "FatFs/ff15/ff.h"
FATFS fs; /* FatFs文件系统对象 */
FIL fnew; /* 文件对象 */
UINT fnum; /* 文件成功读写数量 */
FRESULT res_flash; /* 文件操作结果 */
BYTE fileReadBuffer[1024]; /* 读缓冲区 */
BYTE fileWriteBuffer[] = /* 写缓冲区 */
"感谢您选用野火启明瑞萨RA开发板 [FatFs读写测试文件.txt]";
BYTE work[FF_MAX_SS]; /* Work area (larger is better for processing time) */
void hal_entry(void)
{
/* TODO: add your own code here */
LED_Init(); // LED 初始化
Debug_UART4_Init(); // SCI4 UART 调试串口初始化
printf("这是一个串行FLASH的FatFs使用演示例程\r\n");
printf("打开串口助手查看打印的信息\r\n\r\n");
/* 尝试挂载外部FLASH FAT文件系统 */
res_flash = f_mount(&fs, "0:", 1);
if (res_flash == FR_NO_FILESYSTEM)
{
printf(">>> FLASH还没有文件系统,即将进行格式化...\r\n");
/* 格式化 */
res_flash = f_mkfs("0:", NULL, work, sizeof(work));
if (res_flash == FR_OK)
{
printf(">>> FLASH已成功格式化文件系统。\r\n");
/* 格式化后,先取消挂载 */
res_flash = f_mount(NULL,"0:",1);
/* 重新挂载 */
res_flash = f_mount(&fs,"0:",1);
}
else
{
printf(">>> 格式化失败!!\r\n");
while (1);
}
}
else if (res_flash == FR_OK)
{
printf(">>> 文件系统挂载成功,可以进行读写测试。\r\n");
}
else
{
printf("!!外部Flash挂载文件系统失败。(%d)\r\n", res_flash);
printf("!!可能原因:Flash初始化不成功。\r\n");
while (1);
}
/*----------------------- 文件系统测试:写测试 -----------------------------*/
printf("\r\n****** 即将进行文件写入测试 ******\r\n");
/* 打开文件,如果文件不存在则创建它 */
res_flash = f_open(&fnew, "0:FatFs读写测试文件.txt", FA_CREATE_ALWAYS | FA_WRITE);
if ( res_flash == FR_OK )
{
printf(">>> 打开/创建“FatFs读写测试文件.txt”文件成功,向文件写入数据。\r\n");
/* 将指定存储区内容写入到文件内 */
res_flash = f_write(&fnew, fileWriteBuffer, sizeof(fileWriteBuffer), &fnum);
if (res_flash==FR_OK) {
printf(">>> 文件写入成功,写入字节数据:%d\r\n", fnum);
printf(">>> 向文件写入的数据为:%s\r\n", fileWriteBuffer);
} else {
printf("!!文件写入失败:(%d)\n",res_flash);
}
/* 不再读写,关闭文件 */
f_close(&fnew);
}
else
{
printf("!!打开/创建文件失败。\r\n");
}
/*----------------------- 文件系统测试:读测试 -----------------------------*/
printf("****** 即将进行文件读取测试 ******\r\n");
/* 打开文件,该文件前面已创建 */
res_flash = f_open(&fnew, "0:FatFs读写测试文件.txt", FA_OPEN_EXISTING | FA_READ);
if (res_flash == FR_OK)
{
printf(">>> 打开文件成功。\r\n");
res_flash = f_read(&fnew, fileReadBuffer, sizeof(fileReadBuffer), &fnum);
if (res_flash==FR_OK) {
printf(">>> 文件读取成功,读到字节数据:%d\r\n", fnum);
printf(">>> 读取得的文件数据为:%s\r\n", fileReadBuffer);
} else {
printf("!!文件读取失败:(%d)\n",res_flash);
}
/* 不再读写,关闭文件 */
f_close(&fnew);
}
else
{
printf("!!打开文件失败。\r\n");
}
/* 不再使用文件系统,取消挂载文件系统 */
f_mount(NULL,"0:",1);
printf("****** 测试结束 ******\r\n");
while(1);
#if BSP_TZ_SECURE_BUILD
/* Enter non-secure code */
R_BSP_NonSecureEnter();
#endif
}
程序的开头首先初始化调试串口,用来打印程序运行的一些调试信息。 此处的代码是以启明6M5板子的为例,另外两块板子的串口初始化函数名可能不同,读者需要注意。
FatFs 使用的第一步工作就是使用 f_mount 函数挂载文件系统。 f_mount函数有三个形参,第一个参数是指向FATFS变量指针,如果赋值为NULL可以取消物理设备挂载。 第二个参数为逻辑设备编号,使用设备根路径表示,与物理设备编号挂钩, 在 diskio.h 中我们定义外部FLASH存储器的物理编号为0,所以这里使用“0:”。 第三个参数可选0或1,1表示立即挂载,0表示不立即挂载,延迟挂载。 f_mount函数会返回一个 FRESULT 类型的值,指示挂载结果的情况。
如果 f_mount 函数返回值为 FR_NO_FILESYSTEM,说明外部FLASH存储器还没有FAT文件系统。 我们就必须对外部FLASH存储器进行格式化处理,将里面的存储空间以FAT文件系统的格式进行格式化。 使用 f_mkfs 函数可以实现格式化操作。 f_mkfs 函数有三个形参,第一个参数为逻辑设备编号; 第二个参数为格式化选项,选择格式化的类型,比如:FAT16/FAT32/exFAT等,NULL表示使用默认选项进行格式化; 第三个参数指定工作缓存区;第四个参数提供工作缓存区的大小。 格式化成功后需要先取消挂载原来设备,再重新挂载设备。
在设备正常挂载后,就可以进行文件读写操作了。使用文件之前,必须使用f_open函数打开文件,不再使用文件时必须使用f_close函数关闭文件, 这个跟电脑端操作文件步骤类似。f_open函数有三个形参,第一个参数为文件对象指针。第二参数为目标文件,包含绝对路径的文件名称和后缀名。 第三个参数为访问文件模式选择,可以是打开已经存在的文件模式、读模式、写模式、新建模式、总是新建模式等的或运行结果。比如对于写测试, 使用FA_CREATE_ALWAYS和FA_WRITE组合模式,就是总是新建文件并进行写模式。
f_close函数用于不再对文件进行读写操作关闭文件,f_close函数只要一个形参,为文件对象指针。f_close函数运行可以确保缓冲区完全写入到文件内。
成功打开文件之后就可以使用f_write函数和f_read函数对文件进行写操作和读操作。这两个函数用到的参数是一致的,只不过一个是数据写入,一个是数据读取。 f_write函数第一个形参为文件对象指针,使用与f_open函数一致即可。第二个参数为待写入数据的首地址,对于f_read函数就是用来存放读出数据的首地址。 第三个参数为写入数据的字节数,对于f_read函数就是欲读取数据的字节数。第四个参数为32位无符号整形指针,这里使用fnum变量地址赋值给它, 在运行读写操作函数后,fnum变量指示成功读取或者写入的字节个数。之后 我们通过相应函数的返回值赋值到res_flash,后进行判断当前程序是否运行成功我们点亮绿灯和蓝灯,如果其中有一个失败我们点亮红灯。
最后,不再使用文件系统时,使用f_mount函数取消挂载。
25.4.7. 实验验证¶
用USB线连接开发板“USB TO UART”接口跟电脑,在电脑端打开串口调试助手, 把编译好的程序下载到开发板。在串口调试助手可看到FatFs测试的调试信息。