STM32F1开发指南笔记43----文件系统FatFs

时间:2024-04-02 16:39:25

1、文件系统

即使读者可能不了解文件系统,读者也一定对“文件”这个概念十分熟悉。数据在PC上是以文件的形式存储在磁盘中的,这些数据的形式一般为ASCII码或二进制形式。在上一章我们已经写好了SPI FLASH芯片的驱动函数,我们可以非常方便的在SPI FLASH芯片上读写数据。譬如我们需要记录一段文字,可以把这些文字转换成ASCII码,存储在数组中,然后调用SPI_FLASH_BufferWrite函数,把数组内容写入到SPI FLASH芯片的指定地址上,在需要的时候从该地址把数据读取出来,再对读出来的数据以ASCII,码的格式进行解读。
但是这样直接存储数据会带来极大的不便,譬如难以记录有效数据的位置,难以确定存储介质的剩余空间,以及应以何种格式来解读数据。就如同一个巨大的图书馆无人管理,杂乱无章地存放着各种书籍,难以查找所需的文档。想象一下图书馆的采购人员购书后,把书籍往馆内一扔,拍拍屁股走人,当有人来借阅某本书的时候,就不得不一本本的查找。这样直接存储数据的方式对于小容量的存储介质如EEPROM还可以接受,但是对于SPI FLASH芯片或者SD卡之类的大容量设备,我们需要一种高效的方式来管理它的存储内容
这些管理方式即为文件系统,它是为了存储和管理数据,而在存储介质建立的一种组织结构,这些结构包括操作系统引导区、目录和文件。常见的Windows下的文件系统格式包括FAT32、NTFS、exFAT在使用文件系统前,要先对存储介质进行格式化。格式化先擦除原来内容,在存储介质上新建一个文件分配表和目录。这样,文件系统就可以记录数据存放的物理地址,剩余空间。

使用文件系统时,数据都以文件的形式存储。写入新文件时,先在目录中创建一个文件索引,它指示了文件存放的物理地址,再把数据存储到该地址中。当需要读取数据时,可以从目录中找到该文件的索引,进而在相应的地址中读取出数据。具体还涉及到逻辑地址、簇大小、不连续存储等一系列辅助结构或处理过程。
文件系统的存在使我们在存取数据时,不再是简单地向某个物理地址直接读写,而是要遵循它的读写格式。如经过逻辑转换,一个完整的文件可能被分开成多段存储到不连续的物理地址,使用目录或链表的方式来获知下一段的位置
上一章的SPI FLASH芯片驱动只完成了向物理地址写入数据的工作,而根据文件系统格式的逻辑转换部分则需要额外的代码来完成。实质上,这个逻辑转换部分可以理解为当我们需要写入一段数据时,由它来求解向什么物理地址写入数据、以什么格式写入及写入一些原始数据以外的信息(如目录)。这个逻辑转换部分代码我们也习惯称之为文件系统。

2、FatFs文件系统简介

上面提到的逻辑转换部分代码(文件系统)即为本章的要点,文件系统庞大而复杂,它需要根据应用的文件系统格式而编写,而且一般与驱动层分离开来,很方便移植,所以工程应用中一般是移植现成的文件系统源码。

FatFs是面向小型嵌入式系统的一种通用的FAT文件系统。它完全是由AISI C语言编写并且完全独立于底层的IO介质。因此它可以很容易地不加修改的移植到其他的处理器当中,如8051、PIC、AVR、SH、Z80、H8、ARM等。FatFs支持FAT12、FAT16、FAT32等格式,所以我们利用前面写好的SPI FLASH芯片驱动,把FatFs文件系统代码移植到工程之中,就可以利用文件系统的各种函数,对SPI FLASH芯片以“文件”格式进行读写操作了。

FatFs文件系统的源码可以从fatfs官网下载:http://elm-chan.org/fsw/ff/00index_e.html

3、FatFs的目录结构

在移植FatFs文件系统到开发板之前,我们先要到FatFs的官网获取源码。官网有对FatFs做详细的介绍。解压后可以看到里面有doc和src两个文件夹。doc文件夹里面是一些使用帮助文档。src才是FatFs文件系统的源码。
STM32F1开发指南笔记43----文件系统FatFs

doc帮助文档:

STM32F1开发指南笔记43----文件系统FatFs

