打印
[znFAT的移植与应用]

znFAT学习笔记01(对FAT的理解)_2014_11_14

[复制链接]
2567|4
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
petp|  楼主 | 2014-11-14 16:00 | 只看该作者 |只看大图 回帖奖励 |倒序浏览 |阅读模式
本帖最后由 petp 于 2014-11-14 16:32 编辑

***************
学习背景
***************


FATFS是开发板上最常用的文件系统,流行的STM32开发板基本都采用FATFS;增强型51开发板也会用文件系统,FATFS和 znFAT都有。相较而言,FATFS的应用更广,但是缺少介绍的书籍,而znFAT有比较详尽的介绍,所以用来FAT入门还是不错的,不过znFAT的例程是少的可怜。

文件系统一般都是移植好的(直接复制,改些接口参数就好用了),所以从应用角度看,没有必要为了偶尔一次的应用而学习FAT。所以学习FAT,可能就是用文件系统的频率比较高,或者感兴趣。

******************

文件诞生的环境

首先看一下 的FAT32 文件系统整体结构。



这样看,有些枯燥,更改一下形式,



这些扇区、表、簇等结构的定义,要熟悉,才能继续,

MBR DBR 和 FSINFO 的大小都是一个扇区,而保留扇区是若干扇区组成,FAT表也是由若干扇区(与SD容量正相关)组成,簇一般是8个扇区。与示意图的比例不一致,示意图重点在于表明相互联系。




************************************
先看MBR (对于 《znFAT》的第五章)

《znFAT》首先利用WinHex 软件查看 SD卡 的0扇区(即MBR)。
我自己也模仿了一下,

测试报告:
总计容量: 3,904,897,024 字节 = 3.6 GB


柱面数: 474
磁头数: 255
扇区/磁道: 63
字节/扇区: 512
总计扇区数: 7,626,752
剩余扇区: 11,942 扇区
SMART: 函数不正确。


分区 1
扇区2,048 - 7,626,751
分区表: 扇区0
文件系统: FAT32
名称: 系统
总计容量: 3,903,848,448 字节 = 3.6 GB
总计扇区数: 7,624,704
可用扇区: 7,608,320
第一数据扇区: 16,384
字节/扇区: 512
字节/簇: 4,096
剩余簇: 951,038 = 100% 空闲
总计簇: 951,040
FAT1 = FAT2

于是,配合 znFAT 的两个结构体,就可以理解 MBR的内容了。

结构体都是用的 UINT8,不只这两个结构用UINT8,大部分znFAT的结构都使用UINT8 变量。原因是因为 大端和小端变换。


所以,MBR使用了两个结构体表达,MBR结构体 代表了 整个扇区,DPT_Item 代表 了其中的 分区记录。

怎样读取,书上给了部分代码示意,其实MBR的作用主要是引出 DBR,所以在 znFAT 只 初始化时出现。



*****************************

更重要的DBR (对于第六章)

DBR重要的原因是母凭子贵,它有BPB



可以看出,BPB 包含可以用来定位的信息(相对计算)

在初始化函数中,涉及到的计算大多和BPB有关,例如:

要找到FAT表,找到首目录,都需要 BPB 中的数据来定位。

书上除了使用WinHex 分析外,还讲了利用这些知识识别 假U 盘。

书上的DBR结构体代码主要是示意,用来理解DBR还是适合的。

不过,要注意:
UINT32 BPB_Sector_No;   //DBR(BPB)所在扇区号
UINT32 Total_SizeKB;    //磁盘的总容量,单位为KB
UINT32 BytesPerSector;         //每个扇区的字节数

可以利用MBR计算出


UINT32 FATsectors;      //FAT表所占扇区数
UINT32 SectorsPerClust; //每簇的扇区数
UINT32 FirstFATSector;         //第一个FAT表所在扇区
UINT32 FirstDirSector;         //第一个目录所在扇区
可以利用 BPB 计算出

UINT32 FSINFO_Sec;      //FSINFO扇区所在的扇区
UINT32Free_nCluster;   //空闲簇的个数
UINT32Next_Free_Cluster; //下一空簇
FSINFO的信息还要利用 FSINFO 扇区才行。

先不加入FSINFO,初始化函数就可以写出来了,

struct znFAT_Init_Args *pInit_Args; //初始化参数结构体指针,用以指向某一存储设备的初始化参数集合

