26.1 文件系统
26.2 FatFs 文件系统简介
2.1 FatFs 的目录结构
2.2 FatFs 帮助文档
2.3 FATFS 源码
src 文件夹下的源码文件功能
(1)integer.h:文件中包含了一些数值类型定义。
(2)diskio.c:包含底层存储介质的操作函数,这些函数需要用户自己实现,主要添加底层驱动
函数。
(3) ffff.c:FatFs 核心文件,文件管理的实现方法。该文件独立于底层介质操作文件的函数,利用
这些函数实现文件的读写。
(4) cc936.c:本文件在 option 目录下,是简体中文支持所需要添加的文件,包含了简体中文的
GBK 和 Unicode 相互转换功能函数。
(5) ffffconf.h: 这个头文件包含了对 FatFs 功能配置的宏定义,通过修改这些宏定义就可以裁剪
FatFs 的功能。如需要支持简体中文,需要把 ffffconf.h 中的 _CODE_PAGE 的宏改成 936 并把
上面的 cc936.c 文件加入到工程之中。
26.3 FatFs 文件系统移植实验
3.1 FatFs 程序结构图
(1) 用户应用程序需要由用户编写,想实现什么功能就编写什么的程序,一般我们只用到 f_mount()、
f_open()、f_write()、f_read() 就可以实现文件的读写操作。
(2) FatFs 组件是 FatFs 的主体,文件都在源码 src 文件夹中,其中 ffff.c、ffff.h、integer.h 以及 diskio.h 四个文件我们不需要改动,只需要修改 ffffconf.h 和 diskio.c 两个文件。
(3) 底层设备输入输出要求实现存储设备的读写操作函数、存储设备信息获取函数等等。
(4) 我们使用SPI Flash 芯片作为物理设备,在上一章节已经编写好了 SPI Flash 芯片的驱动程序,这里我们就直接使用。
3.2 硬件设计
3.3 FatFs 移植步骤
3.4 FatFs 底层设备驱动函数
(1) 宏定义。
(2) 设备状态获取disk_status()。
(3) 设备初始化disk_initialize()。
(4) 读取扇区disk_read()。
(5) 扇区写入disk_write()。
(6) 其他控制disk_ioctl()。
(7) 时间戳获取get_fattime()。
3.5 FatFs 功能配置
(1)_USE_MKFS:格式化功能选择,为使用 FatFs 格式化功能,需要把它设置为 1。
(2)_CODE_PAGE:语言功能选择,并要求把相关语言文件添加到工程宏。为支持简体中文文件名
需要使用“936”,正如在图添加 FatFS 文件到工程 的操作,我们已经把 cc936.c 文件添加到工程
中。
(3)_USE_LFN:长文件名支持,默认不支持长文件名,这里配置为 2,支持长文件名,并指定使
用栈空间为缓冲区。
(4)_VOLUMES:指定物理设备数量,这里设置为 2,包括预留 SD 卡和 SPI Flash 芯片。
(5)_MIN_SS 、_MAX_SS:指定扇区大小的最小值和最大值。SD 卡扇区大小一般都为 512 字节,
SPI Flash 芯片扇区大小一般设置为 4096 字节,所以需要把 _MAX_SS 改为 4096。
3.6 FatFs 功能测试
(1)FatFs 的第一步工作就是使用 f_mount 函数挂载工作区。
(2)如果 f_mount 函数返回值为 FR_NO_FILESYSTEM,说明没有 FAT 文件系统,比如新出厂的 SPI
Flash 芯片就没有 FAT 文件系统。我们就必须对物理设备进行格式化处理。格式化成功后需要先取消挂载原来设备,再重新挂载设备。
(3)在设备正常挂载后,就可以进行文件读写操作了。
(4)f_close 函数用于不再对文件进行读写操作关闭文件,f_close 函数只要一个形参,为文件对象指
针。f_close 函数运行可以确保缓冲区完全写入到文件内。
(5)成功打开文件之后就可以使用 f_write 函数和 f_read 函数对文件进行写操作和读操作。
(6)最后,不再使用文件系统时,使用 f_mount 函数取消挂载。
26.4 FatFs 功能使用实验
4.1 硬件设计
4.2 软件设计
(1)FatFs 多项功能测试
(2)文件信息获取
(3)路径扫描
(4)主函数
main.c
#include "bsp_led.h"
#include "./flash/bsp_SPI_Flash.h"
#include "bsp_usart.h"
#include "bsp_led.h"
#include "ff.h"
#include "string.h"
#include "stdio.h"
/*自添*/
char fpath[100];
FATFS fs; /* FatFs 文件系统对象 */
FIL fnew; /* 文件对象 */
FRESULT res_flash; /* 文件操作结果 */
UINT fnum; /* 文件成功读写数量 */
BYTE ReadBuffer[1024]= {0}; /* 读缓冲区 */
BYTE textFileBuffer[] = /* 写缓冲区 */
"欢迎使用野火 STM32 开发板 今天是个好日子,新建文件系统测试文件\r\n";
/*声明*/
static FRESULT miscellaneous(void);
static FRESULT file_check(void) ;
static FRESULT scan_files (char* path);
extern int f_printf (
FIL* fp, /* Pointer to the file object */
const TCHAR* fmt, /* Pointer to the format string */
... /* Optional arguments... */
);
int main(void)
{
/* 初始化调试串口,一般为串口 1 */
USART_Config();
printf("******** 这是一个 SPI FLASH 文件系统实验 *******\r\n");
//在外部 SPI Flash 挂载文件系统,文件系统挂载时会对 SPI 设备初始化
res_flash = f_mount(&fs,"1:",1);
if (res_flash!=FR_OK) {
printf("!!外部 Flash 挂载文件系统失败。(%d)\r\n",res_flash);
printf("!!可能原因:SPI Flash 初始化不成功。\r\n");
while (1);
} else {
printf("》文件系统挂载成功,可以进行测试\r\n");
}
/* FatFs 多项功能测试 */
res_flash = miscellaneous();
printf("\n*************** 文件信息获取测试 **************\r\n");
res_flash = file_check();
printf("***************** 文件扫描测试 ****************\r\n");
strcpy(fpath,"1:");
scan_files(fpath);
/* 不再使用文件系统,取消挂载文件系统 */
f_mount(NULL,"1:",1);
/* 操作完成,停机 */
while (1) {
}
}
/* FatFs 多项功能测试 */
static FRESULT miscellaneous(void) {
DIR dir;
FATFS *pfs;
DWORD fre_clust, fre_sect, tot_sect;
printf("\n*************** 设备信息获取 ***************\r\n");
/* 获取设备信息和空簇大小 */
res_flash = f_getfree("1:", &fre_clust, &pfs);
/* 计算得到总的扇区个数和空扇区个数 */
tot_sect = (pfs->n_fatent - 2) * pfs->csize;
fre_sect = fre_clust * pfs->csize;
/* 打印信息 (4096 字节/扇区) */
printf("》设备总空间:%10lu KB。\n》可用空间: %10lu KB。\n",
tot_sect *4, fre_sect *4);
printf("\n******** 文件定位和格式化写入功能测试 ********\r\n");
res_flash = f_open(&fnew, "1:FatFs 功能测试文件.txt",FA_CREATE_ALWAYS|FA_WRITE|FA_READ );
res_flash=f_write(&fnew,textFileBuffer,sizeof(textFileBuffer),&fnum);
if ( res_flash == FR_OK )
{
/* 文件定位,定位到文件的末尾 */
res_flash = f_lseek(&fnew,f_size(&fnew)-1);
printf("%d",res_flash);
if (res_flash == FR_OK)
{
/* 格式化写入,参数格式类似 printf 函数 */
f_printf(&fnew,"在原来文件新添加一行内容\n");
f_printf(&fnew,"》设备总空间:%10lu KB。\n》可用空间;%10lu KB。\n",tot_sect *4, fre_sect *4);
/* 文件定位到文件起始位置 */
res_flash = f_lseek(&fnew,0);
/* 读取文件所有内容到缓存区 */
res_flash = f_read(&fnew,ReadBuffer,f_size(&fnew),&fnum);
if (res_flash == FR_OK)
{
printf("》文件内容:\n%s\n",ReadBuffer);
}
}
f_close(&fnew);
printf("\n********** 目录创建和重命名功能测试 **********\r\n");
/* 尝试打开目录 */
res_flash=f_opendir(&dir,"1:TestDir");
if (res_flash!=FR_OK)
{
/* 打开目录失败,就创建目录 */
res_flash=f_mkdir("1:TestDir");
}
else
{
/* 如果目录已经存在,关闭它 */
res_flash=f_closedir(&dir);
/* 删除文件 */
f_unlink("1:TestDir/testdir.txt");
}
if (res_flash==FR_OK)
{
/* 重命名并移动文件 */
res_flash=f_rename("1:FatFs 功能测试文件.txt",
"1:TestDir/testdir.txt");
if (res_flash==FR_OK)
{
printf("》重命名并移动文件操作成功\n");
}
else
{
printf("》重命名并移动文件操作失败:%d\n",res_flash);
}
}
}
else
{
printf("!! 打开文件失败:%d\n",res_flash);
printf("!! 或许需要再次运行“FatFs 移植与读写测试”工程\n");
}
return res_flash;
}
/*文件信息获取*/
static FRESULT file_check(void) {
static FILINFO fno;
/* 获取文件信息,必须确保文件存在 */
res_flash=f_stat("1:TestDir/testdir.txt",&fno);
if (res_flash==FR_OK) {
printf("“testdir.txt”文件信息:\n");
printf("》文件大小: %ld(字节)\n", fno.fsize);
printf("》时间戳: %u/%02u/%02u, %02u:%02u\n",
(fno.fdate >> 9) + 1980, fno.fdate >> 5 & 15, fno.fdate & 31,
fno.ftime >> 11, fno.ftime >> 5 & 63);
printf("》属性: %c%c%c%c%c\n\n",
(fno.fattrib & AM_DIR) ? 'D' : '-', // 是一个目录
(fno.fattrib & AM_RDO) ? 'R' : '-', // 只读文件
(fno.fattrib & AM_HID) ? 'H' : '-', // 隐藏文件
(fno.fattrib & AM_SYS) ? 'S' : '-', // 系统文件
(fno.fattrib & AM_ARC) ? 'A' : '-'); // 档案文件
}
return res_flash;
}
/*路径扫描*/
static FRESULT scan_files (char* path)
{
FRESULT res; //部分在递归过程被修改的变量,不用全局变量
FILINFO fno;
DIR dir;
int i;
char *fn; // 文件名
#if _USE_LFN
/* 长文件名支持 */
/* 简体中文需要 2 个字节保存一个“字”*/
static char lfn[_MAX_LFN*2 + 1];
fno.lfname = lfn;
fno.lfsize = sizeof(lfn);
#endif
//打开目录
res = f_opendir(&dir, path);
if (res == FR_OK) {
i = strlen(path);
for (;;) {
//读取目录下的内容,再读会自动读下一个文件
res = f_readdir(&dir, &fno);
//为空时表示所有项目读取完毕,跳出
if (res != FR_OK || fno.fname[0] == 0) break;
#if _USE_LFN
fn = *fno.lfname ? fno.lfname : fno.fname;
#else
fn = fno.fname;
#endif
//点表示当前目录,跳过
if (*fn == '.') continue;
//目录,递归读取
if (fno.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); //输出文件名
/* 可以在这里提取特定格式的文件路径 */
}//else
} //for
}
return res;
}
void Delay(__IO uint32_t nCount) //简单的延时函数
{
for(; nCount != 0; nCount--);
}
/*********************************************END OF FILE**********************/
其他配置:由于代码过长,不能全篇展示。特附
如出现问题可看:https://blog.youkuaiyun.com/C_say_easy_to_me/article/details/125623523?spm=1001.2014.3001.5502