android应用案例开发大全第3版配套源码光盘_第13章 基于SD卡的FatFs文件系统

第13章 基于SD卡的FatFs文件系统

文件作为信息的集合被存储在硬盘,SD卡、Flash等存储设备上,为了读写更加高效方便,通常使用文件系统来管理文件,常见的文件系统有FAT,NTFS,EXT,UFS,HFS+等等。FatFs作为一款小型嵌入式的通用文件系统,被广泛用于微型微控制器中。它符合ANSI C格式,并与磁盘I / O层完全分离,使得它既独立于平台,又可以集成到资源有限的小型微控制器中。

113.1 什么是文件系统

这里所讲的文件相对于计算机系统而言,如图片、文本文档、程序等都可称之为文件。文件,简而言之就是信息的集合,以特定的结构存储在存储设备中。

文件系统是操作系统用于明确存储设备上的文件的方法和数据结果,即在存储设备上组织文件的方法。从系统角度来看,文件系统是对文件存储设备的地址进行组织和分配,负责文件存储并读存入的文件进行保护和检索的系统。具体的说,它负责为用户建立文件,存入、读出、修改、转储文件,控制文件的存取,当用户不再使用时撤销文件等。

213.2 FatFs文件系统

13.2.1 FatFs运行原理

FatFs(FAT Filesystem)是用于小型嵌入式系统的通用FAT / exFAT文件系统模块,其中FAT(File Allocation Table)是“文件分配表”的意思,简称FAT表,它是微软在FAT文件系统中用于文件索引和定位的一种链式结构,实际就是通过表格记录文件信息和存储位置。

其特点如下:

1、Windows兼容FAT文件系统

2、不依赖平台,易于移植

3、代码和工作区占用空间小

4、支持裁剪、多卷(无论驱动器和分区)连接,RTOS(实时操作系统)等多项配置

FAT文件系统将磁盘以一定数目扇区为单位进行划分,该单位称为簇,而FAT表是记录磁盘数据索引的一种簇链结构。通常情况下,簇的大小是 2n (n为整数)个扇区的大小,一般为512B,1K,2K,4K,8K,16K,32K,64K,通常不超过32K。以FAT16为例,FAT表中的每个记录项占16bit,如果以扇区而不是簇为单位而不是进行磁盘的分配,会大量增加FAT表中的记录项,增加对大文件存取消耗,降低了文件的执行系统效率。

下面通过模拟一个磁盘存储数据的过程简单介绍FAT文件系统的存储原理。假如现在有一个大小为100KB的磁盘,为了存储方便,将其划分为100份,每份1KB(假设为1个簇)。现在使用磁盘顺序存储下面几个文件:A.txt(10KB),B.mp3(56.9KB),C.jpg(23.1KB),其存储结构如图13- 1所示。

0d4de4747e4825baebad2181cbe61cad.png 

图13-1 顺序存储文件示意图

其中目录用于存储文件的文件的名称、起始簇号和偏移地址、存储时间等信息,方便读写文件。为了管理方便将目录又分为10等份,目录内容如表13-1所示。

                 表13-1 目录存储格式

25560c3780b7eaf66efaa61bef721787.png

   这种存储方式能够方便的存储文件,不用担心忘记文件的存储位置,但是该方式也存在不足之处。比如向A.txt增加内容,发现后面近跟着B文件,无法继续增加。另外,如果把B文件删除掉,其空间随之释放,但是B文件释放的空间只能存储不超过57簇的文件。如何才能更合理的存储和管理文件呢?


根据以上问题,设计一种新的存储机制:1、允许文件不连续存储;2、增加一个文件分配表,采用簇号映射文件机制。不连续存储指的是以簇为单位映射文件,即一个簇存储文件的一部分,多个簇存储的内容连接起来就构成一个完整的文件。在这种机制下A文件增加的内容可以存储在C文件后的剩余空间中,如图13-2所示。

99e475a30887517875f727c14387ffc2.png 

图13-2 改进型存储结构示意图

如何读取分散存储的A.txt文件中的内容呢?答案在于文件分配表。文件分配表采用链式结构来表示文件占有簇的关系,具体如表13-2所示。文件分配表内每个记录项占若干比特(Bit),所记录的索引是当前数据所在文件的下一簇数据的簇号。

首先目录位于第1簇,并且仅占一簇,因此第一个索引项内容为FF,表示此文件记录到此结束。从目录中可得知A.txt所存储的数据从第2簇开始,而分配表中第2个记录项索引为3,表示A.txt的数据继第2簇后存储在磁盘的第3簇,同理依次往下查,一直到11簇的索引为FF,表示A.txt文件结束,其他文件同理。当分配表中的索引项为00时,表示对应磁盘中的存储空间未被占用。

表13-2文件分配表(1)

7cf39b7b3dec92d2ffa1feafe1707ae3.png 

当文件重新添加或删减内容后,对应文件分配表中的记录项也随之改变。比如A.txt增加内容存储在磁盘的第93~95簇中,则文件分配表中第11项记录的索引由FF变为93,93表示增添内容的所在簇号;找到第93记录项,其索引号为94......直至第95项记录,登记的索引为FF,表示A.txt文件至此结束,其结构如表13-3所示。

表13-3文件分配表(2)

ea15621293f55627b2fb51d77ace957f.png 

13.2.2 FatFs分区结构

当在分区上创建FAT文件系统时,文件系统便会将分区作为整块可分配的区域进行规划,以便于数据的存储。FAT文件系统主要将分区划分为引导区(包括主引导记录MBR和系统引导记录DBR)、FAT表、根目录区和数据区和保留区,其结构如图13-3所示。

e83474604a59e387987ca411118d45c1.png 图13-3 FatFs分区结构图

MBR:保存磁盘的分区信息(分区的起始地址、分区大小、分区结束地址)。

DBR:保存了当前分区的详细参数(比如FAT表的位置、FAT表的的大小、簇大小、扇区大小、根目录中最大目录项数等等)

FAT表:以簇为单位对数据区重新划分空间,在FAT表中建立了簇的使用情况。

目录:根目录中存在众多的目录项,目录项用于记录了文件名、大小、起始地址等等。

数据区:以簇为单位存储数据。

按FAT表记录簇号的位数,可以分为FAT12、FAT16、FAT32、exFAT等版本。以 FAT16 为例,簇号在FAT表中占据2字节(16位),FAT16可以表示的最大簇号为216=65535,以每簇最大32K计算,其管理的最大磁盘空间为:32KB×65535=2048MB,即FAT16最大支持2GB的磁盘。而FAT32中将FAT表中记录簇号的二进制位数扩展到了32位,故而这种文件系统称为FAT32。FAT32最大支持8TB,exFAT(扩展FAT表)最大支持16EB(理论值:16×1024×1024TB)。

13.2.3 FatFs组织结构及内存占用情况

通常软件架构设计中采用分层设计,即用户编写的应用程序位于上层,跨越应用模块的功能程序(FatFs模块,图形界面等)位于中层,而配置底层设备的驱动程序位于底层。通过分层设计,可以限制各部分间的依赖关系,使系统以更松散的方式耦合,从而提高应用程序的可维护性,并使其更容易扩展,有利于提升性能。

FatFs是独立于底层设备与用户程序之外的通用文件系统模块,也就是中间层插件。用户只需要将硬件驱动连接到FatFs的磁盘接口,即可通过调用FatFs的应用函数对设备进行操作,而不需要关心设备类型,数据如何存储等底层操作。一个完整的FatFs工程由四部分组成:用户程序、FatFs文件系统模块、底层驱动、物理设备,其组织结构如图13-4组成。使用时只需要将底层设备驱动与FatFs模块接口相连接即可。

f3e03a80bee260ff9da84ea4ccf4788f.png

图13-4 FatFs系统组织结构

现在我们已经知道了什么是文件系统,FatFs存储机制和分区结构,在移植FatFs之前,需要评估一下FatFs的资源占用情况,即单片机是否能够运行FatFs模块。内存使用量大小取决于FatFs配置选项,表13-4列举了FatFS在特定配置下的ROM(内部Flash)和RAM使用情况。由于FatFs具有可裁剪配置,根据使用可以对API函数进行裁剪,以减少资源占用,在完全配置下FatFs占用6.3k占用RAM和2.8k ROM,最小占用4.3k RAM和2.3k ROM。已知STM32F103ZE拥有512k Flash和64K SRAM,因此完全可以运行FatFs模块。

表13-4 FatFs模块ROM和RAM消耗参考

8555f59fe985c708a527ee4e0af4c9ba.png 

V表示宏_VOLUMES(卷的个数)定义的值;

F表示打开的文件数。

宏_FS_READONLY表示读写限制,0表示读/写(R/W),1表示只读(R/O);

宏_FS_MINIMIZE用于函数裁剪,0表示全功能版(Full),3为最小功能(Min);

宏_FS_TINY用于模式配置,0表示正常模式,1表示微型模式。

313.3 移植FAT32文件系统

13.3.1 新建FatFs工程

1、获取FAT32源码:打开“配套光盘资料\Kingst-32F1开发板资料\FatFs源码”目录,

找到ff13b版本FatFs源码,或者登陆FatFs官网,下拉找到Resources目录,点击图13-5所示红色方框标记处下载最新版本的FatFs源码,R0.13b为版本号,由于持续更新,版本略有变动。

00d723714bfb74f5eaa4853ee056d3cc.png 图13-5  FatFs官网源码下载

2、复制上一章的SD卡工程,修改一下工程名称,在工程目录下新建一个“FatFs”文件夹,并将下载的FatFs源码全部复制到该文件夹下,如图13-6所示。

其中documents文件夹为说明文档,source文件夹为FatFs源代码,具体相关文件作用如表13-5所示。

3719125c48171d9a7db0939763cdad6a.png

  图13-6 复制FatFs源码

表13-5 FatFs源文件列表

ffd67915b60cb0b27e30b30c43be9b3a.png

3、打开FatFs工程,在Target目录下添加一个Group,命名为“FatFs”,并将“FatFs\sourcr

”文件夹下的FatFs源码添加到工程中,并设置FatFs源码头文件所在文件路径,具体如图13-7所示。至此FatFs源码添加完毕,接下来便是配置工程。

4196c2d88dfca45b4e782af08e8cc9e0.png

图 13-7 添加FatFs源码

13.3.2 FatFs磁盘接口

由于FatFs是独立于编译平台和存储介质之外的文件系统层,它与存储设备之间是完全分离,只需要将底层驱动连接FatFs模块的磁盘接口驱动接口即可,其结构如图13-8所示。其中用于连接底层驱动的磁盘接口位于diskio.c文件中,该文件只包含6个函数,如表13-6所示,通过这6个函数即可将底层存储设备与FatFs相连接。

f3b98bc60f57862586142d19979d9617.png 图13-8 FatFs媒体访问接口

表13-6 FatFs接口函数

dbeb205b1b24bfd91906b44ae33591a2.png

由于FatFs文件系统可以挂载多个存储设备,本文同时将SD卡和SPI Flash接口连接到FatFs磁盘接口中,设置SD卡驱动号为“0”,Flash驱动号为“1”。为了合理使用Flash,只将前10MByte空间分配给FatFs存储文件,剩余空间留给用户存储数据,比如存储汉字字库等等。具体函数如下所示,将源文件修改如下即可。

f34d4b05df3f0daaedcee1eb8d4378e4.png

311eea53314bf6d4b353e177b440f49e.png

b31c95a542bd8545f27fab774d2ab708.png

649329761c06a26f9fd4909b2d5d72df.png

7839aa8af15f700455cddab2963de063.png

在SD卡和FatFs文件系统中,对于Blcok(块)和Sector(扇区)的定义有所区别。在SD卡中,Block为最小读写单位,通常为512Byte;Sector为擦除单位,表示N个Block(N可定义)。而FatFs文件系统中,Sector为存储设备最小存储单元,相当于SD卡中的Block,一般为512Byte;而Block为文件系统读取和写入磁盘的最小内存单位,由于存储的文件通常比较大,文件系统将N个Sector组成一个Block进行操作,通常设置N=8,即4 KByte。

13.3.3 配置FatFs模块

FatFs除了底层磁盘接口函数是必须设置的,其余API操作函数是非必须的,可以根据实际应用对FatFs模块进行裁剪,以减少资源占用。其中FatFs相关配置选项均以宏定义形式定义在ffconf.h文件中。

FatFs模块从版本0.07开始支持长文件名。顾名思义,长文件名功能就是支持定义更长的文件名称。与之相对的是短文件名,短文件名遵循8.3原则,即文件名或目录名不超过8个字节,扩展名(.xxx)不超过3个字节。使用长文件名需要占用额外的缓存,缓存大小通过宏FF_MAX_LFN来设定,如果要完全符合长文件名的特性,则至少需要分配255字节的缓存区。默认情况下,长文件名是禁止的,使能长文件名需要设置ffconf.h中的宏FF_USE_LFN为1、2或3,尤其是配置FF_USE_LFN为2或3时,文件函数访问的文件名工作区缓存位于栈或堆中,占用(_MAX_LFN+1)*2字节,如果内存不足,可能会导致堆栈溢出。

另外FatFs的API函数使用ANSI/OEM代码页(字符集)。代码页规定了适用于特定地区的字符集合以及相应的编码。Windows为自己支持的代码页都编了一个号码,例如代码页936就是简体中文(GBK编码表),各类内码所占Flash大小如图13-7所示。如果支持中文长文件名功能,需要设置宏FF_CODE_PAGE为936,这将会占用大量Flash,如果单片机Flash不足,建议关闭长文件名。

表13-7 长文件名下不同内码表占用Flass大小

4f055b421c064c74ca965f5beb12e522.png 

ffconf.h配置文件如下所示:

debbf245c3d34cddc57e2aaf926fa955.png

至此FatFs配置完毕,接下来便可以通过Fat文件系统中的操作函数读写文件。

413.4 FatFs API函数

FatFs文件系统为应用程序提供操作文件系统所需的函数,只需要调用相关函数,便可以实现对文件的创建、读、写、删除等操作,其运行原理如图13-9所示。根据函数的功能,可以将FatFs操作函数划分为四大类:文件访问、目录访问、文件和目录管理、卷管理和文件系统配置。

2a700ee523e9693cb83b953d25c3d18f.png 图13-9 FatFs控制原理

在介绍应用函数之前,先来了解一下FatFs应用函数的返回值。已知每个应用函数执行完毕后都会返回一个值,用于判断函数的执行状况或者错误原因,FatFs应用函数返回值类型汇总如表13-8所示。实际开发时,通过查看返回值可以快速定位错误类型。

表13-8 FatFs API函数返回类型

b8cd5bcb458a1960270279301e281512.png

接下来仿照在计算机操作SD卡创建、读/写文件的流程,对比介绍FatFs常用的应用函数。现在请打起精神来,认真仔细掌握每个函数的功能与用法。

13.4.1 挂载磁盘——f_mount函数

当挂载SD卡到电脑时,需要将SD卡通过读卡器连接到电脑USB接口,同样挂载SD卡至FatFs时,需要将Micro SD卡插到KST-32开发板的Micro SD卡槽中。如果SD卡上已经建立了FAT分区,在“我的电脑”中可以看到一个新的磁盘,表示SD卡挂载成功,FatFs则是通过f_mount函数挂载SD卡,挂载成功后返回FR_OK。f_mount函数如下所示:

1、函数原型

c4bcce0be2a603e8d48b2bf6941f5c3e.png

2、参数

fs:指向要注册和清除的文件系统对象的指针,空指针取消注册(已注册过的)文件系统对象。

path: 指向逻辑驱动器的字符串指针, 无驱动器号的字符串表示默认驱动器。

opt:  0:不要立即安装(仅在第一次访问卷时安装),1:强制安装卷以检查它是否准备好工作。

3、功能

FatFs需要为每个逻辑驱动器(FAT卷,也就是磁盘)建立工作区(文件系统对象),即在 FatFs 模块上注册 / 注销一个工作区。在使用任何操作函数之前,必须使用该函数为每个卷注册一个工作区。

4、返回值类型

FR_OK

FR_INVALID_DRIVE

FR_DISK_ERR

FR_NOT_READY

FR_NOT_ENABLED

FR_NO_FILESYSTEM

5、示例

本文中SD卡的驱动器号为0,Flash的为1(在diskio.c中定义)。以挂载SD卡为例:

FATFS fs; //定义文件文件工作区

f_mount(&fs, "0:", 1) //为SD卡注册工作区,即挂载SD卡

其中fs为FATFS结构体类型定义的变量,用于记录文件系统类型、扇区大小、簇的扇区数、FAT表的位置及大小。FatFs中声明的结构体类型往往占用较大内存,如果定义为函数内部的局部变量,容易导致堆栈溢出,所以一般定义为全局变量。

13.4.2 格式化磁盘——f_mkfs函数

当SD卡上没有FAT分区或者损坏时,计算机会提示磁盘错误,通常选择格式化SD卡,重新建立FAT分区,如图13-10所示。

eac17b099d875b6b26537f812ad53a59.png 

图13-10 格式化磁盘

FatFs也有格式化功能,当检测到f_mount函数返回的数据类型为FR_NO_FILESYSTEM(没有文件系统)时,可以使用f_mkfs函数格式化SD卡,重新创建FAT分区。f_mkfs函数描述如下:

1、函数原型

FRESULT f_mkfs (

  const TCHAR* path,  //逻辑驱动器号

  BYTE  opt,            //格式选项

  DWORD au,             //分配单位的大小

  void* work,          //工作缓冲区

  UINT len             //工作缓冲区大小

);

2、参数

path:待格式化的逻辑驱动器号(0-9)的字符串指针,如果没有驱动器号,则表示默认驱动器。比如 "0:" 表示驱动器0对应的磁盘。

opt :指定要在驱动器上创建的FAT类型。指定FM_FAT,FM_FAT32,FM_EXFAT和按位或这三者FM_ANY组合的格式选项,默认选择FM_FAT32即可。

au : 指定分配单元大小(簇),如图13-11所示。其有效值必须是扇区的n倍, 对于FAT卷,n为1到128的幂,如果给定0,则选择默认分配单元大小取决于卷大小。分配单元越大,磁盘的空间利用率越差,但是读/写性能越好,通常设默认大小即可。

work :指向用于格式化过程的工作缓冲区的指针。 当给出空指针时,该函数为工作缓冲区分配一个内存块,而len不起作用(仅在FF_USE_LFN == 3时)。

len :工作缓冲区的大小,以字节为单位,缓冲区越大有助于提高格式化的速率。

3、功能

格式化磁盘,在驱动器上创建FAT/exFAT分区。

4、返回值类型

FR_OK

FR_DISK_ERR

FR_NOT_READY

FR_WRITE_PROTECTED

FR_INVALID_DRIVE

FR_MKFS_ABORTED

FR_INVALID_PARAMETER

FR_NOT_ENOUGH_CORE

5、示例

以格式化SD卡为例:

f_mkfs("0:", FM_FAT32, 0, FatBuff, sizeof(FatBuff)); //格式化SD卡

其中BYTE FatBuff[FF_MAX_SS]; 为定义的格式化缓冲区,有利于提高格式化速率。BYTE为FatFs定义的数据类型,原型为 unsigned char。

13.4.3 获取磁盘空间——f_getfree函数

当SD卡挂载成功后,在计算机上会直接显示出SD卡的大小及剩余空间。FatFs中只需调用f_getfree函数,即可获取磁盘的存储空间信息。f_getfree函数描述如下:

1、函数原型

FRESULT f_getfree (

  const TCHAR* path,  //逻辑驱动器号

  DWORD* nclst,       // 空闲簇数量

  FATFS** fatfs       // 对应的文件系统对象

);

2、参数

path:指向以null结尾的字符串的指针,该字符串指定逻辑驱动器。 空字符串表示默认驱动器。

nclst:指向DWORD变量的指针,用于存储空闲簇的数量。DWORD为FatFs中定义的unsigned long型,更多FatFs数据类型可以查看ff.h文件。

fatfs:指向存储指向相应文件系统对象的指针的指针。

3、功能

获取卷上的总簇以及空闲簇的数量,单位是字节(Byte)。

4、返回值类型

FR_OK

FR_DISK_ERR

FR_INT_ERR

FR_NOT_READY

FR_INVALID_DRIVE

FR_NOT_ENABLED

FR_NO_FILESYSTEM

FR_TIMEOUT

5、示例

f_getfree("0:", &fre_clust, &fatfs); //获取SD卡空闲簇数量