UINT8 znFAT_Init(void)
{
struct DBR *pdbr;


znFAT_Device_Read_Sector(MBR_SECTOR,znFAT_Buffer);
   
if(znFAT_Buffer[0]!=DBR_MARK) //检测0扇区是否为DBR扇区
{
  pInit_Args->BPB_Sector_No=Bytes2Value(((((struct MBR *)(znFAT_Buffer))->Part[0]).StartLBA),4);
}
else
{
  pInit_Args->BPB_Sector_No=0;
}

znFAT_Device_Read_Sector((pInit_Args->BPB_Sector_No),znFAT_Buffer); //读取DBR扇区
pdbr=(struct DBR *)znFAT_Buffer;


if(!IS_FAT32_TYPE((pdbr->BS_FilSysType1))) return FSTYPE_NOT_FAT32; //FAT32文件系统类型检验


pInit_Args->FATsectors      =Bytes2Value((pdbr->BPB_FATSz32)    ,4);//装入FAT表占用的扇区数到FATsectors中
pInit_Args->BytesPerSector  =Bytes2Value((pdbr->BPB_BytesPerSec),2);//装入每扇区字节数到BytesPerSector中
pInit_Args->SectorsPerClust =pdbr->BPB_SecPerClus;//装入每簇扇区数到SectorsPerClust 中
pInit_Args->FirstFATSector  =Bytes2Value((pdbr->BPB_RsvdSecCnt) ,2)+pInit_Args->BPB_Sector_No;//装入第一个FAT表扇区号到FirstFATSector 中
pInit_Args->FirstDirSector  =(pInit_Args->FirstFATSector)+(pdbr->BPB_NumFATs)*(pInit_Args->FATsectors); //装入第一个目录扇区到FirstDirSector中
pInit_Args->Total_SizeKB    =Bytes2Value((pdbr->BPB_TotSec32),4)/2;  //磁盘的总容量,单位是KB
}

代码看起来有些复杂,但是逻辑关系是很清楚的。

如上图示意,利用指针把 MBR和DBR 关联起来。

相关帖子

沙发
petp|  楼主 | 2014-11-14 17:19 | 只看该作者
本帖最后由 petp 于 2014-11-14 17:38 编辑

**********************

形同虚设的 FSINFO 扇区

FSINFO扇区没有想当然的出现在第七章,而是出现在下册第二章。原因就在于 这个扇区没有多大意义,而且容易误导。


感兴趣的是,

UINT32 Free_nCluster;   //空闲簇的个数
UINT32 Next_Free_Cluster; //下一空簇

但实际情况是,除了下一空簇和实际不一致外(不能使用),还要维护这个扇区。

另外,还要多出来一个 函数用来 找到 FSINFO扇区,
UINT32 FSINFO_Sec;      //FSINFO扇区所在的扇区

UINT8 Find_FSINFO_Sec(UINT32 *fsinfosec) //寻找FSINFO扇区,里面有剩余簇数与可用的空簇

一般来说FSINFO扇区在DBR扇区的后一个扇区,但不乏有特殊情况,因此这里对DBR+1到       FAT1的前一个扇区进行遍历,以FSINFO扇区的标志字作为认定是FSINFO扇区的条件。

这个扇区会在 写操作的时候,再来理解。现在如果 只读的话,用处不大。


*****************************

文件目录项,没有按顺序先介绍FAT表,直接到簇了。

对应书上第七章。

这部分内容比较简单,以WinHex 验证为主,





最有用的是 文件(或目录)的开始簇,至此文件就被定位找到了。感觉比游戏找宝物简单,只是枯燥些。




***********************************

FAT表



利用类似链表的结构,可以指向下一个文件所在的簇号,这样一个文件的存放位置从头到尾就被记录起来了。结点也称为表项,占4个字节。



至此,FAT32 五大(含FSINFO)结构已经全部登场了。下一步就可以看实例读取数据了。

18.JPG (127.58 KB )

18.JPG

使用特权

评论回复
板凳
petp|  楼主 | 2014-11-15 20:10 | 只看该作者
本帖最后由 petp 于 2014-11-15 20:13 编辑

*******************
直面 《znFAT》这本书的缺点 —— 缺少入门例子

*******************

把FAT 结构都理解后,下一步就是实例了,一步一步来感觉不错。不过看到 第九章之后,会发现 入门的例子都哪去了,被谁给拿走了,只剩下 znFAT 和MP3读取的伪代码。不是说这个例子不好,是这个例子出现的位置不恰当,这个例子属于对于FAT已经很熟悉了,然后 到优化 FAT 的阶段了,那么 还是缺少入门的例子。


