FatFs 文件系统简介
文件系统庞大而复杂,它需要根据 应用的文件系统格式而编写,而且一般与驱动层分离开来,很方便移植,所以工程应用中一般是 移植现成的文件系统源码。 FatFs 是面向小型嵌入式系统的一种通用的 FAT 文件系统。它完全是由 ANSIC 语言编写并且完 全独立于底层的 I/O 介质。因此它可以很容易地不加修改地移植到其他的处理器当中,如 8051、 PIC、AVR、SH、Z80、H8、ARM 等。FatFs 支持 FAT12、FAT16、FAT32 等格式,所以我们利用 前面写好的 SPI Flash 芯片驱动,把 FatFs 文件系统代码移植到工程之中,就可以利用文件系统的 各种函数,对 SPI Flash 芯片以"文件"格式进行读写操作了。
FatFs 文件系统的源码可以从 fatfs 官网下载:
http://elm-chan.org/fsw/ff/00index_e.html
FatFs 的目录结构
先要到 FatFs 的官网获取源码,官 网有对 FatFs 做详细的介绍,有兴趣可以了解。解压之后可看到里面有 doc 和 src 这两个文件夹, 见图 FatFs 文件目录。doc 文件夹里面是一些使用帮助文档;src 才是 FatFs 文件系统的源码。
FatFs 帮助文档
打开 doc 文件夹,可看到如图 doc 文件夹的文件目录 的文件目录:

其中 en 和 ja 这两个文件夹里面是编译好的 html 文档,讲的是 FATFS 里面各个函数的使用方法, 这些函数都是封装得非常好的函数,利用这些函数我们就可以操作 SPI Flash 芯片。这两个文件夹的唯一区别就是 en 文件夹下的文档是英文的,ja 文 件夹下的是日文的。img 文件夹包含 en 和 ja 文件夹下文件需要用到的图片,还有四个名为 app.c 文件,内容都是 FatFs 具体应用例程。00index_e.html 和 00index_j.html 是一些关于 FATFS 的简介, 至于另外两个文件可以不看。
FATFS 源码
打开 src 文件夹,可看到如图 src 文件夹的文件目录 的文件目录:

option 文件夹下是一些可选的外部 c 文件,包含了多语言支持需要用到的文件和转换函数。 diskio.c 文件是 FatFs 移植最关键的文件,它为文件系统提供了最底层的访问 SPI Flash 芯片的方 法,FatFs 有且仅有它需要用到与 SPI Flash 芯片相关的函数。
diskio.h 定义了 FatFs 用到的宏,以 及 diskio.c 文件内与底层硬件接口相关的函数声明。 00history.txt 介绍了 FatFs 的版本更新情况。
00readme.txt 说明了当前目录下 diskio.c 、diskio.h、ff.c、ff.h、integer.h 的功能。
• integer.h:文件中包含了一些数值类型定义。• **diskio.c:**包含底层存储介质的操作函数,这些函数需要用户自己实现,主要添加底层驱动 函数。
•**ff.c:**FatFs 核心文件,文件管理的实现方法。该文件独立于底层介质操作文件的函数,利用 这些函数实现文件的读写。
• cc936.c:本文件在 option 目录下,是简体中文支持所需要添加的文件,包含了简体中文的 GBK 和 Unicode 相互转换功能函数。
•ffconf.h: 这个头文件包含了对 FatFs 功能配置的宏定义,通过修改这些宏定义就可以裁剪 FatFs 的功能。如需要支持简体中文,需要把 ffconf.h 中的 _CODE_PAGE 的宏改成 936 并把 上面的 cc936.c 文件加入到工程之中。*
++小技巧: 建议阅读这些源码的顺序为:integer.h --> diskio.c --> ff.c。阅读文件系统源码 ff.c 文件需 要一定的功底,建议读者先阅读 FAT32 的文件格式,再去分析 ff.c 文件。若仅为使用文件系统, 则只需要理解 integer.h 及 diskio.c 文件并会调用 ff.c 文件中的函数就可以了。++
FatFs 文件系统移植实验
FatFs 程序结构图
移植 FatFs 之前我们先通过 FatFs 的程序结构图了解 FatFs 在程序中的关系网络

