📄 fat16.c
字号:
//-------------------------------------------------------------------------
#include <avr/io.h>
#include <stdint.h>
#include "FAT16.h"
#include "mmc.h"
//------------------------------------------------------------------------
#define SEC_Size 512//every sector's content is 512Byte
#define MBR_Sector 0 //绝对地址
#define FAT_Sector 0 //逻辑地址
//-------------------------------------------------------------------------
uint8_t BUFFER[SEC_Size];
uint8_t PB_RelativeSector;//绝对地址和逻辑地址的映射,即add+it=lba,mbr里唯一我们关注的东西
uint16_t BPB_BytesPerSec;
uint8_t BPB_SecPerClus;
uint16_t BPB_RsvdSecCnt;
uint8_t BPB_NumFATs;
uint16_t BPB_RootEntCnt;
uint16_t BPB_TotSec16;
uint16_t BPB_FATSz16; //FAT占用的sectors
uint32_t BPB_HiddSec;
//-------------------------------------------------------------------------
uint8_t ReadBlock(uint32_t LBA){ //绝对地址读一个扇区
mmcReset();
if(mmcRead(LBA,BUFFER)!=0)return SD_FAIL;
return SD_SUCC;
}
//-------------------------------------------------------------------------
uint8_t WriteBlock(uint32_t LBA){ //绝对地址写一个扇区
mmcReset();
if(mmcWrite(LBA,BUFFER)!=0)return SD_FAIL;
return SD_SUCC;
}
//-------------------------------------------------------------------------
uint8_t ReadFatBlock(uint32_t Add){ //逻辑地址读一个扇区
return ReadBlock(Add+PB_RelativeSector);
}
//-------------------------------------------------------------------------
uint8_t WriteFatBlock(uint32_t Add){ //逻辑地址写一个扇区
return WriteBlock(Add+PB_RelativeSector);
}
//-------------------------------------------------------------------------
void CopyBytes(uint8_t *ps,uint8_t *pd,uint16_t size){ //内存拷贝
for(;size;size--)*pd++=*ps++;
}
//-------------------------------------------------------------------------
uint8_t IsEqual(uint8_t *pa,uint8_t *pb,uint8_t size){ //内存比较
for(;size;size--)if(*pa++!=*pb++)return 0;
return 1;
}
//-------------------------------------------------------------------------
void EmptyBytes(uint8_t *pd,uint16_t size){ //内存清空,因为单片机资源很紧张的,一个512的缓冲区要反复的用
for(;size;size--)*pd++ =0;
}
//-------------------------------------------------------------------------
uint8_t ReadMBR(void){ //读取MBR数据结构
uint8_t ok;
FAT_MBR * MBR=(FAT_MBR*)BUFFER;
ok=ReadBlock(MBR_Sector);//已经把值存到buffer里了
if(ok==SD_FAIL)return SD_FAIL;
if(MBR->MBR_Signature!=0xAA55)return SD_FAIL; //读有效标志,55AA????(网上资料所给)
//获取参数
PB_RelativeSector=MBR->MBR_pb[0].PB_RelativeSector;//读逻辑地址与绝对地址的偏移
return SD_SUCC;
}
//-------------------------------------------------------------------------
uint8_t ReadBPB(void){ //读取BPB数据结构,共4字节
uint8_t ok;
FAT_BPB * BPB=(FAT_BPB*)BUFFER;
ok=ReadFatBlock(FAT_Sector);
if(ok==SD_FAIL)return SD_FAIL;
//获取参数,从512的结构体中获取信息
BPB_BytesPerSec = BPB->BPB_BytesPerSec;//每扇区的字节数,一般为512
BPB_SecPerClus = BPB->BPB_SecPerClus;//每簇扇区数,一般为8
BPB_RsvdSecCnt = BPB->BPB_RsvdSecCnt;//保留扇区数,一般为2
BPB_NumFATs = BPB->BPB_NumFATs;//FAT 表数目,一般为2
BPB_RootEntCnt = BPB->BPB_RootEntCnt;//根目录区的目录项数,来计算根目录的扇区数
BPB_TotSec16 = BPB->BPB_TotSec16;//总扇区数
BPB_FATSz16 = BPB->BPB_FATSz16;//FAT 表所占的扇区数, 以16 位表示,这里一般一个表242项,每项4B,这样242*4/512=2个
BPB_HiddSec = BPB->BPB_HiddSec;//隐藏扇区数,一般为0
return SD_SUCC;
}
//-------------------------------------------------------------------------
uint32_t DirStartSec(void){ //获取根目录开始扇区号
return BPB_RsvdSecCnt+BPB_NumFATs*BPB_FATSz16;////因为dbr是逻辑第0扇区,所以其有2个扇区这么大,是操作系统的第一个扇区(逻辑上),所以根目录直接从fat上来
}
//-------------------------------------------------------------------------
uint16_t GetDirSecCount(void){ //目录项占用的扇区数
return BPB_RootEntCnt*32/BPB_BytesPerSec;//每个 目录项有 32个字节
}
//-------------------------------------------------------------------------
uint32_t DataStartSec(void){ //获取数据区开始扇区号
return DirStartSec()+GetDirSecCount();
}
//-------------------------------------------------------------------------
uint32_t ClusConvLBA(uint16_t ClusID){ //获取一个簇的开始扇区
return DataStartSec()+BPB_SecPerClus*(ClusID-2);
}
//-------------------------------------------------------------------------
uint16_t ReadFAT(uint16_t Index){ //读取文件分配表的指定项,index是fat表项
uint16_t *RAM=(uint16_t*)BUFFER;
uint32_t SecID;
SecID=BPB_RsvdSecCnt+Index/256;///BPB_RsvdSecCnt 14 2 保留扇区数,每个扇区有256项,大于该项则跳到下一个扇区,index/256则可以取出块号,加上保留的区,一般为2,这里是读了bpb之后得到的
ReadFatBlock(SecID);
return RAM[Index%256];//读出第index取余之后的那项
}
//-------------------------------------------------------------------------
void WriteFAT(uint16_t Index,uint16_t Value){ //写文件分配表的指定项,把16位,2B的簇号写入,每个fat表项为2B大小
uint16_t *RAM=(uint16_t*)BUFFER;
uint32_t SecID;
SecID=BPB_RsvdSecCnt+Index/256;
ReadFatBlock(SecID);
RAM[Index%256]=Value;
WriteFatBlock(SecID);
}
//-------------------------------------------------------------------------
uint16_t GetEmptyDIR(void){ //获取根目录中可以使用的一项
uint16_t i,DirSecCut,DirStart,m,ID=0;
DirSecCut=GetDirSecCount();////目录项占用的扇区数
DirStart=DirStartSec();
for(i=0;i<DirSecCut;i++){
ReadFatBlock(DirStart+i);//读出一个扇区来,16*32=512,每个文件有32个字节的属性,也就是说最多放16*32(共有32个扇区)个文件
for(m=0;m<16;m++){//一个一个文件找过去
if(BUFFER[m*32]==0)return ID;//监察表的第一项,也就是偏移量为0的项,如果为00h, 表示目录项为空
if(BUFFER[m*32]==0xe5)return ID;//若为E5H, 表明目录项曾被使用, 但对应的文件或文件夹已被删除
ID++;
}
}
return ID;
}
//-------------------------------------------------------------------------
//获得和文件名对应的目录
uint8_t GetFileID(uint8_t Name[11],DIR *ID,uint16_t *pIndex){
uint16_t i,DirSecCut,DirStart,m;
DirSecCut = GetDirSecCount();//////目录项占用的扇区数
DirStart = DirStartSec();
for(i=0,*pIndex=0;i<DirSecCut;i++){
ReadFatBlock(DirStart+i);
for(m=0;m<16;m++){
if(IsEqual(Name,(uint8_t *)&((DIR*)&BUFFER[m*32])->FileName,11)){//isequal内存比较函数 , name和filename的 头指针,长度传进去即可
*ID = *((DIR*)&BUFFER[m*32]);//如果成立,就把id取出
return 1; //找到对应的目录项,返回1.
}
(*pIndex)++;//记录下第几块扇区
}
}
return 0; //没有找到对应的目录项,返回0.
}
//-------------------------------------------------------------------------
uint16_t GetNextFAT(void){ //获取一个空的FAT项
uint16_t FAT_Count,i;
FAT_Count=BPB_FATSz16*256; //FAT表总项数
for(i=0;i<FAT_Count;i++){
if(ReadFAT(i)==0)return i;
}
return 0;
}
//-------------------------------------------------------------------------
void ReadDIR(uint16_t Index, DIR* Value){ //读取根目录的指定项
uint32_t LBA = DirStartSec()+Index/16;//index的最大模值是16
ReadFatBlock(LBA);
CopyBytes((uint8_t *)&BUFFER[(Index%16)*32],(uint8_t *)Value,32);//把32个字节长度都copy下来,注意都要从512byte的缓冲区里copy出来,不然系统资源不够用
}
//-------------------------------------------------------------------------
void WriteDIR(uint16_t Index, DIR* Value){ //写根目录的指定项
uint32_t LBA = DirStartSec()+Index/16;
ReadFatBlock(LBA);
CopyBytes((uint8_t *)Value,(uint8_t *)&BUFFER[(Index%16)*32],32);
WriteFatBlock(LBA);
}
//-------------------------------------------------------------------------
void CopyFAT(void){ //复制文件分配表,使其和备份一致
uint16_t i;
for(i=0;i<BPB_FATSz16;i++){
ReadFatBlock(BPB_RsvdSecCnt+i);
WriteFatBlock(BPB_RsvdSecCnt+BPB_FATSz16+i);
}
}
//-------------------------------------------------------------------------
uint8_t CreateFile(uint8_t *Name,uint32_t Size){ //创建一个空文件
uint16_t ClusID, ClusNum, ClusNext, i,dirIndex;
DIR FileDir;//创建fileDir结构体
if(GetFileID(Name,&FileDir,&dirIndex)==1)return SD_FAIL; //文件已存在,有的话函数才返回1,filedir是buffer[0]
ClusNum=Size/(BPB_SecPerClus*BPB_BytesPerSec)+1;//计算出他要几个cluser,如果大小和一个cluster相等的话就放在2号里
EmptyBytes((uint8_t *)&FileDir,sizeof(DIR));//在作上面一步时就已经把filedir保存下来了,把其清空
CopyBytes(Name,(uint8_t *)&FileDir.FileName,11);
FileDir.FilePosit.Size=Size;//size of the file
FileDir.FilePosit.Start=GetNextFAT();//start cluster need to search a blank fatnumber
ClusID=FileDir.FilePosit.Start;//把fat中的值放入clusterID里,得到首簇号,然后再去改Fat的ClusID项的内容,里面的内容再用GetNextFAT()来做
for(i=0;i<ClusNum-1;i++){//总的cluster数 ,一个一个写过来
WriteFAT(ClusID,0xffff);//先写入结束号,再修改,主要为的是i到底以后还会做一次循环,所以在最后一次上写入结束簇
ClusNext=GetNextFAT();//得到下一个cluster号
WriteFAT(ClusID,ClusNext);
ClusID=ClusNext;
}
WriteFAT(ClusID, 0xffff);
WriteDIR(GetEmptyDIR(),&FileDir);//把大小都写入,其他默认为0
CopyFAT();//在FAT2中也copy一份
return SD_SUCC;
}
//-------------------------------------------------------------------------
//读文件
uint8_t ReadFile(uint8_t Name[11],uint32_t Start,uint32_t len,uint8_t *p){//*P是缓冲区的指针,但是如果要读很多簇的话可能片内资源不够多
uint16_t BytePerClus,ClusID,m,dirIndex;
uint32_t LBA;
uint8_t i;
DIR FileDir;
if(GetFileID(Name,&FileDir,&dirIndex)==0)return SD_FAIL;//文件不存在,这是已经把dir的内容复制到filedir中去了
BytePerClus=BPB_SecPerClus*BPB_BytesPerSec; //每簇的字节数, BPB_SecPerClus,BPB_BytesPerSec是全局变量
m=Start/BytePerClus; //计算开始位置包含的簇数,start给的是地址,也就是字节数,因为要定位成物理上的扇区,所以要计算之前的扇区,因为可能不是从头开始读
ClusID=FileDir.FilePosit.Start; //文件的首簇号,也是第一个数据的存放地址
for(i=0;i<m;i++)ClusID=ReadFAT(ClusID); //计算开始位置所在簇的簇号,因为不是FileDir.FilePosit.Start开始的,而是从给定的start开始的
i=(Start%BytePerClus)/BPB_BytesPerSec; //计算开始位置所在扇区的簇内偏移,先计算出簇模内的字节数,再计算出扇区的偏移量
LBA=ClusConvLBA(ClusID)+i; //计算开始位置的逻辑扇区号,ClusConvLBA(ClusID)地作用是获取一个簇的开始扇区
m=(Start%BytePerClus)%BPB_BytesPerSec; //计算开始位置在扇区内偏移,计算出开始位置相对于起始位置的扇区的偏移,有利用物理磁盘的读写
READ:
for(;i<BPB_SecPerClus;i++){
ReadFatBlock(LBA++);
for(;m<BPB_BytesPerSec;m++){
*p++=BUFFER[m];
if(--len==0)return SD_SUCC; //如果读取完成就退出
}
m=0;
}
i=0;
ClusID=ReadFAT(ClusID); //下一簇簇号,先找到一簇,分n个扇区读出其内容(data区),然后将内容512次的复制下来,完了再到下一簇,知道长度为0为止(支持读出部分值)
LBA=ClusConvLBA(ClusID);
goto READ;
}
//-------------------------------------------------------------------------
//写文件
uint8_t WriteFile(uint8_t Name[11],uint32_t Start,uint32_t len,uint8_t *p){
uint16_t BytePerClus,ClusID,m,dirIndex;
uint32_t LBA;
uint8_t i;
DIR FileDir;
if(GetFileID(Name,&FileDir,&dirIndex)==0)return SD_FAIL;//文件不存在
BytePerClus=BPB_SecPerClus*BPB_BytesPerSec; // 每簇的字节数
m=Start/BytePerClus; //计算开始位置包含的簇数
ClusID=FileDir.FilePosit.Start; //文件的开始簇号
for(i=0;i<m;i++)ClusID=ReadFAT(ClusID); //计算开始位置所在簇的簇号
i=(Start%BytePerClus)/BPB_BytesPerSec; //计算开始位置所在扇区的簇内偏移
LBA=ClusConvLBA(ClusID)+i; //计算开始位置的逻辑扇区号
m=(Start%BytePerClus)%BPB_BytesPerSec; //计算开始位置在扇区内偏移
WRITE:
for(;i<BPB_SecPerClus;i++){
ReadFatBlock(LBA);
for(;m<BPB_BytesPerSec;m++){
BUFFER[m]=*p++;
if(--len==0){ //如果读取完成就退出
WriteFatBlock(LBA); //回写扇区
return SD_SUCC;
}
}
m=0;
WriteFatBlock(LBA++); //回写扇区
}
i=0;
ClusID=ReadFAT(ClusID); //下一簇簇号
LBA=ClusConvLBA(ClusID);
goto WRITE;
}
//-------------------------------------------------------------------------
uint8_t InitFat16(void){ //初始化FAT16的变量
if(ReadMBR()==SD_FAIL)return SD_FAIL;//读出MBR的结构
if(ReadBPB()==SD_FAIL)return SD_FAIL;//读出bpb的结构,一定要放在 最先的 地方 ,因为里面的 东西全局变量要用
return SD_SUCC;
}
//-------------------------------------------------------------------------
//删除文件
uint8_t EreaseFile(uint8_t Name[11]){
uint16_t ClusID,ClusNext,i,dirIndex;
DIR FileDir;
if(GetFileID(Name,&FileDir,&dirIndex)==0)return SD_FAIL; //文件不存在
ClusID=FileDir.FilePosit.Start; //文件的开始簇号
EREASEFAT:
if((ClusNext=ReadFAT(ClusID))!=0xffff){ //删除FAT表中的链表
WriteFAT(ClusID,0x0000);
ClusID=ClusNext;
}else{
WriteFAT(ClusID,0x0000);
goto EREASEFATEND;
}
goto EREASEFAT;
EREASEFATEND:
FileDir.FileName.NAME[0]=0xe5; //删除Dir中的文件记录
WriteDIR(dirIndex,&FileDir);
CopyFAT(); //FAT2<-FAT1
return SD_SUCC;
}
//-------------------------------------------------------------------------
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -