STM32H743-ARM例程14-FATFS
目录
- 实验平台
- FATFS
- FATFS介绍
- FATFS模块的层次结构
- 文件系统
- FAT卷
- 扇区
- 簇
- 数据存储形式
- FATFS的应用接口函数
- STM32CubeMX生成工程
- 实验代码
- 实验现象
实验平台
硬件:银杏科技GT7000双核心开发板-ARM-STM32H743XIH6,银杏科技iToolXE仿真器
软件:最新版本STM32CubeH7固件库,STM32CubeMX v6.10.0,开发板环境MDK v5.35,串口工具putty
FATFS
上一个章节我们介绍了SD卡,并且对SD卡进行了简单的识别和读写测试,对于这种直接进行SD卡的操作,在实际应用是不理想的,使用起来十分不方便,所以本章我们引入FATFS这个软件工具,通过FATFS我们可以像电脑一样用文件的形式管理SD卡,让SD卡使用起来更加方便。
本章实验我们通过FATFS文件系统,在STM32上实现文件管理功能,对SD卡进行读写文件操作。
FATFS介绍
FATFS(File Allocation Table File System)是专为小型嵌入式系统设计的通用FAT文件系统模块,完全采用标准C语言编写,具有硬件平台无关性。可移植到8051、PIC、AVR、ARM等多种单片机,仅需修改磁盘I/O层即可适配不同硬件。它支持FAT12、FAT16、FAT32,及exFAT格式,适用于SD卡、U盘等存储介质的管理;有独立的缓冲区,可以对多个文件进行读/写,并特别对8位单片机和16位单片机做了优化。
FATFS特点:
- 可跨平台移植
- 同时挂载多个存储设备(如 SD 卡 + NAND Flash)
- 支持线程安全操作(如 FreeRTOS、RT-Thread)
- 代码量精简,ROM/RAM 占用低
- 长文件名支持:可选 Unicode(UTF-16)或 ANSI 编码
- 支持多扇区读写,提升大文件操作效率
- 支持不同大小的扇区,扇区大小可以是512字节、1024字节、2048字节或4096字节
- 最小卷大小:128个扇区
- 最大卷大小:FAT中是4G个扇区,exFAT中几乎是无限制的
- 最大单个文件大小:FAT卷中是4GB,exFAT中几乎是无限制的
- 簇大小限制:FAT卷中一个簇最大128个扇区,exFAT卷中是16MB
FATFS模块的层次结构
(1) 底层接口:
包括存储媒介读/写接口(disk I/O)和供给文件创建修改时间的实时时钟,需要我们根据平台和存储介质编写移植代码。
(2) 中间层FATFS模块:
实现了FAT 文件读/写协议。FATFS模块提供的是ff.c和ff.h,除有必要,使用者一般不用修改使用时将头文件直接包含进去即可。
(3) 最顶层是应用层:
使用者无需理会FATFS的内部结构和复杂的FAT协议,只需要调用FATFS模块提供给用户的一系列应用接口函数,如f_open;f_read,f_write 和f_close等,就可以像在PC上读/写文件那样简单。
文件系统
FAT(File Allocation Table)文件系统是一种广泛应用于存储设备(如U盘、SD卡、硬盘等)的文件系统架构,最早由微软开发并用于DOS和Windows系统。FAT 文件系统的核心是 文件分配表(FAT),用于记录文件在存储设备上的分布情况。
发展至今,FAT文件系统有:FAT12、FAT16和FAT32。FAT16的单个分区容量不能超过2GB;FAT32的单个分区容量不能超过2TB,单个文件大小不能超过4GB。FAT文件系统是向后兼容的,即FAT32兼容FAT16和FAT12。
exFAT(Extended File Allocation Table,扩展文件分配表)是微软在2006年推出的一种现代文件系统,专为大容量存储设备(如U盘、SD卡、SSD等)设计,旨在解决FAT32的局限性(如4GB单文件限制)。广泛用于嵌入式系统、消费电子产品和固态存储设备中。
FAT卷
FAT文件系统可以称为一个逻辑卷或者逻辑驱动器,例如计算机上的C盘、D盘。一个卷又包括三个或四个区域,每个区域占用1个或多个扇区。排列如下:
- 保留区域,用于存储卷的配置数据。
- FAT区域,用于存储数据区的分配表。
- 根目录区域,在FAT32卷上没有这个区域。
- 数据区域,存储文件和目录的内容。
扇区
扇区存储设备的最小物理存储单元,通常由512字节(传统)或4KB(现代高级格式化)组成。FATFS支持512字节、1024字节、2048字节和4096字节等几种大小的扇区。存储数据的基本单位,文件系统(如FAT、NTFS)通过管理扇区来读写文件。类似于书本的“页”,操作系统按页(扇区)读取数据。文件系统通过簇管理扇区。
簇
簇(Cluster) 是文件系统(如FAT、NTFS、ext4等)管理存储设备的最小逻辑存储单元,由一个或多个扇区(Sector)组成。文件系统通过簇来分配磁盘空间,避免直接操作扇区(提高效率)。扇区 = 书本的“页”(物理最小单位)。簇 = 章节(文件系统管理的最小单位)。
簇大小影响空间利用率和性能:
- 小簇 → 适合小文件,减少浪费。
- 大簇 → 适合大文件,提升速度。
FAT的具体类型如下。
- FAT12卷,簇的个数≤4085。
- FAT16卷,4086≤簇的个数≤65525。
- FAT32卷,簇的个数≥65526。
数据存储形式
FAT文件系统使用的是小端字节序(little endian),所以,如果处理器使用的是大端字节序(big endian),那么读取FAT文件系统的文件,就需要进行字节序的转换。另外,字(word)数据也不一定是边界对齐的,所以在读写FAT文件系统的文件时,最好使用字节数组的形式,逐个字节进行读写。
FATFS的应用接口函数
卷管理和系统配置相关函数,返回值都为FRESULT
函数名 | 函数功能 | 函数原型 |
---|---|---|
f_mount | 注册或注销一个卷,使用一个卷之前必须调用此函数并挂载文件系统 | f_mount(FATFS* fs,const TCHAR*path,BYTE opt) |
f_mkfs | 在一个逻辑驱动器上创建FAT卷,也就是进行格式化 | f_mkfs(const TCHAR* path,BYTE opt,DWORD au,void* work,UINT len) |
f_fdisk | 在一个物理驱动器上创建分区 | f_fdisk(BYTE pdrv,const DWORD* szt,void* work) |
f_getfree | 返回一个卷上剩余簇的个数 | f_getfree(const TCHARpath,DWORDnclst,FATFS**fatfs) |
f_getlabel | 获取一个卷的标签 | f_getlabel(const TCHARpath,TCHARlabel,DWORD*vsn) |
f_setlabel | 设置一个卷的标签 | f_setlabel(const TCHAR*label) |
FatFS对文件系统是以卷为单位进行管理的,一个卷就相当于计算机上的一个逻辑分区,如C盘和D盘。在嵌入式设备中,一般一个存储介质只有一个卷,不需要用函数f_fdisk()进行分区,例如,一个SD卡是一个卷,一个SPI-Flash存储芯片是一个卷。在FatFS中,卷的编号默认是用字符串“0:”“1:”“2:”等表示的,其中的数字表示卷的编号。
要管理一个卷上的文件系统,首先需要用函数f_mount()注册这个卷,如果这个函数的返回值为FR_NO_FILESYSTEM,就表示卷上还没有文件系统,需要用函数f_mkfs()对卷进行格式化。注册一个卷之后,用户才能进行创建文件、打开文件、读写文件数据等操作。
文件和目录管理相关函数,主要创建目录、删除文件、修改目录、检测文件或者目录是否存在。
函数名 | 函数功能 | 函数原型 |
---|---|---|
f_stat | 检查一个文件或目录是否存在 | f_stat(const TCHAR* path,FILINFO* fno) |
f_unlink | 删除一个文件或目录 | f_unlink(const TCHAR*path) |
f_rename | 重命名或移动一个文件或目录 | f_rename(const TCHAR* path_old,const TCHAR* path_new) |
f_chmod | 改变一个文件或目录的属性 | f_chmod(const TCHAR*path,BYTE attr,BYTE mask); |
f_utime | 改变一个文件或目录的时间戳 | f_utime(const TCHAR* path,const FILINFO* fno) |
f_mkdir | 创建一个新的目录 | f_mkdir(const TCHAR*path); |
f_chdir | 改变当前工作目录 | f_chdir(const TCHAR*path); |
f_chdrive | 改变当前驱动器 | f_chdrive(const TCHAR*path) |
f_getcwd | 获取当前驱动器的当前工作目录 | f_getcwd(TCHAR*buff,UINT len) |
文件访问相关函数,打开文件、读写数据、关闭文件等操作,这些函数与计算机上C语言访问文件的函数是类似的。
函数名 | 函数功能 | 函数原型 |
---|---|---|
f_open | 打开一个文件 | f_open(FIL* fp,const TCHAR* path,BYTE mode) |
f_close | 关闭一个打开的文件 | f_close(FIL*fp) |
f_read | 从文件读取数据 | f_read(FIL* fp,void* buff,UINT btr,UINT *br) |
f_write | 将数据写入文件 | f_write(FIL* fp,const void* buff,UINT btw,UINT*bw) |
f_lseek | 移动读写操作的指针 | f_lseek(FIL*fp,FSIZE_t ofs) |
f_truncate | 截断文件 | f_truncate(FIL*fp) |
f_sync | 将缓存的数据写入文件 | f_sync(FIL*fp) |
f_forward | 读取数据直接传给数据流设备 | f_forward(FIL* fp,UINT(* func)(const BYTE* ,UINT),UINT btf,UINT *bf) |
f_expand | 为文件分配连续的存储空间 | f_expand(FIL*fp,FSIZE_t szf,BYTE opt) |
f_gets | 从文件读取一个字符串 | TCHAR *f_gets(TCHAR * buff,int len,FIL * fp) |
f_putc | 向文件写入一个字符 | int f_putc(TCHAR c,FIL*fp); |
f_puts | 向文件写入一个字符串 | intf puts(const TCHAR *str,FIL *cp) |
f_printf | 用格式写入字符串 | int f_printf(FIL*fp,const TCHAR *str,…); |
f_tell | 获取当前的读写指针 | #define f_tell(fp) ((fp)->fptr) |
f_eof | 检测是否到文件尾端 | #define f_eof(fp) (int)(fp)->fptr == (fp)->obj.objsize)) |
f_size | 获取文件大小,单位:字节 | #define f_size(fp) (fp)->obj.objsize) |
f_error | 检测是否有错误,返回0表示无错误 | #define f_error(fp) (fp)->err) |
STM32CubeMX生成工程
我们参考前面章节STM32H743-结合CubeMX新建HAL库MDK工程,打开CubeMX软件,重复步骤不再展示,我们来看配置FATFS部分如下图所示:
SDMMC配置:
配置FATFS:
实验代码
1. 主函数
int main(void)
{HAL_Init();SystemClock_Config();MX_GPIO_Init();MX_SDMMC1_SD_Init();MX_USART6_UART_Init();MX_FATFS_Init();uart6.printf("\033[1;32;40m"); uart6.printf("\r\n ****** FatFs Example ****** \r\n \r\n");// 在外部 SD 卡挂载文件系统,文件系统挂载时会对 SD 卡初始化// note:必须先要保证SD卡正常拥有FAT文件系统,如果没有会失败。f_res = f_mount(&fs, "0:", 1);/*----------------------- 格式化测试 ---------------------------*/uart6.printf("\r\n ****** Register the file system object to the FatFs module ****** \r\n");/* 如果没有文件系统就格式化创建创建文件系统 */if(f_res == FR_NO_FILESYSTEM){uart6.printf("The SD card does not yet have a file system and is about to be formatted... \r\n");/* 格式化 */f_res = f_mkfs("0:", 0, 4096, work_buffer, sizeof(work_buffer));if(f_res == FR_OK){uart6.printf("The SD card successfully formatted the file system\r\n");/* 格式化后,先取消挂载 */f_res = f_mount(NULL, "0:", 1);/* 重新挂载 */f_res = f_mount(&fs, "0:", 1);}else{uart6.printf("The format failed\r\n");while(1);}}else if(f_res != FR_OK){uart6.printf(" mount error : %d \r\n", f_res);while(1);}else{uart6.printf(" mount sucess!!! \r\n");}/*----------------------- 文件系统测试:写测试 -----------------------------*//* 打开文件,如果文件不存在则创建它 */uart6.printf("\r\n ****** Create and Open new text file objects with write access ****** \r\n");f_res = f_open(&file, "0:FatFs STM32cube.txt", FA_CREATE_ALWAYS | FA_WRITE);if(f_res == FR_OK){uart6.printf(" open file sucess!!! \r\n");/* 将指定存储区内容写入到文件内 */uart6.printf("\r\n****** Write data to the text files ******\r\n");f_res = f_write(&file, WriteBuffer, sizeof(WriteBuffer), &fnum);if(f_res == FR_OK){uart6.printf(" write file sucess!!! (%d)\n", fnum);uart6.printf(" write Data : %s\r\n", WriteBuffer);}else{uart6.printf(" write file error : %d\r\n", f_res);}/* 不再读写,关闭文件 */f_close(&file);}else{uart6.printf(" open file error : %d\r\n", f_res);}/*------------------- 文件系统测试:读测试 ------------------------------------*/uart6.printf("\r\n****** Read data from the text files ******\r\n");f_res = f_open(&file, "0:FatFs STM32cube.txt", FA_OPEN_EXISTING | FA_READ);if(f_res == FR_OK){uart6.printf(" open file sucess!!! \r\n");f_res = f_read(&file, ReadBuffer, sizeof(ReadBuffer), &fnum);if(f_res == FR_OK){uart6.printf("read sucess!!! (%d)\n", fnum);uart6.printf("read Data : %s\r\n", ReadBuffer);}else{uart6.printf(" read error!!! %d\r\n", f_res);}}else{uart6.printf(" open file error : %d\r\n", f_res);}/* 不再读写,关闭文件 */f_close(&file);/* 不再使用文件系统,取消挂载文件系统 */f_mount(NULL, "0:", 1);/* 操作完成,停机 */while (1){}
}
实验现象
我们在GT7000开发板上插入SD卡后运行程序,通过putty串口工具可以看到如下打印信息,并且在SD卡中创建并写入输入到文件中。