OpenEdv-开源电子网

 找回密码
 立即注册
正点原子全套STM32/Linux/FPGA开发资料,上千讲STM32视频教程免费下载...
查看: 8310|回复: 5

FAT16图文详解

[复制链接]

12

主题

39

帖子

4

精华

高级会员

Rank: 4

积分
724
金钱
724
注册时间
2013-5-10
在线时间
2 小时
发表于 2013-12-25 22:36:07 | 显示全部楼层 |阅读模式
注:FAT16驱动代码不是本人编写的,是从网上下载的,本人只是对该代码进行研读学习,并做下笔记。该FAT16驱动应该是比较老的了,猜测应该在DOS时代比较流行,但放在今天,对于刚刚进阶FAT16的小伙伴来说,还是很适合初学者学习的好资料!笔者也相信,只要小伙伴们静下心来,慢慢读懂该代码,相信很快就能在脑海中形成一张FAT16的总览图了。
笔者对代码进行了简单测试,在STM32平台上对2G SD卡进行了读写TXT操作,没有问题。当然,这个代码功能还是很简单的,只有创建、读、写文件3个操作,而且写操作不能修改文件的大小,即没有追加功能,文件的大小是由CreateFile一开始创建好了的

以下为源码部分:
  1. #include "stm32f10x.h"
  2. #include "fat16.h"
  3. #include "sd.h"

  4. //****************************************************************************************************
  5. //全局变量定义
  6. u16 BytesPerSector;
  7. u16 ResvdSectors;
  8. u16 RootDirCnt;
  9. u16 SectorsPerFAT;
  10. u16 DirStartSector;
  11. u16 DataStartSector;
  12. u16 DBRStartSector;
  13. u8 SectorsPerClus;
  14. u8 FATCount;
  15. u8 SectorBuf[512];

  16. //读取一个逻辑扇区
  17. static u8 ReadBlock(u16 LBA)
  18. {
  19.         return SD_ReadSector(SectorBuf, LBA + DBRStartSector, 1);
  20. }

  21. //写入一个逻辑扇区
  22. static u8 WriteBlock(u16 LBA)
  23. {
  24.         return SD_WriteSector(SectorBuf, LBA + DBRStartSector, 1);
  25. }

  26. //将文件名格式化成标准的DOS 8.3格式的文件名
  27. static void NameFormat(const char* SrcName, char* DstName)
  28. {
  29.         u8 i, j;

  30.         //首先用空格初始化目标缓冲区
  31.         for(i = 0; i < 11; i++)
  32.                 *(DstName + i) = 0x20;

  33.         //其次拷贝文件名
  34.         for(i = 0, j = 0; i < 8; i++, j++)
  35.         {
  36.                 if((*SrcName) == '.')          
  37.                 {
  38.                         SrcName++;
  39.                         break;
  40.                 }
  41.                 else
  42.                 {
  43.                         *(DstName + j) = *SrcName++;
  44.                 }
  45.         }

  46.         //最后拷贝扩展名
  47.         for(i = 0, j = 8; i < 3; i++, j++)
  48.         {
  49.                 if((*SrcName) == 0)          break;
  50.                 else        
  51.                 {
  52.                         *(DstName + j) = *SrcName++;
  53.                 }
  54.         }
  55. }

  56. //比较两个缓冲区的前size个字节是否完全相同
  57. static u8 IsEqual(void* Src1, void* Src2, u32 size)
  58. {
  59.         u8 *p1, *p2;

  60.         p1 = Src1;
  61.         p2 = Src2;
  62.         for(; size--; )
  63.         {
  64.                 if((*p1++) != (*p2++))
  65.                         return 0;

  66.         }
  67.         return 1;
  68. }

  69. //将簇号转换为逻辑扇区号
  70. static u16 Clus2Sector(u16 clus)
  71. {
  72.         return (DataStartSector + ((clus - 2) * SectorsPerClus));
  73. }

  74. //读取主引导记录MBR
  75. static u8 ReadMBR(void)
  76. {
  77.         tMBR *pmbr = (tMBR *)SectorBuf;

  78.         //因为此时的DBRStartSector还未被赋值,等于0,所以这里读取的是物理扇区0
  79.         if(0 == ReadBlock(0))        return 0;
  80.         if(0xAA55 != pmbr->Flag)        return 0;
  81.         //通过磁盘分区表DPT字段来获取系统引导扇区DBR的扇区偏移量
  82.         DBRStartSector = (pmbr->DPT[0].LBAoffest[1] << 16) + pmbr->DPT[0].LBAoffest[0];        

  83.         return 1;
  84. }

  85. //读取系统引导扇区DBR
  86. static u8 ReadDBR(void)
  87. {
  88.         tDBR *pdbr = (tDBR*)SectorBuf;

  89.         if(0 == ReadBlock(0)) return 0;
  90.         if(0xAA55 != pdbr->Flag)        return 0;

  91.         //通过系统引导扇区中的BPB字段,计算磁盘的相关参数
  92.         BytesPerSector = (pdbr->BPB.BytesPerSector[1] << 8) + pdbr->BPB.BytesPerSector[0];
  93.         SectorsPerClus = pdbr->BPB.SectorsPerClus;
  94.         ResvdSectors = (pdbr->BPB.ResvdSectors[1] << 8) + pdbr->BPB.ResvdSectors[0];
  95.         FATCount = pdbr->BPB.FATCount;
  96.         RootDirCnt = (pdbr->BPB.DirCount[1] << 8) + pdbr->BPB.DirCount[0];
  97.         SectorsPerFAT = (pdbr->BPB.SectorsPerFAT[1] << 8) + pdbr->BPB.SectorsPerFAT[0];
  98.         DirStartSector = ResvdSectors + SectorsPerFAT * FATCount;
  99.         DataStartSector = DirStartSector + 32;

  100.         return 1;
  101. }

  102. //读取FAT表项的值
  103. static u16 ReadFAT(u16 Index)
  104. {
  105.         u16 *pItem = (u16*)&SectorBuf[0];

  106.         //因为1扇区 = 256个FAT表项,所以Index >> 8表示从FAT开始的扇区偏移
  107.         if(0 == ReadBlock((Index >> 8) + ResvdSectors)) return 0;
  108.         //Index % 256 表示扇区内的字偏移
  109.         return *(pItem + (Index % 256));
  110. }

  111. //写入某一FAT表项的值
  112. static u16 WriteFAT(u16 Index, u16 val)
  113. {
  114.         u16 *pItem = (u16*)&SectorBuf[0];
  115.         //计算Index所在的逻辑扇区号
  116.         u16 sector = (Index >> 8) + ResvdSectors;

  117.         if(0 == ReadBlock(sector)) return 0;
  118.         //Index % 256 表示扇区内的字偏移
  119.         *(pItem + (Index % 256)) = val;
  120.         if(0 == WriteBlock(sector)) return 0;
  121.         return 1;
  122. }

  123. //将FAT1的某一扇区拷贝到FAT2所对应的扇区
  124. //sector表示从FAT1开始的扇区偏移
  125. static u8 CopyFAT(u16 sector)
  126. {
  127.         if(!ReadBlock(ResvdSectors + sector)) return 0;
  128.         if(!WriteBlock(ResvdSectors + SectorsPerFAT + sector)) return 0;
  129.         return 1;
  130. }

  131. //FAT16初始化
  132. u8 FAT_Init(void)
  133. {
  134.         //先读取MBR,找到系统引导扇区的位置
  135.         if(0 == ReadMBR()) return 0;
  136.         //再读取系统引导扇区中的BPB,获取磁盘的相关参数
  137.         if(0 == ReadDBR()) return 0;

  138.         return 1;        
  139. }

  140. //查找根目录下是否存在name所对应的文件,如果存在则将该文件信息存放到dir所指向的结构体中
  141. u8 GetFileDir(const char* name, tDIR *dir)
  142. {
  143.         u8 i, j;
  144.         tDIR *pDir;
  145.         char DOSname[11];

  146.         //第一步要将name格式化成标准8.3格式的文件名
  147.         NameFormat(name, DOSname);

  148.         //因为根目录区总共占32个扇区
  149.         for(j = 0; j < 32; j++)
  150.         {
  151.                 if(0 == ReadBlock(DirStartSector + j)) return 0;
  152.                 //而每个扇区又包含16个目录项
  153.                 for(i = 0; i < 16; i++)
  154.                 {
  155.                         //每个目录项又占32个字节,所以这里用i << 5表示目录项在一个扇区中的字节偏移
  156.                         pDir = (tDIR *)&SectorBuf[i << 5];
  157.                         //通过文件名来查找文件
  158.                         if(IsEqual(DOSname, pDir->Name, 11))
  159.                         {
  160.                                 *dir = *pDir;
  161.                                 return 1;        
  162.                         }
  163.                 }
  164.         }
  165.         return 0;
  166. }

  167. //将文件信息写入Index所指定的目录项中
  168. static u8 WriteDir(u16 Index, tDIR *dir)
  169. {
  170.         tDIR *pDir;
  171.         //计算Index所在的逻辑扇区偏移,Index / 16表示从目录区开始的扇区偏移量
  172.         u16 sector = Index / 16 + DirStartSector;

  173.         if(!ReadBlock(sector)) return 0;
  174.         pDir = (tDIR*)&SectorBuf[0];
  175.         //Index % 16表示1个扇区内的目录项偏移
  176.         *(pDir + (Index % 16)) = *dir;
  177.         if(!WriteBlock(sector)) return 0;

  178.         return 1;
  179. }

  180. //从根目录区中获取一个空的目录项
  181. static u16 GetEmptyDir(void)
  182. {
  183.         u8 j, i;
  184.         u16 index = 0;

  185.         //因为根目录区总共占32个扇区
  186.         for(i = 0; i < 32; i++)
  187.         {
  188.                 if(!ReadBlock(DirStartSector + i)) return 0xffff;
  189.                 //而每个扇区又包含16个目录项
  190.                 for(j = 0; j < 16; j++)
  191.                 {
  192.                         //每个目录项又占32个字节,所以这里用j * 32表示目录项在一个扇区中的字节偏移
  193.                         if(0 == SectorBuf[j * 32])
  194.                                 return index;
  195.                         index++;        
  196.                 }
  197.         }

  198.         return 0xffff;
  199. }

  200. //获取一个空的FAT表项,即一个空簇的簇号
  201. static u16 GetEmptyFAT(void)
  202. {
  203.         u16 i, j;
  204.         u16 *pItem;

  205.         //遍历FAT表所占的每个扇区
  206.         for(i = 0; i < SectorsPerFAT; i++)
  207.         {
  208.                 if(0 == ReadBlock(i + ResvdSectors)) return 0;
  209.                 pItem = (u16*)&SectorBuf[0];
  210.                 //遍历扇区内的每个FAT表项
  211.                 for(j = 0; j < 256; j++)
  212.                 {
  213.                         if(*(pItem + j) == 0) return ((i << 8) + j);
  214.                 }
  215.         }
  216.         return 0;
  217. }
