Zynq Fatfs文件系统应用笔
Hello,panda
笔记介绍基于所描述的Zynq Fatfs基于Xilinx xilffsv3.0和Sdpsv2.4,文件系统采用在Bare-Metal和轻量级操作系统中常用的FatFs,版本为v0.10b。
在开始介绍FatFs文件系统在Zynq实现之前一定要先对FAT32文件系统有一个清晰的了解。
1 FAT32文件系统
应用笔记针对SD上的FAT32文件系统,在对文件系统作详细介绍之前,先回顾一下硬盘的结构,如图1:
图1 硬盘结构
对一个机械硬盘而言,柱面、磁头、扇区确定唯一的扇区物理地址,在数据组织上来看,总是按照主引导区→引导扇区→数据→引导扇区…来排列,一块硬盘上,基本分区的最大数目为4个,大于4个的被自动分配为扩展分区。SD卡的分区结构和普通磁盘类似,沿用了普通机械硬盘的大多数概念术语。因为它无需启动,所以MBR区没有引导信息,图2是SD卡文件系统的典型结构。
图2 SD卡文件系统结构
1.1 主引导记录(MBR)
在一个硬盘中主引导分区位于硬盘的起始扇区,一共512个字节,包含了446字节的MBR和64字节的DPT,并以55 AA作为结束标志。
因为SD卡不用启动,因此MBR区域不包含引导数据,SD卡的MBR如图2所示,数据均为小端模式,低字节在前。
图2 SD卡MBR区域
图2所示为SD卡物理地址为0开始的512字节区域,红蓝线标注的数据包含了两个重要信息:
① 红线数据:表示下一个分区的扇区地址,这里是0x2000,即下一个分区从第8192个扇区开始,也就是DBR在第8192个物理分区(0逻辑分区)。
② 蓝线数据:表示总扇区数为0x01DAAC00,那么可以计算得到当前SD卡的总容量为:
ρ=0x01DAAC00*512= 14.83GB
1.2 引导扇区DBR
引导扇区DBR共512个字节,以55 AA结束,前11个字节为跳转指令和文件系统类型、版本号信息,结构如图3所示。
图3 DBR内容
DBR为逻辑分区起始位置,需要关注以下重要信息:
① 偏移地址0x0D标记每簇的扇区数,图3显示为64,即每簇为32KB,文件系统中保存数据的最小单位为簇,只要本簇中写入了当前文件的数据,它就不可以被其他文件使用。
② 偏移地址0xE~0xF标记了本分区的保留扇区数,图3中为598个,也就是说下一个分区(FAT表)的起始扇区为逻辑扇区598。
③ 偏移地址0x10~0x11标记了FAT表的个数,图3显示为2个。
④ 偏移地址0x24~0x27标记了FAT表的大小,图3显示为3797个。
1.3 信息分区FSINFO
信息分区FSINFO的结构非常简单,用以记录文件系统中空闲簇的数量以及下一可用簇的簇号等信息,以供操作系统作为参考,常位于1号逻辑扇区。如图4所示。
图4 FSINFO内容
图4中红色方框的为FSINFO签名,固定为0x61417272;蓝色方框中内容为文件系统的空簇数,根据使用情况动态变化;黑色方框为下一个空簇的位置,根据使用情况动态变化。
通常情况下,2号扇区也以55 AA结束,6号扇区是DBR的备份,7号扇区是FSINFO的备份,8号扇区是2号扇区的备份。
1.4 FAT表
从FAT表开始便是文件系统的核心内容,文件占用磁盘的最小单位是簇。即使文件只有一个字节,那么它也占用一个簇的磁盘空间,大文件会占用多个簇。同一个文件的数据并不是连续的存放在一段磁盘空间内,而是分成若干段,像链子一样存放,称之为文件的链式存储。为了实现文
件的链式存储,文件系统必须准确地记录哪些簇已经被文件占用,还必须为每个已经占用的簇指明存储后继的下一个簇的簇号,对于文件的最后一簇,则要指明本簇无后继簇。这些都是由 FAT 表来保存的,FAT 表对应表项中记录着它所代表的簇的有关信息:诸如是空,是不是坏簇,是否是已经是某个文件的尾簇等。
对于FAT表而言,它的重要使命是:描述簇的分配状态以及标明文件或目录的下一簇的簇号。
1.4.1 FAT表分析
FAT表中每簇的地址固定为32bit,按四字节对其进行划分。并由0开始进行编号。0号和1号簇由系统保留作特殊使用,从2号簇开始对应文件系统的实际数据区的簇号,FAT表中的簇地址编号与数据区的簇号相同。
在创建文件系统(格式化)时,所有的FAT表均被清空,FAT1和FAT2的0、1号表项写入特定值,2号表项常为根目录,因此2号表被写入结束标志。
① FAT32文件系统FAT表的0号表项固定为0x0FFFFFF8;
② 1号表项正常情况为0xFFFFFFFF或0x0FFFFFFF,也可能被用于记录脏标志,以说明文件系统没有被正常卸载或者磁盘表面存在错误;
③ 若簇未被分配使用,则其FAT表项为0;
④ 若该簇被使用,那么FAT表项值就是该文件下一个存储位置的簇号,如是文件结束簇,则写结束标志“0x0FFFFFFF”;
⑤ 若该簇存在坏扇区,则用“0xF7FFFFF”标记该簇为坏簇;
⑥ 新建目录时,只为其分配一个簇的空间,对应的FAT表现写入结束标志,当目录超过一个簇时则在空闲区再为其追加一个簇并重新建立FAT表链。
1.4.2 FAT表实例
图5是一个SD的FAT32文件系统FAT表,每四个字节一组表示一个簇的信息,簇号从0开始。
① 0x0FFFFFF8: FAT表起始标志;
② 0xFFFFFFFF:不用,默认值;
③ 0x0FFFFFFF:根目录所在簇;
④ 从FAT表项第3项开始记录存储文件的信息,FAT表第三项记录了存储文件的下一个簇号为4,…直到第七项结束。
图5 FAT32文件系统FAT表实例
根目录所在的簇为3号簇,这个偏移地址可以通过计算得出。
① 逻辑地址:保留区大小+FAT表大小*2,那么本文中描述的文件系统根目录的位置为 598+3797*2 =8192。即根目录从逻辑扇区第8192扇区开始。
② 物理地址:隐藏区域+逻辑地址。本例中隐藏区域在MBR表中可以看到是8192,那么根目录的物理地址就是从第16384个扇区开始。
本例中每32KB为一个簇,2号簇即存储根目录的实际数据区域。
1.5 根目录
根目录在文件系统建立时即已被创建,其目的就是存储目录(也称文件夹)或文件的目录项。每个目录项的大小为32个字节,这32个字节可以是长文件名目录项、文件目录项、子目录项等。图6是本例中根目录的内容。
图6 根目录内容
首先重点提醒图6中显示‘X’号的文件是已经删除但没有格式化的文件数据,仍然可以恢复读出,所以“重要数据”删除后一定要记得深度格式化。
1.5.1 短文件名目录项
目录所在的扇区,都是以 32 Bytes 划分为一个单位,每个单位称为一个目录项(DirectoryEntry ),即每个目录项的长度都是 32 Bytes 。根目录由若干个目录项组成,一个目录项占用 32 个字节,可以是长文件名目录项、文件目录项、子目录项等。32 字节的具体定义如下表1所示。
表1 FAT32短文件名目录项定义
字节偏移(16进制) | 字节数 | 定义 | |
0x0~0x7 | 8 | 文件名 | |
0x8~0xA | 3 | 扩展名 | |
0xB* (不可取值0F,0F表示长文件名目录段) | 1 | 属性字节 | 00000000读写 |
00000001只读 | |||
00000010 隐藏 | |||
00000100 系统 | |||
00001000 卷标 | |||
00010000 子目录 | |||
00100000 归档 | |||
0xC | 1 | 系统保留 | |
0xD | 1 | 创建时间的10ms位 | |
0xE~0xF | 2 | 文件创建时间 | |
0x10~0x11 0x12~0x13 0x14~0x15 0x16~0x17 0x18~0x19 0x1A~0x1B 0x1C~0x1F | 2 2 2 2 2 2 4 | 文件创建日期 文件最后访问日期 文件起始簇号的高16位 文件最近修改时间 文件最近修改日期 文件起始簇号的低16位 文件的长度(字节),单文件最大4GB |
下面分析图6中第一个32字节的内容,分析结果写入表2。
表2 根目录第一个32字节内容
字节偏移(16进制) | 字节内容 | 含义 |
0x0~0x7 | 47,49,52,47,52,41,57,20 | 文件名”girlraw”的ASIC码 |
0x8~0xA | 52,41,57 | 扩展名raw |
0xB | 20 | 归档 |
0xC | 1 | 系统保留 |
0xD | AE |
|
0xE~0xF | 8C56 | Bit[15:11]:小时,17时 Bit[10:5]:分18分 Bit[4:0]:秒,2秒为单位,44秒 |
0x10~0x11 | 4703 | Bit[15:9]:年,1980相对值,2015 Bit[8:5]:月,8月 Bit[4:0]:日,3日 |
0x12~0x13 | 470A |
|
0x14~0x15 | 0000 | 文件起始簇号的高16位 |
0x16~0x17 | 729D |
|
0x18~0x19 | 46F7 |
|
0x1A~0x1B | 0003 | 文件起始簇号的低16位,文件存储第一个簇的位置是3号簇 |
0x1C~0x1F | 00028000 | 163840个字节 |
1.5.2 长文件名目录项
FAT32 文件系统在为文件分配短文件名目录项的同时会为其分配长文件名目录项,文件系统在为文件创建长文件名(Long File Name,LFN)类型的目录项时,并没有舍弃原有的短文件名目录项,具有LFN的文件同时也有一个常规的SFN(Short File Name,短文件名)类型目录项。之所以仍然需要SFN,是因为LFN目录项只包含文件的名字,而不包括任何有关时间、大小及起始簇号等信息,这些信息仍然需要用SFN目录项来记录。
如果一个文件的文件名超过了 8 个字符,则会为其名字截短后为其建立短文件名。将短文件名存储在短文件名目录项中,长文件名则存放在长文件名目录项中。
长文件名目录项具有以下特点:
① LFN 和 SFN 目录项结构在相同位置有一个属性标志字节,LFN 目录项使用一个特定的属性值,以说明它是一个长文件名项;
② 项中的其他字节,使用UTF-16 编码,存储 13 个 Unicode字符的文件名,每个字符占用两个字节;
③ 如果文件名长于 13 个字符,则继续为其分配LFN 项,直到够用为止;
④ 所有 LFN 都包含一个校验和,通过这个校验和将其与相应的SFN 项关联起来;
⑤ 一个文件的所有 LFN 项按倒序排列在它的 SFN项前面,即文件名的第一部分距离 SFN 是最近的。
表3是长文件名目录项的具体结构。
表3 长文件目录项定义
偏移字节 | 字节数 | 含义 |
0x00 | 1 | 如目录项使用中则为序列号,未分配则为0x00,曾经使用但已经被删除则为0xE5。一个文件的第一个长文件名目录项的序号为0x01,然后依次递增,若是最后一项则将该目录项序号与0x40进行OR运算后写入。 |
0x01~0x0A | 10 | 文件名的1~5字符(Unicode),未使用部分先填充两个0x00,然后用0xFF填充 |
0xB | 1 | 长文件名目录项属性标志0x0F |
0xC | 1 | 保留未使用 |
0xD | 1 | 校验和,如一个长文件名需要几个长文件名目录项进行存储,则他们具有相同的校验和 |
0x0E~0x19 | 12 | 文件名的6~11个字符,未使用部分先填充两个0x00,然后用0xFF填充 |
0x1A~0x1B | 1 | 保留未使用 |
0x1C~0x1F | 4 | 文件名的12~13个字符,未使用部分先填充两个0x00,然后用0xFF填充 |
图7是根目录下长文件名目录项的截图。
图7 长文件名目录项
如图7所示,红色方框部分文件名占用三个目录项,最下面的是短文件名目录项,指定了文件的所有属性;紧挨短文件名目录项倒着向上数是第一第二条长文件名目录项。
① 第一条长文件名目录项的序列号直接为0x01,这和短文件名目录项第七个字节的跳转指示序号相同;第二条长文件名目录项未终止项,值为0x02 or 0x40 = 0x42;
② 第一和第二条长文件名目录项具有相同校验和0x69。
1.5.3 子目录
在FAT32文件系统中,除根目录在创建文件系统时即被建立并分配空间外,其他所有的子目录都是在使用过程中按需建立。新建一个子目录时,在其父目录中建立目录项,在空闲空间中为其分配一个簇并对该簇进行清零操作,同时将这个簇号记录在它的目录项中。如果在根目录下创建一个子目录,那么这个子目录就是根目录的子目录,称根目录为这个子目录的父目录。
创建子目录时,在其父目录所在簇为子目录建立目录项,记录了子目录的起始簇号。同时,在为子目录分配目录项的簇中,用前两个目录项描述它与父目录的关系。
图8 子目录项前两个目录的内容
从图8子目录项的前两个目录可以看到,文件系统创建了.和..两个目录,其中.目录项描述了该子目录自身的信息;..目录项描述了其父目录的信息(创建该目录所在目录的第一个簇号,根目录为0x00),建立了子目录和父目录之间的联系。从第三条目录项开始为下级子目录或文件建立的目录项。
① “.”目录项,该目录指向自身,图8中它的簇号为0x03,就是当前所在簇;
② “..”目录项指向它的父目录所在簇,图8中它的父目录簇号为0x00,那么指示创建该目录的目录为根目录。
1.6 FAT32文件操作
本节简要介绍建立、读取和删除文件的步骤。
1.6.1 建立文件
目标:在根目录下建立一个叫QspiFlash的子目录,并向子目录中写入名为“Zynq Qspi 控制器应用笔记.docx”的文件。簇大小格式化为32KB,待写入的文件大小为33KB,需占用两个簇。
第一步:读取引导扇区DBR,根据引导扇区的信息来定位FAT表、根目录和数据区的位置;
第二步:查看根目录下的每个目录项,找到名字为“QspiFlash”且具有子目录属性的目录项,找到它的起始簇号为0x03;
第三步:读取3号簇的内容,查找每一个目录项,直到找到未分配的位置;
第四步:找到后写入文件名“ZynqQspi 控制器应用笔记.docx”及其相关属性;
第五步:为文件数据分配空间。转到FAT表,寻找FAT表中未使用的空间,发现4号簇未使用,将4号簇中写入结束标记;
第六步:将数据写到4号簇中并将簇号写到文件目录项中,为剩下1KB数据寻找下一个可用簇;
第七步:在FAT表中寻找可用簇,发现5号簇可用,那么将4号FAT表项值该为5并在5号FAT表项值中写入结束标记;
1.6.2 读取文件
读取文件的过程和建立文件的过程相反。
第一步:和建立文件第一步第二步相同;
第二步:在3号簇中查找每一个目录项,找到名为“Zynq Qspi 控制器应用笔记.docx”的文件并获取其相关属性;
第四步:读取第一个簇的数据并转到FAT表,将下一个簇的数据读出,直到全部读出。
1.6.3 删除文件
删除文件实质就是:
① 将簇所在FAT清零;
② 将文件目录项的第一个字节清零;
删除文件仅删除FAT表和置目录项标记,实际文件数据并未及时删除,可以根据文件系统的构成特点全部恢复或部分恢复。
2 SD卡
SD卡系列规范由Panasonic、SanDisk、Toshiba等发起,处理器端的SDHost、SD卡(SD Device)和文件系统都必须遵循下列规范:
《SD Specifications Part1 Physical Layer Specification》
《SD Specifications PartA2 SD Host Controller Standard Specification》
《SD Specifications Part2 File System Specification》
2.1 SD卡物理特性
(1)容量:SD标准协会定义,容量≤2GB的为标准卡,超过2GB的为大容量卡。
(2)电压:高电压供2.7~3.6V;双电压供电LowVoltage Range 和2.7~3.6V双电压;
(3)速度:默认模式0~25MHz时钟频率,高速模式0~50MHz时钟频率;
2.2 总线访问
SD卡均能通过4bit/1bitSD模式或模式访问,这里只对SD模式作简要说明。
上电SD默认为1bit SD模式,Host可通过命令动态重配。命令和应答通过CMD线传输,数据通过DATA线传输。命令访问格式如图9所示。
图9 SD卡命令访问及应答格式
SD总线数据访问有两种格式,分别是常规数据访问和宽位宽数据访问。
图10 常规数据访问
常规数据访问如图10所示,总是以MSB的格式输出,在4bit模式下,CRC每通道单独校验,但校验状态只通过DATA0输出,CRC校验位为16bit。宽位宽数据访问如图11所示,也是MSB的格式输出,但它的位宽单位固定是512bit。
图11 宽位宽数据访问
SD卡擦除写入数据的最小单位为Block,一个Block的大小固定为512B。对于标准卡,擦除起始地址和擦除结束地址命令后跟的地址单位都是字节,因此需要文件系统保证512B对齐;对于高容量卡,擦除起始地址和擦除结束地址命令后跟的地址单位都是Block。
2.3 SD卡初始化
SD卡初始化包括SD复位、工作参数握手等工作,初始化流程如图12所示。
图12 SD卡初始化流程
有关SD物理层更加详细的操作以及详细的命令含义请查找SD协会颁布的标准文档:《SD Specifications Part 1 Physical Layer Specification》。
2.4 SD读写
SD初始化完成后,对它的读写操作实际上访问其扇区数据。通过发送CMD25实现写数据、CMD18命令实现读数据;至于读写数据到哪个位置(扇区),数据的内容是什么,SD卡物理层并不关心,都是通过文件系统进行管理。
3 Zynq SD控制器
Zynq SD控制器符合《SDSpecifications Part A2:SD Host Controller Standard Specification》的定义,有SDMA(单操作DMA)、ADMA1(有4K边界限制)和ADMA2(在32位系统中允许任何位置和任意大小)。接口兼容eMMC、MMC3.31、SDIO2.0、SD2.0、SPI,支持SDHC、SDHS器件。下面描述SD控制器的一些关键特点。
3.1 控制器带宽控制
控制器采用读和写通道各自双缓冲FIFO的机制提高吞吐带宽。
3.1.1 双缓冲FIFO
SD控制器读写通道采用独立的512byte深度的双缓冲FIFO,在写操作时,处理器向第二个FIFO DMA数据时,可将第一个FIFO的数据写到SD总线;在读操作时,SD总线向第一个FIFO写数据时,处理器可将数据从第二个FIFO的数据读出。通过双缓冲机制以保证最大带宽。
在写传输中,只有当其中一个FIFO中数据准备好且SD总线空闲时HOST才会向SD卡写数据,因此不会发生FIFOUnderrun的条件。在DMA模式下,只有当FIFO可以接受一个Block的数据后才释放DMA读请求命令;在非DMA模式下,只有buffer write ready中断产生后才可写数据到FIFO。
在读传输过程中,当FIFO都写满后HOST会停止SD时钟暂停传输,因此不会出现Overrun的情况,在DMA模式下接收到一个Block数据后才释放DMA写命令;在非DMA模式,接收到readready interrupt后处理器方将数据写到内存。
3.1.2 Stream 读写
这种模式既可以在DMA模式下使用,也可以在非DMA模式下使用,写操作时WRITE_DAT_UNTIL_STOP(CMD20)开始,直到STOP_TRANSMISSION结束;读操作时READ_DAT_UNTIL_STOP(CMD11)开始,直到STOP_TRANSMISSION结束。
在这种模式下因为不知道数据的长度,所以在数据传输中建议将Block Size寄存器设置为最大的512Byte,这样子当读写512Byte数据后会切换FIFO。
3.2 编程模型
本节描述HOST到SD卡传输的编程模型。
3.2.1 数据传输协议
SD卡传输协议包含三种模式:
(1) 单块传输:总块数HOST已知,但一次只传输一个块;
(2) 多块传输:总块数HOST已知,一次传输一个或多个块;
(3) 无限制传输:总块数HOST未知,传输直到收到中断传输命令执行,传输中断命令在SD Card为CMD12,SDIO为CMD52;
3.2.2 无DMA传输
下图13是无DMA传输的流程图,在无DMA传输过程中,处理器需要等待SD控制器中断或轮询状态,数据组织也完全需要通过处理器来完成,占用CPU资源很大。以下是对流程图步骤的分解:
(1)设置BlockSize寄存器,Block_Size_Block_Count.Transfer_Block_Size,对CMD17, CMD18, CMD24, CMD25, CMD53访问有效,仅在未发生传输时可以修改。
(2)设置BlockCount寄存器,Block_Size_Block_Count.Blocks_Count_for_
Current_Transfer。仅在多块传输模式下有效,控制器在每传输完一块后自减,只有在空闲时可以修改。
(3)设置Argument寄存器,该寄存器需写入SD访问CMD的bit[39:8];
(4)设置Muti/Single模式,BlockCount使能,Data传输方向,AutoCMD12等,配置Transfer_Mode_Command寄存器。
(5)写Command寄存器,当写寄存器的最高字节时,SD控制器发出命令;
(6)等待命令完成中断;
(7)向中断状态寄存器位写‘1’清除中断位;
(8)读命令响应寄存器获取从SD卡得到的相关信息;
(9)判断是写操作还是读操作;
(11)等待bufferwrite ready中断,在无DMA写操作模式下,MCU冲当Master,在收到该中断后,通过BufferData Port register向FIFO中写数据,只有当FIFO为空或者可以接一整帧的数据后释放该中断。
图13 是无DMA传输模式流程图
3.2.3 单DMA传输
单DMA传输的流程图如图14所示,在这种模式下,数据搬运通过SDMA完成,无需处理器参与,释放了CPU资源。
在SDMA模式下,SystemAddress存储的是数据传输的实际地址。
图14 单DMA模式下操作流程图
3.2.4 使用ADMA传输
使用ADMA传输和SDMA传输类似,只是ADMA的性能更好。ADMA传输的流程图如图15所示。
使用ADMA传输时,ADMASystem Address寄存器里存储的是地址描述表(descriptor table)的首地址,当Block Count Enable使能时,通过Block Size和Block Count寄存器确定数据传输的大小,但总大小需和descriptor table的描述相同,由于Block Count寄存器只有16bit,表示的范围有限。当Block Count Enable关闭时,传输数据的大小只由descriptor table决定。地址描述表有32位地址模式和64位地址模式两种,在32位系统中采用32位地址模式。
图15 使用ADMA传输流程图
3.2.5 地址描述表
ADMA1有4K边界限制,ADMA2没有,在一般的系统中ADMA2使用更加灵活。两者的地址描述表(descriptor table)也不同。图16是ADMA descriptor table的结构。
图16 ADMA descriptortable结构
(1) ADMA1地址描述表
使用ADMA1的起始地址必须是4K边界的顶部,但它可以减少descriptortable的尺寸,。
ADMA1 descriptortable每一条为32bit,图17是descriptor table的具体含义,其内容由Host驱动程序组织,请注意ADMA1 descriptor table存储也必须遵循4KB边界限制。
图17 ADMA1 descriptor table位定义
① Nop,表示无操作,ADMA直接跳过这一条;
② Set,表示获取数据长度,ADMA启动默认为4KB,通过该命令修改;
③ Tran,表示获取首地址并启动传输,地址必须是4KB顶部;
④ Link,表示链接到下一个descriptor table的地址。
(2) ADMA2地址描述表
使用ADMA2只需要保证32位对其即可,其descriptortable的地址既可以是32位也可以是64位,分别用于32位和64位的系统。图18是ADMA2 descriptor table位定义。
图18 ADMA2descriptor table位定义
图18中所示Length域最大为65536字节(Length=0x0000),超过64KB即要利用Link命令新建新的descriptortable,直到可以将整个文件全部传输完毕。如果文件传输完毕需要使能中断,那么END位和Int位均要置1。
3.2.6 中断传输操作
SD存储卡采用CMD12,SDIO采用CMD52命令,有两种情况下的终止传输:
(1) 终止无限制传输;
(2) 终止多重块传输;
终止方式也有同步终止和异步终止两种方式:
(1)同步终止:向BlockGap Control寄存器的Block Gap Request写‘1’停止当前传输,等待传输完成后释放终止命令,最后再进行数据线和命令线的复位。
(2)异步模式:在CommandInhibit不为‘1’的任何时刻;
一般来说,只有在事先并不知道传输文件长度的情况下才会选择中断传输操作。
4 FatFs文件系统及实现
Xilinx SDK提供非操作系统下的基于FatFsv0.10b的轻量级文件系统实现,SDK2015.2下的库版本是xilffsv3.0。
4.1 FatFS文件系统
FatFS文件系统是开源的轻量级文件系统,作者思路非常清晰,代码短小精悍,有FatFS通用版和FatFs Tiny板两个版本。通用板适合在ARM、PowerPC等RAM较大的处理器上实现,Tiny版适合在C51、AVR等RAM较小的处理器上实现。FATFS具有以下突出特点:
(1) 结构清晰,代码量少,文件系统和IO底层分开,特别适合初学者学习;
(2) 支持最多10个逻辑盘符和两级目录;
(3) 支持FAT12/FAT16和FAT32文件系统;
(4) 支持长文件名,默认支持8字节的短文件名。
从实现上说,FatFs文件系统只负责文件系统的文件管理,对底层只规定了调用接口函数规范,具体的驱动需根据不同平台区别设计。
4.1.1 FatFs文件系统的结构
FatFs文件系统最多可以挂在10个卷,图19是FatFs文件系统可以支持的设备层次结构图。这种,文件系统层与底层独立的结构可以让应用层访问不同的地层设备。目前,FatFs主要是用于SD(MMC)文件系统,用于U盘、NAND Flash还比较少。
图19 FatFs 支持设备层次结构
4.1.2 FatFs文件系统实现函数详解
可以通过FatFs官网获取最新版本的函数接口技术文档或搜索关键字“FatFs通用FAT文件系统 0.09a中文手册”获得国内技术人员翻译的中文文档,这里不再详述。
4.2 Zynq FatFs实现
Zynq FatFs实现的结构如图20所示,实现是完成diskio.c中的相关调用函数。
图20 FatFs实现结构
4.2.1 用户应用程序
用户应用程序就是调用文件系统的相关数据进行创建目录、格式化、写入文件、读出文件等操作。以下是向SD卡写入文件的例子。
/***************************************************************************************
* SDWriteAccess()
* Description : This function provides theSD Card interface for the Simplified header
* functionality of Write.
* Argument(s) : SourceAddress is address inDDR data space;
* LengthBytes is the number of bytes to move;
* filename is the name of the Write file inSDcard
* Return(s) : XST_SUCCESS if the controller initializescorrectly
* XST_FAILURE if the controllerfails to initializes correctly.
* Caller(s) : any.
* Note(s) : none.
****************************************************************************************/
FILfil; /* File object */
u32SDWriteAccess(u32 SourceAddress,u32 LengthBytes,constchar *filename)
{
FRESULT rc; /* Result code*/
UINT
strcpy_rom(buffer,filename);
file_name= (char*)buffer;
rc= f_open(&fil, file_name, FA_OPEN_ALWAYS | FA_WRITE);
if (rc) {
"SD: Unable to Write File %s\n", file_name);
return XST_FAILURE;
}
fsize);
if (rc) {
"Shift Pointer To the End of File Failed \r\n");
return XST_FAILURE;
}
rc= f_write(&fil,(void*)SourceAddress,LengthBytes,&bw);
if(rc){
"Write File To SD Card Failed \r\n");
return XST_FAILURE;
}
fsize);
if(rc){
return XST_FAILURE;
}
rc= f_close(&fil);
if(rc){
return XST_FAILURE;
}
return XST_SUCCESS;
}
4.2.2 FatFs模块
FatFs模块主要是设置其编译选项,如设置编译复杂度宏_FS_MINIMIZE,支持长文件名宏_MAX_LFN等,根据需求对照ff.h里面的注释或说明手册设置即可。
4.2.3 底层IO接口
底层IO接口,由用户在diskio.c函数中实现,主要是实现:
① SD卡初始化,通过disk_initialize函数完成,主要实现图12所列的SD卡初始化流程;
② 读数据,通过disk_read实现,从SD卡读取若干扇区的数据;
③ 写数据,通过disk_write实现,向SD卡写入若干扇区的数据;
Xilinxxilffsv3.0文件系统库目前采用ADMA2传输数据,用轮询状态的方式判断数据传输情况,本用于FSBL从SD卡搬运启动镜像,在实际使用的过程中可改为中断的方式释放CPU资源。
在驱动调试的过程中常常会遇到SD卡一直写保护的现象,这种现象产生的原因一般是:a)TF卡没有WP引脚,但是在Vivado Block Design中配置ZYNQ时为其配置了该引脚导致误判;b)Linux驱动默认会一直判断硬件WP状态,在操作TF卡时应注释该功能。