LV16-22-FatFs-01-FatFs文件系统

本文主要是FatFs文件系统相关笔记,若笔记中有错误或者不合适的地方,欢迎批评指正😃。

点击查看使用工具及版本
Windows windows11
Ubuntu Ubuntu16.04的64位版本
VMware® Workstation 16 Pro 16.2.3 build-19376536
SecureCRT Version 8.7.2 (x64 build 2214) - 正式版-2020年5月14日
开发板 正点原子 i.MX6ULL Linux阿尔法开发板
uboot NXP官方提供的uboot,NXP提供的版本为uboot-imx-rel_imx_4.1.15_2.1.0_ga(使用的uboot版本为U-Boot 2016.03)
linux内核 linux-4.15(NXP官方提供)
STM32开发板 正点原子战舰V3(STM32F103ZET6)
点击查看本文参考资料
  • 通用
分类 网址说明
官方网站https://www.arm.com/ARM官方网站,在这里我们可以找到Cotex-Mx以及ARMVx的一些文档
https://www.st.com/content/st_com/zh.htmlST官方网站,在这里我们可以找到STM32的相关文档
https://www.stmcu.com.cn/意法半导体ST中文官方网站,在这里我们可以找到STM32的相关中文参考文档
http://elm-chan.org/fsw/ff/00index_e.htmlFatFs文件系统官网
教程书籍《ARM Cortex-M3权威指南》ARM公司专家Joseph Yiu(姚文祥)的力作,中文翻译是NXP的宋岩
《ARM Cortex-M0权威指南》
《ARM Cortex-M3与Cortex-M4权威指南》
开发论坛http://47.111.11.73/forum.php开源电子网,正点原子的资料下载及问题讨论论坛
https://www.firebbs.cn/forum.php国内Kinetis开发板-野火/秉火(刘火良)主持的论坛,现也做STM32和i.MX RT
https://www.amobbs.com/index.php阿莫(莫进明)主持的论坛,号称国内最早最火的电子论坛,以交流Atmel AVR系列单片机起家,现已拓展到嵌入式全平台,其STM32系列帖子有70W+。
http://download.100ask.net/index.html韦东山嵌入式资料中心,有些STM32和linux的相关资料也可以来这里找。
博客参考http://www.openedv.com/开源网-原子哥个人博客
http://blog.chinaaet.com/jihceng0622博主是原Freescale现NXP的现场应用工程师
cortex-m-resources这其实并不算是一个博客,这是ARM公司专家Joseph Yiu收集整理的所有对开发者有用的官方Cortex-M资料链接(也包含极少数外部资源链接)
  • STM32
STM32STM32 HAL库开发实战指南——基于F103系列开发板野火STM32开发教程在线文档
STM32库开发实战指南——基于野火霸道开发板野火STM32开发教程在线文档
  • SD卡
SD Association提供了SD存储卡和SDIO卡系统规范
点击查看相关文件下载
STM32F103xx英文数据手册STM32F103xC/D/E系列的英文数据手册
STM32F103xx中文数据手册STM32F103xC/D/E系列的中文数据手册
STM32F10xxx英文参考手册(RM0008)STM32F10xxx系列的英文参考手册
STM32F10xxx中文参考手册(RM0008)STM32F10xxx系列的中文参考手册
Arm Cortex-M3 处理器技术参考手册-英文版Cortex-M3技术参考手册-英文版
STM32F10xxx Cortex-M3编程手册-英文版(PM0056)STM32F10xxx/20xxx/21xxx/L1xxxx系列Cortex-M3编程手册-英文版
SD卡相关资料——最新版本有关SD卡的一些资料可以从这里下载
SD卡相关资料——历史版本有关SD卡的一些历史版本资料可以从这里下载,比如后边看的SD卡2.0协议
SD 2.0 协议标准完整版这是一篇关于SD卡2.0协议的中文文档,还是比较有参考价值的,可以一看

一、文件系统简介

1. 直接存储的弊端?