复制代码

FAT总览图:


 


FAT16初始化:


 


创建文件:
  1. //新建一个文件
  2. //注意:文件的大小已固定为size字节,即使新建的文件没有写任何内容,文件的大小始终为size大小;
  3. //                即使对该文件写入了超过size大小的内容,文件的大小依然不改变
  4. //该函数有待进一步优化,对于大文件的创建,该函数速度非常缓慢
  5. u8 CreateFile(const char* name, u32 size)
  6. {
  7.         tDIR dir = {0};        //一定要初始化为0,否则在WINDOWS系统下无法识别文件
  8.         u16 ClusID;
  9.         u16 i;
  10.         u16 FATSector;
  11.         //计算一簇所占的字节数
  12.         u32 BytesPerClus = BytesPerSector * SectorsPerClus;

  13.         //文件已存在,则返回
  14.         if(GetFileDir(name, &dir)) return 0;

  15.         //首先从根目录区获取一个空的目录项
  16.         i = GetEmptyDir();
  17.         if(i == 0xffff) return 0;

  18.         //从FAT表中获取一个空的FAT表项
  19.         ClusID = GetEmptyFAT();
  20.         //立即将该空的FAT表项填充为0xFFFF,以免后面再次获取空的FAT表项时错误的分配到同一表项
  21.         if(0 == WriteFAT(ClusID, 0xFFFF)) return 0;

  22.         //然后给该目录项填充文件信息
  23.         NameFormat(name, dir.Name);
  24.         dir.Attri = 0;
  25.         dir.FirstClus = ClusID;
  26.         dir.Length[0] = size;
  27.         dir.Length[1] = size >> 16;
  28.         //将目录信息回写到目录表中
  29.         if(0 == WriteDir(i, &dir)) return 0;

  30.         //计算分配到的FAT表项在FAT表中的扇区偏移
  31.         FATSector = ClusID / 256;
  32.         for(/*文件所占簇个数*/i = size / BytesPerClus; i != 0; i--)
  33.         {
  34.                 u16 NextClus;

  35.                 //获取下一个空簇的簇号
  36.                 NextClus = GetEmptyFAT();
  37.                 if(!NextClus) return 0;
  38.                 //此部分有待优化
  39.                 if(0 == WriteFAT(ClusID, NextClus)) return 0;
  40.                 if(0 == WriteFAT(NextClus, 0xFFFF)) return 0;
  41.                 //当下一FAT表项所在位置不在当前FAT扇区时,立即将当前FAT1扇区的内容拷贝到对应的FAT2中
  42.                 if(FATSector != (NextClus / 256))
  43.                 {
  44.                         CopyFAT(FATSector);
  45.                         //并将下一FAT表项所在扇区偏移赋值给FATSector
  46.                         FATSector = NextClus / 256;
  47.                 }
  48.                 ClusID = NextClus;
  49.         }
  50.         //将最后一扇区拷贝到FAT2中的相应位置
  51.         CopyFAT(FATSector);

  52.         return 1;
  53. }                                