用户应用程序需要由用户编写,想实现什么功能就编写什么的程序,一般我们只用到 f_mount()、 f_open()、f_write()、f_read() 就可以实现文件的读写操作。 FatFs 组件是 FatFs 的主体,文件都在源码 src 文件夹中,其中 ff.c、ff.h、integer.h 以及 diskio.h 四 个文件我们不需要改动,只需要修改 ffconf.h 和 diskio.c 两个文件。 底层设备输入输出要求实现存储设备的读写操作函数、存储设备信息获取函数等等。我们使用 SPI Flash 芯片作为物理设备,编写好了 SPI Flash 芯片的驱动程序,这里我们就 直接使用。
FatFs 移植步骤
实现 SPI Flash 芯片驱动程序,并实现了读写测试,为移植 FatFs 方便,我们直 接拷贝一份工程,我们在工程基础上添加 FatFs 组件,并修改 main 函数的用户程序即可。
- 先拷贝一份 SPI Flash 芯片测试的工程文件 (我这里是读写w25q64工程),并修改文件夹名为"SPI---FatFs 文件 系统"。将 FatFs 源码中的 src 文件夹整个文件夹拷贝一份至"SPI---FatFs 文件系统 USER"文件 夹下并修改名为"FATFS",

2.使用 KEIL 软件打开工程,并 将 FatFs 组件文件添加到工程中,需要添加有 ff.c、diskio.c 和 cc936.c 三个文件

3.添加 FATFS 文件夹到工程的 include 选项中。打开工程选项对话框,选择"C/C++"选项下的 "Include Paths"项目,在弹出路径设置对话框中选择添加"FATFS"文件夹

4.如果现在编译工程,可以发现有两个错误,一个是来自 diskio.c 文件,提示有一些头文件没找 到,diskio.c 文件内容是与底层设备输入输出接口函数文件,不同硬件设计驱动就不同,需要的文 件也不同;另外一个错误来自 cc936.c 文件,提示该文件不是工程所必需的,这是因为 FatFs 默认 使用日语,我们想要支持简体中文需要修改 FatFs 的配置,即修改 ffconf.h 文件。至此,将 FatFs 添加到工程的框架已经操作完成,接下来要做的就是修改 diskio.c 文件和 ffconf.h 文件。
FatFs 底层设备驱动函数
FatFs 文件系统与底层介质的驱动分离开来,对底层介质的操作都要交给用户去实现,它仅仅是 提供了一个函数接口而已。表 FatFs 移植需要用户支持函数 为 FatFs 移植时用户必须支持的函数。 通过表 FatFs 移植需要用户支持函数 我们可以清晰知道很多函数是在一定条件下才需要添加的, 只有前三个函数是必须添加的。我们完全可以根据实际需求选择实现用到的函数。
前三个函数是实现读文件最基本需求。接下来三个函数是实现创建文件、修改文件需要的。为实 现格式化功能,需要在 disk_ioctl 添加两个获取物理设备信息选项。我们一般只要实现前面六个 函数就可以了,已经足够满足大部分功能。
为支持简体中文长文件名称需要添加 ff_convert 和 ff_wtoupper 函数,实际这两个已经在 cc936.c 文件中实现,我们只要直接把 cc936.c 文件添加到工程中就可以。
后面六个函数一般都不用。如真有需要可以参考 syscall.c 文件 (srcoption 文件夹内)。

