⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 fat16.c

📁 实现了avr下对sd卡的读写,支持文件存取,删除的功能
💻 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 + -