其中fre_clust为定义的unsigned long型变量,在定义的FATFS类型的fs结构体中,n_fatent成员记录了总的簇数(实际簇数需要减2),csize成员记录了每个簇的大小,通过计算即可获取总的空间及剩余空间。

13.4.4 创建文件夹——f_mkdir函数

由于计算机上属于图像化界面操作,如果想要创建一个文件夹,只需要双击打开磁盘后,右键点击“新建文件夹”,输入相应的名称即可创建成功。而FatFs创建文件夹需要用到f_mkdir函数,直接输出路径即可创建成功。f_mkdir描述如下:

1、函数原型

FRESULT f_mkdir (

  const TCHAR* path //文件夹路径

);

2、参数

path: 指向以null结尾的字符串的指针,该字符串指定要创建的目录名称。

3、功能

创建一个文件夹,如果想继续在文件夹中创建文件夹,只需要输入正确的路径即可。

4、返回值类型

FR_OK

FR_DISK_ERR

FR_INT_ERR

FR_NOT_READY

FR_NO_PATH

FR_INVALID_NAME

FR_DENIED

FR_EXIST

FR_WRITE_PROTECTED

FR_INVALID_DRIVE

FR_NOT_ENABLED

FR_NO_FILESYSTEM

FR_TIMEOUT

FR_NOT_ENOUGH_CORE

5、示例

比如在SD卡上创建一个名为“KST-32”的文件夹,并在该文件夹下创建一个Test文件夹,代码如下:

f_mkdir("0:KST-32"); //在磁盘0上创建KST-32文件夹

f_mkdir("0:KST-32/Test"); //在KST-32文件夹中创建Test文件夹

13.4.5 打开一个文件——f_open函数

在计算机中创建一个文本文件只需要右键单击“新建”,选择“文本文件”,输入名称即可,创建成功后,双击该文件即可打开。而FatFs中则是使用f_open函数创建或打开文件,与计算机打开“文本文件”类似,使用f_open打开的文件,读/写指针(类似于光标)始终位于文件开头。f_open函数描述如下:

1、函数原型

FRESULT f_open (

  FIL* fp,             //指向文件对象结构的指针

  const TCHAR* path, //文件名

  BYTE mode            //模式

);

2、参数

fp: 指向空白文件对象结构的指针。

path: 指向以null结尾的字符串的指针,该字符串指定要打开或创建的文件名。

mode: 指定文件的访问类型和打开方法。 mode标志位如表13-9的组合如下所示:

表13- 9所示 f_open文件访问类型标志位

385de5bba2669cb5a1b6b5787b1639c1.png

3、功能

打开/创建一个文件,可以通过表13-9所示的标志位选择文件的访问类型,表中所示的类型可以通过“或”操作相互组合。

4、返回值类型

FR_OK

FR_DISK_ERR

FR_INT_ERR

 FR_NOT_READY

FR_NO_FILE

FR_NO_PATH

FR_INVALID_NAME

FR_DENIED

FR_EXIST

FR_LOCKED

FR_INVALID_OBJECT

FR_WRITE_PROTECTED

FR_INVALID_DRIVE

FR_NOT_ENABLED

FR_NO_FILESYSTEM

FR_TIMEOUT

FR_NOT_ENOUGH_CORE

FR_TOO_MANY_OPEN_FILES

5、示例

在KST-32文件夹下创建一个Test.txt,并允许进行写操作

f_open(&fnew, "0:KST-32/Test.txt", FA_OPEN_ALWAYS | FA_WRITE);

读文件操作如下:

f_open(&fnew, "0:KST-32/Test.txt",  FA_READ); //打开0:KST-32/Test.txt

其中fnew为FIL结构体类型变量,用于存储文件的相关信息。

13.4.6 写文件——f_write函数

FatFs通过f_write函数向文件写入数据,默认从文件开头开始写,如果文件非空,原数据将会被覆盖。连续写入数据时,读/写指针会自动移动到下一个地址,不需要手动移动(除非中途关闭后重新打开)。f_write函数描述如下:

1、函数原型

FRESULT f_write (

  FIL* fp,            //指向文件对象结构的指针

  const void* buff, //指向要写入的数据的指针

  UINT btw,          //要写入的字节数

  UINT* bw           //返回写入的字节数

);

2、参数

fp:指向打开文件对象结构的指针。

buff:指向要写入的数据的指针。

btw:要在UINT类型范围内写入的字节数,UINT为unsigned int,是FatFs定义的类型。

bw:指向UINT变量的指针,返回写入的字节数。 无论函数返回代码如何,该值在函数调用后始终有效。

3、功能

将数据写入文件。该函数可以从读/写指针指向的位置将数据写入文件,函数成功后,通过检查* bw可以判断磁盘是否已满,如果* bw

4、返回值类型

FR_OK

 FR_DISK_ERR

FR_INT_ERR

FR_DENIED

FR_INVALID_OBJECT

FR_TIMEOUT

5、示例

向Test.txt写入字符串“Hello World ! ”

f_write(&fnew, "Hello World !", sizeof("Hello World !")-1, &bw);

其中fnew指向被操作的文件,需要与打开的文件一致,否则操作失败。

13.4.7 关闭已打开文件——f_close函数

文件写入成功后,计算机中点击“关闭”按钮即可关闭文本文档(前提需要先保存文件)。而FatFs中则是通过f_close函数实现,并且无需点击保存,写入成功即保存。f_close函数描述如下:

