FatFs文件系统
前言
在芯片(如:STM32F103VET6单片机)使用过程中,为了方便数据的存储,我们加入了串行的外部Flash(SPI通信)。在使用存储的时候,如需要记录一个字符串“我学完FatFs文件系统觉得我现在强的可怕”,我们可以把这些文字转化成 ASCII 码,存储在数组中,然后调用写函数,把数组内容写入到Flash芯片的指定地址上,在需要的时候从该地址把数据读取出来,再对读出来的数据以 ASCII 码的格式进行解读。
我们可以发现有几个问题:
(1)难以记录有效数据的位置;
(2)难以确定存储介质的剩余空间;
(3)不明确应该以何种格式来解读数据。
直接存储数据的方式对于小容量的存储介质如 EEPROM 还可以接受,但对于 SPI Flash 芯片或者 SD 卡之类的大容量设备,我们需要一种高效的方式来管理它的存储内容。
补充小知识:ASCII表示一个字符需要占用七位二进制数,最左边为0,所以要占用一个字节。
一、文件系统
文件系统是对数据进行管理的方式,使用文件系统可以有效的管理存储介质(Flash,EEPROM,SD卡,磁盘等);
使用文件系统时,它为了存储和管理数据,在存储介质建立了一些组织结构这些结构包括操作系统引导区、目录和文件。常见的windows下的文件系统格式包括FAT32、NTFS、exFAT。在使用文件系统前,要先对存储介质进行格式化。格式化时会在存储介质上新建一个文件分配表和目录。这样,文件系统就可以记录数据存放的物理地址,剩余空间。
使用文件系统时,数据都以文件的形式存储。写入新文件时,先在目录中创建一个文件索引,它指示了文件存放的物理地址,再把数据存储到该地址中。当需要读取数据时,可以从目录中找到该文件的索引,进而在相应的地址中读取出数据。具体还涉及到逻辑地址、簇大小、不连续存储等一系列辅助结构或处理过程,文件系统的存在使存取数据时,不再是简单地向某物理地址直接读写,而是要遵循它的读写格式。如经过逻辑转换,一个完整的文件可能被分开成多段存储到不连续的物理地址,使用目录或链表的方式来获知下一段的位置。
补充小知识:
关于内存扇区、块、簇的基本概念:
扇区(sector):flash可操作的最小单元,通常指我们擦除的最小单元大小,以sdnand举例,通常最小为512Byte
块(block) 以及簇(cluster):其实这是两个相同的概念,只是由于历史原因,在不同系统上的不同称呼,在windows中称簇,而在linux中称块。一个簇/块由多个扇区组成,由于一个扇区的空间较小,因此文件系统通过会将多个扇区组合在一起形成一个簇,并以簇为单位进行读写操作!一个簇通常可以由 2、4、8、… 、2的n次方个扇区组成。
目录和文件分配表的概念
目录:告诉我文件的各个属性,比如名字,大小,时间,读写属性等等;
文件分配表:告诉我们这个文件的存储地方,怎么读取这个文件,如下图从目录读完读第一个扇区,第一个读完读取第二个……等等。
文件系统的空间示意图:存储了A.TXT,B.TXT,C.TXT文件
目录示意图:记录了文件的开始簇位置、大小等信息
文件分配表:文件 a.txt 我们根据目录项中指定的 a.txt 的首簇为 2,然后找到文件分配表的第 2 簇记录,上面登记 的是 3,就能确定下一簇是 3。找到文件分配表的第 3 簇记录,上面登记的 是 4,就能确定下一簇是 4…直到指到第 11 簇,发现下一个指向是 FF,就是结束。文件便读取完毕。
删除B.TXT文件,创建D.TXT文件后的空间示意图
删除B.TXT文件,创建D.TXT文件后的目录示意图
删除B.TXT文件,创建D.TXT文件后的文件分配表示意图
以上图片及文字引用野火教程视频
以下是我对FatFs使用的学习所得,和大家分享一下。
二、FatFs文件系统
1.FatFs文件系统简介
FatFs是面向小型嵌入式系统的一种通用的FAT文件系统。它完全是由AISIC语言编写并且完全独立于底层的I/O介质。因此它可以很容易地不加修改地移植到其他的处理器当中,如8051、PIC、AVR、SH、Z80、H8、ARM等。FatFs支持FAT12、FAT16、FAT32(现在主要用的)等格式。
利用前面写好的SPlFlash芯片驱动,把FatFs文件系统代码移植到工程之中,就可以利用文件系统的各种函数,对SPIFlash芯片以“文件”格式进行读写操作了。
补充小知识
C语言中的文件操作
文件的打开操作 :
fopen 打开一个文件 文件的关闭操作 :
fclose 关闭一个文件 文件的读写操作:
fgetc 从文件中读取一个字符
fputc
写一个字符到文件中去
fgets 从文件中读取一个字符串
fputs 写一个字符串到文件中去
fprintf 往文件中写格式化数据
fscanf 格式化读取文件中数据
fread 以二进制形式读取文件中的数据
fwrite 以二进制形式写数据到文件中去
getw 以二进制形式读取一个整数
putw
以二进制形式存贮一个整数 文件状态检查函数:
feof 文件结束
ferror 文件读/写出错
clearerr 清除文件错误标志
ftel了解文件指针的当前位置
文件定位函数
rewind 反绕
fseek 随机定位
2.下载地址
FatFs文件系统的源码可以从fatfs官网下载:http://elm-chan.org/fsw/ff/00index%20e.html
打开链接下拉到最下面可看到如下内容:
3.源码文件内容解释
解压之后会得以下内容:在documents文件夹里面是一些使用的帮助文档;在source中是FatFs文件系统的源代码,source文件夹就是我们需要添加到工程中的文件。
document:使用的帮助文档;对于整个document文件我们不需要关注,当然文件夹中有官网链接可以点进去看看,多了解一下。
source:FatFs文件系统的源代码,即需添加的文件;
source文件打开有以下文件:
diskio.c:包含底层存储介质的操作函数,这些函数需要用户自己实现,主要添加底层驱动函数。(我们主要操作的也是diskio.c和diskio.h)
ff.c: FatFs核心文件,文件管理的实现方法。该文件独立于底层介质操作文件的函数,利用这些函数实现文件的读写。(我们不需要动它)
ff.h:文件中包含了一些数值类型定义:UINT、BYTE、WORD、DWORD等类型,为避免因内核不同而导致一些字节类型的定义不统一(屏蔽编译器差异)。
ffconf.h:这个头文件包含了对FatFs功能配置的宏定义,通过修改这些宏定义就可以裁剪FatFs的功能。如需支持简体中文,需把ffconf.h中的_CODE_PAGE的宏改成936,这个字库在ffunicode.c中。(这其中的宏定义也是我们需要根据自己需求进行更改)
下图是FatFs在程序中的关系网络:
下图是各文件解释:
4.文件移植
首先加入source文件夹中的所有.c文件添加到工程当中:
之后,我们只需要修改diskio.c中下图所示的函数:
!!!注意:上方图片蓝色划线的函数,不要在应用层进行调用,即只能在diskio.c中修改,不能拿到除这个文件以外的文件中调用
具体修改内容(一)diskio.c
1.定义自己的逻辑设备号,并且添加存储介质所对应的相关头文件
2.修改disk_stataus函数:官方对该函数解释
该函数作用是来查询当前驱动器(存储介质)状态。
DSTATUS disk_status (BYTE pdrv/* [IN] Physical drive number */);
传参:(BYTE pdrv)物理驱动号(即1中定义的逻辑设备号)
返回值:(DSTATUS 类型跳进去就会发现是unsigned char)STA_NOINIT:标识该设备未初始化成功,未进入就绪状态;STA_PROTECT:表示该设备已进行了写保护。如果设置了STA_NODISK,则无效;返回值的最低位为0:表示该设备初始化成功。
以下代码中,我把W25Q128内存分为两部分:一部分用来存储图片(之后用USB模拟U盘来用,直接可以把图片拖进存储中),一部分用来存储数据。以下代码没有写写保护判断,根据个人使用情况可自行添加。
/*-----------------------------------------------------------------------*/
/* 获取设备状态 */
/*-----------------------------------------------------------------------*/
DSTATUS disk_status (
BYTE pdrv /* 物理编号 */
)
{
DSTATUS status = STA_NOINIT;
switch (pdrv)
{
case SD_CARD:
break;
case FLASH_PICTURE:
/* SPI Flash状态检测:读取SPI Flash 设备ID */
if(sFLASH_ID == SPI_FLASH_ReadID())//#define sFLASH_ID 0XEF4018 //这为W25Q128设备返回值
{
/* 设备ID读取结果正确 */
status &= ~STA_NOINIT;
}
else
{
/* 设备ID读取结果错误 */
status |= STA_NOINIT;
}
break;
case FLASH_DATA:
/* SPI Flash状态检测:读取SPI Flash 设备ID */
if(sFLASH_ID == SPI_FLASH_ReadID())//#define sFLASH_ID 0XEF4018 //这为W25Q128设备返回值
{
/* 设备ID读取结果正确 */
status &= ~STA_NOINIT;
}
else
{
/* 设备ID读取结果错误 */
status |= STA_NOINIT;
}
default:
status = STA_NOINIT;
}
return status;
}
3.修改disk_initialize函数:官方对该函数解释
该函数作用是对驱动器(存储介质)进行初始化,让该介质处于正常读写状态。
DSTATUS disk_initialize (BYTE pdrv /* [IN] Physical drive number */);
传参:(BYTE pdrv)物理驱动号(即1中定义的逻辑设备号)
返回值:(DSTATUS 类型跳进去就会发现是unsigned char)STA_NOINIT:标识该设备未初始化成功,未进入就绪状态;STA_PROTECT:表示该设备已进行了写保护。如果设置了STA_NODISK,则无效;返回值的最低位为0:表示该设备初始化成功。
/*-----------------------------------------------------------------------*/
/* 设备初始化 */
/*-----------------------------------------------------------------------*/
DSTATUS disk_initialize (
BYTE pdrv /* 物理编号 */
)
{
uint16_t i;
DSTATUS status = STA_NOINIT;
switch (pdrv)
{
case SD_CARD:
break;
case FLASH_PICTURE: /* SPI Flash */
/* 初始化SPI Flash */
SPI_Flash_Init();//就是对存储介质所连接芯片的IO进行初始化,对IO口所对应的SPI进行初始化
/* 延时一小段时间 */
i=500;
while(--i);
/* 唤醒SPI Flash */
SPI_Flash_WAKEUP();
/* 获取SPI Flash芯片状态 */
status=disk_status(FLASH_PICTURE); //01
break;
case FLASH_DATA: /* SPI Flash */
/* 初始化SPI Flash */
SPI_Flash_Init();
/* 延时一小段时间 */
i=500;
while(--i);
/* 唤醒SPI Flash */
SPI_Flash_WAKEUP();
/* 获取SPI Flash芯片状态 */
status=disk_status(FLASH_DATA);
break;
default:
status = STA_NOINIT;
}
return status;
}
4.修改disk_read函数:官方对该函数解释
该函数作用是对驱动器(存储介质)进行初始化,让该介质处于正常读写状态。
DRESULT disk_read (
BYTE pdrv, // [IN] Physical drive number
BYTE* buff, // [OUT] Pointer to the read data buffer
LBA_t sector, // [IN] Start sector number
UINT count // [IN] Number of sectros to read
);
传参:BYTE pdrv:物理驱动号(即1中定义的逻辑设备号)BYTE* buff:数据缓存区;DWORD sector:读取的扇区首地址32位;DWORD sector:扇区个数。
返回值:(DSTATUS 类型跳进去就会发现是unsigned char)RES_OK :成功;RES_ERROR:读/写失败;RES_WRPRT:写保护;RES_NOTRDY:没准备好;RES_PARERR:无效参数。
注:FatFs支持的扇区大小为512到4096个字节,在FLASH中扇区是擦除的最小单位,而在FatFs中扇区(簇)是读/写都是最小的单位,在下面函数中,形参sector是FatFs中的扇区(簇)地址,代表第几个扇区(簇),则默认每个扇区设置大小为4096个字节,那么传到Flash读取的地址的时候需要给sector向左移动12位扩大4096倍
补充小知识:Flash——W25Q128 (128Mbit,16MByte),被组织为65536个可编程的页,每页256bytes。擦除方式分为16页一组(即一个扇区4kbytes),128页一组(即8个扇区32kbytes),256页一组(即16个扇区或1个块64kbytes),或整个芯片擦除。该芯片有4096个可擦除扇区,或256个可擦除块。
/*-----------------------------------------------------------------------*/
/* 读扇区:读取扇区内容到指定存储区 */
/*-----------------------------------------------------------------------*/
DRESULT disk_read (
BYTE pdrv, /* 设备物理编号(0..) */
BYTE *buff, /* 数据缓存区 */
DWORD sector, /* 扇区首地址 */
UINT count /* 扇区个数(1..128) */
)
{
DRESULT status = RES_PARERR;
switch (pdrv)
{
case SD_CARD:
break;
case FLASH_PICTURE:
sector+=256;
//一个数左移12位,就是扩大2的12次方,4096倍,即FatFs的最小单位设置成4096个字节
SPI_Flash_Read(buff, sector <<12, count<<12);
status = RES_OK;//在这默认全部读取正常
break;
case FLASH_DATA:
sector+= 1536;
SPI_Flash_Read(buff, sector <<12, count<<12);
status = RES_OK;
break;
default:
status = RES_PARERR;
}
return status;
}
5.修改disk_write函数:官方对该函数解释
该函数作用是对驱动器(存储介质)进行初始化,让该介质处于正常读写状态。
DRESULT disk_write (
BYTE pdrv, // [IN] Physical drive number
const BYTE* buff, // [IN] Pointer to the data to be written
LBA_t sector, // [IN] Sector number to write from
UINT count // [IN] Number of sectors to write
);
传参:BYTE pdrv:物理驱动号(即1中定义的逻辑设备号) const BYTE* buff:欲写入数据的缓存区;LBA_t sector:写入扇区首地址32位;UINT count:扇区个数。
返回值:(DSTATUS 类型跳进去就会发现是unsigned char)RES_OK :成功;RES_ERROR:读/写失败;RES_WRPRT:写保护;RES_NOTRDY:没准备好;RES_PARERR:无效参数。
/*-----------------------------------------------------------------------*/
/* 写扇区:见数据写入指定扇区空间上 */
/*-----------------------------------------------------------------------*/
//下方宏定义可能因为版本不同宏定义不同
#if FF_FS_READONLY == 0 //根据这个宏定义判断这个函数要不要加进去,是个写保护,是0加进去,1不加进去
DRESULT disk_write (
BYTE pdrv, /* 设备物理编号(0..) */
const BYTE *buff, /* 欲写入数据的缓存区 */
DWORD sector, /* 扇区首地址 */
UINT count /* 扇区个数(1..128) */
)
{
uint32_t write_addr;
DRESULT status = RES_PARERR;
if (!count) {
return RES_PARERR; /* Check parameter */
}
switch (pdrv)
{
case SD_CARD:
break;
case FLASH_PICTURE:
sector+=256;
while(count--)
{
write_addr = sector<<12;
SPI_Flash_Erase_Sector(write_addr);//擦除
SPI_Flash_Write((u8 *)buff,write_addr,count<<12);//写入
sector++;
buff+=4096;//默认FatFs的一个扇区(簇)4096个大小
}
/*
//一般使用过程中都操作一个,可以直接按照下面这样写,但按照上面这样写更严谨
write_addr = sector<<12;
SPI_Flash_Erase_Sector(write_addr);//擦除
SPI_Flash_Write((u8 *)buff,write_addr,count<<12);//写入
*/
status = RES_OK;
break;
case FLASH_DATA:
sector+=1536;
{
write_addr = sector<<12;
SPI_Flash_Erase_Sector(write_addr);//擦除
SPI_Flash_Write((u8 *)buff,write_addr,count<<12);//写入
sector++;
buff+=4096;
}
status = RES_OK;
break;
default:
status = RES_PARERR;
}
return status;
}
#endif
因为FatFs文件系统会考虑到存储介质,需要擦除或者同步的时候需要等待。我们可以把这种等待、擦除放到后面移植的disk_ioctl函数中,但是一般在Flash的SPI_Flash_Write函数中直接把等待的写进去了,所以也就没往disk_ioctl函数放写完的等待函数。
6.修改disk_ioctl函数:官方对该函数解释
该函数作用是对驱动器(存储介质)进行初始化,让该介质处于正常读写状态。
DRESULT disk_ioctl (
BYTE pdrv, // [IN] Drive number
BYTE cmd, // [IN] Control command code
void* buff // [I/O] Parameter and data buffer
);
传参:BYTE pdrv:物理驱动号(即1中定义的逻辑设备号)BYTE cmd:命令(具体有哪些命令可以点进”官方对该函数的解释“查看);void* buff:数据缓冲区,既有可能输出也有可能输入,根据命令不同,可能会有缓存区。
返回值:(DSTATUS 类型跳进去就会发现是unsigned char)RES_OK :成功;RES_ERROR:读/写失败;;RES_NOTRDY:没准备好;RES_PARERR:无效参数。
主要以下五个命令:(主要是红色)
CTRL_SYNC: 确保设备已完成挂起的写入过程。 如果磁盘I / O层或存储设备具有回写缓存,则脏缓存数据必须立即提交到介质。 如果在disk_write函数中完成了对介质的每次写入操作,则此命令将不执行任何操作。
GET_SECTOR_COUNT: 获取设备上可用于文件系统的容量大小。比如设备容量是16MB,但我只用6MB做文件系统使用,那么此时该命令返回的值应该是6MB。根据变量类型的定义可以看出是32位 ,也就是说容量不能超出4GB。
GET_SECTOR_SIZE: 获取一个扇区大小的命令。
GET_BLOCK_SIZE: 获取设备中一个块大小的命令。
CTRL_TRIM: 通知设备不再需要扇区块上的数据,可以将其擦除。 如果不支持此功能或闪存设备不执行此命令,则无需执行任何操作。
主要是这三个:
GET_SECTOR_COUNT、GET_SECTOR_SIZE 、GET_BLOCK_SIZE
DRESULT disk_ioctl (
BYTE pdrv, /* 物理编号 */
BYTE cmd, /* 控制指令 */
void *buff /* 写入或者读取数据地址指针 */
)
{
DRESULT status = RES_PARERR;
switch (pdrv)
{
case SD_CARD:
break;
case FLASH_PICTURE:
switch (cmd)
{
case CTRL_SYNC://写入同步在disk_write函数已经完成,这里默认返回ok
status = RES_OK;
break;
case GET_SECTOR_COUNT:
//大小:1280*4096/1024/1024=5MB
*(DWORD * )buff = 1280;//存储空间大小,有多少个FatFs扇区(sector)
//总共16MB,分给这个区5MB,5*1024(KB)*1024(Byte)/4096=1280,按照最大扇区设置的4096个字节,分了1280个扇区给它
status = RES_OK;
break;
/* 扇区大小 */
case GET_SECTOR_SIZE :
*(WORD * )buff = 4096;//每个FatFs扇区(sector)的大小,有多少个字节
status = RES_OK;
break;
/* 同时擦除扇区个数 */
case GET_BLOCK_SIZE ://获取擦除的最小个数,以FatFs扇区(sector)为单位,注意:FatFs的扇区和Flash的扇区不是一个概念
*(DWORD * )buff = 1;
status = RES_OK;
break;
case CTRL_TRIM:
status = RES_OK;
break;
default:
status = RES_PARERR;
break;
}
break;
case FLASH_DATA:
{
switch (cmd)
{
case CTRL_SYNC:
status = RES_OK;
break;
case GET_SECTOR_COUNT:
*(DWORD * )buff = 2560;
status = RES_OK;
break;
/* 扇区大小 */
case GET_SECTOR_SIZE :
*(WORD * )buff = 4096;
status = RES_OK;
break;
/* 同时擦除扇区个数 */
case GET_BLOCK_SIZE :
*(DWORD * )buff = 1;
status = RES_OK;
break;
case CTRL_TRIM:
status = RES_OK;
break;
default:
status = RES_PARERR;
break;
}
break;
}
break;
default:
status = RES_PARERR;
}
return status;
}
#endif
7.添加DWORD get_fattime 函数:官方对该函数解释
作用就是获取时间,就是你创建或者修改文件的时间。
DWORD get_fattime (void)//返回
{/*
time_t t;
struct tm *stm;
t = time(0);
stm = localtime(&t);
*//*
return (DWORD)(stm->tm_year - 80) << 25 |
(DWORD)(stm->tm_mon + 1) << 21 |
(DWORD)stm->tm_mday << 16 |
(DWORD)stm->tm_hour << 11 |
(DWORD)stm->tm_min << 5 |
(DWORD)stm->tm_sec >> 1;*/
return
((DWORD)(2024- 1980) << 25)| // Year 2024
((DWORD)5 << 21)| // Month 5
((DWORD)20 << 16)| // Mday 20
((DWORD)0 << 11)| // Hour 0
((DWORD)0 << 5)| // Min 0
((DWORD)0 >> 1); // Sec 0 秒要除以2
}
具体修改内容(二)ffconf.h
(1)修改FF_MAX_SS为4096,理由:调用函数的时候会传入一个buff(即数据),这个buff可以跳入FATFS这个结构体,在最后一个定义的数组可查到,这个数组的长度按照我们的定义应为4096个字节;
(2)修改FF_VOLUMES为3(修改范围在1-10),理由:这个宏定义的是我们用了几个存储介质,从上面看到我们用了3个,如果使用人增加了存储介质,可以自行再增加;
(3)修改FF_USE_MKFS为1,理由:这个宏为了使用f_mkfs进行创建卷,所以要把它打开。
(4)修改FF_USE_LFN为1,理由:为了支持长文件名
(5)将FF_CODE_PAGE按照下面根据自己使用情况进行修改
5.函数调用
总共有以下函数:
文件访问
f_open:打开/创建文件;f_closf_close:关闭打开的文件;f_read:从文件中读取数据;f_write:将数据写入文件;f_lseek:移动读/写指针,扩展大小;f_truncate:截断文件大小;sync:刷新缓存的数据;f_forward:将数据转发到流;f_expand:为文件分配一个连续的块;f_gets:读取字符串;f_putc:写一个字符;f_puts:写一个字符串;f_printf:编写格式化字符串;f_tell:获取当前读/写指针;f_eof:测试文件结束;f_size:获取尺寸;f_error:测试错误;
目录访问
f_opendir:打开目录;f_closedir :关闭打开的目录;f_readdir:读取目录项;f_findfirst打开目录并且读取匹配的第一个项目;f_findnext:读取下一个匹配的项目。
文件和目录管理
f_stat:检查文件或者子目录是否存在;f_unlink:删除文件或者子目录;f_rename:重命名/移动文件或者子目录;f_chmod:更改文件或者子目录的属性;f_utime:更改文件或者子目录的时间戳;f_mkdir:创建子目录;f_chdir:更改当前目录;f_chdrive:更改电流驱动器;f_getcwd:检索当前目录和驱动器;
卷管理和系统配置
f_mount:注册/注销卷的工作区;f_mkfs:在逻辑驱动器上创建FAT卷;f_fdisk:在物理驱动器上创建分区;f_getfree:获取卷上的可用空间;f_getfree:获取卷上的可用空间;f_getlabel:获取卷标;f_setlabel:设置卷标;f_setcp:设置活动代码页;
以上函数大多都会有一个FRESULT类型的返回值:FatFs学习(1)——枚举:返回值FRESULT
typedef enum {
FR_OK = 0, /* (0) Succeeded *///函数执行成功,无错误。
FR_DISK_ERR, /* (1) A hard error occurred in the low level disk I/O layer *///磁盘I/O层硬错误。
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 *///没有有效的FAT卷。
FR_MKFS_ABORTED, /* (14) The f_mkfs() aborted due to any parameter error *///MKFS终止。
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 *///无法分配 LFN 工作缓冲区。
FR_TOO_MANY_OPEN_FILES, /* (18) Number of open files > _FS_SHARE *///打开的文件数目> FF_FS_LOCK。
FR_INVALID_PARAMETER /* (19) Given parameter is invalid *///参数无效。
} FRESULT;
我们使用文件系统的时候首先要使用f_mount挂在文件系统,其次使用f_mkfs创建一个卷积并且格式化,最后可以创建一个文件。具体我们写一个例子来说明:我们使用2:FLASH_DATA这个存储空间举例,在该存储介质中,我们创建1个文件夹,以时间命名
#include "ff.h" //要包含此头文件
FATFS fs; /* FatFs文件系统对象 */
FRESULT res_csv;/* 文件操作结果 */
FIL fnew_csv; /* 文件对象 */
UINT fnum_csv; /* 文件成功读写的数量,是一个返回值*/
char filename[14] = {'0',':','2','0'};//文件名
char *fileheader = "Time,,Alarm value,,Alarm Way\r\n";//写入文件内容
void Filesystem_init(void)
{
uint16_t i;
char buffer[30];
res_csv = f_mount(&fs_csv,"2:",1);
if(res_csv == FR_NO_FILESYSTEM)
{
/* 格式化,操作FLASH_DATA存储介质,并且操作硬盘:0;默认自行分配大小:0*/
res_csv=f_mkfs("2:",0,0);
if(res_csv == FR_OK)
{
/* 格式化后,先取消挂载 */
res_csv = f_mount(NULL,"2:",1);
/* 重新挂载 */
res_csv = f_mount(&fs_csv,"2:",1);
/*每天建立一个文件*/
filename[4] = system.time.year / 0x10 + 0x30;filename[5] = system.time.year % 0x10 + 0x30;
filename[6] = system.time.month / 0x10 + 0x30;filename[7] = system.time.month % 0x10 + 0x30;
filename[8] = system.time.day / 0x10 + 0x30;filename[9] = system.time.day % 0x10 + 0x30;
filename[10] = '.';filename[11] = 'c';filename[12] = 's';filename[13] = 'v';
/*建立当天记录文件并写入起始目录*/
res_csv = f_open(&fnew_csv,filename,FA_CREATE_ALWAYS | FA_WRITE);//文件存在则覆盖重新创建,不存在就创建创建|可写
if(res_csv == FR_OK)
{
res_csv = f_write(&fnew_csv,fileheader,strlen(fileheader),&fnum_csv);
}
f_close(&fnew_csv);
}
else
{
res_csv=f_mkfs("2:",0,0);
if(res_csv == FR_OK)
{
/* 格式化后,先取消挂载 */
res_csv = f_mount(NULL,"2:",1);
res_csv = f_mount(&fs_csv,"2:",1);
/*每天建立一个文件*/
filename[4] = system.time.year / 0x10 + 0x30;system[5] = system.time.year % 0x10 + 0x30;
filename[6] = system.time.month / 0x10 + 0x30;system[7] = system.time.month % 0x10 + 0x30;
filename[8] = system.time.day / 0x10 + 0x30;system[9] = system.time.day % 0x10 + 0x30;
filename[10] = '.';filename[11] = 'c';filename[12] = 's';filename[13] = 'v';
/*建立当天记录文件并写入起始目录*/
res_csv = f_open(&fnew_csv,filename,FA_CREATE_ALWAYS | FA_WRITE);
if(res_csv == FR_OK)
{
res_csv = f_write(&fnew_csv,fileheader,strlen(fileheader),&fnum_csv);
}
f_close(&fnew_csv);
}
}
}
else if(res_csv == FR_OK)
{
/*每天建立一个文件*/
filename[4] = system.time.year / 10 + 0x30;filename[5] = system.time.year % 10 + 0x30;
filename[6] = system.time.month / 10 + 0x30;filename[7] = system.time.month % 10 + 0x30;
filename[8] = system.time.day / 10 + 0x30;filename[9] = system.time.day % 10 + 0x30;
filename[10] = '.';filename[11] = 'c';filename[12] = 's';filename[13] = 'v';
/*查看今天的数据文件是否存在,如果没有就建立文件并写入起始数据(分类信息)*/
res_csv = f_open(&fnew_csv,filename,FA_OPEN_EXISTING | FA_READ | FA_WRITE);
if(res_csv != FR_OK)
{
res_csv = f_open(&fnew_csv,filename,FA_CREATE_ALWAYS | FA_WRITE);
if(res_csv == FR_OK)
{
res_csv = f_write(&fnew_csv,fileheader,strlen(fileheader),&fnum_csv);
}
}
/*检查文件头是否正确*/
else
{
f_read(&fnew_csv,buffer,30,&fnum_csv);
for(i = 0;i < 30;i ++)
{
if(buffer[i] != fileheader[i])
{//和我要写的内容不一样的话我重新写
f_write(&fnew_csv,fileheader,30,&fnum_csv);
}
}
}
}
f_close(&fnew_csv);
}
int main(void)
{
Filesystem_init();
while(1);
}
如果读者您感觉文字看着有点难理解,并且有足够的时间学习,可以点解此链接,进行视频学习:FatFs视频教程