我们前边已经学习了SPI FLASH和SD卡的读写,我们可以使用SPI FLASH直接存储数据,当需要记录字符“STM32 SPI FLASH” 时。 可以把这些文字转化成ASCII码,存储在数组中,然后调用SPI_FLASH_BufferWrite函数,把数组内容写入到SPI Flash芯片的指定地址上,在需要的时候从该地址把数据读取出来,再对读出来的数据以ASCII码的格式进行解读。

image-20230513212219299

但是,这样直接存储数据会带来极大的不便,如难以记录有效数据的位置,难以确定存储介质的剩余空间,以及应以何种格式来解读数据。就如同一个巨大的图书馆无人管理,杂乱无章地存放着各种书籍,难以查找所需的文档。想象一下图书馆的采购人员购书后,把书籍往馆内一扔,当有人来借阅某本书的时候,就不得不一本本地查找。这样直接存储数据的方式对于小容量的存储介质如 EEPROM 还可以接受,但对于 SPI Flash 芯片或者 SD 卡之类的大容量设备,我们需要一种高效的方式来管理它的存储内容。

这些管理方式即为文件系统,它是为了存储和管理数据,而在存储介质建立的一种组织结构,这些结构包括操作系统引导区、目录和文件。 常见的 windows 下的文件系统格式包括 FAT32、 NTFS、exFAT。 在使用文件系统前,要先对存储介质进行格式化。格式化先擦除原来内容,在存储介质上新建一个文件分配表和目录。这样,文件系统就可以记录数据存放的物理地址,剩余空间。

2. 文件系统是什么?

前边其实已经提到了,负责管理和存储文件信息的软件机构称为文件管理系统,简称文件系统。即在磁盘上组织文件的方法。常用的文件系统有以下几种:

  • FAT / FATFS

  • NTFS: 基于安全性的文件系统,是Windows NT所采用的独特的文件系统结构。

  • CDFS:CDFS是大部分的光盘的文件系统。

  • exFAT

3. Windows上的文件系统

上边我们知道,文件系统,就是对数据进行管理的方式。使用文件系统可有效地管理存储介质。

image-20230513212053503

磁盘的物理结构如下:

image-20230513212429380

使用文件系统时, 它为了存储和管理数据,在存储介质建立了一些组织结构,这些结构包括操作系统引导区、目录和文件。常见的windows下的文件系统格式包括FAT32、 NTFS、 exFAT。在使用文件系统前,要先对存储介质进行格式化。格式化时会在存储介质上新建一个文件分配表和目录。这样,文件系统就可以记录数据存放的物理地址,剩余空间。

4. 磁盘分区表

Windows操作系统为了便于用户对磁盘的管理。加入了磁盘分区的概念,即将一块磁盘逻辑划分为几块,它会把磁盘的分区信息记录到硬盘分区表中。在硬盘分区表中,描述了各个逻辑分区的属性,如分区开始和结束位置所在的物理地址(柱面号、扇区号),空间大小等信息。

image-20230513213050886

5. 文件系统的结构与特性

使用文件系统时,数据都以文件的形式存储。写入新文件时,先在目录中创建一个文件索引,它指示了文件存放的物理地址,再把数据存储到该地址中。当需要读取数据时,可以从目录中找到该文件的索引,进而在相应的地址中读取出数据。具体还涉及到逻辑地址、簇大小、不连续存储等一系列辅助结构或处理过程。

文件系统的存在使存取数据时,不再是简单地向某物理地址直接读写,而是要遵循它的读写格式。如经过逻辑转换,一个完整的文件可能被分开成多段存储到不连续的物理地址,使用目录或链表的方式来获知下一段的位置。

6. 文件系统的基本原理?

6.1 文件系统空间

如下图所示的文件系统的空间示意图:

image-20230513213306696

我们存储了A.TXT,B.TXT, C.TXT文件 。

6.2 目录

文件系统会有一个目录,通过这个目录记录文件的开始簇位置、大小等信息 ,如下图:

image-20230513213440862

6.3 文件分配表

假设我们刚才存储的三个文件的文件分配表如下图:

image-20230513213642798

文件 A.TXT 我们根据目录项中指定的 A.TXTt 的首簇为 2,然后找到文件分配表的第 2 簇记录,上面登记的是 3,就能确定下一簇是 3。找到文件分配表的第 3 簇记录,上面登记的是 4,就能确定下一簇是 4 ……直到指到第 11 簇,发现下一个指向是 FF,就是结束。文件便读取完毕。

