【STM32进阶笔记】深入解析FATFS文件系统(下篇)
本专栏争取每周三更新直到更新完成,期待大家的订阅关注,欢迎互相学习交流。
本文需要一些SD卡和内存管理等前置知识,后续文章会介绍,这里先介绍一下FATFS文件系统。关于FATFS的文章分为上下两篇,上篇主要介绍什么是FAT文件系统以及FATFS的移植,下篇主要介绍FATFS的一些API函数并给出一些简单的应用示例。
目录
一、结构体介绍
在开始正式介绍FATFS的API函数之前,我们先来看几个关键的结构体,主要是简单了解一下其中的内容。
1.1 文件对象结构体
/* File object structure (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;
1.2 目录对象结构体
/* Directory object structure (DIR) */
typedef struct {
FATFS* fs; /* Pointer to the owner file system object (**do not change order**) */
WORD id; /* Owner file system mount ID (**do not change order**) */
WORD index; /* Current read/write index number */
DWORD sclust; /* Table start cluster (0:Root dir) */
DWORD clust; /* Current cluster */
DWORD sect; /* Current sector */
BYTE* dir; /* Pointer to the current SFN entry in the win[] */
BYTE* fn; /* Pointer to the SFN (in/out) {file[8],ext[3],status[1]} */
#if _FS_LOCK
UINT lockid; /* File lock ID (index of file semaphore table Files[]) */
#endif
#if _USE_LFN
WCHAR* lfn; /* Pointer to the LFN working buffer */
WORD lfn_idx; /* Last matched LFN index number (0xFFFF:No LFN) */
#endif
#if _USE_FIND
const TCHAR* pat; /* Pointer to the name matching pattern */
#endif
} DIR;
1.3 文件信息结构体
/* File information structure (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.1 f_open函数
*path:文件名指针;
mode:模式标志,共有以下几种模式:
模式 | 含义 |
---|---|
FA_READ | 指定对对象的读访问权限。可以从文件中读取数据。 |
FA_WRITE | 指定对对象的写访问。数据可以写入文件。结合 FA _ READ 进行读写访问。 |
FA_OPEN_EXISTING | 打开文件。如果文件不存在,函数将失败 (默认值)。 |
FA_OPEN_ALWAYS | 如果文件存在,则打开该文件;如果没有,将创建一个新文件。 |
FA_CREATE_NEW | 创建一个新文件。如果文件存在,函数将失败。 |
FA_CREATE_ALWAYS | 创建一个新文件。如果该文件存在,它将被截断并覆盖。 |
当 _ FS _ READONLY = = 1时(只读模式),模式标志 FA _ WRITE、 FA _ CREATE _ ALWAYS、 FA _ CREATE _ NEW 和 FA _ OPEN _ ALWAYS 不可用。
typedef enum {
FR_OK = 0, /* (0) 成功*/
FR_DISK_ERR, /* (1) 低级磁盘 I/O 层发生了一个硬错误 */
FR_INT_ERR, /* (2) 断言失败 */
FR_NOT_READY, /* (3) 物理驱动无法工作 */
FR_NO_FILE, /* (4) 无法找到文件 */
FR_NO_PATH, /* (5) 无法找到路径 */
FR_INVALID_NAME, /* (6) 路径名格式无效 */
FR_DENIED, /* (7) 由于禁止访问或目录已满而拒绝访问 */
FR_EXIST, /* (8) 由于禁止访问而拒绝访问 */
FR_INVALID_OBJECT, /* (9) 文件/目录对象无效 */
FR_WRITE_PROTECTED, /* (10) 物理驱动器受写保护 */
FR_INVALID_DRIVE, /* (11) 逻辑驱动器号无效 */
FR_NOT_ENABLED, /* (12) 卷没有工作区 */
FR_NO_FILESYSTEM, /* (13) 没有有效的FAT卷 */
FR_MKFS_ABORTED, /* (14) 由于任何参数错误,f _ mkfs ()中止 */
FR_TIMEOUT, /* (15) 无法获得在规定期限内访问卷的授权 */
FR_LOCKED, /* (16) 根据文件共享策略拒绝该操作 */
FR_NOT_ENOUGH_CORE, /* (17) 无法分配 LFN工作缓冲区 */
FR_TOO_MANY_OPEN_FILES, /* (18) 打开的文件数 > _ FS _ SHARE */
FR_INVALID_PARAMETER /* (19) 给定的参数无效 */
} FRESULT;
2.2 f_close函数
2.3 f_read函数
*buff:指向存储读取数据的数组指针;
btr:在 UINT 类型范围内要读取的字节数;
*br:指向 UINT 变量的指针,以返回读取的字节数。无论结果如何,该值在函数调用后始终有效;
2.4 f_write函数
*buff:指向要写入的数据的指针;
btw:指定要在 UINT 类型范围内写入的字节数;
*bw:指向 UINT 变量的指针,以返回写入的字节数;
经测试发现,写完成后必须关闭文件才能保存写的数据,如果写完成后不关闭文件直接读取,新写入的内容不会被读取出来。
2.5 f_size获取文件大小
f_size 函数获取文件的大小,返回文件的大小(以字节为单位)。它是作为宏实现的,只需要输入一个指向打开的文件对象结构的指针即可,使用比较简单,这里之所以单独介绍这个函数,是因为我们后续在使用SD卡读取文件时,很多时候需要先知道文件大小,然后开辟合适的空间来存储读取出来的文件内容。f_size函数的定义如下
#define f_size(fp) ((fp)->fsize)
三、目录操作函数
3.1 f_opendir函数
*path:指向指定要打开的目录名称的空终止字符串的指针;
f_opendir函数是打开一个已存在的目录,并为后续的调用创建一个目录对象。该目录对象结构可以在任何时候不经任何步骤而被丢弃。
3.2 f_closedir函数
3.3 f_readdir函数
*fno:指向文件信息结构的指针,以存储有关已读项的信息;
当启用相对路径特性(_ FS _ RPATH >= 1)时,点条目不会被过滤掉,它们将出现在读取条目中。当所有目录项都已读取且没有要读取的项时,空字符串存储在 fno-> fname []中,不会出现任何错误。当指向 fno 的空指针被赋值时,目录对象的读索引将被重绕。
启用 LFN 特性(长文件名)后,文件信息结构中的 fno-> lfname 和 fno-> lfsize 在使用之前必须使用有效值进行初始化。lfname 指向 LFN 读取缓冲区。lfsize 是以 TCHAR 为单位的 LFN 读缓冲区的大小。如果不需要 LFN,则将 lfname设置为一个空指针,则不返回 LFN。
这里简单介绍几句相对路径和绝对路径,至于更加详细的内容还需要大家自行搜索。其实很好理解,绝对路径是指文件在硬盘上真正存在的路径,绝对路径是唯一的,只有一个,而相对路径是相对于当前目录或者当前工作目录的路径,表示文件相对于当前位置的路径。我们常见的带盘符的路径都是绝对路径,比如C:\Users\de’l’l\Desktop\doc这种,而相对路径是相对一个目标对象而言的,它有自己的表示方式,“.”表示当前所在目录,“…”代表上一层目录,“…\”代表上一层目录的上一层目录。
对于f_readdir函数,给出了示例程序,这里贴一下,后续会使用该函数来打印一下某个特定文件夹下的文件
FRESULT scan_files (
char* path /* Start node to be scanned (also used as work area) */
)
{
FRESULT res;
FILINFO fno;
DIR dir;
int i;
char *fn; /* This function assumes non-Unicode configuration */
#if _USE_LFN
static char lfn[_MAX_LFN + 1]; /* Buffer to store the LFN */
fno.lfname = lfn;
fno.lfsize = sizeof lfn;
#endif
res = f_opendir(&dir, path); /* Open the directory */
if (res == FR_OK) {
i = strlen(path);
for (;;) {
res = f_readdir(&dir, &fno); /* Read a directory item */
if (res != FR_OK || fno.fname[0] == 0) break; /* Break on error or end of dir */
if (fno.fname[0] == '.') continue; /* Ignore dot entry */
#if _USE_LFN
fn = *fno.lfname ? fno.lfname : fno.fname;
#else
fn = fno.fname;
#endif
if (fno.fattrib & AM_DIR) { /* It is a directory */
sprintf(&path[i], "/%s", fn);
res = scan_files(path);
path[i] = 0;
if (res != FR_OK) break;
} else { /* It is a file. */
printf("%s/%s\n", path, fn);
}
}
f_closedir(&dir);
}
return res;
}
四、文件/目录管理函数
下面介绍的这几个文件/目录管理函数的使用都比较简单,下面就不再单独给出应用示例了。
4.1 f_unlink函数
文件/子目录不能有只读属性(AM _ RDO) ,否则函数将被 FR _ DENIED 拒绝。
子目录必须为空并且不能被工作目录,否则函数将被拒绝。
当启用文件锁定功能时,它将被安全地拒绝。
4.2 f_rename函数
*path_new:新对象名称;
4.3 f_mkdir函数
五、示例程序
5.1 文件操作函数使用示例
为了方便大家更好地理解FATFS的API函数的使用方法,我们这里通过一个小例子来演示一下,本例需要用到以下内容
本例主要目的是在一张空的SD卡中创建并打开一个新的.txt文件,向文件中写入“ABCDEFGH”,然后读取文件内容并显示,最后读取一下文件大小。
这里贴一下核心代码,代码中有个别打开文件和关闭文件的操作没有进行返回值检测,不影响实际测试,这里特地说明一下。
f_mount(fs[0],"0:",1); //挂载SD卡
// 打开一个文件,如果文件不存在,则创建一个
res = f_open(&fil, "ERTUElec.txt",FA_OPEN_ALWAYS | FA_WRITE);
// 判断是否创建成功
if (!res)
{
LCD_ShowString(30,80,200,16,16,"File Creat Success!");
}
else
{
LCD_ShowString(30,80,200,16,16,"File Creat faild! ");
}
// 写入数据
res = f_write(&fil,&writeData[0],8,(UINT*)&bw);
// 判断是否写入成功
if (!res)
{
LCD_ShowString(30,100,200,16,16,"File Write Success!");
f_close(&fil);
}
else
{
LCD_ShowString(30,100,200,16,16,"File Write faild! ");
}
f_open(&fil, "ERTUElec.txt",FA_READ);
// 读取文件
res = f_read(&fil,&readData[0],8,(UINT*)&br);
// 判断是否读取成功
if (!res)
{
LCD_ShowString(30,120,200,16,16,"File Read Success!");
}
else
{
LCD_ShowString(30,120,200,16,16,"File Read faild! ");
}
// 关闭打开的文件
res = f_close(&fil);
// 判断是否关闭成功
if (!res)
{
LCD_ShowString(30,140,200,16,16,"File Close Success!");
}
else
{
LCD_ShowString(30,140,200,16,16,"File Close faild! ");
}
// 显示读取内容
sprintf ((char*)string,"Content is %s",readData);
LCD_ShowString(30,160,200,16,16,string);
// 获取文件大小(字节数)
size = f_size(&fil);
// 显示文件大小
sprintf ((char*)string,"Size is %d",size);
LCD_ShowString(30,180,200,16,16,string);
下面看一下LCD上显示的信息
5.2 目录操作函数使用示例
本示例比较简单,就是使用f_readdir函数来读取一个特定文件加下的全部文件名并通过串口打印。首先我们在SD卡中新建一个文件夹,文件夹内添加一些文件用来测试
然后我们利用介绍f_readdir函数时给出的示例函数将NEW文件夹内的全部文件名通过串口打印出来,示例函数在上面已经给出了,这里就不再重复介绍了,在使用时只需要输入文件夹路径即可
scan_files("0:/NEW");
上电后观察串口输出内容
作者:二土电子