FATFS文件系统
一、文件系统简介
文件系统:为了存储和管理数据而建立的一种组织结构
FAT(File Alloction Table,文件分配表)
使用文件系统前,需先对存储设备进行格式化,擦除原来的数据,在存储设备上建立一个文件分配表和目录。
FAT32文件系统布局:
系统引导扇区:引导程序,以及文件系统信息(扇区字节数/每簇扇区数/保留扇区数等)
文件分配表:记录文件存储中簇与簇之间连接的信息
根目录:存在所有文件和子目录信息(文件名/文件夹名/创建时间/文件大小)
数据区:文件等数据存放地方,占用大部分的磁盘空间
FAT文件系统用“簇” 作为数据单元,一个“簇”由一组(2的整数次幂)连续的扇区组成,而一个扇区的大小为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文件结构:
FATFS文件系统的移植需要修改2个文件,ffconf.h和diskio.c。
文件名 | 功能名 | 说明 |
ffconf.h | FATFS模块配置文件 | 根据需求来配置 |
ff.h | FATFS和应用模块共用的包含文件 | 不需要修改 |
ff.c | FATFS模块源码(文件系统API) | 不需要修改 |
diskio.h | FATFS和disk I/O模块共用的包含文件 | 不需要修改 |
diskio.c | FATFS和disk I/O模块接口层文件 | 与硬件相关代码 |
ffunicode.c | FATFS所支持的字体代码转换表 | 不需要修改 |
ffsystem.c | FATFS的OS相关函数示例代码 | 没用到 |
ffcong.h:
FF_FS_READONLY:文件系统只读,默认为0,如果置1那么FatFs会关闭所有能修改文件的函数,大大减少FatFs的体积。
FF_FS_MINIMIZE:文件系统最小化,这个配置也可以减少FatFs的体积,它主要是通过关闭一些不常用的函数实现的;默认为0,即所有基础函数全开;置1的时候会关闭f_stat、f_getfree、f_unlink、f_mkdir、f_truncate和f_rename;置2的时候在上面的基础上再关闭f_opendir、f_readdir、f_closedir函数;置3的时候在上面的基础上再关闭f_lseek函数。
FF_USE_MKFS:文件系统格式化,默认为0,如果需要支持格式化可以置1开启这个功能。
FF_CODE_PAGE:文件系统语言,通过这个可以修改文件系统支持的语言,默认为437(英语),简体中文对应936,繁体中文对应950,语言全开就置0。
FF_USE_LFN:长文件名支持,默认为0,即不支持;这个配置一个有3种选项,区别在于文件名的储存方式;置1时,文件名储存在内存的BSS段中,此时是线程不安全的;置2时,文件名储存在栈中;置3时,文件名储存在堆中,这种方式是最推荐的。
FF_MAX_LFN:文件名长度,这个是和上面的配置对应的,如果没有使能长文件名支持,那么可以忽略该配置,文件名的长度最大可以设置为255字节。
FF_VOLUMES:储存介质数量,默认为1,如果单片机挂载了多于1种储存介质并且都有挂载文件系统,那么可以根据需要设置。
FF_MULTI_PARTITION:多分区支持,默认为0,如果文件系统需要支持多分区可以开启该配置,开启后需要用户创建分区表。
FF_MIN_SS和FF_MAX_SS:最小最大扇区大小,默认都为512,一般的存储介质扇区大小都是512字节,如果有不同可以根据储存芯片参数修改。
FF_FS_NORTC:时间戳支持,默认为0,即支持时间戳。
FF_FS_LOCK:文件锁支持,默认为0,它可以控制文件系统同时可以开启多少文件,一般建议设置成1,即同时只能开启一个文件。
FF_FS_REENTRANT:可重入支持,默认为0,单片机中有操作系统的话建议开启,它可以防止操作系统对文件系统进行异常操作。
配置项 | 配置项说明 | 设定值 | |
系统配置 | FF_FS_TINY | 配置使用FATFS为正常模式还是Tiny模式 | 0 |
FF_FS_EXFAT | 使用或禁用exFAT文件系统(使能exFAT需使能长文件名) | 1 | |
功能函数配置 | FF_FS_READONLY | 使能或禁止与写相关函数,即配置只读 | 0 |
FF_USE_MKFS | 使能或禁用f_mkfs函数,即是否使能格式化 | 1 | |
FF_USE_FASTSEEK | 使能快速搜索功能,加快f_lseek/read/write函数执行 | 1 | |
FF_USE_LABEL | 使能或禁止支持磁盘盘符读取与设置函数 | 1 | |
FF_USE_STRFUNC | 设置是否支持字符串类操作 | 1 | |
FF_STRF_ENCODE | 设置字符串I/O函数读写文件字符编码 | 0 | |
命名空间和本地环境配置 | FF_CODE_PAGE | 设置语言类型,简体中文设置为963 | 963 |
FF_USE_LFN | 使能或禁止长文件名,取值范围(0~3),存储地方不同 | 3 | |
磁盘配置 | FF_VOLUMES | 设置FATFS支持的逻辑设备数目 | 3 |
FF_MAX_SS | 设置最大扇区大小 | 512 |
diskio.c需要实现的函数:
disk_initialize:初始化磁盘驱动器
disk_status:获取磁盘状态
disk_read:从磁盘驱动器读扇区
disk_write:从磁盘驱动器写扇区
disk_ioctl:控制设备实现指定功能,用于辅助FATFS中其他API
get_fattime:获取当前时间
ffconf.h文件中FF_FS_NORTC宏为0时需要实现
二、基于GD32F427移植FATFS文件系统
移植步骤:
(1)移植FATFS源代码
FatFs的官网:FatFs - Generic FAT Filesystem Module
将下载好的源文件,一直到工程 文件夹中
(2)将GD例程中的sdcard源码移植到工程中的存放硬件驱动的文件夹中
(3) 修改diskio.c中各个函数
/*-----------------------------------------------------------------------*/
/* Low level disk I/O module SKELETON for FatFs (C)ChaN, 2025 */
/*-----------------------------------------------------------------------*/
/* If a working storage control module is available, it should be */
/* attached to the FatFs via a glue function rather than modifying it. */
/* This is an example of glue functions to attach various exsisting */
/* storage control modules to the FatFs module with a defined API. */
/*-----------------------------------------------------------------------*/#include "ff.h" /* Basic definitions of FatFs */
#include "diskio.h" /* Declarations FatFs MAI *//* Example: Declarations of the platform and disk functions in the project */
#include "sdcard.h"/* Example: Mapping of physical drive number for each drive */
#define SD_CARD 0 /* Map FTL to physical drive 0 */
#define SD_CARD_BLOCK_SIZE 1
extern sd_card_info_struct sd_cardinfo;/*-----------------------------------------------------------------------*/
/* Get Drive Status */
/*-----------------------------------------------------------------------*/DSTATUS disk_status (BYTE pdrv /* Physical drive nmuber to identify the drive */
)
{switch (pdrv) {case SD_CARD :return 0;}return STA_NOINIT;}/*-----------------------------------------------------------------------*/
/* 功能:初始化存储介质
/* 参数:pdrv,物理硬盘号
/* 说明:因为本例程只有一个存储介质,且将其定义为0,可以认为sdcard定义为盘符0的存储介质 */
/*-----------------------------------------------------------------------*/DSTATUS disk_initialize (BYTE pdrv /* Physical drive nmuber to identify the drive */
)
{DSTATUS stat;switch (pdrv) {case SD_CARD :stat &= ~STA_NOINIT;return 0;}return STA_NOINIT;
}/*-----------------------------------------------------------------------*/
/* 功能:读数据
/* 参数:pdrv,物理硬盘号
/* 参数:buff,指向要读取数据的指针
/* 参数:sector,待读取的起始扇区号
/* 参数:count,待读取的扇区数
/* 说明:当只需要读一个扇区时,调用单块读函数
/* 当需要读多个扇区时,调用多块读函数,提高效率/* 注意:官方提供的驱动函数中第二个参数为读取的地址,所以 扇区号 ✖ 每个扇区大小 = 地址 */
/*-----------------------------------------------------------------------*/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 */
)
{if((buff == NULL) || (count == 0)) {return RES_PARERR;}DRESULT res;sd_error_enum SD_stat = SD_OK;switch (pdrv) {case SD_CARD :if(count > 1) {SD_stat = sd_multiblocks_read((uint32_t *)buff, sector * sd_cardinfo.card_blocksize,sd_cardinfo.card_blocksize, count);} else{SD_stat = sd_block_read((uint32_t *)buff, sector * sd_cardinfo.card_blocksize,sd_cardinfo.card_blocksize);}if(SD_stat == SD_OK) {res = RES_OK ;} else {res = RES_ERROR ;} return res;}return RES_PARERR;
}/*-----------------------------------------------------------------------*/
/* 功能:写数据
/* 参数:pdrv,物理硬盘号
/* 参数:buff,指向要写入数据的指针
/* 参数:sector,待写入的起始扇区号
/* 参数:count,待写入的扇区数
/* 说明:当只需要写一个扇区时,调用单块写函数
/* 当需要写多个扇区时,调用多块写函数,提高效率/* 注意:官方提供的驱动函数中第二个参数为写入的地址,所以 扇区号 ✖ 每个扇区大小 = 地址
/*-----------------------------------------------------------------------*/#if FF_FS_READONLY == 0DRESULT 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 */
)
{if((buff == NULL) || (count == 0)) {return RES_PARERR;}DRESULT res;sd_error_enum SD_stat = SD_OK;switch (pdrv) {case SD_CARD :if(count > 1) {SD_stat = sd_multiblocks_write((uint32_t *)buff, sector * sd_cardinfo.card_blocksize,sd_cardinfo.card_blocksize, count);} else {SD_stat = sd_block_write((uint32_t *)buff, sector * sd_cardinfo.card_blocksize,sd_cardinfo.card_blocksize);}if(SD_stat == SD_OK) {res = RES_OK ;}else{res = RES_ERROR ;}return res;}return RES_PARERR;
}#endif/*-----------------------------------------------------------------------*/
/* 功能:获取底层IO信息
/* 参数:pdrv,物理硬盘号
/* 参数:cmd,控制命令,不同命令返回不同的数据
/* 参数:buff,指向数据缓存的指针 */
/*-----------------------------------------------------------------------*/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 SD_CARD :switch(cmd) {/*获取扇区数*/case GET_SECTOR_COUNT:*(DWORD *)buff = sd_cardinfo.card_capacity / (sd_cardinfo.card_blocksize);break;/*获取扇区大小*/case GET_SECTOR_SIZE:*(WORD *)buff = sd_cardinfo.card_blocksize;break;/*获取块大小*/case GET_BLOCK_SIZE:*(DWORD *)buff = SD_CARD_BLOCK_SIZE;break;}res = RES_OK;return res;}return RES_PARERR;
}/*-----------------------------------------------------------------------*/
/* 功能:获取时间戳 */
/*-----------------------------------------------------------------------*/DWORD get_fattime(void)
{return 0;
}
(5)修改ffconf.h文件(可选)
三、测试用例
#include "main.h"
#include "ff.h"
#include "diskio.h"
#include <stdio.h>
#include <string.h>/* 定义测试文件名 */
#define TEST_FILE_NAME "test.txt"
#define TEST_DIR_NAME "test_dir"/* 全局变量 */
FATFS fs; // 文件系统对象
FIL file; // 文件对象
FRESULT fr; // 文件操作结果
UINT bw, br; // 写入/读取的字节数
BYTE buffer[512]; // 数据缓冲区
sd_error_enum sd_error;
uint16_t i = 5;
sd_card_info_struct sd_cardinfo;void check_root_files() {DIR dir;FILINFO fno;FRESULT fr;fr = f_opendir(&dir, ""); // 打开根目录if(fr == FR_OK) {printf("根目录文件列表:\r\n");while(1) {fr = f_readdir(&dir, &fno);if(fr != FR_OK || fno.fname[0] == 0) break;// 打印所有文件/文件夹(包括短文件名)printf("- %s %s\r\n", fno.fname, (fno.fattrib & AM_DIR) ? "[目录]" : "[文件]");}f_closedir(&dir);} else {printf("读取根目录失败: %d\r\n", fr);}
}/*!\brief main function\param[in] none\param[out] none\retval none
*/
int main(void)
{// debug_init();bsp_uart_init();printf("fatfs demo\r\n");/* initialize the card */do {sd_error = sdcard_init();} while((SD_OK != sd_error) && (--i));printf("Starting FATFS tests...\r\n");/* 1. 挂载文件系统 */fr = f_mount(&fs, "0:", 1);if(fr != FR_OK){printf("f_mount failed: %d\r\n", fr);return 0;}printf("File system mounted successfully\r\n");/* 2. 创建并写入文件 */fr = f_open(&file, TEST_FILE_NAME, FA_WRITE | FA_CREATE_ALWAYS);if(fr == FR_OK){printf("File %s created successfully\r\n", TEST_FILE_NAME);/* 填充测试数据 */strcpy((char*)buffer, "Hello, FATFS on GD32F427VGT6!\r\nThis is a test file.\r\n");/* 写入数据到文件 */fr = f_write(&file, buffer, strlen((char*)buffer), &bw);if(fr == FR_OK){printf("Wrote %d bytes to file\r\n", bw);}else{printf("f_write failed: %d\r\n", fr);}/* 关闭文件 */f_close(&file);}else{printf("f_open for write failed: %d\r\n", fr);}/* 3. 读取文件内容 */fr = f_open(&file, TEST_FILE_NAME, FA_READ);if(fr == FR_OK){printf("Reading from %s:\r\n", TEST_FILE_NAME);/* 读取文件内容 */fr = f_read(&file, buffer, sizeof(buffer)-1, &br);if(fr == FR_OK){buffer[br] = '\0'; // 添加字符串结束符printf("Read %d bytes:\r\n%s\r\n", br, buffer);}else{printf("f_read failed: %d\r\n", fr);}/* 关闭文件 */f_close(&file);}else{printf("f_open for read failed: %d\r\n", fr);}/* 4. 创建目录 */fr = f_mkdir(TEST_DIR_NAME);if(fr == FR_OK){printf("Directory %s created successfully\r\n", TEST_DIR_NAME);}else if(fr == FR_EXIST){printf("Directory %s already exists\r\n", TEST_DIR_NAME);}else{printf("f_mkdir failed: %d\r\n", fr);}/* 5. 在子目录中创建文件 */char subfile[32];sprintf(subfile, "%s/subfile.txt", TEST_DIR_NAME);fr = f_open(&file, subfile, FA_WRITE | FA_CREATE_ALWAYS);if(fr == FR_OK){printf("File %s created successfully\r\n", subfile);strcpy((char*)buffer, "This file is in a subdirectory\r\n");fr = f_write(&file, buffer, strlen((char*)buffer), &bw);if(fr == FR_OK){printf("Wrote %d bytes to %s\r\n", bw, subfile);}else{printf("f_write to %s failed: %d\r\n", subfile, fr);}f_close(&file);}else{printf("f_open for %s failed: %d\r\n", subfile, fr);}/* 6. 删除文件 */
// fr = f_unlink(TEST_FILE_NAME);
// if(fr == FR_OK)
// {
// printf("File %s deleted successfully\r\n", TEST_FILE_NAME);
// }
// else
// {
// printf("f_unlink failed: %d\r\n", fr);
// }//check_root_files();/* 7. 卸载文件系统 */fr = f_mount (NULL, "0:", 0);if(fr == FR_OK){printf("File system unmounted successfully\r\n");}else{printf("f_mount unmount failed: %d\r\n", fr);}printf("FATFS tests completed\r\n");while(1) {}
}