底层设备驱动函数是存放在 diskio.c 文件,我们的目的就是把 diskio.c 中的函数接口与 SPI Flash 芯 片驱动连接起来。总共有五个函数,
cpp/* 为每个设备定义一个物理编号 */ #define ATA 0 // 预留 SD 卡使用 #define SPI_FLASH 1 // 外部 SPI Flash分别为设备状态获取 (disk_status)、设备初始化 (disk_initialize)、 扇区读取 (disk_read)、扇区写入 (disk_write)、其他控制 (disk_ioctl)
接下来,我们对每个函数结合 SPI Flash 芯片驱动做详细讲解
宏定义
cpp
/* 为每个设备定义一个物理编号 */
#define SD_CARD 0 // 预留 SD 卡使用
#define SPI_FLASH 1 // 外部 SPI Flash
这两个宏定义在 FatFs 中非常重要,FatFs 是支持多物理设备的,必须为每个物理设备定义一个不 同的编号。
SD 卡是预留接口,可以实现使用读写 SD 卡内文件。
设备状态获取
cpp
DSTATUS disk_status(
BYTE pdrv /* Physical drive nmuber to identify the drive */
)
{
DSTATUS stat = 0;
uint32_t flash_result;
switch (pdrv)
{
case SD_CARD:
// SD¿¨×´Ì¬·µ>>Ø·ÖÖ§
// translate the reslut code here
return stat;
case SPI_FLASH:
flash_result = SPI_FLASH_ReadDeviceID();
if (flash_result == 0x16)
{
stat &= ~STA_NOINIT;
}
else
{
stat |= STA_NOINIT;
}
// translate the reslut code here
return stat;
}
return STA_NOINIT;
}
disk_status 函数只有一个参数 pdrv,表示物理编号。一般我们都是使用 switch 函数实现对 pdrv 的分支判断。对于 SD 卡只是预留接口,留空即可。对于 SPI Flash 芯片,我们直接调用在 SPI_FLASH_ReadID() 获取设备 ID,然后判断是否正确,如果正确,函数返回正常标准;如果 错误,函数返回异常标志。SPI_FLASH_ReadID() 是定义在 bsp_spi_flash.c 文件中,上一章节已做 了分析。
设备初始化
cpp
DSTATUS disk_initialize(
BYTE pdrv /* Physical drive nmuber to identify the drive */
)
{
DSTATUS stat;
switch (pdrv)
{
case SD_CARD:
// SD¿¨³õʼ>>¯·ÖÖ§
// translate the reslut code here
return stat;
case SPI_FLASH:
// SPI_FLASH³õʼ>>¯·ÖÖ§
SPI_FLASH_Init();
stat = disk_status(SPI_FLASH);
// translate the reslut code here
return stat;
}
return STA_NOINIT;
}
disk_initialize 函数也是有一个参数 pdrv,用来指定设备物理编号。对于 SPI Flash 芯片我们调 用 SPI_FLASH_Init() 函数实现对 SPI Flash 芯片引脚 GPIO 初始化配置以及 SPI 通信参数配置。 SPI_Flash_WAKEUP() 函数唤醒 SPI Flash 芯片,当 SPIFlash 芯片处于睡眠模式时需要唤醒芯片才 可以进行读写操作。
最后调用 disk_status 函数获取 SPI Flash 芯片状态,并返回状态值。
读取扇区
cpp
DRESULT disk_read(
BYTE pdrv, /* 设备物理编号 (0..) */
BYTE *buff, /* 数据缓存区 */
DWORD sector, /* 扇区首地址 */
UINT count /* 扇区个数 (1..128) */
)
{
DRESULT res;
uint32_t ReadAddr;
switch (pdrv)
{
case SD_CARD:
// sd¿¨¶ÁÈ¡·ÖÖ§
// translate the reslut code here
return res;
case SPI_FLASH:
// flash¶ÁÈ¡·ÖÖ§
// 把要读取的扇区号转换成地址
/* 扇区偏移 6MB,外部 Flash 文件系统空间放在 SPI Flash 后面的空间 */
sector+=1536;
ReadAddr = FLASH_SECTOR_SIZE * sector;
SPI_FLASH_BufferRead((uint8_t *)buff, ReadAddr, count * FLASH_SECTOR_SIZE);
// translate the reslut code here
// ĬÈÏÿ´Î¶¼¶ÁÈ¡Õý³£
return RES_OK;
}
return RES_PARERR;
}
disk_read 函数有四个形参。pdrv 为设备物理编号。buff 是一个 BYTE 类型指针变量,buff 指向用 来存放读取到数据的存储区首地址。sector 是一个 DWORD 类型变量,指定要读取数据的扇区首 地址。count 是一个 UINT 类型变量,指定扇区数量。
BYTE 类型实际是 unsigned char 类型,DWORD 类型实际是 unsigned long 类型,UINT 类型实际是 unsigned int 类型,类型定义在 integer.h 文件中。
开发板使用的 SPI Flash 芯片型号为 W25Q128FV,每个扇区大小为 4096 个字节 (4KB),总共有 16M 字节空间,为兼容后面实验程序,我们只将后部分 10MB 空间分配给 FatFs 使用,前部分 6MB 空间用于其他实验需要,即 FatFs 是从 6MB 空间开始,为实现这个效果需要将所有的读写 地址都偏移 1536 个扇区空间。
对于 SPI Flash 芯片,主要是使用 SPI_FLASH_BufferRead() 实现在指定地址读取指定长度的数据, 它接收三个参数,第一个参数为指定数据存放地址指针。第二个参数为指定数据读取地址,这里 使用左移运算符,左移 12 位实际是乘以 4096,这与每个扇区大小是息息相关的。第三个参数为读取数据个数,也是需要使用左移运算符。
扇区写入
cpp
#if _USE_WRITE
DRESULT disk_write(
BYTE pdrv, /* 设备物理编号 (0..) */
const BYTE *buff, /* 欲写入数据的缓存区 */
DWORD sector, /* 扇区首地址 */
UINT count /* 扇区个数 (1..128) */
)
{
DRESULT res;
uint32_t WriteAddr;
switch (pdrv)
{
case SD_CARD:
// translate the arguments here
// translate the reslut code here
return res;
case SPI_FLASH:
/* 扇区偏移 6MB,外部 Flash 文件系统空间放在 SPI Flash 后面的空间 */
sector+=1536;
while (count--)
{
// 写入之前先擦除扇区
SPI_FLASH_SectorErase(sector * FLASH_SECTOR_SIZE);
// 把要写入的扇区号转换成地址
WriteAddr = FLASH_SECTOR_SIZE * sector;
SPI_FLASH_BufferWrite((uint8_t *)buff, WriteAddr, 1 * FLASH_SECTOR_SIZE);
// translate the reslut code here
sector++;
buff += FLASH_SECTOR_SIZE;
}
// 默认每次写入都正常
return RES_OK;
}
return RES_PARERR;
}
#endif
disk_write 函数有四个形参,pdrv 为设备物理编号。buff 指向待写入扇区数据的首地址。sector, 指定要读取数据的扇区首地址。count 指定扇区数量。对于 SPI Flash 芯片,在写入数据之前 需要先擦除,所以用到扇区擦除函数 (SPI_FLASH_SectorErase)。然后就是在调用数据写入函数 (SPI_FLASH_BufferWrite) 把数据写入到指定位置内。
其他控制
cpp
#if _USE_IOCTL
DRESULT disk_ioctl(
BYTE pdrv,/* 物理编号 */
BYTE cmd, /* 控制指令 */
void *buff /* 写入或者读取数据地址指针 */
)
{
DRESULT res;
switch (pdrv)
{
case SD_CARD:
// Process of the command for the ATA drive
return res;
case SPI_FLASH:
// ´æ´¢ÏµÍ³ÓжàÉÙ¸ösector£¬Îļþϵͳͨ¹ý¸ÃÖµ>>ñµÃ´æ´¢½éÖʵÄÈÝÁ¿
switch (cmd)
{
/* 扇区数量 */
case GET_SECTOR_COUNT:
*(DWORD *)buff = (8 * 1024 * 1024 / FLASH_SECTOR_SIZE)-1536;
res = RES_OK;
break;
/*扇区大小*/
case GET_SECTOR_SIZE:
*(WORD *)buff = FLASH_SECTOR_SIZE;
res = RES_OK;
break;
/*获取擦除的最小个数,以sector为单位*/
case GET_BLOCK_SIZE:
*(DWORD *)buff = 1;
res = RES_OK;
break;
case CTRL_SYNC:
res = RES_OK;
break;
default:
res = RES_PARERR;
break;
}
return res;
}
return RES_PARERR;
}
#endif
disk_ioctl 函数有三个形参,pdrv 为设备物理编号,cmd 为控制指令,包括发出同步信号、获取扇 区数目、获取扇区大小、获取擦除块数量等等指令,buff 为指令对应的数据指针。 对于 SPI Flash 芯片,为支持 FatFs 格式化功能,需要用到获取扇区数量 (GET_SECTOR_COUNT) 指令和获取擦除块数量 (GET_BLOCK_SIZE) 指令。另外,SD 卡扇区大小为 512 字节,SPI Flash 芯片一般设置扇区大小为 4096 字节,所以需要用到获取扇区大小 (GET_SECTOR_SIZE) 指令。
时间戳获取
cpp
__weak DWORD get_fattime(void)
{
/* 返回当前时间戳*/
return ((DWORD)(2015 - 1980) << 25) /* Year 2015 */
| ((DWORD)1 << 21) /* Month 1 */
| ((DWORD)1 << 16) /* Mday 1 */
| ((DWORD)0 << 11) /* Hour 0 */
| ((DWORD)0 << 5) /* Min 0 */
| ((DWORD)0 >> 1); /* Sec 0 */
}
get_fattime 函数用于获取当前时间戳,在 ff.c 文件中被调用。FatFs 在文件创建、被修改时会记录 时间,这里我们直接使用赋值方法设定时间戳。为更好的记录时间,可以使用控制器 RTC 功能, 具体要求返回值格式为:
• bit31:25 ------从 1980 至今是多少年,范围是 (0..127) ;
• bit24:21 ------月份,范围为 (1..12) ;
• bit20:16 ------该月份中的第几日,范围为 (1..31) ;
• bit15:11------时,范围为 (0..23);
• bit10:5 ------分,范围为 (0..59);
• bit4:0 ------秒/ 2,范围为 (0..29)。
FatFs 功能配置
ffconf.h 文件是 FatFs 功能配置文件,我们可以对文件内容进行修改,使得 FatFs 更符合我们的要 求。ffconf.h 对每个配置选项都做了详细的使用情况说明。下面只列出修改的配置,其他配置采 用默认即可。
cpp
#define _USE_MKFS 1
#define _CODE_PAGE 936
#define _USE_LFN 2
#define _VOLUMES 2
#define _MIN_SS 512
#define _MAX_SS 4096
1)**_USE_MKFS:**格式化功能选择,为使用 FatFs 格式化功能,需要把它设置为 1。
- **_CODE_PAGE:**语言功能选择,并要求把相关语言文件添加到工程宏。为支持简体中文文件名 需要使用"936",正如在图添加 FatFS 文件到工程 的操作,我们已经把 cc936.c 文件添加到工程 中。
3)**_USE_LFN:**长文件名支持,默认不支持长文件名,这里配置为 2,支持长文件名,并指定使 用栈空间为缓冲区。
**_VOLUMES:**指定物理设备数量,这里设置为 2,包括预留 SD 卡和 SPI Flash 芯片。
**_MIN_SS 、_MAX_SS:**指定扇区大小的最小值和最大值。SD 卡扇区大小一般都为 512 字节, SPI Flash 芯片扇区大小一般设置为 4096 字节,所以需要把 _MAX_SS 改为 4096。
FatFs支持长文件名和大小写
修改宏_USE_LFN 1
cpp
#define _USE_LFN 1
#define _MAX_LFN 255
/* The _USE_LFN option switches the LFN feature.
/
/ 0: Disable LFN feature. _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.
此时会报错:..\..\Output\Fatfs.axf: Error: L6218E: Undefined symbol ff_convert (referred from ff.o).
..\..\Output\Fatfs.axf: Error: L6218E: Undefined symbol ff_wtoupper (referred from ff.o).
需要加入ccsbcs.c文件

接下来还会报错:..\..\User\Fatfs\option\ccsbcs.c(269): error: #35: #error directive: This file is not needed at current configuration. Remove from the project.
修改宏:_CODE_PAGE 437
cpp
#define _CODE_PAGE 437
/* This option specifies the OEM code page to be used on the target system.
/ Incorrect setting of the code page can cause a file open failure.
/
/ 1 - ASCII (No extended character. Non-LFN cfg. only)
/ 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)
FstFs支持中文
修改ccsbcs.c文件为cc936.c文件
修改宏:_CODE_PAGE 936
FatFs 功能测试
移植操作到此,就已经把 FatFs 全部添加到我们的工程了,这时我们编译功能,顺利编译通过,没 有错误。接下来,我们就可以使用编写图 FatFs 程序结构图 中用户应用程序了。
主要的测试包括格式化测试、文件写入测试和文件读取测试三个部分,主要程序都在 main.c 文 件中实现。
变量定义
cpp
FATFS fs; /* FatFs 文件系统对象 */
FIL fnew; /* 文件对象 */
FRESULT res_flash; /* 文件操作结果 */
UINT fnum; /* 文件成功读写数量 */
BYTE buffer[1024]= {0}; /* 读缓冲区 */
BYTE textFileBuffer[] = /* 写缓冲区 */
FATFS 是在 ff.h 文件定义的一个结构体类型,针对的对象是物理设备,包含了物理设备的物理编 号、扇区大小等等信息,一般我们都需要为每个物理设备定义一个 FATFS 变量。
FIL 也是在 ff.h 文件定义的一个结构体类型,针对的对象是文件系统内具体的文件,包含了文件 很多基本属性,比如文件大小、路径、当前读写地址等等。如果需要在同一时间打开多个文件进 行读写,才需要定义多个 FIL 变量,不然一般定义一个 FIL 变量即可。
FRESULT 是也在 ff.h 文件定义的一个枚举类型,作为 FatFs 函数的返回值类型,主要管理 FatFs 运行中出现的错误。总共有 19 种错误类型,包括物理设备读写错误、找不到文件、没有挂载工 作空间等等错误。这在实际编程中非常重要,当有错误出现时我们要停止文件读写,通过返回值 我们可以快速定位到错误发生的可能地点。如果运行没有错误才返回 FR_OK。
fnum 是个 32 位无符号整形变量,用来记录实际读取或者写入数据的数组。
buffer 和 textFileBuffer 分别对应读取和写入数据缓存区,都是 8 位无符号整形数组。
主函数
cpp
int main(void)
{
/* 初始化 LED */
LED_GPIO_Config();
LED_BLUE;
/* 初始化调试串口,一般为串口 1 */
Debug_USART_Config();
printf("****** 这是一个 SPI FLASH 文件系统实验 ******\r\n");
// 在外部 SPI Flash 挂载文件系统,文件系统挂载时会对 SPI 设备初始化
res_flash = f_mount(&fs, "1:", 1);
/*----------------------- 格式化测试 ---------------------------*/
/* 如果没有文件系统就格式化创建创建文件系统 */
if (res_flash == FR_NO_FILESYSTEM)
{
printf("》FLASH 还没有文件系统,即将进行格式化...\r\n");
/* 格式化 */
res_flash = f_mkfs("1:", 0, 0);
if (res_flash == FR_OK)
{
printf("》FLASH 已成功格式化文件系统。\r\n");
/* 格式化后,先取消挂载 */
res_flash = f_mount(NULL, "1:", 1);
/* 重新挂载 */
res_flash = f_mount(&fs, "1:", 1);
}
else
{
LED_RED;
printf("《《格式化失败。》》\r\n");
while (1)
;
}
}
else if (res_flash != FR_OK)
{
printf("!!外部 Flash 挂载文件系统失败。(%d)\r\n", res_flash);
printf("!!可能原因:SPI Flash 初始化不成功。\r\n");
while (1)
;
}
else
{
printf("》文件系统挂载成功,可以进行读写测试\r\n");
}
/*---------------------- 文件系统测试:写测试 ----------------------*/
/* 打开文件,如果文件不存在则创建它 */
printf("\r\n****** 即将进行文件写入测试... ******\r\n");
res_flash = f_open(&fnew, "1:FatFs 读写测试文件.txt",
FA_CREATE_ALWAYS | FA_WRITE);
if (res_flash == FR_OK)
{
printf("》打开/创建 FatFs 读写测试文件.txt 文件成功,向文件写入数据。\r\n");
/* 将指定存储区内容写入到文件内 */
res_flash = f_write(&fnew, WriteBuffer, sizeof(WriteBuffer), &fnum);
if (res_flash == FR_OK)
{
printf("》文件写入成功,写入字节数据:%d\n", fnum);
printf("》向文件写入的数据为:\r\n%s\r\n", WriteBuffer);
}
else
{
printf("!!文件写入失败:(%d)\n", res_flash);
}
/* 不再读写,关闭文件 */
f_close(&fnew);
}
else
{
LED_RED;
printf("!!打开/创建文件失败。\r\n");
}
/*------------------- 文件系统测试:读测试 --------------------------*/
printf("****** 即将进行文件读取测试... ******\r\n");
res_flash = f_open(&fnew, "1:FatFs 读写测试文件.txt",
FA_OPEN_EXISTING | FA_READ);
if (res_flash == FR_OK)
{
LED_GREEN;
printf("》打开文件成功。\r\n");
res_flash = f_read(&fnew, ReadBuffer, sizeof(ReadBuffer), &fnum);
if (res_flash == FR_OK)
{
printf("》文件读取成功, 读到字节数据:%d\r\n", fnum);
printf("》读取得的文件数据为:\r\n%s \r\n", ReadBuffer);
}
else
{
printf("!!文件读取失败:(%d)\n", res_flash);
}
}
else
{
LED_RED;
printf("!!打开文件失败。\r\n");
}
/* 不再读写,关闭文件 */
f_close(&fnew);
/* 不再使用文件系统,取消挂载文件系统 */
f_mount(NULL, "1:", 1);
/* 操作完成,停机 */
while (1)
{
}
}
首先,初始化 RGB 彩灯和调试串口,用来指示程序进程。 FatFs 的第一步工作就是使用 f_mount 函数挂载工作区。f_mount 函数有三个形参,第一个参数是 指向 FATFS 变量指针,如果赋值为 NULL 可以取消物理设备挂载。第二个参数为逻辑设备编号, 使用设备根路径表示,与物理设备编号挂钩,在代码清单: 文件系统-1 中我们定义 SPI Flash 芯片 物理编号为 1,所以这里使用"1:"。第三个参数可选 0 或 1,1 表示立即挂载,0 表示不立即挂 载,延迟挂载。f_mount 函数会返回一个 FRESULT 类型值,指示运行情况。 如果 f_mount 函数返回值为 FR_NO_FILESYSTEM,说明没有 FAT 文件系统,比如新出厂的 SPI Flash 芯片就没有 FAT 文件系统。我们就必须对物理设备进行格式化处理。使用 f_mkfs 函数可以 实现格式化操作。f_mkfs 函数有三个形参,第一个参数为逻辑设备编号;第二参数可选 0 或者 1, 0 表示设备为一般硬盘,1 表示设备为软盘。第三个参数指定扇区大小,如果为 0,表示通过代码 清单: 文件系统-6 中 disk_ioctl 函数获取。格式化成功后需要先取消挂载原来设备,再重新挂载设 备。 在设备正常挂载后,就可以进行文件读写操作了。使用文件之前,必须使用 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 变量指示成功读取或者写入的字节个数。 最后,不再使用文件系统时,使用 f_mount 函数取消挂载。