48. FATFS文件管理系统

2026-07-06 20:27:51

一、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 的配置

设置语言类型,简体中文设置为 936。

#define FF_CODE_PAGE 936

【3】、FF_USE_STRFUNC 的配置

设置是否支持字符串类操作。

#define FF_USE_STRFUNC 1

#define FF_PRINT_LLI 1

#define FF_PRINT_FLOAT 1

#define FF_STRF_ENCODE 3

【4】、FF_VOLUMES 的配置

设置 FATFS 支持的逻辑设备数目。

5.2、修改sidkio.c文件中的5个函数

我们将默认的存储设备注释掉,添加自己需要的存储设备。

#define DEV_SD 0

获取磁盘状态函数:

/*-----------------------------------------------------------------------*/

/* Get Drive Status */

/*-----------------------------------------------------------------------*/

DSTATUS disk_status (

BYTE pdrv /* Physical drive nmuber to identify the drive */

)

{

return RES_OK;

}

初始化磁盘驱动器函数:

/*-----------------------------------------------------------------------*/

/* Inidialize a Drive */

/*-----------------------------------------------------------------------*/

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;

}

从磁盘驱动器读扇区函数:

/*-----------------------------------------------------------------------*/

/* 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 */

)

{

int result;

switch (pdrv)

{

case DEV_SD :

result = SD_ReadData(&g_sd_handler, sector, count, buff);

if (result)

{

return RES_PARERR;

}

}

return RES_OK;

}

向磁盘驱动器写扇区函数:

/*-----------------------------------------------------------------------*/

/* Write Sector(s) */

/*-----------------------------------------------------------------------*/

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;

}

控制设备实现指定功能函数:

/*-----------------------------------------------------------------------*/

/* Miscellaneous Functions */

/*-----------------------------------------------------------------------*/

DRESULT disk_ioctl (

BYTE pdrv, /* Physical drive nmuber (0..) */

BYTE cmd, /* Control code */

void *buff /* Buffer to send/receive control data */

)

{

DRESULT result = RES_ERROR;

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();

HAL_NVIC_SetPriorityGrouping(NVIC_PRIORITYGROUP_4);

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;

}

真相!“三国演义”中的张飞,是房山人!
iPhone 照片显示日期时间怎么用?2 招为照片加上时间戳标记