FAT32文件系统的存储机制及其在单片机上的实现 FAT32文件系统您一定不会陌生,最多看到它是在windows操作系统里,但在一些嵌入式产品(如手机、MP3、MP4等)中,也能看到它的身影。从某种意义上来讲,FAT32文件系统是非常成功的,使我们可以脱离底层储存设备驱动,更为方便高效地组织数据。给单片机系统中的大容量存储器(如SD卡、CF卡、硬盘等)配以FAT32文件系统,将是非常有意义的(如创建的数据文件可以在windows等操作系统中直接读取等)。
FAT32本身是比较复杂的,对其进行讲解的最好方法就是实际演练。笔者手里持有一张刚以FAT32格式化的SD卡,我们就围绕它来讲解FAT32的实现机理。
FAT32分为几个区域,这里将用实例的方法对它们的结构与在文件存储中的功能进行详细的剖析。
1、实例说明
此实例首先在一张空的SD卡(已被格式化为FAT32格式)上创建一个文本文件,并在其中输入20个字符。再将它插入到单片机系统中,实现对这个文件的读
取,将文件内容输出在调试终端上。
2、实现过程
1)格式化与创建文件
Windows上的磁盘格式化与文件创建就不用多说了。如下图:
2)DBR(DOS BOOT RECORD 操作系统引导记录区)
DBR是我们进军FAT32的首道防线。其实DBR中的BPB部分才是这一区域的核心部分(第12~90字节为BPB),只有深入详实的理解了BPB的意义,
才能够更好的实现和操控FAT32。关于DBR在FAT32中的地位就不多说了,
以下面实际的DBR内 图所示:
上面的数据看起来杂乱不堪,无从下手,其实对我们有用的数据只不过90
个字节(如图中彩色线标记的字节)。仅仅是这90个字节就可以告诉我们关于
磁盘的很多信息,比如每扇区字节数、每簇扇区数、磁道扇区数等等。对于这
些信息的读取,只要遵循DBR中的字段定义即可。(比如图中紫色字段的两个
字节表示这张磁盘的每一个扇区有512个字节,具体的计算方法见下文)
字段定义如下表(BPB后面的422个字节对我们的意义不大,表中省略):
字段名称长度含义偏移量
jmpBoot 3 跳转指令 0
3
OEMName 8 这是一个字符串,标识了格式化该分
区的操作系统的名称和版本号
BytesPerSec 2 每扇区字节数11
SecPerClus 1 每簇扇区数13
RsvdSecCnt 2 保留扇区数目 14 NumFATs 1 此卷中FAT表数 16
RootEntCnt 2 FAT32为0 17
TotSec16 2 FAT32为0 19
Media 1 存储介质 21
FATSz16 2 FAT32为0 22
SecPerTrk 2 磁道扇区数 24
NumHeads 2 磁头数 26 HiddSec 4 FAT区前隐扇区数 28
TotSec32 4 该卷总扇区数 32
FATSz32 4 FAT表扇区数 36
ExtFlags 2 FAT32特有 40
FSVer 2 FAT32特有 42
RootClus 4 根目录簇号 44
FSInfo 2 文件系统信息48
BkBootSec 2 通常为6 50
Rerved 12 扩展用 52 DrvNum 1 - 64 Rerved1 1 - 65 BootSig 1 - 66 V olID 4 - 67 FilSysType 11 - 71 FilSysType1 8 - 82 DBR的实现代码:
struct FAT32_DBR
{
unsigned char BS_jmpBoot[3]; //跳转指令offt: 0
unsigned char BS_OEMName[8]; // offt: 3
unsigned char BPB_BytesPerSec[2];//每扇区字节数offt:11
unsigned char BPB_SecPerClus[1]; //每簇扇区数offt:13
unsigned char BPB_RsvdSecCnt[2]; //保留扇区数目offt:14
unsigned char BPB_NumFATs[1]; //此卷中FAT表数offt:16
unsigned char BPB_RootEntCnt[2]; //FAT32为0 offt:17
unsigned char BPB_TotSec16[2]; //FAT32为0 offt:19
unsigned char BPB_Media[1]; //存储介质offt:21
unsigned char BPB_FATSz16[2]; //FAT32为0 offt:22
unsigned char BPB_SecPerTrk[2]; //磁道扇区数offt:24
unsigned char BPB_NumHeads[2]; //磁头数offt:26
unsigned char BPB_HiddSec[4]; //FAT区前隐扇区数 offt:28
unsigned char BPB_TotSec32[4]; //该卷总扇区数offt:32
unsigned char BPB_FATSz32[4]; //一个FAT表扇区数 offt:36
unsigned char BPB_ExtFlags[2]; //FAT32特有offt:40
unsigned char BPB_FSVer[2]; //FAT32特有offt:42
unsigned char BPB_RootClus[4]; //根目录簇号offt:44
unsigned char FSInfo[2]; //保留扇区FSINFO扇区数offt:48
unsigned char BPB_BkBootSec[2]; //通常为6 offt:50
unsigned char BPB_Rerved[12]; //扩展用offt:52
unsigned char BS_DrvNum[1]; // offt:64
unsigned char BS_Rerved1[1]; // offt:65
unsigned char BS_BootSig[1]; // offt:66
unsigned char BS_VolID[4]; // offt:67
unsigned char BS_FilSysType[11]; // offt:71
unsigned char BS_FilSysType1[8]; //"FAT32 " offt:82
};
在程序中我们采用以上的结构体指针对扇区数据指针进行转化,就可以直接读取数据中的某一字段,如要读取BPB_BytesPerSec,可以这样来作:((struct FAT32_DBR *)pSector)-> BPB_BytesPerSec
用如上语句就可以得到这一字段的首地址。
心细的读者可能会发现读回来的字节拼在一起,与实际的数据并不吻合。例如BPB_BytesPerSec读出来的内容是“00 02”,在程序中我们把00作为int型变量的高字节,把02作为其低字节,那么这个变量的值为2,而实际的SD卡里的扇区大小为512个字节,这512与2之间相去甚远。是什么造成这种现象的呢?
这就是大端模式与小端模式在作怪。上面我们合成int型变量的方法(00为高字节,02为低字节)为小端模式。而如果我们改用大端模式来进行合成的话,结果就会不同:将02作高字节,而把00作低字节,变量值就成了0x0200(十进制的512),这样就和实际数据吻合了。可见FAT32中字节的排布是采用小端模式的。在我们程序中需要将它转为大端模式的表达方式。在笔者的程序有这样一个函数lb2
bb,专门将小端模式转为大端模式,程序如下:
unsigned long lb2bb(unsigned char *dat,unsigned char len) //小端转为大端{
unsigned long temp=0;
unsigned long fact=1;
unsigned char i=0;
for(i=0;i<len;i++)
{
temp+=dat[i]*fact;
fact*=256;
}
return temp;
}
这样就可以从BPB中读出关于磁盘的各种参数信息,为我们后面的工作作准备。而这个从BPB中读取参数装入到参数表中以备后用的过程就是FAT32的初始化了。具体的实现如下:
先定义用来装入从BPB中读取的参数的结构:
struct FAT32_Init_Arg
{
unsigned char BPB_Sector_No; //BPB所在扇区号
unsigned long Total_Size; //磁盘的总容量
unsigned long FirstDirClust; //根目录的开始簇
unsigned long FirstDataSector; //文件数据开始扇区号
unsigned int BytesPerSector; //每个扇区的字节数
unsigned int FATctors; //FAT表所占扇区数
unsigned int SectorsPerClust; //每簇的扇区数
unsigned long FirstFATSector; //第一个FAT表所在扇区
unsigned long FirstDirSector; //第一个目录所在扇区
unsigned long RootDirSectors; //根目录所占扇区数
unsigned long RootDirCount; //根目录下的目录与文件数
};