6.4 文件变化了怎么办?

我们删除B.TXT文件,创建D.TXT文件后的空间示意图:

image-20230513214214831

删除B.TXT文件,创建D.TXT文件后的目录示意图 :

image-20230513214328749

文件分配表将变成如下所示:

image-20230513214506250

二、 FatFs简介

1. FatFs是什么?

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

2. 它有什么特点

(1)Windows 兼容的 FAT 文件系统(支持 FAT12/FAT16/FAT32)

(2)与平台无关,移植简单

(3)代码量少、效率高

(4)多种配置选项。例如,支持多卷(物理驱动器或分区,最多 10 个卷);多个 ANSI/OEM 代码页包括 DBCS;支持长文件名、 ANSI/OEM 或 Unicode;支持 RTOS;支持多种扇区大小;只读、最小化的 API 和 I/O 缓冲区等

FATFS 的这些特点,加上免费、开源的原则,使得 FATFS 应用非常广泛。

3. 官网与资料

我们可以在这里找到FatFs文件系统的参考文档以及文件系统的压缩包:FatFs - Generic FAT Filesystem Module (elm-chan.org)

image-20230513214818295

我们下载最新版本的FatFs文件系统,并解压,我们会得到如下文件:

image-20230513215401849
  • documents目录下是一些帮助文档,里面是编译好的 html 文档,讲的是 FATFS 里面各个函数的使用方法,这些函数都是封装得非常好的函数,利用这些函数我们就可以操作 SPI Flash 芯片。

  • source目录下是FatFs文件系统的源码。

(1)diskio.c 文件是 FatFs 移植最关键的文件,它为文件系统提供了最底层的访问 SPI Flash 芯片以及SD卡的方法, FatFs 有且仅有它需要用到与 SPI Flash 芯片和SD卡相关的函数。

(2)diskio.h 定义了 FatFs 用到的宏,以及 diskio.c 文件内与底层硬件接口相关的函数声明。

(3)00history.txt 介绍了 FatFs 的版本更新情况。

(4)00readme.txt 说明了当前目录下 ff.c、ffconf.h、ff.h、diskio.h、diskio.c、ffunicode.c、ffsystem.c等文件的功能。

4. 系统结构

我们上边看到源码目录中有那么多文件,它们有什么联系呢?这些重要的文件功能如下:

文件名 说明
diskio.c FATFS和disk I/O模块接口层源文件,在这里实现对存储介质的初始化和读写操作。
diskio.h FATFS和disk I/O模块接口层头文件,包括函数声明和一些返回值的定义。
ff.c FatFs 核心文件,文件管理的实现方法。该文件独立于底层介质操作文件的函数,利用这些函数实现文件的读写。如f_open等 。
ff.h FATFS和应用模块公用的包含文件,包括函数声明、一些数据类型的定义和重命名,都在这里。。
ffsystem.c 用户为fatf提供操作系统相关函数的示例代码,我感觉这像是给操作系统用的,里边有一些锁的函数实现,这里边的函数声明在ff.h中。
ffunicode.c 编码的一些格式,包含了简体中文的GBK 和 Unicode 相互转换功能函数。
ffconf.h FatFs模块配置文件。这个头文件包含了对 FatFs 功能配置的宏定义,通过修改这些宏定义就可以裁剪FatFs 的功能。

它们之间的关系网络如下图:

image-20230514073151016

(1)用户应用程序:我们实际用来实现功能的部分,比如说我们要打开文件,就f_open(),关闭文件就用f_close(),读写文件就用f_read()和f_write()等。

(2)FatFs组件,这就是我们使用的FatFs文件系统,我们在应用程序使用f_open()这些函数在哪实现的?就是在ff.c、ffsystem.c中实现,在ff.h中声明,由用户调用,像一些读写的功能,我们其实是可以配置的,比如设置成只读,那么写的操作就会被屏蔽,这就是通过ffconf.h中的预编译宏来进行配置,选中的才会被编译到最终的可执行文件。那么f_read()、f_write()这些函数怎么去操作存储介质,完成存储和读取的?这个时候就来到了diskio.c和diskio.h中,这两个文件中实现存储介质的初始化和读写等相关功能。

(3)底层设备输入输出:这一部分就是实际对存储介质的操作了,比如SPI FLASH或者SD卡的初始化,读和写操作等,这里完成相关功能由diskio.c调用,实现对底层的操作,这一层是直接操作底层硬件设备的。

(4)物理设备:这一层自然就是我们的物理存储设备了,比如SPI FLASH、SD卡或者EEPROM等。

三、FatFs重要文件

接下来我们来了解几个文件,看依稀文件中有哪些重要的内容,方便后边移植。

1. ffconf.h

我们先来看一下配置文件,里边定义了大量的宏,我们来看一些重要的。

1.1 FF_FS_READONLY

1
#define FF_FS_READONLY	0

该选项切换只读配置。(0:读写或1:只读),只读配置将会删除写相关的API函数,例如f_write(), f_sync(), f_unlink(), f_mkdir(), f_chmod(), f_rename(), f_truncate(), f_getfree()以及一些可选的写函数。

1.2 FF_USE_MKFS

1
#define FF_USE_MKFS		0

这个宏,选择是否启用 f_mkfs() 函数,这个函数是用来创建文件系统的,应该是会格式化原有的文件系统。

1.3 FF_VOLUMES

1
#define FF_VOLUMES		1

这个就比较重要了,它是用来设置我们的这个文件系统支持多少个存储介质,也就是要使用的卷(逻辑驱动器)的数量。范围是1~10,比如说我们现在有两个存储介质,SPI FLASH和SD卡,我们可能需要两个都使用,这样的话,我们这个宏就需要大于2,不然有一个是无法操作的。

1.4 FF_XXX_SS

1
2
#define FF_MIN_SS		512
#define FF_MAX_SS 512

指定扇区大小的最小值和最大值。 SD 卡扇区大小一般都为 512 字节, SPI Flash 芯片扇区大小一般设置为 4096 字节,所以若是使用的时候需要把 FF_MAX_SS 改为 4096。

注意:这里我们使用的存储设备的扇区大小非512,需要修改时,FF_MAX_SS与FF_MIN_SS不相等的时候,我们需要在后边实现disk_ioctl函数,在这个函数中获取不同设备扇区大小,否则可能会报 The physical drive cannot work这一类错误。

2. diskio.c

2.1 disk_status()

1
2
3
DSTATUS disk_status (
BYTE pdrv /* Physical drive nmuber to identify the drive */
)

【函数说明】这个函数主要是获取存储设备的状态,根据定义的逻辑驱动器号来选择获取不同的设备的状态,这个状态的获取需要我们在对应的存储设备驱动文件中实现,并在这里调用。

image-20230514080259689

【参数说明】

  • pdrv :BYTE类型,逻辑驱动器的编号。

2.2 disk_initialize()

1
2
3
DSTATUS disk_initialize (
BYTE pdrv /* Physical drive nmuber to identify the drive */
)

【函数说明】这个函数主要是初始化存储设备,需要我们自己实现对应的初始化函数,并在这里调用。

image-20230514080246220

【参数说明】

  • pdrv :BYTE类型,逻辑驱动器的编号。

2.3 disk_read()

1
2
3
4
5
6
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 */
)

【函数说明】这个函数主要是从存储设备读取数据,需要我们自己实现对应的初始化函数,并在这里调用。

image-20230514080320055

【参数说明】

  • pdrv :BYTE类型,逻辑驱动器的编号。
  • buff :BYTE *类型,表示读取的数据将要存放的位置,需要传入一个地址用于存储读取的数据。
  • sector :LBA_t类型,表示要读取哪一个扇区。
  • count :UINT类型,表示要读取的扇区数量。

2.4 disk_write()

1
2
3
4
5
6
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 */
)

【函数说明】这个函数主要是向存储设备写入数据,需要我们自己实现对应的初始化函数,并在这里调用。

image-20230514080329214

【参数说明】

  • pdrv :BYTE类型,逻辑驱动器的编号。
  • buff :BYTE *类型,表示要写入的数据存放的位置,需要传入一个地址用于存储将要写入的数据。
  • sector :LBA_t类型,表示要写入到哪一个扇区。
  • count :UINT类型,表示要写入的扇区数量。

2.5 disk_ioctl()

1
2
3
4
5
DRESULT disk_ioctl (
BYTE pdrv, /* Physical drive nmuber (0..) */
BYTE cmd, /* Control code */
void *buff /* Buffer to send/receive control data */
)

【函数说明】这个函数主要是对设备执行一些自定义的命令,比如读取设备信息等,具体要实现什么功能需要自己定义,并且实现,然后在这里调用。

image-20230514080340682

【参数说明】

  • pdrv :BYTE类型,逻辑驱动器的编号。
  • cmd :BYTE类型,表示要执行的命令类型,根据不同的命令实现不同的功能。
  • buff :void *类型,表示一个缓冲区的地址,存放数据,看自己的需求了。

【注意事项】注意我们在这里读写的时候最好实现获取存储设备块大小的相关命令,块的大小在读写的时候会用到,不实现的话会有很多坑。

2.6 get_fattime()

1
2
3
4
DWORD get_fattime (void)
{
return 0;
}

【函数说明】这个函数主要是获取当前的时间。

image-20230514080618616

【参数说明】none

【注意事项】这个函数在新版本的FatFs中没有实现,但是会有调用,所以这个我们需要自己实现一下,可以什么都不做,但是要有。

3. ff.c

这里主要是一些应用层的函数,这里就不详细说了,详细的可以查看官网说明文档:FatFs - Generic FAT Filesystem Module (elm-chan.org)的Application Interface,这些文档其实在我们下载的FatFs包中有本地的,也可以看。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
f_mount - 注册/注销一个工作区域(Work Area)
f_open - 打开/创建一个文件
f_close - 关闭一个文件
f_read - 读文件
f_write - 写文件
f_lseek - 移动文件读/写指针
f_truncate -截断文件
f_sync - 冲洗缓冲数据 Flush Cached Data
f_forward - 直接转移文件数据到一个数据流
f_stat - 获取文件状态
f_opendir - 打开一个目录
f_closedir -关闭一个已经打开的目录
f_readdir - 读取目录条目
f_mkdir - 创建一个目录
f_unlink -删除一个文件或目录
f_chmod - 改变属性(Attribute)
f_utime -改变时间戳(Timestamp)
f_rename - 重命名/移动一个文件或文件夹
f_chdir - 改变当前目录
f_chdrive - 改变当前驱动器
f_getcwd - 获取当前工作目录
f_getfree - 获取空闲簇 Get Free Clusters
f_getlabel - Get volume label
f_setlabel - Set volume label
f_mkfs - 在驱动器上创建一个文件系统
f_fdisk - Divide a physical drive
f_gets - 读一个字符串
f_putc - 写一个字符
f_puts - 写一个字符串
f_printf - 写一个格式化的字符串
f_tell - 获取当前读/写指针
f_eof - 测试文件结束
f_size - 获取文件大小
f_error - 测试文件上的错误

四、移植说明

FatFs 文件系统与底层介质的驱动分离开来,对底层介质的操作都要交给用户去实现,它仅仅是提供了一个函数接口而已,下表是为 FatFs 移植时用户必须支持的函数(新版本的FatFs系统在函数命名或者宏的命名或者文件名可能于下表有一些出入,但是大概都是一个意思):

image-20230602190402837

FatFs 移植需要用户支持函数我们可以清晰知道很多函数是在一定条件下才需要添加的,只有前三个函数是必须添加的。我们完全可以根据实际需求选择实现用到的函数。

前三个函数是实现读文件最基本需求。接下来三个函数是实现创建文件、修改文件需要的。为实现格式化功能,需要在 disk_ioctl 添加两个获取物理设备信息选项。我们一般只要实现前面六个函数就可以了,已经足够满足大部分功能。

为支持简体中文长文件名称需要添加 ff_convert 和 ff_wtoupper 函数,实际这两个已经在 cc936.c文件中实现,我们只要直接把 cc936.c 文件添加到工程中就可以。后面六个函数一般都不用。