STM32进阶笔记——FATFS文件系统(上)_stm32 fatfs-CSDN博客
STM32进阶笔记——FATFS文件系统(下)_stm32 文件系统怎样获取文件大小-CSDN博客
STM32——FATFS文件基础知识_stm32 fatfs-CSDN博客
021 - STM32学习笔记 - Fatfs文件系统(三) - 细化与总结_fatfs遍历文件-CSDN博客
希望这几篇文章不会莫名消失吧。
Fatfs
在嵌入式系统中,对于数据的存储和管理至关重要。STMicroelectronics的STM32系列微控制器提供了丰富的外设和功能,使得与外部存储设备(如SD卡)进行交互变得更加简单高效。
了解FatFs文件系统
FatFs是一款用于嵌入式系统的开源文件系统库,支持FAT12、FAT16、FAT32格式的文件系统。它提供了一套简单易用的API,能够方便地在嵌入式系统中实现对SD卡等存储设备的文件操作。
FATFS文件系统特点
- 1、Windows兼容的FAT文件系统(支持FAT12、FAT16和FAT32)
- 2、与平台无关,移植简单。全C语言编写
- 3、代码量少、效率高
- 4、多种配置选项
- 1)支持多卷(物理驱动器或分区,最多10卷)
- 2)多个AHSI/OEM代码页包括DBCS
- 3)支持长文件名、ANSI/OEM或Unicode
- 4)支持RTOS
- 5)支持多种扇区大小
- 6)只读、最小化的API和I/O缓冲区等
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上读/写文件那样简单
大部分的可以移植的小系统或者应用,都是采用类似这种将与底层打交道的源码开发给用户编写,然后提供顶层配置文件供配置
diakio.c和diskio.h是硬件层
ff.c和ff.h是FATFS的文件系统层和文件系统的API层。
FATFS移植步骤
FATFS模块在移植的时候,一般只需要修改2个文件,即ffconf.h和diskio.c。FATFS模块的所有配置项都是存放在ffconf.h里面,可以通过配置里面的一些选项来满足自己的要求。disk.c是硬件层,负责与底层硬件接口适配。
1、数据类型:在integer.h里面去定义好的数据类型。需要了解用的编译器的数据类型,并根据编译器定义好数据类型。
为支持简体中文长文件名称需要添加ff_convert和ff_wtoupper 函数,实际这两个已经在cc936.c 文件中实现,我们只要直接把cc936.c文件添加到工程中就可以。
2、配置:通过ffconf.h配置FTAFS的相关功能,以满足需要。
FatFs 文件系统与底层介质的驱动分离开来,对底层介质的操作都要交给用户去实现,它仅仅是 提供了一个函数接口而已。
表FatFs移植需要用户支持函数为FatFs移植时用户必须支持的函数。 通过表FatFs 移植需要用户支持函数我们可以清晰知道很多函数是在一定条件下才需要添加的, 只有前三个函数是必须添加的。我们完全可以根据实际需求选择实现用到的函数。
前三个函数是实现读文件最基本需求。接下来三个函数是实现创建文件、修改文件需要的。为实 现格式化功能,需要在disk_ioctl添加两个获取物理设备信息选项。我们一般只要实现前面六个 函数就可以了,已经足够满足大部分功能。
disk_initalize函数
disk_status函数
disk_write函数
disk_ioctl函数
get_fattime函数
3、函数编写:打开diskio.c进行底层驱动编写,一般需要编写6个接口函数底层设备驱动函数是存放在diskio.c文件,我们的目的就是把diskio.c中的函数接口与SPIFlash芯 片驱动连接起来。总共有五个函数,分别为设备状态获取(disk_status)、设备初始化(disk_initialize)、 扇区读取(disk_read)、扇区写入(disk_write)、其他控制(disk_ioctl)。
4. f fconf.h 文件是 FatFs 功能配置文件,我们可以对文件内容进行修改,使得FatFs更符合我们的要 求。
ffconf.h 对每个配置选项都做了详细的使用情况说明。下面只列出修改的配置,其他配置采 用默认即可。
- 1) _USE_MKFS:格式化功能选择,为使用FatFs格式化功能,需要把它设置为1。
- 2) _CODE_PAGE:语言功能选择,并要求把相关语言文件添加到工程宏。为支持简体中文文件名 需要使用“936”,正如在图添加FatFS文件到工程的操作,我们已经把cc936.c文件添加到工程 中。 3) _USE_LFN:长文件名支持,默认不支持长文件名,这里配置为2,支持长文件名,并指定使 用栈空间为缓冲区。
- 4) _VOLUMES:指定物理设备数量,这里设置为2,包括预留SD卡和SPIFlash芯片。
- 5) _MIN_SS 、_MAX_SS:指定扇区大小的最小值和最大值。SD卡扇区大小一般都为512字节, SPI Flash 芯片扇区大小一般设置为4096字节,所以需要把_MAX_SS改为4096。
- 6)_USE_STRFUNC。这个用来设置是否支持字符串类操作,比如f_putc,f_puts等。
FATFS开放函数
f_mount-注册/注销一个工作区域(Work Area)
f_open-打开/创建一个文件
f_close-关闭一个文件
f_read-读文件
f_write-写文件
f_Iseek-移动文件读/写指针
f_truncate-截断文件
f_sync-冲洗缓冲数据Flush Cached Data
f_forward-直接转移文件数据到一个数据流
f_stat-获取文件状态
f_opendir-打开一个目录
f_closedir-关闭一个已经打开的目录
f_readdir-读取目录条目
f_mkdir-创建一个目录
f_unlink-删除一个文件或目录
f_chmod-改变属性(Attribute)
f_utime-改变时间戳(Timestamp)
f_rename-重命名/移动一个文件或文件夹
f_chdir-改变当前目录
f_chdrive-改变当前驱动器
f_getcwd-获取当前工作目录
f_getfree-获取空闲簇Get Free Clusters
f_getlabel-Get volume label
f_setlabel-Set Volume label
f_mkfs-Divide a physical drive
f_gets-读取一个字符串
f_putc-写一个字符
f_puts-写一个字符串
f_printf-写一个格式化的字符串 f_printf函数是格式化写入函数,需要把ffconf.h文件中的 _USE_STRFUNC配置为1才支持。 f_printf函数用法类似C库 函数printf函数,只是它将数据直接写入到文件中。
f_tell-获取当前读/写指针
f_eof-测试文件结束
f_size-获取文件大小
f_error-测试文件上的错误
程序细化
获取FLASH空间信息
static FRESULT miscellaneous(void)
{
FATFS *fs;
DWORD fre_clust,fre_sect,tot_sect;
printf("\r\n--------------------获取设备信息--------------------\r\n");
/* 获取卷3的设备信息 */
res = f_getfree("3:",&fre_clust,&fs);
if(res)
{
printf("\r\n未获取到设备信息!\r\n");
return res;
}
/* 计算得到的总的扇区个数和空扇区个数 */
tot_sect = (fs->n_fatent -2) * fs->csize;
fre_sect = fre_clust * fs->csize;
/* 打印信息(4096字节/扇区) */
printf("》设备总空间:%10lu KB。\n》可用空间::%10lu KB。\n",tot_sect*4 , fre_sect*4);
return res;
}
文件定位操作
printf("\r\n--------------------文件定位操作--------------------\r\n");
res = f_open(&fp,"3:FatFs文件系统测试例程.txt",FA_OPEN_EXISTING | FA_READ );
if(res == FR_OK)
{
res = f_lseek(&fp,fp.fsize/2); //使用文件结构体的成员属性fsize获取文件大小,将文件指针定位到文件内容的中间
//res = f_lseek(&fp,f_size(&fp)/2); //使用f_size()获取文件大小,将文件指针定位到文件内容的中间
printf("\r\n文件打开成功,准备读取数据!\r\n");
res = f_read(&fp,&readBuffer,sizeof(readBuffer),&fnum);
if(res==FR_OK)
{
printf("》文件读取成功,读到字节数据:%d\r\n",fnum);
printf("》读取得的文件数据为:\r\n%s \r\n", readBuffer);
}
else
{
printf("!!文件读取失败:(%d)\n",res);
}
f_close(&fp);
}
else
{
printf("\r\n文件打开失败,失败代码 = %d\r\n",res);
}
创建目录及重命名
printf("\r\n--------------------目录创建和重命名--------------------\r\n");
res = f_opendir(&dir,"3:Hello");
if(res != FR_OK)
{
printf("\r\n不存在该目录,将创建新的Hello文件夹\r\n");
res = f_mkdir("3:Hello"); //如果目录不存在,则创建目录
}
else
{
printf("\r\n存在该目录,关闭目录并删除!\r\n");
res = f_closedir(&dir);
f_unlink("3:Hello/testdir.txt");
}
if(res == FR_OK)
{
printf("\r\n将FatFs文件系统测试例程.txt复制到Hello下,并重命名为testdir.txt\r\n");
res = f_rename("3:FatFs文件系统测试例程.txt","3:Hello/testdir.txt");
}
readFile(&fp,"3:Hello/testdir.txt");
文件/文件夹信息获取
static FRESULT file_check(const TCHAR *path)
{
FILINFO fInfo;
/* 获取文件信息 */
res = f_stat(path,&fInfo);
if(res == FR_OK)
{
printf("“%s”文件信息:\n",path);
printf("》文件大小:%ld(字节)\n",fInfo.fsize);
printf("》时间戳:%u/%02u/%02u,%02u:%02u\n",(fInfo.fdate >> 9)+1980,fInfo.fdate >> 5&15,fInfo.fdate & 31,fInfo.ftime>>11,fInfo.ftime >>5 &63);
printf("》属性:%c%c%c%c%c\n\n",
(fInfo.fattrib & AM_DIR)?'D':'-', //目录
(fInfo.fattrib & AM_RDO)?'R':'-', //只读文件
(fInfo.fattrib & AM_HID)?'H':'-', //隐藏文件
(fInfo.fattrib & AM_SYS)?'S':'-', //系统文件
(fInfo.fattrib & AM_ARC)?'A':'-'); //档案文件
}
elsec
{
printf("\r\n文件打开失败,失败代码 = %d\r\n",res);
}
return res;
}
res = file_check("3:Hello/testdir.txt");
在主函数中调用函数
文件遍历
static FRESULT Scan_files(char *path)
{
FRESULT res;
FILINFO fInfo;
DIR dir;
int i;
char *fn;
#if _USE_LFN //如果使用长文件名
static char lfn[_MAX_LFN*2+1];
fInfo.lfname = lfn;
fInfo.lfsize = sizeof(lfn);
#endif
res = f_opendir(&dir,path); //打开目录
if(res == FR_OK)
{
i = strlen(path);
for(;;)
{
res = f_readdir(&dir,&fInfo); //读取目录下的内容,再读会自动读到下一个文件
if(res != FR_OK||fInfo.fname[0] == 0)
break;
#if _USE_LFN
/*这里其实不用看的,我们之前已经启用长文件名了,但是需要注意的
是,虽然启用了长文件名,当存储文件名不够13个字节时,文件系统仍
然会将文件名存储到fname中,只有文件名超过13时,才会存储到lfname中*/
fn = *fInfo.lfname ? fInfo.lfname:fInfo.fname;
#else
fn = fInfo.name;
#endif
if(*fn == '.') //如果遇到点,则表示当前目录,跳过即可
continue;
if(fInfo.fattrib & AM_DIR) //遇到目录时,递归调用
{
sprintf(&path[i],"/%s",fn); //将获取到的文件名合成为完整文件名(即包含卷标和目录名的)
res = Scan_files(path); //递归遍历
path[i] = 0;
if(res != FR_OK) //如果打开失败,跳出循环
break;
}
else
{
printf("%s/%s\r\n",path,fn);
}
}
}
return res;
}