复制代码


 



读文件:
  1. //读取文件
  2. u8 ReadFile(const char* name, u32 offest, void* dst, u32 len)
  3. {
  4.         tDIR dir;
  5.         u16 ClusID;
  6.         u16 StartSector;
  7.         u32 FileSize;
  8.         u32 PtrByteOffest, PtrSectorOffest, PtrClusOffest;
  9.         u16 i;
  10.         u8 *pDst = (u8*)dst;

  11.         //首先找到文件对应的目录项
  12.         if(!GetFileDir(name, &dir)) return 0;

  13.         FileSize = (dir.Length[1] << 16) + dir.Length[0];
  14.         //文件指针超出文件尾则返回
  15.         if(offest > FileSize) return 0;
  16.         //len大于文件长度的情况
  17.         if((offest + len) > FileSize)
  18.                 len = FileSize - offest;

  19.         ClusID = dir.FirstClus;
  20.         //文件指针相对于文件头的扇区偏移
  21.         trSectorOffest = offest / BytesPerSector;
  22.         //文件指针相对于当前扇区的字节偏移
  23.         trByteOffest = offest % BytesPerSector;
  24.         //文件指针相对于文件头的簇偏移
  25.         trClusOffest = PtrSectorOffest / SectorsPerClus;

  26.         //找到文件指针所在簇号
  27.         for(i = 0; i < PtrClusOffest; i++)
  28.                 ClusID = ReadFAT(ClusID);
  29.         //文件指针相对于系统分区的扇区偏移
  30.         StartSector = Clus2Sector(ClusID) + PtrSectorOffest;

  31.         while(1)
  32.         {
  33.                 //2.从指针所在的扇区开始,遍历文件的每个扇区
  34.                 for(; PtrSectorOffest < SectorsPerClus; PtrSectorOffest++)
  35.                 {
  36.                         if(!ReadBlock(StartSector++)) return 0;

  37.                         //1.从指针所在扇区的字节偏移开始,遍历扇区的每个字节
  38.                         for(; PtrByteOffest < BytesPerSector; PtrByteOffest++)
  39.                         {
  40.                                 *pDst++        = SectorBuf[PtrByteOffest];
  41.                                 len--;
  42.                                 if(0 == len) return 1;
  43.                         }
  44.                         trByteOffest = 0;
  45.                 }
  46.                 //读取下一簇号
  47.                 ClusID = ReadFAT(ClusID);
  48.                 //获取下一簇所在的扇区号
  49.                 StartSector = Clus2Sector(ClusID);
  50.                 PtrSectorOffest = 0;
  51.         }
  52. }
