当前位置: 首页 > news >正文

FATFS文件系统原理及其移植详解

一、FATFS简介

FATFS 是一个完全免费开源的 FAT/exFAT 文件系统模块,专门为小型的嵌入式系统而设计。它完全用标准 C 语言(ANSI C C89)编写,所以具有良好的硬件平台独立性,只需做简单的修改就可以移植到 8051、PIC、AVR、ARM、Z80、RX 等系列单片机上。它支持 FATl2、FATl6 和 FAT32,支持多个存储媒介;有独立的缓冲区,可以对多个文件进行读/写,并特别对 8 位单片机和 16 位单片机做了优化。

1. FAT文件系统布局

FAT32文件系统布局

  • 系统引导扇区:引导程序,以及文件系统信息(扇区字节数/每簇扇区数/保留扇区数等)。
  • 文件分配表:记录文件存储中簇与簇之间连接的信息。
  • 根目录:存在所有文件和子目录信息(文件名/文件夹名/创建时间/文件大小)。
  • 数据区:文件等数据存放地方,占用大部分的磁盘空间。

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
编译测试
移植完成
调试问题

如图所示,开发者首先需要配置 ffconf.h ,然后编写 diskio.c ,接着编译并测试文件系统。如果测试失败,则需要回到步骤C进行调试。

二、FATFS模块的层次结构

FATFS模块的层次结构图

  • 底层接口(Low level device controls)
    • FatFs 通过硬件抽象层与具体的存储设备交互,即磁盘接口。支持多种存储设备媒介(SD 卡、USB、NAND、NOR Flash)。
    • 提供各种存储媒介的读/写接口(disk_read()、disk_write())
    • 提供给文件创建、修改时间的实时时钟,需要我们根据平台和存储介质编写移植代码。
  • 缓存管理层
    • FatFs 会在 RAM 中保留一个扇区缓冲区,用于加速文件读写操作,将块设备的读写操作优化为扇区大小。
  • FATFS 文件系统层(FatFs Module)
    • 实现了 FAT 文件读/写协议,主要由ff.cff.h组成。使用者一般不用修改,使用时将头文件直接包含进去。
    • 主要的数据结构
      • 文件控制块(FIL):表示一个已打开的文件,包含文件的状态、文件的指针、当前位置等信息。
      • 目录控制块(DIR):表示一个已打开的目录,包含目录的结构、当前读取位置等。
      • 文件系统对象(FATFS):用于表示整个文件系统的状态,包括当前挂载的信息、文件系统类型、扇区大小等。
      • 文件分配表(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” 接口实现步骤大体如下:

  1. 初始化存储介质和相应的硬件。
  2. 实现 disk_initialize 函数,用于初始化存储介质。
  3. 实现 disk_read 和 disk_write 函数,分别用于读写操作。
  4. 实现 disk_ioctl 函数,用于执行控制命令。

在实际的嵌入式系统开发过程中,开发者需要根据硬件的特性和存储介质的规格来定制这些函数,确保文件系统能够高效且正确地工作。

三、FATFS的移植步骤

FATFS 模块在移植的时候,我们一般只需要修改 2 个文件,即 ffconf.h 和 diskio.c。

1. 准备工作

1.1 硬件需求分析

硬件平台的特性直接影响到移植的难易程度和最终效果。硬件需求分析主要包括以下几点:

  1. CPU架构 :了解目标平台使用的CPU架构(如ARM, MIPS等),因为FatFS对不同架构的支持程度不同。
  2. 存储介质 :确认存储介质的类型(如NOR Flash, NAND Flash, SD卡等),因为不同的存储介质可能需要不同的驱动程序。
  3. 内存大小 :评估目标硬件的RAM大小,以确保系统有足够的内存运行FatFS及其堆栈。
  4. 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文件夹,里面就是我们所要的源文件。

FATFS的源码文件介绍

  • ffsystem.c 通常是一个可选文件,提供了操作系统依赖的函数,这些函数用于动态内存分配和多任务环境中的互斥锁管理,只有当启用 FF_FS_REENTRANT 配置时,才需要移植,不启用时不用添加进工程目录。

1.3 移植工具和环境搭建

  1. 交叉编译器 :获取对应目标平台的交叉编译器,如arm-none-eabi-gcc。
  2. 开发板和调试器 :准备目标硬件平台的开发板以及必要的调试工具,如JTAG/SWD调试器。
  3. 集成开发环境 :选择一个合适的IDE进行代码编辑和编译,如KEIL或VS Code。
  4. 版本控制系统 :使用版本控制系统管理代码,如Git,保证代码的安全性和易于团队协作。

2. 配置文件修改(ffconf.h)

配置文件 ffconf.h 中的各项设置决定了FatFS的行为,根据平台特性和具体disk进行必要的修改是关键步骤。以下是部分关键配置项:

FATFS配置项

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个宏的取值可以忽略。

    get_fattime函数

FF_CODE_PAGE 的配置

  • 该选项用于选择目标系统的文件系统语言

FF_CODE_PAGE的配置

  • 下面示例设置语言类型为简体中文
#define FF_CODE_PAGE	936

FF_USE_STRFUNC 的配置

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)。

