|
本帖最后由 慕名而来 于 2020-11-24 21:03 编辑
51单片机读取SD卡数据的方法在网络上有大量的文字、经验、教程,但是51单片机用到FAT系统应名读取SD卡上的文件对于新手而言就总也遇到问题的,当然,这方面的讲解、例程在网上也是资源非常多的,有些例程拿来就可以用的,但用过的都知道,FAT无论是前些年很火的znFAT还是后来用量很大的FATS,这些用在STM单片机上非常好用但用到C51单片机上就略显庞大了,最近一直在玩TFT彩屏,早就玩过自建字库显示汉字了,这次主要就是想玩玩图片显示,对于845*480分辨率TFT而言,一张BMP图片就有120多万个字节(1.16Mb),所有在设计PCB的时候就加入了SD卡座,起初只是想用通过偏移地址直接读SD卡中的数据,最终通过修改例程完成了自己的通过FAT根据文件名称来读取SD卡的简单FAT代码,作为新手的一点心得在此分享给想玩的新手共乐吧。
还是要说说那几句老话善意提示:
1、新手如果只是想读出SD卡中存放的任意BMP图片(其他文件需要有针对的修改代码)可以看看。
2、辛苦调试、辛苦码字、热心分享如果转载请注明出处,基础代码源自网络的开放资源,如有雷同存属巧合。
3、代码仅用于学习、验证、演示,请勿用于商业用途。
俗话说,没有规矩不成方圆,首先说几个约定:
1、文中使用的是Micro SD Card卡(TF卡),图片文件通过USB读卡器存入SD卡中。
2、SD卡格式化为FAT32格式。
3、BMP文件格式为24位色。
4、文件名称为3位数字(短文件名称),例如:001.bmp。
5、代码仅对应.bmp文件的查询,仅根据文件名称来查找文件。
6、我们只在根目录中存储文件。(因为需要存储的文件不多,我没有琢磨文件夹子目录存储的情况。)
7、文件大小通过电脑自带的画图软件修改成与彩屏像素一致,本例使用的TFT_LCD分辨率为854*480,为适应分辨率被8整除,显示的图片像素为848*480。
因为我们只是想在SD卡中找到图片文件并在TFT上显示出来,所以可以简化FAT功能来适应我们的用途。关于FAT32系统的完整的工作原理还是请移步百度吧,我也说不清楚的。我采用的是网上最常见的方法,就是在电脑上用WinHex软件打开SD卡,一切相关的东西就一目了然了,可以看到文件的根目录扇区地址(最多见的就是8192)、FAT1、FAT2、用户文件等等。这些东西初看起来简单清晰,但真正研究起来对于新手也是晕得很,我就是从迷瞪看画到一点点理出脉络的,回过头来才发现这东西其实很简单的,下面进入正题。
在电脑桌面上,当我们把一个图片文件复制粘贴到SD卡里后,FAT32系统都做了些什么呢?如果在根目录下储存文件,系统会在SD卡的根目录下建立一个32字节的根目录目录文件,根目录文件中有几个位置存放了几个我们查找文需要用到的数据:
1、0x0-0x7=短文件名;
2、0x8-0xA=文件扩展名;
3、0x14-0x15=文件起始簇高8位地址;
4、0x1A-0x1B=文件起始簇低8位地址;
5、0x1C-0x1F=文件长度。
根据上述数据可以读出目标文件,由FAT32管理的文件会有一个头部信息,头信息的位置0X0A-0X0D=文件数据的起始位置。
如果此SD卡具有主引导记录区MBR则会在MBR下方的位置0x1C6-0x1C9上记录DBR的的位置。在此需要说的是有些SD卡没有这个MBR分区,此时其物理0扇区就是逻辑0扇区,读SD卡0扇区即可得到DBR数据,应用代码还可以简化。
而这个DBR(引导扇区)中的几个位置也记录了几个我们查找文件必要用到的数据:
1、0X2C-0X2F=根目录所在簇;(常见卡多为2)
2、0X0E-0X0F=保留扇区数;
3、0X10=FAT表个数;
4、0X24-0X27=FAT表占用扇区数;
5、0X0D=每簇扇区数;
6、0X0B-0X0C=每扇区字节数;
根据上述数据可以计算得出:根目录起始扇区=偏移值+保留扇区数+FAT表大小*FAT表个数+(根目录起始簇-2)*每簇扇区数
由簇号计算相应的扇区号由函数fatClustToSect(u32 cluster)来完成
介绍到这里,我们就大致有了查找文件的路线图了,那就是:
首先要找到DBR扇区------->计算出根目录扇区位置-------->在根目录中读出文件信息-------->找到文件的起始位置------->根据文件的头部信息查出文件数据的起始位置-------->读出文件数据。
具体做法是,读取SD卡0扇区,如果第一个数据是0XEB以及第三个数据是0X90,则此处就是DBR扇区,否则此处就是MBR扇区,则可以根据位置0X1C6起始的4个数据读取DBR扇区数据,(这些由FAT初始化函数FAT_Init(void)完成)而后就可以根据上述规划的路线查找文件了(由文件查找函数FAT_SearchFile(u8* file_num)来完成)。如果文件大小超出了1簇空间,则还要读出FAT1表的数据,根据FAT表的指引逐簇读取文件,(由函数FAT_NextCluster(u32 cluster)来完成输入当前簇返回下一簇)关于FAT表的原理请百度了解。最后要说的是上述这些数据都是小头在前的,需要把读取的数据从新排列好才可以,这些由函数FAT_GetDWord(u16 i)来完成,输入起始位置数据返回4个数据合成的数据。
下面是FAT相关代码:
- #ifndef __FAT_H__
- #define __FAT_H__
- #include "MMC_SD.h"
- typedef unsigned char BYTE;
- typedef unsigned int WORD;
- typedef unsigned long DWORD;
- //FAT原有的函数
- u8 FAT_Init(void);//FAT初始化
- u32 FAT_NextCluster(u32 cluster);//查找下一簇号
- u32 fatClustToSect(u32 cluster);//将簇号转换为扇区号
- u32 FAT_GetDWord(u16 i);
- u8 FAT_SearchFile(u8* file_num);
- //FAT部分数据缓存变量(全局变量)
- extern DWORD idata DBR_address; //逻辑0扇区(启动扇区)扇区号
- extern DWORD idata RootDirClu; //根目录簇号
- extern DWORD idata FatAddress; //保留扇区数
- extern BYTE idata FATnumber; //FAT表份数
- extern DWORD idata FATsectors; //每个FAT表所占扇区数
- extern DWORD idata RootAddress; //根目录所在扇区
- extern BYTE idata ClusterSize; //每簇扇区数
- extern WORD idata SectorSize; //每扇区字节数
- extern DWORD idata FileCluster; //当前文件簇号
- extern DWORD idata FileSize; //当前文件大小
- extern unsigned char xdata FatBuffer[];
- extern u16 bmp_sector,file_n;
- #endif
复制代码
- #include <STC8.h>
- #include "MMC_SD.h"
- #include "USART.h"
- #include "FAT.h"
- #include "TFT_LCD.h"
- //----以下8个参数可以在FTA初始化时查询、计算得到-----------------------//
- DWORD idata DBR_address; //逻辑0扇区(启动扇区)扇区号
- DWORD idata RootDirClu; //根目录簇号
- DWORD idata FatAddress; //保留扇区数
- BYTE idata FATnumber; //FAT表份数
- DWORD idata FATsectors; //每个FAT表所占扇区数
- DWORD idata RootAddress; //根目录所在扇区
- BYTE idata ClusterSize; //每簇扇区数
- WORD idata SectorSize; //每扇区字节数
- //----以下2个参数因存储介质中含有已删除文件的残留信息需要甄别计算得到---//
- DWORD idata FileCluster; //当前文件簇号
- DWORD idata FileSize; //当前文件大小
- u16 bmp_sector,file_n;
- u16 k;
- //----------------------------------------------------------------------//
- unsigned char xdata FatBuffer[512]= {0}; //FAT缓冲区
- //====4字节合成函数=================================================
- //输入数据表中的一个数据返回这个数据相邻4个字节合成的大端在前的数据
- u32 FAT_GetDWord(u16 i)
- {
- u32 temp = 0;
- temp += (u32)FatBuffer[i+3]<<24;
- temp += (u32)FatBuffer[i+2]<<16;
- temp += (u32)FatBuffer[i+1]<<8;
- temp += (u32)FatBuffer[i];
- return temp;
- }
- //====FAT初始化函数================================================
- u8 FAT_Init(void)
- {
- u8 flag_SD=1;
- flag_SD=SD_Init();
- if(flag_SD!=0)
- {
- Many_char_out(50, 50,200,32,"SD卡挂载失败",32,Yellow,1);
- return flag_SD;
- }
- else
- {
- //Many_char_out(50, 50,200,32,"SD卡挂载成功",32,Yellow,1);
- SD_ReadSingleBlock(0,FatBuffer);//读物理0扇区
- //DBR数据表在逻辑0扇区
- //如果物理0扇区是逻辑0扇区则偏移值为0
- //否则提取此扇区0x1C6开始的4位偏移数据
- if((FatBuffer[0]==0xEB)&&(FatBuffer[2]==0x90))
- {
- DBR_address=0;
- }
- else
- {
- DBR_address = FAT_GetDWord(0x1C6);
- }
- SD_ReadSingleBlock(DBR_address, FatBuffer);//读取逻辑0扇区DBR数据
- //1、根目录簇号地址为DBR(0X2F+0X2E+0X2D+0X2C)
- RootDirClu=FAT_GetDWord(0x2C); //根目录簇号
- //2、保留扇区数地址为DBR(0X0F+0X0E)+MBR偏移量MBR(0X1C6)
- FatAddress=((u16)FatBuffer[0x0F]<<8)+FatBuffer[0x0E]+DBR_address;//保留扇区数
- //3、FAT表份数地址为DBR(0X10)
- FATnumber=FatBuffer[0x10]; //FAT表个数
- //4、每个FAT表占用的扇区数地址为DBR(0X27+0X26+0X25+0X24)
- FATsectors=FAT_GetDWord(0x24); //每个FAT表所占扇区数
- //5、每簇扇区数地址为DBR(0X0D)
- ClusterSize=FatBuffer[0x0D]; //每簇扇区数
- //6、根目录起始扇区=偏移值+保留扇区数+FAT表大小*FAT表个数+(根目录起始簇-2)*每簇扇区数
- RootAddress =FatAddress+FATsectors*FATnumber+(RootDirClu-2)*ClusterSize;//根目录起始扇区(逻辑地址)
- //7、每扇区字节数地址为(0X0C+0X0B)
- SectorSize=((u16)FatBuffer[0x0C]<<8)+FatBuffer[0x0B];//每扇区字节数
- }
- return 0;
- }
- //====传入当前簇号(2~n),返回下一簇的簇号==========================================
- u32 FAT_NextCluster(u32 cluster)
- {
- SD_ReadSingleBlock(FatAddress+(cluster/128), FatBuffer); //读取簇所在的fat扇区,4个字节表示一个簇地址、512/128=4
- cluster = FAT_GetDWord((cluster%128)*4);
- return cluster;
- }
- //====将簇号转变为扇区号=============================================================
- //有些2G及2G以下SD卡物理0扇区与逻辑0扇区重合、此时DBR_address=0
- //4G卡及有MBR数据表的SD卡物理0扇区偏移DBR_address后为逻辑0扇区
- u32 fatClustToSect(u32 cluster)
- {
- return RootAddress+(DWORD)(cluster-2)*(DWORD)ClusterSize;
- }
- //====在根目录下查找文件函数(成功返回0)========================================
- //只适合查找以小于256的数字为文件名称的文件
- u8 FAT_SearchFile(u8* file_num)
- {
- u8 j;
- u32 staSec=0;
- u16 i;
- FileCluster = 0; //目标文件所在的簇号
- FileSize = 0; //目标文件的大小
- staSec=fatClustToSect(RootDirClu); //根据根目录簇号计算根目录扇区号
- for(j=0;;j++) //一直搜、直到搜到为止
- {
- SD_ReadSingleBlock(staSec+j, FatBuffer); //读取根扇区,得到文件信息,文件名、大
- for(i=0; i<16; i++) //在当前的根目录扇区搜索文件、文件命名规则为数字“001”-“999”
- {
- if( (FatBuffer[32*i] == (*file_num))&&
- (FatBuffer[32*i+1] == (*(file_num+1)))&&
- (FatBuffer[32*i+2] == (*(file_num+2)))&&
- (FatBuffer[32*i+8] == 'B')&&
- (FatBuffer[32*i+9] == 'M')&&
- (FatBuffer[32*i+10]== 'P') )
- {
- FileCluster += (u32)FatBuffer[32*i+0x15]<<24; //计算目标文件所处的簇号
- FileCluster += (u32)FatBuffer[32*i+0x14]<<16;
- FileCluster += (u32)FatBuffer[32*i+0x1B]<<8;
- FileCluster += (u32)FatBuffer[32*i+0x1A];
- FileSize = FAT_GetDWord(32*i+0x1C); //计算目标文件的大小
- return(0);
- }
- }
- }
- return(1); //查找成功返回0、失败返回1
- }
复制代码
最后,给出BMP图片显示函数,此函数与TFT驱动桥接的函数有两个,一个是清屏函数,另一个是画点函数。
void BMP_Decode(u8* file_num)
{
//变量定义
u8 flag_FAT=1;
u16 xdata sector = 0;
u16 xdata color=0,count=0;
u32 xdata BmpAddress,BmpCluster; //BMP起始扇区和簇号
u32 xdata DataOffset; //BMP数据偏移量
u16 xdata BmpPixelY,BmpPixelX; //BMP原始XY像素
u16 xdata BmpPointX=0,BmpPointY=0; //指向原始BMP图的当前像素点
u8 xdata RGBPoint=0,PixelBytes; //每像素的字节数
u8 xdata i=0,AddBytes=0; //补充字节(仅用于4字节对齐处理)
flag_FAT=FAT_Init();
if(flag_FAT!=0)
{
Many_char_out(50, 100,200,32,"FAT系统异常",32,Yellow,1);
}
else
{
//查找文件、获得文件所在的簇号FileCluster和文件大小FileSize
if(FAT_SearchFile(file_num)) //返回值为1时文件查找失败
{
Many_char_out(50, 100,200,32,"没有此文件",32,Yellow,1);
return; //查找不到则跳出函数
}
BmpCluster = FileCluster; //获目标取文件起始簇
BmpAddress = fatClustToSect(BmpCluster); //获取目标文件的起始扇区
SD_ReadSingleBlock(BmpAddress, FatBuffer); //读出目标文件的第一扇区数据、获取文件信息
BmpPixelX = (u16)FAT_GetDWord(0x12); //获取BMP水平像素=854
BmpPixelY =(u16)FAT_GetDWord(0x16); //获取BMP垂直像素=480
ClearScreen(0x0000); //清屏(黑底色)
DataOffset = FAT_GetDWord(0x0A); //BMP数据偏移量计算,即头信息的大小
PixelBytes = FatBuffer[0x1c]/8; //2为16位色,3为24位色,4为32位色
//BMP图片4字节对齐,水平像素必须是4的倍数
if(((u32)BmpPixelX*PixelBytes)%4)
{
AddBytes=4-((u32)BmpPixelX*PixelBytes%4);
}
count=(u16)DataOffset; //指向数据偏移处
while(1)
{
if(PixelBytes == 3) //24位颜色图
{
switch (RGBPoint) //取RGB 565
{
case 0:
color += ((u16)FatBuffer[count]>>3) ;
break ;
case 1:
color += ((u16)FatBuffer[count]>>2)<<5 ;
break;
case 2 :
color += ((u16)FatBuffer[count]>>3)<<11 ;
break ;
}
}
if(++RGBPoint==PixelBytes) //得到了一个完整的像素
{
RGBPoint = 0;
//----数据显示到TFT上--------------------------------
LCD_Fast_DrawPoint(BmpPointX,(479-BmpPointY),color); //自下向上输出显示与BMP图片数据存储方向吻合
//---------------------------------------------------
color = 0;
BmpPointX++; //水平像素加一
if(BmpPointX==BmpPixelX) //一行结束
{
i=AddBytes;
while(i--) count++;
BmpPointX=0;
BmpPointY++;
BmpPixelY--; //一行结束,Bmp图Y方向像素计数减1
}
if(BmpPixelY<=0) break;
}
if(++count>=512) //判断是否输出完本扇区
{
count = 0;
sector++; //扇区数加一
if(sector == ClusterSize) //ClusterSize为每簇扇区数,判断是否完成本簇输出
{
sector =0;
BmpCluster = FAT_NextCluster(BmpCluster); //若本簇输出完,读取下一簇为第几簇
BmpAddress = fatClustToSect(BmpCluster);
}
SD_ReadSingleBlock(BmpAddress+sector, FatBuffer); //读BMP下一扇数据
}
}
}
}
|
本帖子中包含更多资源
您需要 登录 才可以下载或查看,没有账号?立即注册
x
打赏
-
查看全部打赏
|