单片机读取FLASHWord格式.docx
《单片机读取FLASHWord格式.docx》由会员分享,可在线阅读,更多相关《单片机读取FLASHWord格式.docx(20页珍藏版)》请在冰豆网上搜索。
![单片机读取FLASHWord格式.docx](https://file1.bdocx.com/fileroot1/2022-12/11/1a421744-2331-40b4-b6cc-c243516e25d0/1a421744-2331-40b4-b6cc-c243516e25d01.gif)
每簇扇区数
13
RsvdSecCnt
保留扇区数目
14
NumFATs
此卷中FAT表数
16
RootEntCnt
FAT32为0
17
TotSec16
19
Media
存储介质
21
FATSz16
22
SecPerTrk
磁道扇区数
24
NumHeads
磁头数
26
HiddSec
4
FAT区前隐扇区数
28
TotSec32
该卷总扇区数
32
FATSz32
FAT表扇区数
36
ExtFlags
FAT32特有
40
FSVer
42
RootClus
根目录簇号
44
FSInfo
文件系统信息
48
BkBootSec
通常为6
50
Reserved
12
扩展用
52
DrvNum
-
64
Reserved1
65
BootSig
66
VolID
67
FilSysType
71
FilSysType1
82
DBR的实现代码:
structFAT32_DBR
{
unsignedcharBS_jmpBoot[3];
//跳转指令offset:
0
unsignedcharBS_OEMName[8];
//offset:
3
unsignedcharBPB_BytesPerSec[2];
//每扇区字节数offset:
unsignedcharBPB_SecPerClus[1];
//每簇扇区数offset:
unsignedcharBPB_RsvdSecCnt[2];
//保留扇区数目offset:
unsignedcharBPB_NumFATs[1];
//此卷中FAT表数offset:
unsignedcharBPB_RootEntCnt[2];
//FAT32为0offset:
unsignedcharBPB_TotSec16[2];
unsignedcharBPB_Media[1];
//存储介质offset:
unsignedcharBPB_FATSz16[2];
unsignedcharBPB_SecPerTrk[2];
//磁道扇区数offset:
unsignedcharBPB_NumHeads[2];
//磁头数offset:
unsignedcharBPB_HiddSec[4];
//FAT区前隐扇区数offset:
unsignedcharBPB_TotSec32[4];
//该卷总扇区数offset:
unsignedcharBPB_FATSz32[4];
//一个FAT表扇区数offset:
unsignedcharBPB_ExtFlags[2];
//FAT32特有offset:
unsignedcharBPB_FSVer[2];
unsignedcharBPB_RootClus[4];
//根目录簇号offset:
unsignedcharFSInfo[2];
//保留扇区FSINFO扇区数offset:
unsignedcharBPB_BkBootSec[2];
//通常为6offset:
unsignedcharBPB_Reserved[12];
//扩展用offset:
unsignedcharBS_DrvNum[1];
//offset:
unsignedcharBS_Reserved1[1];
unsignedcharBS_BootSig[1];
unsignedcharBS_VolID[4];
unsignedcharBS_FilSysType[11];
// offset:
unsignedcharBS_FilSysType1[8];
//"
FAT32"
offset:
};
在程序中我们采用以上的结构体指针对扇区数据指针进行转化,就可以直接读取数据中的某一字段,如要读取BPB_BytesPerSec,可以这样来作:
((structFAT32_DBR*)pSector)->
BPB_BytesPerSec
用如上语句就可以得到这一字段的首地址。
心细的读者可能会发现读回来的字节拼在一起,与实际的数据并不吻合。
例如BPB_BytesPerSec读出来的内容是“0002”,在程序中我们把00作为int型变量的高字节,把02作为其低字节,那么这个变量的值为2,而实际的SD卡里的扇区大小为512个字节,这512与2之间相去甚远。
是什么造成这种现象的呢?
这就是大端模式与小端模式在作怪。
上面我们合成int型变量的方法(00为高字节,02为低字节)为小端模式。
而如果我们改用大端模式来进行合成的话,结果就会不同:
将02作高字节,而把00作低字节,变量值就成了0x0200(十进制的512),这样就和实际数据吻合了。
可见FAT32中字节的排布是采用小端模式的。
在我们程序中需要将它转为大端模式的表达方式。
在笔者的程序有这样一个函数lb2bb,专门将小端模式转为大端模式,程序如下:
unsignedlonglb2bb(unsignedchar*dat,unsignedcharlen)//小端转为大端
unsignedlongtemp=0;
unsignedlongfact=1;
unsignedchari=0;
for(i=0;
i<
len;
i++)
{
temp+=dat[i]*fact;
fact*=256;
}
returntemp;
}
这样就可以从BPB中读出关于磁盘的各种参数信息,为我们后面的工作作准备。
而这个从BPB中读取参数装入到参数表中以备后用的过程就是FAT32的初始化了。
具体的实现如下:
先定义用来装入从BPB中读取的参数的结构:
structFAT32_Init_Arg
unsignedcharBPB_Sector_No;
//BPB所在扇区号
unsignedlongTotal_Size;
//磁盘的总容量
unsignedlongFirstDirClust;
//根目录的开始簇
unsignedlongFirstDataSector;
//文件数据开始扇区号
unsignedintBytesPerSector;
//每个扇区的字节数
unsignedintFATsectors;
//FAT表所占扇区数
unsignedintSectorsPerClust;
//每簇的扇区数
unsignedlongFirstFATSector;
//第一个FAT表所在扇区
unsignedlongFirstDirSector;
//第一个目录所在扇区
unsignedlongRootDirSectors;
//根目录所占扇区数
unsignedlongRootDirCount;
//根目录下的目录与文件数
当然也可以用零散的变量来存储参数,但用结构体更方便管理,也会使程序更为整洁。
FAT32的初始化将向结构中装入参数,实现如下:
voidFAT32_Init(structFAT32_Init_Arg*arg)
structFAT32_BPB*bpb=(structFAT32_BPB*)(FAT32_Buffer);
//将数据缓冲区指针转为structFAT32_BPB型指针
arg->
BPB_Sector_No=FAT32_FindBPB();
//FAT32_FindBPB()可以返回BPB所在的扇区号
Total_Size=FAT32_Get_Total_Size();
//FAT32_Get_Total_Size()可以返回磁盘的总容量,单位是兆
FATsectors=lb2bb((bpb->
BPB_FATSz32),4);
//装入FAT表占用的扇区数到FATsectors中
FirstDirClust=lb2bb((bpb->
BPB_RootClus),4);
//装入根目录簇号到FirstDirClust中
BytesPerSector=lb2bb((bpb->
BPB_BytesPerSec),2);
//装入每扇区字节数到BytesPerSector中
SectorsPerClust=lb2bb((bpb->
BPB_SecPerClus),1);
//装入每簇扇区数到SectorsPerClust中
arg->
FirstFATSector=lb2bb((bpb->
BPB_RsvdSecCnt),2)+arg->
BPB_Sector_No;
//装入第一个FAT表扇区号到FirstFATSector中
RootDirCount=lb2bb((bpb->
BPB_RootEntCnt),2);
//装入根目录项数到RootDirCount中
RootDirSectors=(arg->
RootDirCount)*32>
>
9;
//装入根目录占用的扇区数到RootDirSectors中
FirstDirSector=(arg->
FirstFATSector)+(bpb->
BPB_NumFATs[0])*(arg->
FATsectors);
//装入第一个目录扇区到FirstDirSector中
FirstDataSector=(arg->
FirstDirSector)+(arg->
RootDirSectors);
//装入第一个数据扇区到FirstDataSector中
3)FAT(文件分配表)
FAT表是FAT32文件系统中用于磁盘数据(文件)索引和定位引进的一种链式结构。
可以说FAT表是FAT32文件系统最有特色的一部分,它的链式存储机制也是FAT32的精华所在,也正因为有了它才使得数据的存储可以不连续,使磁盘的功能发挥得更为出色。
FAT表到底在什么地方?
它到底是什么样子的呢?
从第一步从BPB中提取参数中的FirstFATSector就可以知道FAT表所在的扇区号。
其实每一个FAT表都有另一个与它一模一样的FAT存在,并且这两个FAT表是同步的,也就是说对一个FAT表的操作,同样地,也应该在另一个FAT表进行相同的操作,时刻保证它们内容的一致。
这样是为了安全起见,当一个FAT因为一些原因而遭到破坏的时候,可以从另一个FAT表进行恢复。
FAT表的内容如下图所示:
上图就是一个实际的FAT表。
前8个字节“F8FFFF0FFFFFFFFF”为FAT32的FAT表头标记,用以表示此处是FAT表的开始。
后面的数据每四个字节为一个簇项(从第2簇开始),用以标记此簇的下一个簇号。
拿我们创建的那个叫TEST.TXT(大小为20个字节)的文件来说,如果这个文件的开始簇为第2簇的话,那么就到FAT表里来查找,看文件是否有下一个簇(如果文件大小大于一个簇的容量,必须会有数据存储到下一个簇,但下一个簇与上一个簇不一定是连续的),可以看到“簇2”的内容为“FFFFFF0F”,这样的标记就说明这个文件到第2簇就已经结束了,没有后继的簇,即此文件的大小是小于一个簇的容量的。
上面讲了很多,都是围绕簇这样一个词来讲的,簇又是什么?
为什么要将它引入到FAT32里来呢?
磁盘上最小可寻址存储单元称为扇区,通常每个扇区为512个字节。
由于多数文件比扇区大得多,因此如果对一个文件分配最小的存储空间,将使存储器能存储更多数据,这个最小存储空间即称为簇。
根据存储设备(磁盘、闪卡和硬盘)的容量,簇的大小可以不同以使存储空间得到最有效的应用。
在早期的360KB磁盘上,簇大小为2个扇区(1,024字节);
第一批的10MB硬盘的簇大小增加到8个扇区(4,096字节);
现在的小型闪存设备上的典型簇大小是8KB或16KB。
2GB以上的硬盘驱动器有32KB的簇。
如果对于容量大的存储定义了比较小的簇的话,就会使FAT表的体积很大,从而造成数据的冗余和效率的下降。
需要指出的是,簇作为FAT32进行数据存储的最小单位,内部扇区是不能进一步细分的,即使一个文件的数据写到一个簇中后,簇中还有容量的剩余(里部扇区没有写满),哪怕这个簇只写了一个字节,其它文件的数据也是不能接在后面继续数据的,而只能另外找没有被占用的簇。
我们按照初始化参数表中的SectorsPerClust可以知道一个簇中的扇区数,笔者的SD卡实测簇大小为4个扇区,按照上面的说法,TEST.TXT这样一个只有20个字节的文件,也会占用一个簇的容量,让我们在Windows里看看它的实际占用空间的情况。
从上图可以看到文件大小为20个字节,但占用空间却是2048个字节(一个簇的容量,4个扇区)。
TEST.TXT容量只有20个字节,所以只占用了一个簇,可能FAT表中还看不出链式结构,现在我们再创建一个文件,使它占用26个簇,如下:
可以看到图中红色标记的就是文件所占用的26个簇。
从第4簇开始,簇项4的内容为“05000000”(小端模式),说明下一个簇为第5簇,而簇项5的内容为“06000000”,说明下一个簇为第6簇……依此类推,直到内容为“FFFFFF0F”,说明无后继簇,文件数据到此结束。
FAT表中的链式存储结构已经非常明显。
把我们从FAT表中分析的结果与Windows的统计结束进行对比,说明我们的解理是正确的,如下图:
从上面可以看到,当数据结束于某一簇时,FAT32就用“FFFFFF0F”来对其进行标记。
其实还有其实的标记以表达其它的簇属性,如“00000000”表示未分配的簇,“FFFFFFF7”表示坏簇等。
给出一个簇号,计算出它的后继簇号,是实现FAT32的重点,实现如下:
unsignedlongFAT32_GetNextCluster(unsignedlongLastCluster)
unsignedlongtemp;
structFAT32_FAT*pFAT;
structFAT32_FAT_Item*pFAT_Item;
temp=((LastCluster/128)+Init_Arg.FirstFATSector);
//计算给定簇号对应的簇项的扇区号
FAT32_ReadSector(temp,FAT32_Buffer);
pFAT=(structFAT32_FAT*)FAT32_Buffer;
pFAT_Item=&
((pFAT->
Items)[LastCluster%128]);
//在算出的扇区中提取簇项
returnlb2bb(pFAT_Item,4);
//返回下一簇号
那么FAT表有多大呢?
FAT表中每四个字节表示一个簇,所以FAT表的大小由实际的簇数来决定。
从这里也可以看出,如果簇过大,则FAT表比较小,但会造成空间的浪费,而如果簇过小,可以减小空间的浪费,但会使FAT表变得臃肿。
FAT表的大小也可以从BPB参数FATsectors读出。
从上面的BPB图可以得知笔者的SD卡的FAT表大小为958个扇区(“BE 03 00 00”的大端表示)。
如果这958个扇区每四个字节都表示一个簇项,则它可以表示(958*512/4)-2=122622个簇(减去2是因为有8个字节的FAT表头标识。
看看我们计算的是否正确呢,下面是Winhex计算出来的簇数:
与Winhex计算的结果是吻合的,我们对FAT表与簇的理解是正确的。
看完上面对FAT表的讲解中,你可能会问:
一个文件数据的首簇号怎样来确定呢?
只有知道了一个文件数据的首簇号才能继续查找下一簇数据的位置,直到数据结束。
下面将要讲到的“根目录区”就可以由一个文件的文件名来查到它的首簇。
4)根目录区
在FAT32中其实已经把文件的概念进行扩展,目录同样也是文件,从根目录的地位与其它目录是相同的,因此根目录也被看作是文件。
既然是文件就会有文件名,根目录的名称就是磁盘的卷标。
如笔者的SD卡在格式会时设置卷标为znmcu,则根目录的名称就为ZNMCU,如下图:
每一个文件都对应一个描述它属性的结构,定义如下:
FAT32文件目录项32个字节的定义
字节偏移量
字数量
定义
0~7
文件名
8~10
扩展名
属性字节
0x00(读写)
0x01(只读)
0x02(隐藏)
0x04(系统)
0x08(卷标)
0x10(子目录)
0x20(归档)
系统保留
创建时间的10毫秒位
14~15
文件创建时间
16~17
文件创建日期
18~19
文件最后访问日期
20~21
文件起始簇号的高16位
22~23
文件的最近修改时间
24~25
文件的最近修改日期
26~27
文件起始簇号的低16位
28~31
表示文件的长度
根目录区所在扇区可从BPB参数FirstDirSector获取,从BPB图得FirstDirSector=FirstFATSector+BPB_NumFATs*FATsectors=2053。
根目录区的初始大小为一个簇,实际的内容如下:
图中的记录1描述根目录,前八个字节为文件名“ZNMCU”(长度小于8的部分用空格符补齐),下面的三个字节为扩展名“”(长度小于3的部分用空格符补齐),08表示此文件为卷标,开始簇高字节为00 00,低字节为00 00,开始簇为0,文件长度为0。
记录2描述TEST.TXT文件,文件名为“TEST”,扩展名为“TXT”,20表示此文件为归档,开始簇为3(“00 00 00 03”),长度为20。
记录3描述BIGTEST.TXT文件,文件名为“BIGTES~1”,扩展名为“TXT”,开始簇为4,长度为5200字节(00 00 CB 20)。
可以看到FAT32中的文件名都以大写字母表示,长度不足的部分用空格符补齐,所以我们要读取的文件TEST.TXT就变成了“TEST.TXT”,这将有助于文件名的匹配,我们不用去处理不等长文件名所带来的麻烦。
另外,还会发现长度过长的部分会被~1所替换,如果替换后有文件与之重名,则~后面的数字将增加为2。
文件目录项结构的实现如下:
structdirentry
unsignedchardeName[8];
//文件名
unsignedchardeExtension[3];
//扩展名
unsignedchardeAttributes;
//文件属性
unsignedchardeLowerCase;
//系统保留
unsignedchardeCHundredth;
//创建时间的10毫秒位
unsignedchardeCTime[2];
//文件创建时间
unsignedchardeCDate[2];
//文件创建日期
unsignedchardeADate[2];
//文件最后访问日期
unsignedchardeHighClust[2];
//文件起始簇号的高16位
unsignedchardeMTime[2];
//文件的最近修改时间
unsignedchardeMDate[2];
//文件的最近修改日期
unsignedchardeLowCluster[2];
// 文件起始簇号的低16位
unsignedchardeFileSize[4];
//表示文件的长度
我们最终要实现的是对TEST.TXT文件的读取,须要作到给定文件名后,可以得到相应文件的首簇。
主要的思想就是对根目录区中(本实例只针对根目录中的文件进行