1、函数原型

FRESULT f_close (

  FIL* fp     //指向文件对象的指针

);

2、参数

fp:指向要关闭的打开文件对象结构的指针。

3、功能

关闭一个已打开的文件。如果打开的文件过多而没有及时关闭,会占用大量内存,造成程序崩溃,因此当操作完毕一个文件后要及时关闭文件。

4、返回值类型

FR_OK

FR_DISK_ERR

 FR_INT_ERR

FR_INVALID_OBJECT

FR_TIMEOUT

5、示例

关闭先前打开的Test.txt:

 f_close(&fnew); //关闭Test.txt文件,fnew指向文件

 

13.4.8 移动“光标”——f_lseek函数

如果使用f_open函数重新打开文本,“光标”自动归零。计算机中通过鼠标可以自由移动光标,选择编辑的位置。而FatFs中通过f_lseek函数移动读/写指针至任意位置。f_lseek函数描述如下:

1、函数原型

FRESULT f_lseek (

  FIL*    fp,  /* [IN] File object */

  FSIZE_t ofs  /* [IN] File read/write pointer */

);

2、参数

fp:指向打开文件对象的指针。

ofs:相对于文件起始位置的偏移地址。

3、功能

移动文件对象中的文件读/写指针,而不对文件进行任何读/写操作。当需要快速移动至文件末尾时,可以通过f_size(fp)快速获取文件大小。

4、返回值类型

FR_OK

FR_DISK_ERR

FR_INT_ERR

FR_INVALID_OBJECT

FR_TIMEOUT

5、示例

 移动光标至Test.txt文件末尾(假设fnew为Test.txt文件指针,并且已文件打开)

f_lseek(&fnew, f_size(&fnew)); 

13.4.9 读文件——f_read函数

FatFs中读文件函数f_read与f_write函数用法类似,首先在使用f_open函数打开文件时,赋予文件“读访问权”,然后便可以直接读取数据,连续读数据时,读/写指针会自动移位。f_read函数描述如下:

1、函数原型

FRESULT f_read (

  FIL* fp,     //指向文件对象结构的指针

  void* buff,  //读取数据缓冲区

  UINT btr,    //要读取的字节数

  UINT* br     //返回读取的字节数

);

2、参数

fp:指向打开文件对象的指针。

buff:指向缓冲区的指针,用于存储读取数据。

btr:UINT类型范围内要读取的字节数。

br:指向UINT变量的指针,用于返回读取的字节数。无论函数返回代码如何,该值在函数调用后始终有效。

3、功能

从文件中读取数据。函数成功后,通过检查* br可以判断是否达到文件末尾,当函数返回的字节数* br(实际读到的数据)< btr(计划读取的长度)时,说明已到达文件的末尾。

4、返回值类型

FR_OK

FR_DISK_ERR

FR_INT_ERR

FR_DENIED

FR_INVALID_OBJECT

FR_TIMEOUT

5、示例

读取10个字节至数据缓存区:

f_read(&fnew, ReadBuf, 10, &br);

其中ReadBuf为定义的数据缓冲区指针。

13.4.10 FatFs简单读写文件

本节以挂载SD卡为例,在SD中创建一个“KST-32”文件夹;然后在该文件夹下创建一个“Test.txt”文件;文本文件创建成功后,向其写入字符串“Hello World!”,然后关闭文件。等待1秒后,重新打开文件,然后另起一行写入“Welcome to use FatFs.”;最后将两次写入的字符串读出并显示到LCD上。

如果开发板上没有SD卡,可以通过挂载SPI Flash验证,所有操作方式不变,只需在挂载磁盘时时,选择Flash磁盘对应的驱动号“1”即可。

具体代码如下:

e7bdde96de5429c505cc13fa42287076.png

a422135ac173d380e0052ad5fdf1ba6e.png

25a794574754ebc8ceadf334923c4205.png

将代码下载到开发板中,可以通过LCD中的提示观察实验步骤。如果手头有SD卡读卡器,实验完毕后可以将SD卡挂载到计算机中,查看实验结果。

13.4.11 其他FatFs API函数

由于FatFs API函数数量较多,在此不再一一介绍。更多函数可以直接登录FatFs官网,在图13-11所示“Application Interface”栏中可以找到所有操作函数,函数的具体功能如表13-10所示。点击相关函数即可查看具体使用方法和示例,由于版本原因,不同版本函数用法可能有所差别,但是功能基本一致。

1a9b3e58270cfadbd2f74f13e1cfd5f0.png 图13-11 FatFs 应用函数

表13-10 FatFs操作函数

2a4607340d0525e71c5b4848b719b831.png

513.5 改写事件记录器

在第9章通过Flash构建了一个简单的事件记录器,用来存储每分钟的温湿度值。由于数据是以二进制形式存储在Flash中,只能通过串口小范围输出数据,不便于数据的查看和分析。学习了FatFs后,可以将数据以文件的形式存储在SD卡。

该例程首先在SD卡中创建一个Recorder文件夹,并以日期(年-月-日格式)为名称,每天分别创建一个文本文件用于存储当天数据,程序每分钟存储一次当前时间和温湿度数据。具体代码如下:

1cbb9804a2765d2c4aaa901d8a13c644.png

