1。 FATFS简介
FATFS文件系统是一个用于在微控制器上运行的开源文件系统,支持FAT/FATFS、NTFS、exFAT等主流文件系统,且一直保持更新。在此以FatFs官网最新版本v0.15进行移植。
2. 移植具体操作
2.1 下载FatFs源码
FATFS源码在其官网就有下载链接,下载后解压即可,官网页面如图1所示:
图1.FATFS官网页面
将其翻至最下面,就可以找到下载链接,如图2所示:
图2.FATFS下载链接
2.2 FATFS代码结构
FATFS源码解压后,其一级目录结构如图3所示:
图3.FATFS源码一级目录结构
其source文件夹下各文件作用如下所示:
source
├── 00history.txt //历史版本信息
├── 00readme.txt //FATFS简介
├── diskio.c //磁盘IO适配文件
├── diskio.h //磁盘IO适配头文件
├── ff.c //FATFS主要实现文件
├── ff.h //FATFS主要实现头文件
├── ffconf.h //FATFS配置头文件
├── ffsystem.c //系统调用适配文件
└── ffunicode.c //Unicode适配文件
在移植过程中,主要对diskio.c、ffconf.h进行修改。
2.3 修改前后文件对比
后文所有代码比对均默认左侧为原代码,右侧为修改后的代码。
2.3.1 diskio.c文件修改比对
diskio.c文件主要实现对存储介质的硬件适配,需要将FLASH读写、初始化等代码移植至此文件内的固定接口。
2.3.1.1头文件添加及宏定义变量修改对比
如图4所示:
图4.diskio.c文件头文件添加及宏定义变量修改对比
#include "Spi.h" //引用SPI初始化
#include "W25q64.h" //引用FLASH文件操作函数,文件内代码均在上篇文章中。
#define SPI_FLASH 3 //定义驱动卷名
FATFS中文件目录格式为为驱动卷名+“:”+文件名,假如说SPI_FLASH文件系统一级目录内有“aaa.txt”这个文件,那么其目录格式为"3:aaa.txt"。3就是宏定义的驱动卷名,文件系统挂载什么的如果要挂载SPIFLASH就是挂载"3:"目录,其余驱动卷或设备同理。
在C语言中上述的写法更简单,C语言支持宏定义的字符串拼接,具体示范如下列代码所示:
#define SPI_FLASH_DIR "3:"
res_flash = f_mount(&fs,SPI_FLASH_DIR,1);
res_flash = f_open(&fnew,SPI_FLASH_DIR"ABC.txt",FA_CREATE_ALWAYS | FA_WRITE);
#define SPI_FLASH_DIR 3
res_flash = f_mount(&fs,"3:",1);
res_flash = f_open(&fnew,"3:ABC.txt",FA_CREATE_ALWAYS | FA_WRITE);
这两段代码在实现效果上并无差别。
2.3.1.2 磁盘存储介质状态查询接口修改对比
如图5所示:
图5.diskio.c文件磁盘存储介质状态查询修改对比
需要将对磁盘存储介质的硬件状态查询移植至此文件内的固定接口。具体操作为在switch (pdrv) 内添加一条分支,返回值分为STATUS_NOINIT、STATUS_NODISK、STATUS_PROTECT与RES_OK四种情况,分别代表磁盘存储介质未初始化、没有对应驱动卷名、磁盘写保护与磁盘正常。
2.3.1.3 磁盘存储介质初始化接口修改对比
如图6所示:
图6.diskio.c文件磁盘存储介质初始化修改对比
需要对磁盘存储介质的硬件初始化移植至此文件内的固定接口。具体操作为在switch (pdrv) 内添加一条分支,调用SPI_FLASH_Init()函数进行初始化。可用返回值与状态查询的返回值一致,在这里我图省事,直接调用了状态查询函数。
2.3.1.4 磁盘存储介质数据读取接口修改对比
如图7所示:
图7.diskio.c文件磁盘存储介质数据读取接口修改对比
需要对磁盘存储介质的硬件数据读取移植至此文件内的固定接口。具体操作为在switch (pdrv) 内添加一条分支,调用SPI_FLASH_Read()函数进行读取。其中读取数据的存储地址为*buff,读取数据的扇区逻辑位号为sector,读取数据的扇区数量为count。
在图7中可以看到,扇区逻辑区块地址(LBA)与扇区数量均左移12位,即乘4096。然而这两个数据乘4096的原因不一样。扇区逻辑区块地址乘4096是为了将扇区逻辑区块地址转化位扇区物理地址,而扇区数量乘4096是为了将扇区数量转化为扇区数据读取数量。
PS:LBA是非常单纯的一种定址模式﹔从0开始编号来定位区块,第一区块LBA=0,第二区块LBA=1,依此类推。这种定址模式取代了原先操作系统必须面对存储设备硬件构造的方式。
2.3.1.5 磁盘存储介质数据写入接口修改对比
如图8所示:
图8.diskio.c文件磁盘存储介质数据写入接口修改对比
需要对磁盘存储介质的硬件数据写入移植至此文件内的固定接口。具体操作为在switch (pdrv) 内添加一条分支,调用SPI_FLASH_Write()函数进行写入。其中写入数据的存储地址为*buff,写入数据的扇区逻辑位号为sector,写入数据的扇区数量为count。至于为什么向左移12位,与读取数据相同,都是将扇区逻辑区块地址转化为扇区物理地址,将扇区数量转化为扇区数据写入数量。
2.3.1.6 磁盘存储介质信息接口修改对比
如图9所示:
图9.diskio.c文件磁盘存储介质信息接口修改对比
需要对磁盘存储介质的硬件信息查询移植至此文件内的固定接口。具体操作为在switch (pdrv) 内添加一条分支,在其中添加一个switch,检索传入的cmd,需要对cmd建立3条分支,分别为GET_SECTOR_COUNT、GET_SECTOR_SIZE与GET_BLOCK_SIZE,分别将物理扇区总数量、物理扇区大小与擦除块数量返回给*buff并返回RES_OK即可。
2.3.1.7 磁盘存储介质写入时间函数
在FATFS文件系统中并不附带获取时间的函数接口,但是创建文件、修改文件等操作都需要获取当前时间,因此需要添加一个获取当前时间的函数接口。具体操作为使用弱定义,定义FATFS文件系统中的获取时间函数内容
__weak DWORD get_fattime(void) // 获取时间
{
return ((DWORD)(2024-1980)<<25) // 设置年份为2024
| ((DWORD)1<<21) // 设置月份为1
| ((DWORD)1<<16) // 设置日期为1
| ((DWORD)1<<11) // 设置小时为1
| ((DWORD)1<<5) // 设置分钟为1
| ((DWORD)1<<1); // 设置秒数为1
}
FATFS采用时间戳的方式来记录时间,具体格式如图10所示:
图10.FATFS文件系统时间戳格式
2.3.2 ffconf.h文件修改对比
ffconf.h文件为FATFS文件系统配置文件,其中定义了FATFS文件系统的配置参数。
2.3.2.1 文件系统格式化宏定义修改对比
如图11所示:
图11.文件系统格式化宏定义修改对比
这个选项会打开f_mkfs()函数,允许对文件系统格式化,或在没有文件系统的情况下建立文件系统
2.3.2.2 文件系统命名格式与命名空间宏定义修改对比
如图12所示:
图12.文件系统命名格式与命名空间宏定义修改对比
由于FATFS文件系统默认命名为日文,需要将FF_CODE_PAGE的值修改,以支持中文命名。FF_USE_LEN决定了实现长文件支持所使用的内存方式,0为不使用LFN,1为使用LFN,但没有线程安全,2为使用LFN并使用堆内存,3为使用LFN并使用栈内存。
2.3.2.3 文件系统驱动卷数量与最大扇区内存修改对比
如图13所示:
图13.文件系统驱动卷数量与最大扇区内存修改对比
修改FF_VOLUMES的值,以便支持多个卷。修改FF_MAX_SS的值,以便支持更大的扇区。
2.3.2.5 文件系统时间戳宏定义修改对比
如图14所示:
图14.文件系统时间戳宏定义修改对比
FF_FS_NORTC=1表示使用时间函数,FF_FS_NORTC=-1表示不使用时间函数。
3. 移植后main文件使用演示
首先需要引用“ff.h”头文件,然后定义如下全局变量:
FATFS fs;
FIL fnew;
FRESULT res_flash;
UINT fnum;
由于FATFS文件系统的结构体都比较大,在main函数中定义会导致堆栈溢出。
然后定义一些临时变量,用于存储文件名和文件内容以及FATFS文件系统运行状态:
BYTE buffer[4096] = {0};
BYTE textFileBuffer[] = "ABCDEFG";
uint8_t c[256] = {0};
我所使用的开发板为野火STM32F103指南者开发板,驱动卷命名为"3",具体执行代码如下所示
int main()
{
HSE_SetSysClock(RCC_PLLMul_9); // 设置系统时钟为9倍,72MHz
Usart_init(); // 初始化串口
USART_SendString(USART1,"Systeam is OK.");
res_flash = f_mount(&fs,"3:",1); // 挂载文件系统
USART_SendByte(USART1,res_flash);
if(res_flash == FR_NO_FILESYSTEM) // 检测是否存在文件系统
{
res_flash = f_mkfs("3:",NULL,buffer,4096); // 创建文件系统
if(res_flash == FR_OK) // 判断是否创建成功
{
USART_SendString(USART1,"FATFS has been mkf.");
res_flash = f_mount(NULL,"3:",0); // 卸载文件系统
res_flash = f_mount(&fs,"3:",1); // 重新挂载文件系统
}
else // 创建失败
{
USART_SendString(USART1,"FATFS mkf filed.");
USART_SendByte(USART1,res_flash);
while(1) // 死循环
{
}
}
}
else if(res_flash !=FR_OK) // 挂载失败
{
USART_SendString(USART1,"mount ERROR.");
while(1) // 死循环
{
}
}
else // 挂载成功
{
USART_SendString(USART1,"mount OK.");
}
res_flash = f_open(&fnew,"3:ABC.txt",FA_CREATE_ALWAYS | FA_WRITE); // 创建文件
USART_SendByte(USART1,res_flash);
if(res_flash == FR_OK) // 判断是否创建成功
{
USART_SendString(USART1,"File open is OK.");
}
res_flash = f_write(&fnew,"ABCDEFG",7,&fnum); // 写入数据
if(res_flash == FR_OK) // 判断是否写入成功
{
USART_SendString(USART1,"File write is OK.");
}
else // 写入失败
{
USART_SendByte(USART1,res_flash);
}
f_close(&fnew); // 关闭文件
if(res_flash == FR_OK) // 判断是否关闭成功
{
USART_SendString(USART1,"File close is OK.");
}
else // 关闭失败
{
USART_SendByte(USART1,res_flash);
}
res_flash = f_unmount("3:"); // 卸载文件系统
USART_SendByte(USART1,res_flash);
res_flash = f_mount(&fs,"3:",1); // 重新挂载文件系统
USART_SendByte(USART1,res_flash); // 判断是否重新挂载成功
res_flash = f_open(&fnew,"3:ABC.txt",FA_OPEN_EXISTING | FA_READ); // 打开文件
if(res_flash == FR_OK) // 判断是否打开成功
{
USART_SendString(USART1,"File open is OK.");
USART_SendString(USART1,c);
}
else // 打开失败
{
USART_SendByte(USART1,res_flash);
}
res_flash = f_read(&fnew,c,7,&fnum); // 读取文件内容
if(res_flash == FR_OK) // 判断是否读取成功
{
USART_SendString(USART1,"File read is OK.");
USART_SendString(USART1,c);
}
else // 读取失败
{
USART_SendByte(USART1,res_flash);
}
f_close(&fnew); // 关闭文件
res_flash = f_unmount("3:"); // 卸载文件系统
USART_SendByte(USART1,res_flash);
if(res_flash == FR_OK) // 判断是否卸载成功
{
USART_SendString(USART1,"unmount OK.");
}
while(1){
}
}
烧录结果
如图15所示:
图15.烧录结果