FATFS文件系统原理及其移植详解
一、FATFS简介
FATFS 是一个完全免费开源的 FAT/exFAT 文件系统模块,专门为小型的嵌入式系统而设计。它完全用标准 C 语言(ANSI C C89)编写,所以具有良好的硬件平台独立性,只需做简单的修改就可以移植到 8051、PIC、AVR、ARM、Z80、RX 等系列单片机上。它支持 FATl2、FATl6 和 FAT32,支持多个存储媒介;有独立的缓冲区,可以对多个文件进行读/写,并特别对 8 位单片机和 16 位单片机做了优化。
1. FAT文件系统布局
- 系统引导扇区:引导程序,以及文件系统信息(扇区字节数/每簇扇区数/保留扇区数等)。
- 文件分配表:记录文件存储中簇与簇之间连接的信息。
- 根目录:存在所有文件和子目录信息(文件名/文件夹名/创建时间/文件大小)。
- 数据区:文件等数据存放地方,占用大部分的磁盘空间。
FAT 文件系统用 “簇” 作为数据单元,一个 “簇” 由一组连续的扇区组成,而一个扇区的大小为 512 字节。所有的簇从 2 开始进行编号,每个簇都有自己的地址编号,用户文件和数据都存储在簇中。
2. 文件系统类型及选择
在了解了FatFS文件系统的基本概念和用途之后,接下来我们将深入探讨FatFS支持的不同文件系统类型及其选择依据。我们将分析FAT12、FAT16和FAT32文件系统的结构特点,并探讨如何根据不同的需求和资源情况选择合适的文件系统。此外,我们还将分析FatFS如何实现高内存效率以及如何保持其强移植性。
2.1 FatFS支持的文件系统类型
2.1.1 FAT12 简介
FAT12(File Allocation Table 12)是最早的文件系统类型,它在1981年随着IBM的个人计算机一起发布。FAT12主要被设计用于软盘驱动器,并支持小容量存储设备。由于其索引表项仅占12位,所以其最大支持的存储容量受到限制,通常不超过16MB。
FAT12的核心优势在于其简单性和广泛兼容性,使其适用于对容量要求不高的环境。然而,由于其存储效率低下和容量大小限制,它已被逐渐淘汰。
2.1.2 FAT16 简介
FAT16是对FAT12的改进,它在1984年被引入到MS-DOS 3.0中。FAT16的索引表项使用了16位,这大大增加了单个文件的最大大小以及文件系统的总容量,使其能够支持最大2GB的存储空间。
FAT16相比于FAT12的优势在于更大的文件和存储空间支持,使其能够用于更多类型的存储设备。它广泛应用于早期的硬盘、光盘、闪存驱动器等。FAT16的文件名采用8.3格式,即最多8个字符的文件名,加上3个字符的文件扩展名。
2.1.3 FAT32文件系统优势
FAT32是在FAT16的基础上进一步演进的文件系统,首次出现在Windows 95的OSR2版本中。FAT32将索引表项扩展到32位,不仅支持更大的单个文件大小和存储空间(最大32GB),而且还提供了更好的存储效率和更佳的性能。
FAT32的主要优势在于其兼容性、稳定性和广泛的应用。它支持长达255个字符的长文件名(VFAT),大大优于FAT16的8.3格式。由于其广泛的应用和良好的设备支持,FAT32适合各种嵌入式和消费类电子产品。
2.2 高内存效率的实现机制
2.2.1 内存优化策略
FatFS文件系统设计时,内存效率是一个重要的考量。系统采用多种内存优化策略,以减少对嵌入式设备有限内存资源的占用。
例如,为了优化内存使用,FatFS支持缓冲区的动态分配,允许文件系统根据需要分配和释放内存,而不是占用固定大小的内存。此外,FatFS的代码非常紧凑高效,以减少内存占用。对于那些运行在内存受限环境中的嵌入式设备来说,这是一项重要的优化。
2.2.2 内存使用分析
要深入了解FatFS的内存使用情况,可以参考内存映射机制。这涉及到了操作系统级别的内存管理,但是FatFS的实现允许我们对其使用的内存有一个概览。
例如,在一个典型的嵌入式系统中,FatFS会分配一块内存作为文件操作的缓冲区,这个区域的大小可以根据实际需要进行配置。这种机制允许FatFS在进行大文件读写操作时,将数据分块读入内存,减少一次性内存占用,避免内存溢出的风险。
在实际应用中,FatFS会根据文件系统的配置文件和实际应用场景,动态地分配和管理内存。
2.3 强移植性设计
为了实现高移植性,FatFS采取了多种策略:
- 首先,文件系统的大部分实现都是用C语言编写的,这确保了其能够在多种平台上编译和运行。
- 其次,FatFS提供了一系列抽象层,将硬件和平台相关的操作与核心文件系统逻辑分离开来。这使得移植到新平台变得简单,只需提供针对该平台的具体实现即可。
下面是基本移植过程流程图:
如图所示,开发者首先需要配置 ffconf.h
,然后编写 diskio.c
,接着编译并测试文件系统。如果测试失败,则需要回到步骤C进行调试。
二、FATFS模块的层次结构
- 底层接口(Low level device controls)
- FatFs 通过硬件抽象层与具体的存储设备交互,即磁盘接口。支持多种存储设备媒介(SD 卡、USB、NAND、NOR Flash)。
- 提供各种存储媒介的读/写接口(disk_read()、disk_write())
- 提供给文件创建、修改时间的实时时钟,需要我们根据平台和存储介质编写移植代码。
- 缓存管理层
- FatFs 会在 RAM 中保留一个扇区缓冲区,用于加速文件读写操作,将块设备的读写操作优化为扇区大小。
- FATFS 文件系统层(FatFs Module)
- 实现了 FAT 文件读/写协议,主要由
ff.c
和ff.h
组成。使用者一般不用修改,使用时将头文件直接包含进去。 - 主要的数据结构
- 文件控制块(FIL):表示一个已打开的文件,包含文件的状态、文件的指针、当前位置等信息。
- 目录控制块(DIR):表示一个已打开的目录,包含目录的结构、当前读取位置等。
- 文件系统对象(FATFS):用于表示整个文件系统的状态,包括当前挂载的信息、文件系统类型、扇区大小等。
- 文件分配表(FAT):用于管理文件的簇链,标识文件占用了哪些簇,以及这些簇是否已被分配、空闲或损坏。
- 实现了 FAT 文件读/写协议,主要由
- 应用层(Application)
- 使用者无需理会 FATFS 的内容结构和复杂的 FAT 协议,只需要调用 FATFS 模块提供给用户的一系列应用接口函数,如f_open,f_read,f_write和f_close等,这些函数用于用户操作文件和目录,如打开、读取、写入和关闭文件等,就像在 PC 机上读/写文件。
1. 应用层核心接口
1.1 文件和目录操作功能
FatFS 提供了一套函数库(接口),使开发者能够方便地在 FAT 文件系统上操作文件、目录,甚至是卷。
/******************************************** 1.文件操作 *******************************************/FRESULT f_open (FIL* fp, const TCHAR* path, BYTE mode); // 打开/创建一个文件
FRESULT f_close (FIL* fp); // 关闭一个打开的文件
FRESULT f_read (FIL* fp, void* buff, UINT btr, UINT* br); // 从文件中读取数据
FRESULT f_write (FIL* fp, const void* buff, UINT btw, UINT* bw); // 往文件中写入数据TCHAR* f_gets (TCHAR* buff, int len, FIL* fp); // 读一个字符串
int f_putc (TCHAR c, FIL* fp) // 写一个字符
int f_puts (const TCHAR* str, FIL* cp); // 写一个字符串
int f_printf (FIL* fp, const TCHAR* str, ...); // 写一个格式化字符串FRESULT f_lseek (FIL* fp, FSIZE_t ofs); // 文件指针定位
FRESULT f_truncate (FIL* fp); // 截断文件
FRESULT f_sync (FIL* fp); // 刷新文件
FRESULT f_stat (const TCHAR* path, FILINFO* fno); // 获取文件信息FRESULT f_findfirst (DIR* dp, FILINFO* fno, const TCHAR* path, const TCHAR* pattern); // 获取第一个文件
FRESULT f_findnext (DIR* dp, FILINFO* fno); // 获取下一个文件
FRESULT f_expand (FIL* fp, FSIZE_t fsz, BYTE opt); // 扩展文件
FRESULT f_forward (FIL* fp, UINT(*func)(const BYTE*,UINT), UINT btf, UINT* bf); // 将数据转发到流#define f_tell(fp) ((fp)->fptr) // 获取文件指针位置
#define f_size(fp) ((fp)->obj.objsize) // 获取文件大小
#define f_eof(fp) ((int)((fp)->fptr == (fp)->obj.objsize)) // 判断文件是否到达文件末尾
#define f_rewind(fp) f_lseek((fp), 0) // 移动文件指针到文件开头/******************************************** 2.目录操作 *******************************************/
FRESULT f_opendir (DIR* dp, const TCHAR* path); // 打开一个目录
FRESULT f_closedir (DIR* dp) // 关闭一个打开的目录
FRESULT f_readdir (DIR* dp, FILINFO* fno); // 读取目录条目
FRESULT f_mkdir (const TCHAR* path); // 创建一个目录
FRESULT f_unlink (const TCHAR* path); // 删除一个文件或目录
FRESULT f_rename (const TCHAR* path_old, const TCHAR* path_new); // 重命名/移动一个文件或目录
FRESULT f_chmod (const TCHAR* path, BYTE attr, BYTE mask); // 修改文件/目录属性
FRESULT f_utime (const TCHAR* path, const FILINFO* fno); // 更新文件/目录修改时间
FRESULT f_chdir (const TCHAR* path); // 改变当前目录
FRESULT f_getcwd (TCHAR* buff, UINT len); // 获取当前目录#define f_rmdir(path) f_unlink(path) // 删除一个目录
#define f_rewinddir(dp) f_readdir((dp), 0) // 移动目录指针到目录开头/****************************************** 3.卷管理操作 *****************************************/FRESULT f_mount (FATFS* fs, const TCHAR* path, BYTE opt); // 注册/注销一个工作区
FRESULT f_mkfs (const TCHAR* path, const MKFS_PARM* opt, void* work, UINT len); // 格式化,创建一个文件系统
FRESULT f_getfree (const TCHAR* path, DWORD* nclst, FATFS** fatfs); // 获取磁盘信息以及空闲簇数量
FRESULT f_chdrive (const TCHAR* path) // 改变当前驱动器
FRESULT f_getlabel (const TCHAR* path, TCHAR* label, DWORD* vsn); // 获取卷标
FRESULT f_setlabel (const TCHAR* label); // 设置卷标
FRESULT f_fdisk (BYTE pdrv, const LBA_t ptbl[], void* work); // 磁盘分区
FRESULT f_setcp (WORD cp); // 设置代码页#define f_error(fp) ((fp)->err) // 获取错误代码
#define f_unmount(path) f_mount(0, path, 0) // 注销一个工作区
2. FAT模块层(ff.c)
在 FatFS 的设计中,硬件 I/O 操作与文件系统逻辑被完全分离开来,这是 FatFS 优秀的架构设计之一。这种设计使得 FatFS 可以在多种不同的硬件平台和存储介质上运行,而无需针对每一种硬件进行大量的修改。
硬件I/O抽象层为文件系统提供了一个统一的接口,这些接口包括初始化( disk_initialize )、读取扇区( disk_read )、写入扇区( disk_write )等。通过抽象层,我们可以更方便地将 FatFS 应用于不同的硬件平台,只需要按照 FatFS 所需的接口实现相应的硬件I/O函数即可。
例如,FAT为硬件读定义了如下接口,用户通过实现 disk_read
函数,就可以将任何存储介质与 FatFS 连接起来。同样的方式,也可以实现写入操作的 disk_write
、同步操作的 disk_sync
等函数,将硬件的细节从文件系统的实现中分离出来。
DRESULT disk_read (BYTE pdrv, // 驱动器号BYTE* buff, // 数据缓冲区LBA_t sector, // 起始扇区号UINT count // 要读取的扇区数量
);
3. 硬件接口层(diskio.c)
diskio.c
是与硬件存储设备通信的接口层。它定义了一系列与底层磁盘I/O操作相关的函数,比如读取、写入扇区等。编写 diskio.c
时,需要根据实际硬件平台的存储设备特性,实现这些函数。
FatFS 文件系统中的 “disk I/O” 接口实现步骤大体如下:
- 初始化存储介质和相应的硬件。
- 实现 disk_initialize 函数,用于初始化存储介质。
- 实现 disk_read 和 disk_write 函数,分别用于读写操作。
- 实现 disk_ioctl 函数,用于执行控制命令。
在实际的嵌入式系统开发过程中,开发者需要根据硬件的特性和存储介质的规格来定制这些函数,确保文件系统能够高效且正确地工作。
三、FATFS的移植步骤
FATFS 模块在移植的时候,我们一般只需要修改 2 个文件,即 ffconf.h 和 diskio.c。
1. 准备工作
1.1 硬件需求分析
硬件平台的特性直接影响到移植的难易程度和最终效果。硬件需求分析主要包括以下几点:
- CPU架构 :了解目标平台使用的CPU架构(如ARM, MIPS等),因为FatFS对不同架构的支持程度不同。
- 存储介质 :确认存储介质的类型(如NOR Flash, NAND Flash, SD卡等),因为不同的存储介质可能需要不同的驱动程序。
- 内存大小 :评估目标硬件的RAM大小,以确保系统有足够的内存运行FatFS及其堆栈。
- I/O接口 :分析硬件平台提供的I/O接口(如SPI, I2C, USB等),这关系到"disk I/O"接口的实现。
1.2 源码下载
- 从官网(http://elm-chan.org/fsw/ff/00index_e.html)下载最新版本的 FATFS 软件包,
- 解压后可以得到两个文件夹:
- 一个是documents文件夹,这里是FATFS的一些使用文档和说明,以后在文件编程的时候可以查看该文档;
- 另一个是src文件夹,里面就是我们所要的源文件。
- ffsystem.c 通常是一个可选文件,提供了操作系统依赖的函数,这些函数用于动态内存分配和多任务环境中的互斥锁管理,只有当启用 FF_FS_REENTRANT 配置时,才需要移植,不启用时不用添加进工程目录。
1.3 移植工具和环境搭建
- 交叉编译器 :获取对应目标平台的交叉编译器,如arm-none-eabi-gcc。
- 开发板和调试器 :准备目标硬件平台的开发板以及必要的调试工具,如JTAG/SWD调试器。
- 集成开发环境 :选择一个合适的IDE进行代码编辑和编译,如KEIL或VS Code。
- 版本控制系统 :使用版本控制系统管理代码,如Git,保证代码的安全性和易于团队协作。
2. 配置文件修改(ffconf.h)
配置文件 ffconf.h
中的各项设置决定了FatFS的行为,根据平台特性和具体disk进行必要的修改是关键步骤。以下是部分关键配置项:
FF_FS_NORTC 的配置
FF_FS_NORTC
配置项决定是否启用时间戳功能(记录文件创建或修改时间,若fatfs仅配置为只读,则该选项不必启用)
-
不启用:当你并不在意文件的创建或修改时间的时候,可以使能此配置项,此时每一个文件的时间戳将使用一个固定的时间,该时间用接下来的三个宏定义设置(FF_NORTC_MON、 FF_NORTC_MDAY、 FF_NORTC_YEAR)。下面例子不启用时间戳功能,通用固定的时间戳2025年1月1号
#define FF_FS_NORTC 1 #define FF_NORTC_MON 1 #define FF_NORTC_MDAY 1 #define FF_NORTC_YEAR 2025
-
启用:启用后,你需要自行编写
get_fattime()
函数,来为fatfs提供实时时间,此时后面的3个宏的取值可以忽略。
FF_CODE_PAGE 的配置
- 该选项用于选择目标系统的文件系统语言
- 下面示例设置语言类型为简体中文
#define FF_CODE_PAGE 936
FF_USE_STRFUNC 的配置
- 下面示例设置支持字符串类操作,支持
f_printf()
函数打印长整型、浮点型,采用UTF-8字符集。
#define FF_USE_STRFUNC 1
#define FF_PRINT_LLI 1
#define FF_PRINT_FLOAT 1
#define FF_STRF_ENCODE 3
FF_VOLUMES 的配置
- 设置 FATFS 支持的逻辑设备数目,一般取值1-10之间。 如果使用的是单一分区,可能需要设置
FF_VOLUMES
为1。
3. 修改sidkio.c文件中的5个函数
在开始之前,我们首先要根据自己项目的实际情况,添加硬件初始化及磁盘操作的相关头文件,然后将需要安装Fatfs文件系统的存储设备分配物理驱动盘号(Physical drive nmuber)。
#define DEV_SD 0 //为SD卡添加驱动号0
底层磁盘 I/O 模块并不是 FATFS 的一部分,并且必须由用户提供。因为 FATFS 模块完全与磁盘 I/O 层分开,因此需要用户自己根据具体的存储设备来提供这些函数,来实现底层物理磁盘的操作。这些函数一般有 5 个,全部在 diskio.c 里面。
- disk_status(): 获取磁盘状态。
- disk_initialize(): 初始化磁盘(通常是 SPI 或 SD 卡的初始化)。
- disk_read(): 从磁盘读取数据。
- disk_write(): 向磁盘写入数据。
- disk_ioctl(): 控制磁盘的操作,比如获取磁盘扇区、块大小、擦除等。
1. 初始化磁盘驱动器函数
//假设我们安装的是SD卡设备
DSTATUS disk_initialize (BYTE pdrv)
{int result;switch (pdrv) {case DEV_SD :result = SD_Init();if (result) {return STA_NOINIT;}return RES_OK;default :break;}return STA_NOINIT;
}
2. 获取磁盘状态函数
在disk_status()中实现磁盘设备ID读取检测(闪存设备),实现状态信息返回
//假设我们安装的是SPI Flash设备
DSTATUS disk_status (BYTE pdrv)
{DSTATUS stat;int result;//根据实际disk状况,返回对应状态switch(pdrv){case DEV_USB:case DEV_MMC:case DEV_USB:case DEV_RAM:return STA_NOINIT;case DEV_SPI:result = SFC_ReadJEDEC();if((result != 0) && (result != ~0))stat = 0;elsestat = STA_NOINIT;return stat;}return STA_NOINIT;
}
3. 从磁盘驱动器读扇区函数
/*-------------------------------------------------------------*/
/* Read Sector(s)
/*-------------------------------------------------------------*/
DRESULT disk_read (BYTE pdrv, /* Physical drive nmuber to identify the drive */BYTE *buff, /* Data buffer to store read data */LBA_t sector, /* Start sector in LBA */UINT count) /* Number of sectors to read */
{int result;switch (pdrv) {case DEV_SD :result = SD_ReadData(&g_sd_handler, sector, count, buff);if (result){return RES_PARERR;}}return RES_OK;
}
4. 向磁盘驱动器写扇区函数
/*------------------------------------------------------------*/
/* Write Sector(s) */
/*------------------------------------------------------------*/
DRESULT disk_write (BYTE pdrv, /* Physical drive nmuber to identify the drive */const BYTE *buff, /* Data to be written */LBA_t sector, /* Start sector in LBA */UINT count ) /* Number of sectors to write */
{int result;switch (pdrv) {case DEV_SD :result = SD_WriteData(&g_sd_handler, sector, count, (uint8_t *)buff);if (result){return RES_PARERR;}}return RES_OK;
}
5. 控制设备实现指定功能函数
/*------------------------------------------------------------*/
/* Miscellaneous Functions
/*--------------------------------------------------------------*/
DRESULT disk_ioctl (BYTE pdrv, /* Physical drive nmuber (0..) */BYTE cmd, /* Control code */void *buff) /* Buffer to send/receive control data */
{DRESULT result = RES_ERROR;switch (pdrv) {case DEV_SD :switch (cmd){case CTRL_SYNC:result = RES_OK;break;case GET_SECTOR_SIZE:*(DWORD*)buff = 512;result = RES_OK;break;;case GET_BLOCK_SIZE:*(DWORD*)buff = g_sd_handler.SdCard.BlockSize;result = RES_OK;break;case GET_SECTOR_COUNT:*(DWORD*)buff = g_sd_handler.SdCard.BlockNbr * g_sd_handler.SdCard.BlockSize / 512;result = RES_OK;break;default:result = RES_PARERR;}}return result;
}
6. 时间接口函数
在diskio.c文件的最后我们还得提供获取时间的函数,因为ff.c中调用了它,用于记录文件的创建,修改时间,需要用户实现,否则会发生编译出错。对于这部分函数,我们直接返回0即可。
DWORD get_fattime(void)
{return 0;
}
4. 编写测试代码
移植完FatFs后,需要通过f_mount()函数实现对文件系统的挂载,而后ff.c所提供的标准文件操作接口来实现文件读写。
#include "ff.h"int main(void)
{FATFS fs;FIL fil;FRESULT result;char writeData[] = "Hello, world!\r\n";char readData[100] = {0};uint32_t write_data = 0, read_count = 0;uint32_t fSize = 0;HAL_Init();System_Clock_Init(8, 336, 2, 7);Delay_Init();HAL_NVIC_SetPriorityGrouping(NVIC_PRIORITYGROUP_4);UART_Init(&g_usart1_handle, USART1, 115200);// 挂载SD卡设备// 第一个参数是挂载点,第二个参数是挂载设备编号,第三个参数是是立即挂载result = f_mount(&fs, "0:", 1);if (result){printf("mount fail %d\r\n", result);}else{printf("mount success\r\n");}// 打开文件// 第一个参数是文件指针,第二个参数是文件路径,第三个参数是文件打开模式result = f_open(&fil, "0:test.txt", FA_READ | FA_WRITE | FA_OPEN_ALWAYS);if (result){printf("open fail %d\r\n", result);}else{printf("open success\r\n");}// 写入数据f_write(&fil, writeData, sizeof(writeData), (UINT *)&write_data);// 写完文件的读写指针已经到末尾,我们需要重新定位到文件开头f_lseek(&fil, 0);// 获取文件大小fSize = f_size(&fil);// 读取数据// 第一个参数是文件指针,第二个参数是读取的数据,第三个参数是要读取的数据长度,第四个参数保存实际读取的数据长度f_read(&fil, readData, fSize, (UINT *)&read_count);if (read_count){printf("read count: %ld\r\n", read_count);printf("data:\r\n");printf("%s\r\n", readData);}// 关闭文件f_close(&fil);while (1){}return 0; //Never run here
}
四、Fatfs源码解析
重要数据结构
-
FATFS结构体:记录FATFS文件系统本身相关的一些参数
typedef struct {BYTE fs_type; /* FAT sub-type (0:Not mounted) */BYTE drv; /* Physical drive number */BYTE csize; /* 簇的大小(即包含几个扇区,取值为1、2、4...128,一般取8;给文件分配簇的时候一次分配3个簇 */BYTE n_fats; /* Number of FAT copies (1 or 2) */BYTE wflag; /* win[] flag (b0:dirty) */BYTE fsi_flag; /* FSINFO flags (b7:disabled, b0:dirty) */WORD id; /* File system mount ID */WORD n_rootdir; /* Number of root directory entries (FAT12/16) */#if _MAX_SS != _MIN_SSWORD ssize; /* 扇区大小,即占多少字节,可取512、1024、2048、4096 */ #endif#if _FS_REENTRANT_SYNC_t sobj; /* Identifier of sync object */ #endif#if !_FS_READONLYDWORD last_clust; /* Last allocated cluster 最后写入的一个文件的最后一个簇的簇地址,标记的是下一个文件写进来的时候应该写在哪个位置上*/DWORD free_clust; /* Number of free clusters 这里标记的是剩余可用簇的多少*/ #endif#if _FS_RPATHDWORD cdir; /* Current directory start cluster (0:root) */ #endifDWORD n_fatent; /* Number of FAT entries, = number of clusters + 2 */DWORD fsize; /* 一个FAT分区共有多少个扇区*/DWORD volbase; /* Volume start sector 这里标志的是逻辑0扇区,即逻辑0扇区相对于物理0扇区偏移的扇区量,对于我的SD卡,这里应该是0xE3*/DWORD fatbase; /* FAT start sector FAT开始扇区*/DWORD dirbase; /* Root directory start sector (FAT32:Cluster#) 根目录所在的簇*/DWORD database; /* Data start sector 根目录所在的扇区数 */DWORD winsect; /* Current sector appearing in the win[] */BYTE win[_MAX_SS]; /* Disk access window for Directory, FAT (and file data at tiny cfg) */ } FATFS;
-
FIL结构体:记录和特定文件有关的参数;
-
DIR结构体:记录和目录有关的参数;
挂载函数f_mount()解析
对于f_mount的代码,作用是挂载SD卡,即把硬件与软件的连接起来。主要调用find_volume()
函数来完成的。对于该函数,首先需要注意的一个片段是:
bsect = 0;
fmt = check_fs(fs, bsect); /* Load sector 0 and check if it is an FAT boot sector as SFD */
if (fmt == 1 || (!fmt && (LD2PT(vol)))) { /* Not an FAT boot sector or forced partition number */for (i = 0; i < 4; i++) { /* Get partition offset */pt = fs->win + MBR_Table + i * SZ_PTE;br[i] = pt[4] ? LD_DWORD(&pt[8]) : 0;}i = LD2PT(vol); /* Partition number: 0:auto, 1-4:forced */if (i) i--;do { /* Find an FAT volume */bsect = br[i];fmt = bsect ? check_fs(fs, bsect) : 2; /* Check the partition */} while (!LD2PT(vol) && fmt && ++i < 4);
}if (fmt == 3) return FR_DISK_ERR; /* An error occured in the disk I/O layer */
if (fmt) return FR_NO_FILESYSTEM; /* No FAT volume is found */
这一段代码先检测了物理地址的零扇区处。对于部分SD卡,物理地址等于逻辑地址,即找不到MBR区域。
对于另外一部分物理地址和逻辑地址不重合的SD卡,这段代码先通过找物理地址零扇区处记录了逻辑扇区偏移量的那16个字节。
换句话说,这段代码的第一个功能是,找到逻辑扇区相对于物理扇区的偏移量,再把该偏移量赋值给bsect。
当找到逻辑地址的零扇区处后,它需要做的事情就是,需要检测这究竟是什么类型的文件系统。因此,在check_fs()
里面,我们可以看到这样的代码:
BYTE check_fs ( /* 0:Valid FAT-BS, 1:Valid BS but not FAT, 2:Not a BS, 3:Disk error */FATFS* fs, /* File system object */DWORD sect /* Sector# (lba) to check if it is an FAT boot record or not */)
{fs->wflag = 0; fs->winsect = 0xFFFFFFFF; /* Invaidate window */if (move_window(fs, sect) != FR_OK) /* Load boot record */return 3;if (LD_WORD(&fs->win[BS_55AA]) != 0xAA55) /* Check boot record signature (always placed at offset 510 even if the sector size is >512) */return 2; if ((LD_DWORD(&fs->win[BS_FilSysType]) & 0xFFFFFF) == 0x544146) /* Check "FAT" string */return 0;if ((LD_DWORD(&fs->win[BS_FilSysType32]) & 0xFFFFFF) == 0x544146) /* Check "FAT" string */return 0; return 1;
}
这段代码首先是把DBR区域的数据取出来,再进行判断这段代码是否合法,判断分为两个部分:
- 一个是检测末尾的标识符是否为 0x55、0xAA;
- 另一个是检测文件系统是否为FAT。其中,BS_FilSysType = 54,BS_FilSysType32 = 82。
最后,根据check_fs的结果,来进行判断。