4944aeeb2ba666e6c20466c7ae35a8ef.png

c95102d79f2c495dd9d223ba58b566db.png

37f8a4bf0bd263a6c259780937fb62c6.png

613.6 基于SD卡的汉字显示

13.6.1 制作GB2312字库

在第七章讲解ILI9341时,学习了通过汉字取模方式实现汉字显示功能,但是该方式仅适合少量汉字显示场景。学习FatFs之后,我们可以将整个汉字字库存储在SD卡或者Flash中,通过文件系统读取字库中的字模,实现任意汉字显示效果。具体方法如下:

1、打开Pctolcd2002取模软件,点击“选项”,进入设置界面,具体配置如图13-12 所示。

fa8bc7734611f08753ad6fa995bdefd5.png 

图13-12 设置字模格式

2、设置完成后,点击“导入”按钮,选择“生成二进制字库文件”和“保持原始顺序”,

如果想查看生成的字库内容,还可点击“生成索引文件”。选择完毕后,点击“生成国标汉字库”按钮,如图13-13所示。

2122733cb4edd3514cb084dedbcac07f.png 

图13-13 设置取模方式

3、接下来为生成的国标汉字字库选择合适的保存路径,并设置文件名,如图13-14 所示。注意:生成的文件为“.FON”格式,不要更改保存类型。

2e17cf4140df7be8ab65de10521531c0.png 

图13-14 保存GB2312字库

4、将SD卡通过读卡器连接到计算机,在SD卡中新建一个“Font”文件夹,并将生成

的“GB2312.FON”文件保存到该文件夹中,至此字模库制作完成。

13.6.2 显示汉字

字库制作完成后,便可以通过FatFs访问字库,读取相应汉字的字模。关于如何查找字模,可以参考第七章所介绍的根据GB2312编码寻址汉字。

已知GB2312的编码范围为:0xA1A1-0xFEFE,其中汉字编码范围:0xB0A1-0xF7FE,而汉字编码的第一字节0xB0-0xF7(对应区号:16-87),第二个字节0xA1-0xFE(对应位号:01-94)。例如,“啊”字是 GB2312 编码中的第一个汉字,它位于 16 区的 01位,所以其区位码为1601。为了兼容 ASCII码,区号和位号分别加上0xA0偏移就得到GB2312编码,因此“啊”字的GB2312编码为0xB0A1。

已知生成的字库是根据 GB2312的区位码表顺序存储的,每个字模的大小为 16x16/8=32字节。通过GB2312编码查找汉字的原理,得到字模的寻址公式:

Addr = ((((chr>>8)-0xA0-1)*94) +((chr&0x00FF)-0xA0-1))*16*16/8

其中chr为汉字GB2312编码,chr>>8获取编码的高字节;chr&0x00FF:获取编码的低字节;(chr>>8)-0xA0-1:计算汉字对应区号;(chr&0x00FF)-0xA0-1:计算该区的具体位置;94是指一个区中有94个字符;最后*16*16/8是指一个汉字字模占用的空间。

具体汉字显示程序如下:

57f95cce7547c6051443265f44cbc075.png

e3c9cc47b7b3e56091809661a3214b75.png

92c8be467c8dae695bcf176fde067918.png

e8b7b5cd205dccf80ea929ea19e638f8.png

713.7 基于SD卡的BMP解码显示

BMP,又称为Bitmap(位图),可以不作任何变换地保存图像像素域的原始数据,是Windows系统中广泛使用的图像文件格式。计算机中可以通过画图、照片查看器等软件方便的打开BMP格式图片,如何通过单片机在LCD上显示一张BMP格式的图片呢?只需要两步:1、解码BMP图片,2、刷新到LCD。

本质上任何文件都是以二进制数存储在设备上,第七章介绍了如何通过取模方式显示BMP图片,而解码其实就是将BMP文件中像素对应的二进制数据读取出来。

已知BMP格式文件按照数据存储的先后顺序主要分为四部分:

1、文件头(bmp file header)

2、信息头(bitmap information)

3、调色板(color palette)

4、位图数据(bitmap data):就是图像数据

13.7.1 BMP 文件头

文件头,顾名思义就是存储BMP文件的相关信息,包括文件类型、文件大小、保留位和位图数据偏移地址。通过声明一个结构体用于保存文件头信息,相关描述如表13-11所示。

2cad55c29ccc8f219529cddca1d932db.png

表13-11BMP文件头描述

6f44e3cb9fd31ac30487b4aa05b2da83.png

特别提示:BMP格式的文件类型默认为“BM”,相应数据位:01000010  01001101,如果按照char型读取文件类型,读到的数据依次为0x42、0x4D;如果按照unsigned int类型读取,读到的数据为0x4D42。出现这种状况的原因是‬‬STM32 属于小端模式。

大端模式:低位字节存在高地址上,高位字节存在低地址上 。小端模式:高位字节存在高地址上,低位字节存在低地址上。

13.7.2 BMP 信息头

信息头主要存储BMP文件的具体信息,包括大小、图像宽度、图像高度、每个像素所占比特数、压缩类型等等,将其声明为结构体,相关介绍如表13-12所示

be265a471f810612d466a0a35cdd5a0f.png

表13-12 BMP信息头描述

8f9b58eca659b8b039df248f2fa4e402.png