复制代码

 





写文件:
  1. //写文件
  2. u8 WriteFile(const char* name, u32 offest, void* src, u32 len)
  3. {
  4.         tDIR dir;
  5.         u16 ClusID;
  6.         u16 StartSector;
  7.         u32 FileSize;
  8.         u32 PtrByteOffest, PtrSectorOffest, PtrClusOffest;
  9.         u16 i;
  10.         u8 *pSrc = (u8*)src;

  11.         if(!GetFileDir(name, &dir)) return 0;

  12.         FileSize = (dir.Length[1] << 16) + dir.Length[0];
  13.         //文件指针超出文件尾则返回
  14.         if(offest > FileSize) return 0;
  15.         //len大于文件长度的情况
  16.         if((offest + len) > FileSize)
  17.                 len = FileSize - offest;

  18.         ClusID = dir.FirstClus;
  19.         //文件指针相对于文件头的扇区偏移
  20.         trSectorOffest = offest / BytesPerSector;
  21.         //文件指针相对于当前扇区的字节偏移
  22.         trByteOffest = offest % BytesPerSector;
  23.         //文件指针相对于文件头的簇偏移
  24.         trClusOffest = PtrSectorOffest / SectorsPerClus;

  25.         //找到文件指针所在簇号
  26.         for(i = 0; i < PtrClusOffest; i++)
  27.                 ClusID = ReadFAT(ClusID);
  28.         //文件指针相对于系统分区的扇区偏移
  29.         StartSector = Clus2Sector(ClusID) + PtrSectorOffest;

  30.         while(1)
  31.         {
  32.                 //2.从指针所在的扇区开始,遍历文件的每个扇区
  33.                 for(; PtrSectorOffest < SectorsPerClus; PtrSectorOffest++)
  34.                 {
  35.                         if(!ReadBlock(StartSector)) return 0;

  36.                         //1.从指针所在扇区的字节偏移开始,遍历扇区的每个字节
  37.                         for(; PtrByteOffest < BytesPerSector; PtrByteOffest++)
  38.                         {
  39.                                 SectorBuf[PtrByteOffest] = *pSrc++;
  40.                                 len--;
  41.                                 if(0 == len)
  42.                                 {
  43.                                         if(!WriteBlock(StartSector)) return 0;
  44.                                         else return 1;
  45.                                 }
  46.                         }
  47.                         if(!WriteBlock(StartSector++)) return 0;
  48.                         trByteOffest = 0;
  49.                 }
  50.                 //读取下一簇号
  51.                 ClusID = ReadFAT(ClusID);
  52.                 //获取下一簇所在的扇区号
  53.                 StartSector = Clus2Sector(ClusID);
  54.                 PtrSectorOffest = 0;
  55.         }
  56. }