不过入门的例子还是有的,而且还是免费的。例子 摘自这本书,PDF下载地址,(注:本来是个链接,不允许发布。网上搜 STC单片机入门_c,李友全)。

前提:SD卡如何读取,不在这里分析了(上面这本书已经很详细了),这里只理解 znFAT 的基本应用。


**********************

例子 1:读取 FAT 基本数据。
执行效果如下:



struct znFAT_Init_Args idata Init_Args; // 初始化参数集合
unsigned int res=0;
UART_Init();
UART_Send_Str("串口设置完毕\r\n");
znFAT_Device_Init(); // 存储设备初始化
UART_Send_Str("SD卡初始化完毕\r\n");
znFAT_Select_Device(0,&Init_Args); // 选择设备
res=znFAT_Init(); // 文件系统初始化
if(!res) // 文件系统初始化成功
{
UART_Send_Str("Suc. to init FS\r\n");
UART_Send_StrNum("BPB_Sector_No:",Init_Args.BPB_Sector_No);
UART_Send_StrNum("Total_SizeKB:",Init_Args.Total_SizeKB);
UART_Send_StrNum("BytesPerSector:",Init_Args.BytesPerSector);
UART_Send_StrNum("FATsectors:",Init_Args.FATsectors);
UART_Send_StrNum("SectorsPerClust:",Init_Args.SectorsPerClust);
UART_Send_StrNum("FirstFATSector:",Init_Args.FirstFATSector);
UART_Send_StrNum("FirstDirSector:",Init_Args.FirstDirSector);
UART_Send_StrNum("FSsec:",Init_Args.FSINFO_Sec);
UART_Send_StrNum("Next_Free_Cluster:",Init_Args.Next_Free_Cluster);
UART_Send_StrNum("FreenCluster:",Init_Args.Free_nCluster);
}
else // 文件系统初始化失败
{
UART_Send_StrNum("Fail to init FS , Err Code:",res);
}

还有印象吗? Init_Args,


如果以后要用 znFAT,无论简单还是复杂功能,下面三个函数是必须提前执行的(要背下来的,因为的后面的例子都要用到):
struct znFAT_Init_Args idata Init_Args; // 初始化参数集合
znFAT_Device_Init(); // 存储设备初始化
znFAT_Select_Device(0,&Init_Args); // 选择设备
res=znFAT_Init(); // 文件系统初始化
if(!res) // 文件系统初始化成功
{
   。。。。。
}
else
{
  。。。。
}


znFAT_Device_Init(); // 存储设备初始化
这个函数是底层的,和SD卡联系密切,这里不再分析,如果会SD卡操作,理解这个函数非常简单。现在,只需认为底层已经配置好了。



初始化函数,是理解后面操作的基础,这个函数不理解,后面的理解就好出现困难。
在znFAT.c 文件中,定义了这个结构体
struct znFAT_Init_Args *pInit_Args; //初始化参数结构体指针,用以指向某一存储设备的初始化参数集合
                                    //使用之前*必须*先指向结构化变量

/*****************************************************************************
功能:选定一个存储设备
形参:devno:设备号 pinitargs:指向存储设备所对应的文件系统初始化参数集合的指针
返回:0
详解:znFAT是支持多设备的,因此在对设备进行文件系统相关操作之前,必须要先选定
       一个存储设备。此函数对Dev_No这个全局的用于区分存储设备驱动的变量进行设
       定,同时将pInit_Args指向存储设备相应的文件系统初始化参数集合
*****************************************************************************/
UINT8 znFAT_Select_Device(UINT8 devno,struct znFAT_Init_Args *pinitargs) //选择设备并指定参数集合结构指针
{
pInit_Args=pinitargs; //将znFAT的初始化参数集合指针指向设备的初始化参数集合


Dev_No=devno; //设置设备号
return 0;
}


