48. FATFS文件管理系统
一、FATFS简介
FATFS 是一个完全免费开源的 FAT/exFAT 文件系统模块,专门为小型的嵌入式系统而设计。它完全用标准 C 语言(ANSI C C89)编写,所以具有良好的硬件平台独立性,只需做简单的修改就可以移植到 8051、PIC、AVR、ARM、Z80、RX 等系列单片机上。它支持 FATl2、FATl6 和 FAT32,支持多个存储媒介;有独立的缓冲区,可以对多个文件进行读/写,并特别对 8 位单片机和 16 位单片机做了优化。
- 系统引导扇区:引导程序,以及文件系统信息(扇区字节数/每簇扇区数/保留扇区数等)。
- 文件分配表:记录文件存储中簇与簇之间连接的信息。
- 根目录:存在所有文件和子目录信息(文件名/文件夹名/创建时间/文件大小)。
- 数据区:文件等数据存放地方,占用大部分的磁盘空间。
FAT 文件系统用 “簇” 作为数据单元,一个 “簇” 由一组连续的扇区组成,而一个扇区的大小为 512 字节。所有的簇从 2 开始进行编号,每个簇都有自己的地址编号,用户文件和数据都存储在簇中。
二、FATFS模块的层次结构
- 底层接口(Low level device controls)
- 存储媒介读/写接口(disk I/O)和供给文件创建修改时间的实时时钟,需要我们根据平台和存储介质编写移植代码。
- FATFS 模块(FatFs Module)
- 实现了 FAT 文件读/写协议 .FATFS 模块提供的是 ff.c 和 ff.h。除非有必要,使用者一般不用修改,使用时将头文件直接包含进去。
- 应用层(Application)
- 使用者无需理会 FATFS 的内容结构和复杂的 FAT 协议,只需要调用 FATFS 模块提供给用户的一系列应用接口函数,如f_open,f_read,f_write和f_close等,就可以像在 PC 机上读/写文件。
三、FATFS源文件的下载
我们可以从官网(http://elm-chan.org/fsw/ff/00index_e.html)下载最新版本的 FATFS 软件包,解压后可以得到两个文件夹:【documents】 和 【source】。【documents】里面主要是对 FATFS 的介绍,而 【source】里面才是我们需要的源码。
四、FATFS的移植步骤
- 数据类型:在 integer.h 里面去定义好数据的类型。这里需要了解你用的编译器的数据类型,并根据编译器定义好数据类型。
- 配置:通过 ffconf.h 配置 FATFS 的相关功能,以满足你的需要。
- 函数编写:打开 diskio.c,进行底层驱动编写,需要编写 5 个接口函数。
FATFS 模块在移植的时候,我们一般只需要修改 2 个文件,即 ffconf.h 和 diskio.c。FATFS 模块的所有配置项都是存放在 ffconf.h 里面,我们可以通过配置里面的一些选项,来满足自己的需求。接下来我们介绍几个重要的配置选项。
在 ffconf.h 文件中 FF_FS_NORTC 宏为 0 时,我们需要实现 get_fattime() 函数。
因为 FATFS 模块完全与磁盘 I/O 层分开,因此需要下面的函数来实现底层物理磁盘的读写与获取当前时间。底层磁盘 I/O 模块并不是 FATFS 的一部分,并且必须由用户提供。这些函数一般有 5 个,在 diskio.c 里面。
【1】、初始化磁盘驱动器函数
DSTATUS disk_initialize (BYTE pdrv); // 初始化磁盘驱动器
【2】、获取磁盘状态函数
DSTATUS disk_status (BYTE pdrv); // 获取磁盘状态
【3】、从磁盘驱动器读扇区函数
DRESULT disk_read (BYTE pdrv, BYTE* buff, LBA_t sector, UINT count); // 从磁盘驱动器读扇区
【4】、向磁盘驱动器写扇区函数
DRESULT disk_write (BYTE pdrv, const BYTE* buff, LBA_t sector, UINT count); // 向磁盘驱动器写扇区
【5】、控制设备实现指定功能函数
DRESULT disk_ioctl (BYTE pdrv, BYTE cmd, void* buff); // 控制设备实现指定功能,用于辅助FATFS中其它API
FATFS 提供了很多 API 函数。
// 文件操作
FRESULT f_open (FIL* fp, const TCHAR* path, BYTE mode); // 打开/创建一个文件
FRESULT f_close (FIL* fp); // 关闭一个打开的文件
FRESULT f_read (FIL* fp, void* buff, UINT btr, UINT* br); // 从文件中读取数据
FRESULT f_write (FIL* fp, const void* buff, UINT btw, UINT* bw); // 往文件中写入数据
TCHAR* f_gets (TCHAR* buff, int len, FIL* fp); // 读一个字符串
int f_putc (TCHAR c, FIL* fp) // 写一个字符
int f_puts (const TCHAR* str, FIL* cp); // 写一个字符串
int f_printf (FIL* fp, const TCHAR* str, ...); // 写一个格式化字符串
FRESULT f_lseek (FIL* fp, FSIZE_t ofs); // 文件指针定位
FRESULT f_truncate (FIL* fp); // 截断文件
FRESULT f_sync (FIL* fp); // 刷新文件
FRESULT f_stat (const TCHAR* path, FILINFO* fno); // 获取文件信息
FRESULT f_findfirst (DIR* dp, FILINFO* fno, const TCHAR* path, const TCHAR* pattern); // 获取第一个文件
FRESULT f_findnext (DIR* dp, FILINFO* fno); // 获取下一个文件
FRESULT f_expand (FIL* fp, FSIZE_t fsz, BYTE opt); // 扩展文件
FRESULT f_forward (FIL* fp, UINT(*func)(const BYTE*,UINT), UINT btf, UINT* bf); // 将数据转发到流
#define f_tell(fp) ((fp)->fptr) // 获取文件指针位置
#define f_size(fp) ((fp)->obj.objsize) // 获取文件大小
#define f_eof(fp) ((int)((fp)->fptr == (fp)->obj.objsize)) // 判断文件是否到达文件末尾
#define f_rewind(fp) f_lseek((fp), 0) // 移动文件指针到文件开头
// 目录操作
FRESULT f_opendir (DIR* dp, const TCHAR* path); // 打开一个目录
FRESULT f_closedir (DIR* dp) // 关闭一个打开的目录
FRESULT f_readdir (DIR* dp, FILINFO* fno); // 读取目录条目
FRESULT f_mkdir (const TCHAR* path); // 创建一个目录
FRESULT f_unlink (const TCHAR* path); // 删除一个文件或目录
FRESULT f_rename (const TCHAR* path_old, const TCHAR* path_new); // 重命名/移动一个文件或目录
FRESULT f_chmod (const TCHAR* path, BYTE attr, BYTE mask); // 修改文件/目录属性
FRESULT f_utime (const TCHAR* path, const FILINFO* fno); // 更新文件/目录修改时间
FRESULT f_chdir (const TCHAR* path); // 改变当前目录
FRESULT f_getcwd (TCHAR* buff, UINT len); // 获取当前目录
#define f_rmdir(path) f_unlink(path) // 删除一个目录
#define f_rewinddir(dp) f_readdir((dp), 0) // 移动目录指针到目录开头
// 卷管理
FRESULT f_mount (FATFS* fs, const TCHAR* path, BYTE opt); // 注册/注销一个工作区
FRESULT f_mkfs (const TCHAR* path, const MKFS_PARM* opt, void* work, UINT len); // 格式化,创建一个文件系统
FRESULT f_getfree (const TCHAR* path, DWORD* nclst, FATFS** fatfs); // 获取磁盘信息以及空闲簇数量
FRESULT f_chdrive (const TCHAR* path) // 改变当前驱动器
FRESULT f_getlabel (const TCHAR* path, TCHAR* label, DWORD* vsn); // 获取卷标
FRESULT f_setlabel (const TCHAR* label); // 设置卷标
FRESULT f_fdisk (BYTE pdrv, const LBA_t ptbl[], void* work); // 磁盘分区
FRESULT f_setcp (WORD cp); // 设置代码页
#define f_error(fp) ((fp)->err) // 获取错误代码
#define f_unmount(path) f_mount(0, path, 0) // 注销一个工作区
五、源码实现
5.1、修改ffconf.h的配置项
【1】、FF_FS_NORTC 的配置
FF_FS_NORTC 宏为 0 时,我们需要实现 get_fattime() 函数。
#define FF_FS_NORTC 1
#define FF_NORTC_MON 1
#define FF_NORTC_MDAY 1
#define FF_NORTC_YEAR 2022
【2】、FF_CODE_PAGE 的配置
设置语言类型,简体中文设置为 963。
#define FF_CODE_PAGE 936
【3】、FF_USE_STRFUNC 的配置
设置是否支持字符串类操作。
#define FF_USE_STRFUNC 1
【4】、FF_VOLUMES 的配置
设置 FATFS 支持的逻辑设备数目。
5.2、修改sidkio.c文件中的5个函数
#define DEV_SD 0
获取磁盘状态函数:
DSTATUS disk_status (
BYTE pdrv /* Physical drive nmuber to identify the drive */
)
{
return RES_OK;
}
初始化磁盘驱动器函数:
DSTATUS disk_initialize (
BYTE pdrv /* Physical drive nmuber to identify the drive */
)
{
int result;
switch (pdrv)
{
case DEV_SD :
result = SD_Init();
if (result)
{
return STA_NOINIT;
}
}
return RES_OK;
}
从磁盘驱动器读扇区函数:
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 */
)
{
int result;
switch (pdrv)
{
case DEV_SD :
result = SD_ReadData(&g_sd_handler, sector, count, buff);
if (result)
{
return RES_PARERR;
}
}
return RES_OK;
}
向磁盘驱动器写扇区函数:
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 */
)
{
int result;
switch (pdrv)
{
case DEV_SD :
result = SD_WriteData(&g_sd_handler, sector, count, (uint8_t *)buff);
if (result)
{
return RES_PARERR;
}
}
return RES_OK;
}
控制设备实现指定功能函数:
DRESULT disk_ioctl (
BYTE pdrv, /* Physical drive nmuber (0..) */
BYTE cmd, /* Control code */
void *buff /* Buffer to send/receive control data */
)
{
DRESULT result;
switch (pdrv)
{
case DEV_SD :
switch (cmd)
{
case CTRL_SYNC:
result = RES_OK;
break;
case GET_SECTOR_SIZE:
*(DWORD*)buff = 512;
result = RES_OK;
break;;
case GET_BLOCK_SIZE:
*(DWORD*)buff = g_sd_handler.SdCard.BlockSize;
result = RES_OK;
break;
case GET_SECTOR_COUNT:
*(DWORD*)buff = g_sd_handler.SdCard.BlockNbr * g_sd_handler.SdCard.BlockSize / 512;
result = RES_OK;
break;
default:
result = RES_PARERR;
}
}
return result;
}
5.3、main()函数
#include "ff.h"
int main(void)
{
FATFS fs;
FIL fil;
FRESULT result;
char writeData[] = "Hello, Sakura!";
char readData[100] = {0};
uint32_t write_data = 0, read_count = 0;
uint32_t fSize = 0;
HAL_Init();
System_Clock_Init(8, 336, 2, 7);
Delay_Init(168);
UART_Init(&g_usart1_handle, USART1, 115200);
// 挂载SD卡设备
// 第一个参数是挂载点,第二个参数是挂载设备编号,第三个参数是是立即挂载
result = f_mount(&fs, "0:", 1);
if (result)
{
printf("mount fail %d\r\n", result);
}
else
{
printf("mount success\r\n");
}
// 打开文件
// 第一个参数是文件指针,第二个参数是文件路径,第三个参数是文件打开模式
result = f_open(&fil, "0:test.txt", FA_READ | FA_WRITE | FA_OPEN_ALWAYS);
if (result)
{
printf("open fail %d\r\n", result);
}
else
{
printf("open success\r\n");
}
// 写入数据
f_write(&fil, writeData, sizeof(writeData), (UINT *)&write_data);
// 写完文件的读写指针已经到末尾,我们需要重新定位到文件开头
f_lseek(&fil, 0);
// 获取文件大小
fSize = f_size(&fil);
// 读取数据
// 第一个参数是文件指针,第二个参数是读取的数据,第三个参数是要读取的数据长度,第四个参数保存实际读取的数据长度
f_read(&fil, readData, fSize, (UINT *)&read_count);
if (read_count)
{
printf("read count: %ld\r\n", read_count);
printf("data:\r\n");
printf("%s\r\n", readData);
}
// 关闭文件
f_close(&fil);
while (1)
{
}
return 0;
}