Stm32文件系统FATFS
参考资料主要是原子和野火两家的讲解。
1.FATFS简介:
适合嵌入式小型单片机,是一个 独立 的软件层文件系统,我们只需要将底层硬件的读取函数移植到FATFS提供的向下的接口(Media Access Interface),完成之后,就可以像电脑一样使用文件的操作函数(FATFS提供的向上的供我们使用的API函数 (Application Interface) )。
FAFTS中的函数参数介绍中的,IN表示该参数是传入数值;OUT表示,该参数是介质用于存放需要传出数据的载体。
1)初始化磁盘:
-
磁盘分区,将一整块(或多块)物理磁盘划分为多个逻辑上的磁盘(C盘/D盘……),或者是(0/1……)。
-
初始化时在物理磁盘的内存上,最开始的空白区域,会建立一些信息结构(目录,查找转换代码*(把” ……/ …… / ……“的逻辑地址转换成物理地址(第几扇区第几个环道上……地址))* )。
2)系统结构:
-
文件目录:
主要存储文件的各种信息,包括地址,名字,大小,位置等等。
-
文件分配表:
-
文件分配表中存储的是每个文件的各个部分的位置(目录只记录了开始位置)
-
文件查找时,从目录寻找其初始地址,然后没读完一个块(或者一个部分单位)后,都要回文件分配表寻找它下一部分的地址,读完再回文件分配表查找;直到查询到读完整个文件为止。
-
在文件较大时,分成多部分存放(因为文件分布不一定连续)时,文件分配表有极大作用。
-
2.FATFS文件系统
1)组要组成:(需要添加的文件)
-
integer.h: 数值类型的宏定义
-
ffconf.h: FATFS 模块配置文件( 可根据我们需要改变宏,进行对应的裁剪 )
-
ff.c : FATFS 模块的核心文件,我们不需要改动;
-
ff.h :FATFS 和应用模块公用的包含文件( 存放着函数的声明,移植成功后当我们使用后,只需要将其include即可 )
-
diskio.c : FATFS 和 disk I/O 模块接口层文件(就是 我们移植时重点要修改的东西 ,我们需要将底层的操作函数提供给它的接口)
-
cc936.c:主要存放了Unicode(国标码)与GBK相互转换的数组,用于支持中文。
2)需要我们提供的底层接口:
要实现的函数有如下图:(但可根据自己所需要的东西选择性实现,但以下五个是一定要的,其中所谓的可选择,是指disk_ioctl函数中部分命令可不实现)
![](https://gitee.com/silent-sheep/picture-store/raw/master/img/FAFTS%E7%A7%BB%E6%A4%8D%E5%87%BD%E6%95%B0%E6%80%BB%E5%9B%BE.png)
主要是在diskio.c中的五个函数的实现:
-
-
主要是用于查看底层硬件的状态函数,可用读取芯片ID的函数来检测底层的硬件是否正常;
-
该函数的返回值是DSTATES类(宏定义的底层状态 0x00 正常; 0x01 STA_NOINT; 0x02 STA_NODISK; 0x04 STA_PROTECT)。
-
-
-
主要是使用对应底层的初始化函数;对应的API函数是 f_mount() 函数。
-
该函数需要返回值是各种定义好的宏,如果对应的初始化函数没有返回值的话,可以采取读取ID芯片来校验是否初始化成功(经常这么干的,像使用mpu6050时就是)。( 也可以直接调用上面的disk_status函数来检测 )
-
注意:如果底层介质含有低功耗省电模式的话,我们需要同时在这儿函数中加入唤醒函数。
-
-
- 主要是对底层介质的读取多个扇区函数,注意 要提供好对应它的参数的接口函数 ,返回值是RES类(也是宏定义好的类型 0x00 RES_OK ; 0x01 RES_ERROE; …… ),对应的API函数是 f_read() 函数。
- 移植时,主要是提供底层介质的读取函数,注意其所需参数,因为上层对介质的读写操作最小单位是扇区,所以对底层介质的读取时注意读取单位的转换。
-
-
disk_write()的移植与disk_read() 一致,注意移植时的对应的参数即可。
-
需要注意一点: 在ffconf.h的只读宏 _FS_READONLY 应设置为0, 即关闭只读
-
-
-
这个函数主要用于实现FATFS中的一些其他的功能。(注意移植时函数参数的类型,必要时进行强制转换)
-
通过不同的命令参数(宏定义)实现不同的函数功能:([参考上图](#### 2)需要我们提供的底层接口:))
需要注意:其中GET_BOLCK_SIZE:该函数使用改命令是告诉上层函数能够擦除的最小的最小块大小(单位是扇区,多少扇区)。
-
-
get_fattime 获取实时(文件修改)时间所用的,我们可以不需要提供。
3.ffconf.h的配置
-
_VOLUMES : 用于设置 FATFS 支持的逻辑设备数目.
-
_MAX_SS:扇区缓冲的最大值,一般设置为 512(SD卡中) ,FLASH (常用4096);
注意:_MAX_SS的大小,要与diskio_ioctl(GET_SECTOR_SIZE)中单次读取扇区大小一致,若不一致,极其容易导致内存溢出,HARDFAULT.
-
_USE_MKFS:这个用来定时是否使能格式化,设置为 1,允许格式化。
-
_FS_MINIMISE :对文件系统裁剪的宏.
-
_FS_TINY :是否使用微系统。
-
_FS_READONLY :是否只读.
-
_USE_STRFUNC :是否使能格式化操作。
-
_USE_FASTSEEK :是否使能快速定位功能。
-
_USE_LABEL : 置是否支持磁盘盘符(磁盘名字)读取与设置。
-
_CODE_PAGE :设置哪种语言类型,936GBK简体中文,932日文等。
4.FATFS系统API的调用
1)FATFS系统的对象(定义的结构体)
(对于结构体的成员细节,简单看看就好)
-
FATFS文件系统对象:
typedef struct { BYTE fs_type; /* FAT sub-type (0:Not mounted) 文件类型*/ BYTE drv; /* Physical drive number 即是宏定义的盘符0-9*/ BYTE csize; /* Sectors per cluster (1,2,4...128) 扇区/簇*/ BYTE n_fats; /* Number of FAT copies (1 or 2) */ BYTE wflag; /* win[] flag (b0:dirty) */ BYTE fsi_flag; /* FSINFO flags (b7:disabled, b0:dirty) */ WORD id; /* File system mount ID */ WORD n_rootdir; /* Number of root directory entries (FAT12/16) */ #if _MAX_SS != _MIN_SS WORD ssize; /* Bytes per sector (512, 1024, 2048 or 4096) */ #endif #if _FS_REENTRANT _SYNC_t sobj; /* Identifier of sync object */ #endif #if !_FS_READONLY DWORD last_clust; /* Last allocated cluster */ DWORD free_clust; /* Number of free clusters */ #endif #if _FS_RPATH DWORD cdir; /* Current directory start cluster (0:root) */ #endif DWORD n_fatent; /* Number of FAT entries, = number of clusters + 2 */ DWORD fsize; /* Sectors per FAT */ DWORD volbase; /* Volume start sector */ DWORD fatbase; /* FAT start sector */ DWORD dirbase; /* Root directory start sector (FAT32:Cluster#) */ DWORD database; /* Data start sector */ DWORD winsect; /* Current sector appearing in the win[] */ BYTE win[_MAX_SS]; /* Disk access window for Directory, FAT (and file data at tiny cfg) */ } FATFS;
关于该结构体比较多,也比价复杂;需要注意的是其中最后一项定义了一个缓存数组 BYTE win[_MAX_SS] ,如果是512 ( MAX_SS )字节一个扇区的话,这个数组非常大。
若定义为本地变量的话,极其容易导致栈溢出;所以通常定义为全局变量(该结构体是物理磁盘的文件结构的结构体,通常一个物理磁盘只需一个即可),原子的exfuns.c 中使用的是动太内存分配的方式。
-
FRESULT 多个函数的返回类型(ff.h定义的一个枚举变量,实际是u8的枚举)我们常使用来判断API函数是否成功运行并实现预期效果,该枚举如下如下:
typedef enum { FR_OK = 0, /* (0) Succeeded */ FR_DISK_ERR, /* (1) A hard error occurred in the low level disk I/O layer */ FR_INT_ERR, /* (2) Assertion failed */ FR_NOT_READY, /* (3) The physical drive cannot work */ FR_NO_FILE, /* (4) Could not find the file */ FR_NO_PATH, /* (5) Could not find the path */ FR_INVALID_NAME, /* (6) The path name format is invalid */ FR_DENIED, /* (7) Access denied due to prohibited access or directory full */ FR_EXIST, /* (8) Access denied due to prohibited access */ FR_INVALID_OBJECT, /* (9) The file/directory object is invalid */ FR_WRITE_PROTECTED, /* (10) The physical drive is write protected */ FR_INVALID_DRIVE, /* (11) The logical drive number is invalid */ FR_NOT_ENABLED, /* (12) The volume has no work area */ FR_NO_FILESYSTEM, /* (13) There is no valid FAT volume */ FR_MKFS_ABORTED, /* (14) The f_mkfs() aborted due to any parameter error */ FR_TIMEOUT, /* (15) Could not get a grant to access the volume within defined period */ FR_LOCKED, /* (16) The operation is rejected according to the file sharing policy */ FR_NOT_ENOUGH_CORE, /* (17) LFN working buffer could not be allocated */ FR_TOO_MANY_OPEN_FILES, /* (18) Number of open files > _FS_SHARE */ FR_INVALID_PARAMETER /* (19) Given parameter is invalid */ } FRESULT;
-
FIL 文件对象:(同样,建议为全局或者是动态内存分配)
typedef struct { FATFS* fs; /* Pointer to the related file system object (**do not change order**) */ WORD id; /* Owner file system mount ID (**do not change order**) */ BYTE flag; /* Status flags */ BYTE err; /* Abort flag (error code) */ DWORD fptr; /* File read/write pointer (Zeroed on file open) */ DWORD fsize; /* File size */ DWORD sclust; /* File start cluster (0:no cluster chain, always 0 when fsize is 0) */ DWORD clust; /* Current cluster of fpter (not valid when fprt is 0) */ DWORD dsect; /* Sector number appearing in buf[] (0:invalid) */ #if !_FS_READONLY DWORD dir_sect; /* Sector number containing the directory entry */ BYTE* dir_ptr; /* Pointer to the directory entry in the win[] */ #endif #if _USE_FASTSEEK DWORD* cltbl; /* Pointer to the cluster link map table (Nulled on file open) */ #endif #if _FS_LOCK UINT lockid; /* File lock ID origin from 1 (index of file semaphore table Files[]) */ #endif #if !_FS_TINY BYTE buf[_MAX_SS]; /* File private data read/write window */ #endif } FIL;
-
FILINFO 文件信息对象(与文件对象不同,是另外定义的一个结构体)
同样,建议全局或者是动态内存分配。
typedef struct { DWORD fsize; /* File size */ WORD fdate; /* Last modified date */ WORD ftime; /* Last modified time */ BYTE fattrib; /* Attribute属性(被打开文件的权限,类型等) */ TCHAR fname[13]; /* Short file name (8.3 format) */ #if _USE_LFN TCHAR* lfname; /* Pointer to the LFN buffer */ UINT lfsize; /* Size of LFN buffer in TCHAR */ #endif } FILINFO;
2)API函数使用:
-
磁盘驱动器初始化(磁盘挂载函数):初始化0盘,1盘等等,取决于path(自己决定,不过常与ff内定义的驱动器的宏一致,0-9).
FRESULT f_mount(FATFS* fs /*(文件结构指针,全局定义一个即可)*/, const TCHAR * /*path(字符串类型,表示其路径,如”0:“)*/, BYTE opt /*(选项,0/1,通常是1表示立即挂载,0表示稍后挂载。)*/ )
-
格式化磁盘,在物理磁盘上建立FAFTS文件系统的架构(目录、文件分配表等)
FRESULT f_mkfs (const TCHAR* path, /* 逻辑驱动器的路径 如:”0:“ */ BYTE sfd,/* 通常为0,初始化磁盘类型 0:FDISK, 1:SFD */ UINT au/* 格式化的每个扇区大小,0的话,系统会自动分配 */ )
注意:格式化后需要重新挂载磁盘,所以需要执行两步:
1️⃣ 取消挂载 f_monut(NULL,“0:”,1),即将空设备挂到我们的逻辑磁盘上(相当于清空逻辑磁盘)2️⃣ 重新挂载设备 f_mount( &fs ,"0: " ,1).
-
打开文件函数:与c语言相同,注意第三个参数,表示以什么方式(权限)打开。
当我们需要多种权限时,可以将第三个参数以多个可选参数相| (与)的形式写入
FRESULT f_open ( FIL* fp,/* Pointer to the blank file object 文件对象指针,全局定义一个就好 */ const TCHAR* path, /* Pointer to the file name,路径 */ BYTE mode/* Access mode and file open mode flags 权限(打开方式)*/ )
-
写文件函数
FRESULT f_write ( FIL* fp, /* Pointer to the file object */ const void *buff, /* Pointer to the data to be written */ UINT btw, /* Number of bytes to write */ UINT* bw /* Pointer to number of bytes written 写数据的索引,用于查看是否写到文件结束*/ )
-
读文件函数
FRESULT f_read ( FIL* fp, /* Pointer to the file object */ void* buff, /* Pointer to data buffer【OUT】用于存放输出的字符串 */ UINT btr, /* Number of bytes to read 读取字节数*/ UINT* br /* Pointer to number of bytes read 【OUT】读了几个字节,可用于查看是否读到文件结束(br<btr)*/ )
-
光标重定位函数(常在读写函数后使用,因为读写时光标会移动)
FRESULT f_lseek ( FIL* fp, /* Pointer to the file object操作的文件对象 */ DWORD ofs /* File pointer from top of file重定位到的位置(离文件开头多远) */ )
-
文件关闭函数
(注意:f_open使用后一定要f_close,因为在有些系统中,只有在f_close时才将文件缓冲区的数据写到物理磁盘中)
FRESULT f_close (FIL *fp/* Pointer to the file object to be closed */)
-
获取文件大小的函数
FSIZE_t f_size (FIL* fp /* [IN] File object文件对象,返回其大小(字节数) */);
-
指定格式写入文件函数(与printf函数相同,转义字符也相同,注意第一个参数为写入文件对象)
int f_printf ( FIL* fp, /* Pointer to the file object */ const TCHAR* fmt, /* Pointer to the format string */ ... /* Optional arguments... */ )
-
删测文件(文件夹)函数
FRESULT f_unlink ( const TCHAR* path/* Pointer to the file or directory path */)
-
有关文件夹的打开,创建,关闭函数(与上面操作文件没有什么不同,注意参数类型,为dir类(宏定义的结构体))
FRESULT f_opendir ( DIR* dp, /* [OUT] Pointer to the directory object structure */ const TCHAR* path /* [IN] Directory name */ );
FRESULT f_mkdir ( const TCHAR* path /* [IN] Directory name */ );
FRESULT f_closedir ( DIR* dp /* [IN] Pointer to the directory object */ );
-
值得注意的是读取文件夹的函数 (注意:参数类型FILFINO)
(注意它每一次调用会返回该文件夹中下一个文件的FILFINO)
FRESULT f_readdir ( DIR* dp, /* [IN] Directory object */ FILINFO* fno /* [OUT] File information structure */ );
可以使用该函数实现对一个文件夹扫描所有文件的函数:(可以参考官网的递归函数进行扫描的方法)[参考](D:\typora\local notebook\stm32\stm32正点原子图片解码使用简介.md)
5.支持长(中文)文件名
-
1️⃣ 将cc936.c文件加入FATFS文件分组中(加入工程)
-
2️⃣ 将文件ffconf.h 中的三个宏:
#define _USE_LFN 3//(改为非0,使用长文件名,1/2/3是储存的地方不同) //1.BSS全局静态变量区,2.Stack, //3.heap,但需要我们提供动态内存分配的接口: ff_memalloc() ff_memfree(), #define _MAX_LFN 255//文件名允许的最长文件名(_MAX_LFN + 1) * 2个 bytes #define _CODE_PAGE 936 //采用中文GBK编码(936代表中文编码)
但cc936.c中有两个大的转换数组,将会使代码烧录变慢,可以使用像原子一样,将cc936.c写在磁盘中读出。