其中en和ja这两个文件夹里面是编译好的html文档,讲的是FATFA里面各个函数的使用方法,这些函数都是封装的非常好的函数,利用这些函数我们就可以操作SPI FLASH芯片。这两个文件夹的唯一区别就是en文件夹下的文档是英文的,ja文件夹下的是日文的。img文件夹包含en和ja文件夹下文件需要用到的图片,还有四个名为app.c文件,内容都是FatFs具体应用例程。00index_e.html和00index_j.html是一些关于FATFS的简介,至于另外两个文件可以不看。

src FATFS源码:
STM32F1开发指南笔记43----文件系统FatFs

option文件夹下是一些可选的外部c文件。包含了多语言支持需要用到的文件和转换函数。
diskio.c文件是FatFs移植最关键的文件,它为文件系统提供了最底层的访问SPI FLASH芯片的方法,FatFs有且仅有它需要用到与SPI FLASH芯片相关的函数。
diskio.h定义了FatFs用到的宏,以及diskio.c文件内与底层硬件接口相关的函数声明。
00history.txt介绍了FatFs的版本更新情况。
00readme.txt说明了当前目录下diskio.c、diskio.h、ff.c、ff.h、integer.h的功能。
src文件夹下的源码文件功能简介如下:

  1. integer.h:文件中包含了一些数值类型定义
  2. diskio.c:包含了底层存储介质的操作函数,这些函数需要用户自己实现,主要添加底层驱动函数。
  3. ff.c:FatFs核心文件,文件管理的实现方法。该文件独立于底层介质操作文件的函数,利用这些函数实现文件的读写。
  4. cc936.c:本文件在option目录下,是简体中文支持所需要添加的文件,包含了简体中文的GBK和Unicode相互转换功能函数。
  5. ffconf.h:这个头文件包含了对FatFs功能配置的宏定义,通过修改这些宏定义就可以裁剪FatFs的功能。譬如需要支持简体中文,需要把ffconf.h中的_CODE_PAGE的宏改成936并把上面的cc936.c文件加入到工程之中。

建议阅读这些源码的顺序为:integer.h ----> diskio.c ---->ff.c

阅读文件系统源码ff.c文件需要一定的功底,建议读者先阅读FAT32的文件格式,再去分析ff.c文件**。若仅为使用文件系统,则只需要理解integer.h及diskio.c文件并会调用ff.c文件中的函数就可以了**。本章主要讲解如何把FATFS文件系统移植到开发板上,并编写一个简单读写操作范例。

4、FatFs程序结构图

STM32F1开发指南笔记43----文件系统FatFs

用户应用程序需要由用户编写,想实现什么功能就编写什么程序,一般我们只用到f_mount()、f_open()、f_write()、f_read()就可以实现文件的读写操作。

FatFs组件是FatFs的主体,文件都在源码src文件夹中,其中ff.c、ff.h、integer.h以及diskio.h四个文件我们不需要改动,只需要修改ffconf.h和diskio.c两个文件。
底层设备输入输出要求实现存储设备的读写操作函数、存储设备信息获取函数等。我们使用SPI FLASH芯片作为物理设备,在上一章节已经编写好了SPI FLASH芯片的驱动程序,这里我们就直接使用。

FatFs属于软件组件,不需要附带其他硬件电路。我们使用SPI FLASH芯片作为物理存储设备,其硬件电路在上一章已经做了分析,这里就直接使用。上一章我们已经实现了SPI FLASH芯片驱动程序,并实现了读写测试,为移植FatFs方便,我们直接拷贝一份工程,我们在工程基础上添加FatFs组件,并修改main函数的用户程序即可。

5、FatFs移植步骤

① 先拷贝一份SPI FLASH芯片测试的工程文件(整个文件夹),并修改文件夹名为“SPI-FatFs文件系统”。将FatFs源码中的src文件夹整个文件夹拷贝一份至“SPI-FatFs文件系统\USER\”文件夹下并修改名为“FATFS”。
STM32F1开发指南笔记43----文件系统FatFs

② 使用MDK软件打开工程文件,并将FatFs组件文件添加到工程中,需要添加的有ff.c、diskio.c和cc936.c三个文件。
STM32F1开发指南笔记43----文件系统FatFs

③ 添加FATFS文件夹到工程的include选项中。打开工程选项对话框,选择“C/C++”选项下的“include paths”项目,在弹出路径设置对话框中选择添加“FATFS”文件夹。
STM32F1开发指南笔记43----文件系统FatFs

④ 如果现在编译工程,可以发现有两个错误,一个是来自diskio.c文件,提示有一些头文件没找到,diskio.c文件内容是与底层设备输入输出接口函数文件,不同硬件设计驱动就不同,需要的文件也不同;另一个错误来自cc936.c文件,提示该文件不是工程所必需的,这是因为FatFs默认使用日语,我们想要支持简体中文需要修改FatFs的配置,即修改ffconf.h文件。至此,将FatFs添加到工程的框架已经操作完成,接下来要做的就是修改diskio.c文件和ffconf.h文件。

6、FatFs底层设备驱动函数

FatFs文件系统与底层介质的驱动分离开来,对底层介质的操作都要交给用户去实现,它仅仅是提供了一个函数接口而已。下表为FatFs移植时用户必须支持的函数。通过下表我们可以清晰知道很多函数是在一定条件下才需要添加的。只有前三个函数是必须添加的。我们完全可以根据实际需求选择实现用到的函数。
前三个函数是实现读文件最基本需求。接下来三个函数是实现创建文件、修改文件需要的。为实现格式化功能,需要在disk_ioctl添加两个获取物理设备信息选项。我们一般只要实现了前面6个函数就可以了,已经足够满足大部分功能。

为支持简体中文长文件名称需要添加ff_convert和ff_wtoupper函数,实际这两个已经在cc936.c文件中实现了,我们只要直接把cc936.c文件添加到工程中就可以了。
后面6个函数一般都不用。如真有需要可以参考syscall.c文件(src\option文件夹内)

STM32F1开发指南笔记43----文件系统FatFs

底层设备驱动函数是存放在diskio.c文件,我们的目的就是把diskio.c中的函数接口与SPI FLASH芯片驱动连接起来(在diskio.c的函数接口中调用SPI FLASH读写操作等函数)。总共有5个函数,分别为:
① 设备状态获取(disk_status)
② 设备初始化(disk_initialize)
③ 扇区读取(disk_read)
④ 扇区写入(disk_write)
⑤ 其他控制(disk_ioctl)
接下来,我们对每个函数结合SPI FLASH芯片驱动做详细讲解。

STM32F1开发指南笔记43----文件系统FatFs

这两个宏定义在FatFs中非常重要,FatFs是支持多物理设备的,必须为每个物理设备定义一个不同的编号。SD卡是预留接口,在讲解SDIO接口相关章节后会用到,可以实现使用读写SD卡内文件。

STM32F1开发指南笔记43----文件系统FatFs

disk_status函数只有一个参数pdrv,表示物理编号,一般我们都是使用switch函数实现对pdrv的分支判断。对于SD卡只是预留接口,留空即可。对于SPI FLASH芯片,我们直接调用SPI_FLASH_ReadID()函数获取设备ID,然后判断是否正确,如果正确,函数返回正常标准;如果错误,函数返回异常标志。SPI_FLASH_ReadID()是定义在bsp_spi_flash.c文件中的函数,上一章节已经做了分析。

STM32F1开发指南笔记43----文件系统FatFs

STM32F1开发指南笔记43----文件系统FatFs

disk_initialize函数也是有一个参数pdrv,用来指定设备物理编号。对于SPI FLASH芯片,我们调用SPI_FLASH_Init()函数实现对SPI FLASH芯片引脚GPIO初始化配置以及SPI通信参数配置。SPI_FLASH_WAKEUP()函数唤醒SPI FLASH芯片,当SPI FLASH芯片处于睡眠模式时需要唤醒芯片才可以进行读写操作。
最后调用disk_status函数获取SPI FLASH芯片状态,并返回状态值。

STM32F1开发指南笔记43----文件系统FatFs

disk_read函数有4个形参。pdrv为设备物理编号。buff是一个BYTE类型指针变量,buff指向用来存放读取到数据的存储区首地址。sector是一个DWORD类型变量。指定要读取数据的扇区首地址。count是一个UINT类型变量,指定扇区数量。
BYTE类型实际是unsigned char类型,DWORD类型实际是unsigned long类型,UINT类型实际是unsigned int类型,类型定义在integer.h文件中。
开发板使用的SPI FLASH芯片型号为W25Q128FV,每个扇区大小为4096个字节(4KB),总共有16M字节空间,为兼容后面实验程序,我们只将后部分10MB空间分配给FatFs使用,前面6MB空间用于其他实验需要,即FatFs是从6MB空间开始,为实现这个效果需要将所有的读写地址都偏移1536个扇区空间。
对于SPI FLASH芯片,主要是使用SPI_FLASH_BufferRead()实现在指定地址读取指定长度的数据,它接收3个参数,第一个参数为指定数据存放地址指针。第二个参数为指定数据读取地址,这里使用左移运算符,左移12位实际是乘以4096(将扇区个数转化为字节个数),这与每个扇区大小是息息相关的。第三个参数为读取数据个数,也是需要使用左移运算符(将扇区个数转化为字节个数

STM32F1开发指南笔记43----文件系统FatFs

disk_write函数有4个参数,pdrv为设备物理编号。buff指向待写入扇区数据的首地址。sector,指定要写入数据的扇区首地址。count指定扇区数量。对于SPI FLASH芯片,在写入数据之前需要先擦除,所以用到扇区擦除函数(SPI_FLASH_SectorErase)。然后就调用数据写入函数(SPI_FLASH_BufferWrite)把数据写入到指定位置内。

STM32F1开发指南笔记43----文件系统FatFs

STM32F1开发指南笔记43----文件系统FatFs

disk_ioctl 函数有3个参数,pdrv为设备物理编号,cmd为控制指令,包括发出同步信号、获取扇区数目、获取扇区大小、获取擦除块数量等等指令,buff为指令对应的数据指针。
对于SPI FLASH芯片,为支持FatFs格式化功能,需要用到获取扇区数量(GET_SECTOR_COUNT)指令和获取擦除块数量(GET_BLOCK_SIZE)。另外,SD卡扇区大小为512字节,SPI FLASH芯片一般设置扇区大小为4096字节,所以需要用到获取扇区大小(GET_SECTOR_SIZE)指令。

STM32F1开发指南笔记43----文件系统FatFs

get_fattime函数用于获取当前时间戳,在ff.c文件中被调用。FatFs在文件创建、被修改时会记录时间,这里我们直接使用赋值方法设定时间戳。为更好的记录时间,可以使用控制器RTC功能,具体要求返回值格式为:

  1. bit31:25——从1980至今是多少年,范围是(0-127);
  2. bit24:21——月份,范围为(1-12);
  3. bit20:16——该月份中的第几日,范围为(1-31);
  4. bit15:11——时,范围(0-23);
  5. bit10: 5 ——分,范围(0-59);
  6. bit 4 : 0 ——秒/2,范围(0-29).

7、FatFs功能配置

ffconf.h文件是FatFs功能配置文件,我们可以对文件内容进行修改,使得FatFs更符合我们的要求。ffconf.h对每个配置选项都做了详细的使用情况说明。下面只列出修改的配置,其他配置采用默认即可。
STM32F1开发指南笔记43----文件系统FatFs

  1. _USE_MKFS:格式化功能选择,为使用FatFs格式化功能,需要把它设置为1.
  2. _CODE_PAGE:语言功能选择,并要求把相关语言文件添加到工程宏。为支持简体中文文件名需要使用“936”,我们把cc936.c文件添加到工程中。
  3. _USE_LFN:长文件名支持,默认不支持长文件名,这里配置为2,支持长文件名,并指定使用栈空间为缓冲区。
  4. _VOLUMES:指定物理设备数量,这里设置为2,包括预留SD卡和SPI FLASH芯片。
  5. _MIN_SS、_MAX_SS:指定扇区大小的最小值和最大值。SD卡扇区大小一般都为512字节,SPI FLASH芯片扇区大小一般设置为4096字节,所以需要把_MAX_SS改为4096.

8、FatFs功能测试

移植操作到此,就已经把FatFs全部添加到我们的工程了,这时我们编译功能,顺利编译通过,没有错误。接下来,我们就可以编写用户应用程序了。主要的测试包括格式化测试、文件写入测试、文件读取测试三个部分,主要程序都在main.c文件中实现。

STM32F1开发指南笔记43----文件系统FatFs

FATFS是在ff.h文件定义的一个结构体类型,针对的对象是物理设备,包含了物理设备的物理编号、扇区大小等信息,一般我们都需要为每个物理设备定义一个FATFS变量。
FIL也是在ff.h文件定义的一个结构体类型,针对的对象是文件系统内具体的文件,包含了文件很多基本属性,比如文件大小、路径、当前读写地址等。如果需要在同一时刻打开多个文件进行读写,才需要定义多个FIL变量,不然一般定义一个FIL变量即可。
FRESULT也是在ff.h文件定义的一个枚举类型。作为FatFs函数的返回值类型,主要管理FatFs运行中出现的错误。总共有19种错误类型,包括物理设备读写错误、找不到文件、没有挂载工作空间等错误。这在实际编程中非常重要,当有错误出现时我们要停止文件读写,通过返回值我们可以快速定位到错误发生的可能地点。如果运行没有错误才返回FR_OK。
fnum是个32位无符号整形变量,用来记录实际读取或者写入数据的数量。
buffer和textFileBuffer分别对应读取和写入数据缓存区,都是8位无符号整形数组。

STM32F1开发指南笔记43----文件系统FatFs

STM32F1开发指南笔记43----文件系统FatFs

STM32F1开发指南笔记43----文件系统FatFs
首先,初始化RGB彩灯和调试串口,用来指示程序进程。
FatFs的第一步工作就是使用f_mount函数挂载工作区。f_mount函数有3个形参,第一个参数是指向FATFS变量指针,如果赋值为NULL可以取消物理设备挂载。第二个参数为逻辑设备编号,使用设备根路径表示,与物理设备编号挂钩,我们定义SPI FLASH芯片物理编号为1,所以这里使用“1:”。第三个参数可选0或1 , 1表示立即挂载,0表示不立即挂载,延迟挂载。f_mount函数会返回一个FRESULT类型值,指示运行情况。

如果f_mount函数返回值为FR_NO_FILESYSTEM,说明没有FAT文件系统,比如新出厂的SPI FLASH芯片就没有FAT文件系统。我们就必须对物理设备进行格式化处理。使用f_mkfs函数可以格式化操作。f_mkfs函数有3个形参,第一个参数为逻辑设备编号;第二个参数可选0或者1, 0表示设备为一般硬盘,1表示设备为软盘。第三个参数指定扇区大小,如果为0,表示通过disk_ioctl函数获取。格式化成功后需要先取消挂载原来设备,再重新挂载设备。
在设备正常挂载后,就可以进行文件读写操作了。使用文件之前,必须使用f_open函数打开文件不再使用文件必须使用f_close函数关闭文件,这个跟电脑端操作文件步骤类似。f_open函数有3个形参,第一个参数为文件对象指针。第二个参数为目标文件,包含绝对路径的文件名称个后缀名。第三个参数为访问文件模式选择,可以是打开已经存在的文件模式、读模式、写模式、新建模式、总是新建模式等的或运行结果。比如对于写测试,使用FA_CREATE_ALWAYS和FA_WRITE组合模式,就是总是新建文件并进行写模式。
f_close函数用于不再对文件进行读写操作关闭文件,f_close函数只要一个形参,为文件对象指针。f_close函数运行可以确保缓冲区完全写入到文件内。
成功打开文件之后就可以使用f_write函数和f_read函数对文件进行写操作和读操作。这两个函数用到的参数是一致的,只不过一个是数据写入,一个是数据读取。f_write函数第一个形参为文件对象指针,使用与f_open函数一致即可。第二个参数为待写入数据的首地址,对于f_read函数就是用来存放读出数据的首地址。第三个参数为写入数据的字节数,对于f_read函数就是欲读取数据的字节数。第四个参数为32位无符号整形指针,这里使用fnum变量地址赋值给它,在运行读写操作函数后,fnum变量指示成功读取或者写入的字节个数。
最后,不再使用文件系统时,使用f_mount函数取消挂载。

总结:

使用SPI FLASH芯片作为物理设备,不像SD卡那么方便直接用读卡器就可以在电脑端打开验证。另外一个问题,就目前来说,在SPI FLASH芯片上挂载FatFs好像没有实际意义,无法发挥文件系统功能。
实际上,这里归根到底是我们目前没办法在电脑端查看SPI FLASH芯片内FatFs的内容,没办法非常方便的拷贝、删除文件。我们当然不会做无用功,STM32控制器还有一个硬件资源可以解决上面的问题,就是USB。我们可以通过编程把整个开发板变成一个U盘,而U盘存储空间就是SPI FLASN芯片的空间。这样非常方便实现文件读写。至于USB内容,将在USB章节讲解。