13.7.3 调色板

设想一下:画画时为了节省时间,通常将颜料在调色板调色,而不是现用现调。BMP格式中的调色板与此功能类似,即先将图片所用到的像素颜色预先“调好”,使用时直接按照颜色索引去对应调色板“取色(读取数据)”。调色板结构体类型如下所示:

82e94845f4fbd081b37e4aa3eda8e7d4.png

调色板的目的,就是把一张图片所有可用到的颜色依次编号,每种颜色都有唯一的编号。比如单色位图共有21种颜色,即黑和白,假如调色板0表示黑,调色板1表示白,此时每个像素仅用1个比特位存储调色板索引,而不是使用3个字节(R、G、B分量各占一个字节)存储一个像素数据,这样可以节约大量存储空间。但是16位及以上的位图基本不使用调色板,而是直接存储像素数据。以240*320大小的16位位图为例:

调色板占空间:(2^16) * 4=262144字节

直接存储像素所占空间:240 * 320 * 2=110400字节

相比于直接存储,调色板不但没有减小图像所占空间,反而更占空间。因此调色板仅存在于1、4、8位位图中。而16、24、32位位图基本不使用调色板。

13.7.4 位图数据

位图数据就是像素数据,可以通过bfOffBits(像素数据偏移地址)快速定位位图数据。

通常window系统是按四个字节对齐扫描位图数据的,也就是说,位图每行的字节长度应该是4的整数倍,如果不是4的整数倍,系统会自动补全(用0填充)。

window系统显示位图时,扫描顺序按照B、G、R的顺序,而不是R、G、B。以24位位图为例,像素从左到右分别代表B、G、R分量。由于例程中设置ILI9341 TFT-LCD按照BGR的顺序刷新数据,因此不需要对位图数据进行转换,读取后直接写入到LCD即可。

由于16位位图分为RGB555和RGB565格式,而ILI9341 TFT-LCD 设置为16位数据模式,因此需要将读取到的像素数据转换为RGB565格式才能显示。在此通过BMP的压缩类型可以判断像素数据格式。当压缩压缩类型为无压缩时表示像素格式为RGB555,否则为RGB565。

13.7.5 BMP图片显示实验

本节通过对BMP图片进行解码,然后刷新到LCD上。由于BMP图片通常为24位,直接通过单片机解码,然后在进行格式转换降低图片刷新速率。因此可以选择使用图片数据格式转换工具将图片转换为RGB565格式,然后再解码。

1、首先打开Picture2Hex转换工具,该工具位于“Kingst-32F1开发板配套光盘\相关工具软件\取模软件”目录下,如图13-15所示。

d7c27b37008351b20fdb38662a47df59.png 

图13-15 Picture2Hex主界面

2、通过下拉菜单选择合适的图片宽度和高度,并在“RGB”一栏中选择“PixelFormat16bppRGB565”。设置完毕后,点击“File”按钮添加要转换的图片,具体如图13-16所示。

8b4d5bf2585ce83a872c42a5b31760bb.png 

图13-16 设置转换格式

3、转换完毕后,打开Picture2Hex.exe所在文件夹下的“work”文件夹,转换后的图片就保存在该文件夹中,同时还生成相应的二进制像素数据,如图13-17所示。

1c1465573d21b4244537e58cca93ed95.png 

图13-17 转换完毕

4、将SD卡连接至计算机并在其中新建一个“Image”文件夹。将转换后的图片复制到该文件夹下,重新将SD卡连接至KST-32开发板,接下来便可以调用程序实现BMP显示。

本节通过BMS解码显示功能,设计了一个简单的电子相册显示例程。首先在SD卡上新建一个Image文件夹,然后将下载的240*320大小的BMP图片经过Picture2Hex格式转换或者直接存放到Image文件夹中。LCD上会循环显示Image文件夹中的BMP图片。

BMP图片解码显示代码如下所示:

9b9a4932564dd103c876ad3ba7b6b667.png

e74e2e2c3db781128e2414354d46ee41.png

988167bee2ae02655fd33a6076c2f1e5.png

4c2ebbb43ea2e0ef5e90d7846b6d26fb.png

57e7cc36fd92d372be56dc316416f548.png

39c452086bae8d2d8e6ac5b870aa1244.png

889c1e24223f96334f1d5ab7ceee1a7e.png

由于单片机内存不足,不能够直接对图片进行解码,而是边解码边刷新显示。同时由于FatFs频繁传输小数据的速率较慢,使得图片的刷新速率相对比较缓慢。本例程也支持24位BMP图片解码,但是刷新速率要比16位BMP图片慢,大家可以直接从网上下载一张240*320大小的BMP图片至SD卡“Image”文件夹,自行实验。

813.8 课后习题

由于FatFs频繁操作文件的速率比较慢,为了提高汉字的刷新速率,可以将字库从SD卡中安装到Flash的预留空间中(10MByte之后的空间),通过地址偏移直接从Flash中读取字模。当检测到Flash中没有字库或者字库损坏时,通过FatFs从SD卡中重新安装字库至Flash,实现字库自动更新功能。

c27b4dbe3e9d2478fb0725b24e4dbe94.gif

金沙滩工作室

长按识别关注

44f0a1637e55c3ea4357be61f7762b5b.png

知识共享|助力梦想

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值