参考资料主要是原子和野火两家的讲解。
适合嵌入式小型单片机,是一个 独立 的软件层文件系统,我们只需要将底层硬件的读取函数移植到FATFS提供的向下的接口(Media Access Interface),完成之后,就可以像电脑一样使用文件的操作函数(FATFS提供的向上的供我们使用的API函数 (Application Interface) )。
FAFTS中的函数参数介绍中的,IN表示该参数是传入数值;OUT表示,该参数是介质用于存放需要传出数据的载体。
磁盘分区,将一整块(或多块)物理磁盘划分为多个逻辑上的磁盘(C盘/D盘……),或者是(0/1……)。
初始化时在物理磁盘的内存上,最开始的空白区域,会建立一些信息结构(目录,查找转换代码*(把” ……/ …… / ……“的逻辑地址转换成物理地址(第几扇区第几个环道上……地址))* )。
文件目录:
主要存储文件的各种信息,包括地址,名字,大小,位置等等。
文件分配表:
文件分配表中存储的是每个文件的各个部分的位置(目录只记录了开始位置)
文件查找时,从目录寻找其初始地址,然后没读完一个块(或者一个部分单位)后,都要回文件分配表寻找它下一部分的地址,读完再回文件分配表查找;直到查询到读完整个文件为止。
在文件较大时,分成多部分存放(因为文件分布不一定连续)时,文件分配表有极大作用。
integer.h: 数值类型的宏定义
ffconf.h: FATFS 模块配置文件( 可根据我们需要改变宏,进行对应的裁剪 )
ff.c : FATFS 模块的核心文件,我们不需要改动;
ff.h :FATFS 和应用模块公用的包含文件( 存放着函数的声明,移植成功后当我们使用后,只需要将其include即可 )
diskio.c : FATFS 和 disk I/O 模块接口层文件(就是 我们移植时重点要修改的东西 ,我们需要将底层的操作函数提供给它的接口)
cc936.c:主要存放了Unicode(国标码)与GBK相互转换的数组,用于支持中文。
要实现的函数有如下图:(但可根据自己所需要的东西选择性实现,但以下五个是一定要的,其中所谓的可选择,是指disk_ioctl函数中部分命令可不实现)
主要是在diskio.c中的五个函数的实现:
disk_status :
主要是用于查看底层硬件的状态函数,可用读取芯片ID的函数来检测底层的硬件是否正常;
该函数的返回值是DSTATES类(宏定义的底层状态 0x00 正常; 0x01 STA_NOINT; 0x02 STA_NODISK; 0x04 STA_PROTECT)。
disk_initialize :
主要是使用对应底层的初始化函数;对应的API函数是 f_mount() 函数。
该函数需要返回值是各种定义好的宏,如果对应的初始化函数没有返回值的话,可以采取读取ID芯片来校验是否初始化成功(经常这么干的,像使用mpu6050时就是)。( 也可以直接调用上面的disk_status函数来检测 )
注意:如果底层介质含有低功耗省电模式的话,我们需要同时在这儿函数中加入唤醒函数。
disk_read :
disk_write
disk_write()的移植与disk_read() 一致,注意移植时的对应的参数即可。
需要注意一点: 在ffconf.h的只读宏 _FS_READONLY 应设置为0, 即关闭只读
disk_ioctl :
这个函数主要用于实现FATFS中的一些其他的功能。(注意移植时函数参数的类型,必要时进行强制转换)
通过不同的命令参数(宏定义)实现不同的函数功能:([参考上图](#### 2)需要我们提供的底层接口:))
需要注意:其中GET_BOLCK_SIZE:该函数使用改命令是告诉上层函数能够擦除的最小的最小块大小(单位是扇区,多少扇区)。
get_fattime 获取实时(文件修改)时间所用的,我们可以不需要提供。
_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日文等。
(对于结构体的成员细节,简单看看就好)
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;
磁盘驱动器初始化(磁盘挂载函数):初始化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
)
光标重定位函数(常在读写函数后使用,因为读写时光标会移动)
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)
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写在磁盘中读出。