UINT8 znFAT_Init(void)
{
struct DBR *pdbr;

znFAT_Device_Read_Sector(MBR_SECTOR,znFAT_Buffer);

if(znFAT_Buffer[0]!=DBR_MARK) //检测0扇区是否为DBR扇区
{
  pInit_Args->BPB_Sector_No=Bytes2Value(((((struct MBR *)(znFAT_Buffer))->Part[0]).StartLBA),4);
}
else
{
  pInit_Args->BPB_Sector_No=0;
}

znFAT_Device_Read_Sector((pInit_Args->BPB_Sector_No),znFAT_Buffer); //读取DBR扇区
pdbr=(struct DBR *)znFAT_Buffer;

if(!IS_FAT32_TYPE((pdbr->BS_FilSysType1))) return FSTYPE_NOT_FAT32; //FAT32文件系统类型检验

pInit_Args->FATsectors      =Bytes2Value((pdbr->BPB_FATSz32)    ,4);//装入FAT表占用的扇区数到FATsectors中
pInit_Args->BytesPerSector  =Bytes2Value((pdbr->BPB_BytesPerSec),2);//装入每扇区字节数到BytesPerSector中
pInit_Args->SectorsPerClust =pdbr->BPB_SecPerClus;//装入每簇扇区数到SectorsPerClust 中
pInit_Args->FirstFATSector  =Bytes2Value((pdbr->BPB_RsvdSecCnt) ,2)+pInit_Args->BPB_Sector_No;//装入第一个FAT表扇区号到FirstFATSector 中
pInit_Args->FirstDirSector  =(pInit_Args->FirstFATSector)+(pdbr->BPB_NumFATs)*(pInit_Args->FATsectors); //装入第一个目录扇区到FirstDirSector中
pInit_Args->Total_SizeKB    =Bytes2Value((pdbr->BPB_TotSec32),4)/2;  //磁盘的总容量,单位是KB


if(Find_FSINFO_Sec(&(pInit_Args->FSINFO_Sec))) //查找FSINFO信息扇区
{
  return ERR_FAIL;
}


znFAT_Device_Read_Sector((pInit_Args->FSINFO_Sec),znFAT_Buffer);
pInit_Args->Free_nCluster=Bytes2Value(((struct FSInfo *)znFAT_Buffer)->Free_Cluster,4); //获取剩余簇数


if(0XFFFFFFFF==pInit_Args->Free_nCluster) //如果一个磁盘格式化后没有卷标,则其存储空间就没有一点占用,此时FSINFO记录的剩余空簇数为0XFFFFFFFF
{
  pInit_Args->Free_nCluster=(((pInit_Args->Total_SizeKB*2)-(pInit_Args->FirstDirSector))/(pInit_Args->SectorsPerClust))-1;
}


if(Search_Free_Cluster_From_Start(&(pInit_Args->Next_Free_Cluster)))//遍历整个FAT表,搜索可用的空闲簇
{                                                                   //此操作可能会耗费较多时间,但没有办法
  return ERR_FAIL;                                                   //FSINFO中的空闲簇参考值并不保证正确
}                                                                   //修正和维护它反而会更加麻烦

#ifdef RT_UPDATE_FSINFO //及时将FSINFO扇区进行更新
Update_FSINFO();
#endif


#ifndef RT_UPDATE_CLUSTER_CHAIN
#ifndef USE_ALONE_CCCB
Memory_Set((UINT8 *)pcccb_buf,sizeof(UINT32)*CCCB_LEN,0);
#endif
#endif


#ifdef USE_EXCHANGE_BUFFER
#ifndef USE_ALONE_EXB
Memory_Set(pexb_buf,512,0);
#endif
#endif


return ERR_SUCC;
}

作为入门的例子,现在只需理解 蓝色的代码

这些蓝色的代码很容易理解,只需要复习:



对新手来说,代码太长,原因在于 大端和小端 的变换,例如
pInit_Args->FATsectors      =Bytes2Value((pdbr->BPB_FATSz32)    ,4);//装入FAT表占用的扇区数到FATsectors中

如果不涉及大端和小端,那就非常简单了,pInit_Args->FATsectors     = pdbr->BPB_FATSz32;

另外涉及到 简单的运算,例如 第一个FAT表开始扇区号=DBR 区号+保留扇区数
如果不涉及大端和小端,就是  pInit_Args->FirstFATSector  =pdbr->BPB_RsvdSecCnt+pInit_Args->BPB_Sector_No;
当然,这里需要大端和小端变换,所以最终就是 pInit_Args->FirstFATSector  =Bytes2Value((pdbr->BPB_RsvdSecCnt) ,2)+pInit_Args->BPB_Sector_No;


虽然例子中提到了 FSINFO,这里暂时不加入,等写文件时在开始理解。



****************
这样就可以理解入门的例子了。

使用特权

评论回复
地板
znmcu| | 2014-12-26 18:21 | 只看该作者
不错

使用特权

评论回复
5
杨爱林林| | 2016-5-10 10:39 | 只看该作者
最大支持多大的sd卡,4g的支持吗

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

2

主题

7

帖子

0

粉丝