复制代码


 



源码下载:  FAT16.zip

图文PDF下载:  FAT16模块详解.pdf
正点原子逻辑分析仪DL16劲爆上市
回复

使用道具 举报

27

主题

774

帖子

2

精华

论坛大神

Rank: 7Rank: 7Rank: 7

积分
1473
金钱
1473
注册时间
2013-4-12
在线时间
77 小时
发表于 2013-12-25 23:01:31 | 显示全部楼层
回复 支持 反对

使用道具 举报

头像被屏蔽

38

主题

382

帖子

0

精华

高级会员

Rank: 4

积分
596
金钱
596
注册时间
2012-12-5
在线时间
19 小时
发表于 2013-12-25 23:46:31 | 显示全部楼层
提示: 作者被禁止或删除 内容自动屏蔽
回复 支持 反对

使用道具 举报

530

主题

11万

帖子

34

精华

管理员

Rank: 12Rank: 12Rank: 12

积分
165540
金钱
165540
注册时间
2010-12-1
在线时间
2117 小时
发表于 2013-12-26 00:29:29 | 显示全部楼层
不错,cool。
我是开源电子网www.openedv.com站长,有关站务问题请与我联系。
正点原子STM32开发板购买店铺http://openedv.taobao.com
正点原子官方微信公众平台,点击这里关注“正点原子”
回复 支持 反对

使用道具 举报

5

主题

21

帖子

0

精华

初级会员

Rank: 2

积分
63
金钱
63
注册时间
2014-6-28
在线时间
0 小时
发表于 2014-7-31 17:19:20 | 显示全部楼层
最可惜的这给的源码和pdf 下载不了
回复 支持 反对

使用道具 举报

13

主题

314

帖子

0

精华

高级会员

Rank: 4

积分
713
金钱
713
注册时间
2012-7-20
在线时间
102 小时
发表于 2014-7-31 17:27:14 | 显示全部楼层
不错的分析!
互联网,智能设备爱好者,欢迎讨论任何有意思的想法。
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则



关闭

原子哥极力推荐上一条 /2 下一条

正点原子公众号

QQ|手机版|OpenEdv-开源电子网 ( 粤ICP备12000418号-1 )

GMT+8, 2025-7-5 01:55

Powered by OpenEdv-开源电子网

© 2001-2030 OpenEdv-开源电子网

快速回复 返回顶部 返回列表