FATFS默认存储设备配置

#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. 初始化磁盘驱动器函数

disk_initialize函数

//假设我们安装的是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读取检测(闪存设备),实现状态信息返回

disk_status函数

//假设我们安装的是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. 从磁盘驱动器读扇区函数

disk_read函数

/*-------------------------------------------------------------*/
/* 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. 向磁盘驱动器写扇区函数

disk_write函数

/*------------------------------------------------------------*/
/* 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. 控制设备实现指定功能函数

disk_ioctl函数

/*------------------------------------------------------------*/
/* 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的结果,来进行判断。

http://www.xdnf.cn/news/1134343.html

相关文章:

  • 042_封装的实现(属性私有化 / 方法公开)
  • Gradle vs Maven:构建工具世纪对决 —— 像乐高积木与标准模型之间的选择艺术
  • LeetCode经典题解:141、判断链表是否有环
  • LLM指纹底层技术——模型架构
  • mysql 慢sql优化篇
  • OSPF作业
  • 开源 python 应用 开发(六)网络爬虫
  • 从零开发足球比分APP:REST API与WebSocket的完美搭配
  • 数据结构--准备知识
  • Git问题排查与故障解决详解
  • 汽车数字化——65页大型汽车集团企业IT信息化(管理架构、应用架构、技术架构)战略规划【附全文阅读】
  • 【代码】Matlab鸟瞰图函数
  • kimi-k2-api使用示例
  • 技术分享:如何用规则定义生成自定义文件时间戳
  • 面向向量检索的教育QA建模:九段日本文化研究所日本语学院的Prompt策略分析(6 / 500)
  • 【MAC】nacos 2.5.1容器docker安装
  • Python中的列表list、元组(笔记)
  • Vue在线预览Excel和Docx格式文件
  • CentOS网络配置与LAMP环境搭建指南
  • VUEX 基础语法
  • 如何解决WordPress数据库表损坏导致的错误
  • C语言 --- 函数递归
  • 蓝光三维扫描技术:汽车轮毂轴承模具检测的高效解决方案
  • Linux 驱动中 Timer / Tasklet / Workqueue 的作用与对比
  • socket和websocket的区别
  • LeafletJS 进阶:GeoJSON 与动态数据可视化
  • rocky8 --Elasticsearch+Logstash+Filebeat+Kibana部署【7.1.1版本】
  • 【开源.NET】一个 .NET 开源美观、灵活易用、功能强大的图表库
  • MAC 苹果版Adobe Photoshop 2019下载及保姆级安装教程!!
  • 信而泰×DeepSeek:AI推理引擎驱动网络智能诊断迈向 “自愈”时代