数码之家

 找回密码
 立即注册
搜索
查看: 6777|回复: 8

[C51] STC8A单片机+简单FAT读取SD卡中的BMP图片文件

[复制链接]
发表于 2020-11-24 21:03:01 | 显示全部楼层 |阅读模式
本帖最后由 慕名而来 于 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相关代码:
  1. #ifndef __FAT_H__
  2. #define __FAT_H__
  3. #include "MMC_SD.h"
  4. typedef unsigned char BYTE;
  5. typedef unsigned int WORD;
  6. typedef unsigned long DWORD;
  7. //FAT原有的函数
  8. u8 FAT_Init(void);//FAT初始化
  9. u32 FAT_NextCluster(u32 cluster);//查找下一簇号
  10. u32 fatClustToSect(u32 cluster);//将簇号转换为扇区号
  11. u32 FAT_GetDWord(u16 i);
  12. u8         FAT_SearchFile(u8* file_num);
  13. //FAT部分数据缓存变量(全局变量)
  14. extern DWORD idata DBR_address;                //逻辑0扇区(启动扇区)扇区号
  15. extern DWORD idata RootDirClu;                    //根目录簇号
  16. extern DWORD idata FatAddress;                        //保留扇区数
  17. extern BYTE  idata FATnumber;                        //FAT表份数
  18. extern DWORD idata FATsectors;                        //每个FAT表所占扇区数
  19. extern DWORD idata RootAddress;                        //根目录所在扇区
  20. extern BYTE  idata ClusterSize;                        //每簇扇区数
  21. extern WORD  idata SectorSize;                        //每扇区字节数
  22. extern DWORD idata FileCluster;                        //当前文件簇号
  23. extern DWORD idata FileSize;                         //当前文件大小
  24. extern unsigned char xdata FatBuffer[];
  25. extern u16 bmp_sector,file_n;
  26. #endif
复制代码

  1. #include <STC8.h>
  2. #include "MMC_SD.h"
  3. #include "USART.h"
  4. #include "FAT.h"
  5. #include "TFT_LCD.h"
  6. //----以下8个参数可以在FTA初始化时查询、计算得到-----------------------//
  7. DWORD idata DBR_address;                //逻辑0扇区(启动扇区)扇区号
  8. DWORD idata RootDirClu;                    //根目录簇号
  9. DWORD idata FatAddress;                        //保留扇区数
  10. BYTE  idata FATnumber;                        //FAT表份数
  11. DWORD idata FATsectors;                        //每个FAT表所占扇区数
  12. DWORD idata RootAddress;                //根目录所在扇区
  13. BYTE  idata ClusterSize;                //每簇扇区数
  14. WORD  idata SectorSize;                        //每扇区字节数
  15. //----以下2个参数因存储介质中含有已删除文件的残留信息需要甄别计算得到---//
  16. DWORD idata FileCluster;                //当前文件簇号
  17. DWORD idata FileSize;                         //当前文件大小
  18. u16 bmp_sector,file_n;
  19. u16 k;
  20. //----------------------------------------------------------------------//
  21. unsigned char xdata FatBuffer[512]= {0}; //FAT缓冲区
  22. //====4字节合成函数=================================================
  23. //输入数据表中的一个数据返回这个数据相邻4个字节合成的大端在前的数据
  24. u32 FAT_GetDWord(u16 i)
  25. {
  26.     u32 temp = 0;
  27.     temp += (u32)FatBuffer[i+3]<<24;
  28.     temp += (u32)FatBuffer[i+2]<<16;
  29.     temp += (u32)FatBuffer[i+1]<<8;
  30.     temp += (u32)FatBuffer[i];
  31.     return temp;
  32. }
  33. //====FAT初始化函数================================================
  34. u8 FAT_Init(void)
  35. {
  36.     u8 flag_SD=1;
  37.     flag_SD=SD_Init();
  38.     if(flag_SD!=0)
  39.     {
  40.         Many_char_out(50, 50,200,32,"SD卡挂载失败",32,Yellow,1);
  41.         return flag_SD;
  42.     }
  43.     else
  44.     {
  45.         //Many_char_out(50, 50,200,32,"SD卡挂载成功",32,Yellow,1);
  46.         SD_ReadSingleBlock(0,FatBuffer);//读物理0扇区
  47.                 //DBR数据表在逻辑0扇区
  48.                 //如果物理0扇区是逻辑0扇区则偏移值为0
  49.         //否则提取此扇区0x1C6开始的4位偏移数据
  50.                 if((FatBuffer[0]==0xEB)&&(FatBuffer[2]==0x90))       
  51.         {
  52.             DBR_address=0;
  53.         }
  54.         else       
  55.         {
  56.             DBR_address = FAT_GetDWord(0x1C6);
  57.         }
  58.         SD_ReadSingleBlock(DBR_address, FatBuffer);//读取逻辑0扇区DBR数据

  59.         //1、根目录簇号地址为DBR(0X2F+0X2E+0X2D+0X2C)
  60.         RootDirClu=FAT_GetDWord(0x2C);                                //根目录簇号
  61.         //2、保留扇区数地址为DBR(0X0F+0X0E)+MBR偏移量MBR(0X1C6)
  62.         FatAddress=((u16)FatBuffer[0x0F]<<8)+FatBuffer[0x0E]+DBR_address;//保留扇区数
  63.         //3、FAT表份数地址为DBR(0X10)
  64.         FATnumber=FatBuffer[0x10];                                        //FAT表个数
  65.         //4、每个FAT表占用的扇区数地址为DBR(0X27+0X26+0X25+0X24)
  66.         FATsectors=FAT_GetDWord(0x24);                                //每个FAT表所占扇区数
  67.         //5、每簇扇区数地址为DBR(0X0D)
  68.         ClusterSize=FatBuffer[0x0D];                                //每簇扇区数
  69.         //6、根目录起始扇区=偏移值+保留扇区数+FAT表大小*FAT表个数+(根目录起始簇-2)*每簇扇区数
  70.         RootAddress =FatAddress+FATsectors*FATnumber+(RootDirClu-2)*ClusterSize;//根目录起始扇区(逻辑地址)
  71.         //7、每扇区字节数地址为(0X0C+0X0B)
  72.         SectorSize=((u16)FatBuffer[0x0C]<<8)+FatBuffer[0x0B];//每扇区字节数
  73.     }
  74.     return 0;
  75. }
  76. //====传入当前簇号(2~n),返回下一簇的簇号==========================================
  77. u32 FAT_NextCluster(u32 cluster)
  78. {
  79.     SD_ReadSingleBlock(FatAddress+(cluster/128), FatBuffer); //读取簇所在的fat扇区,4个字节表示一个簇地址、512/128=4
  80.     cluster = FAT_GetDWord((cluster%128)*4);
  81.     return cluster;
  82. }
  83. //====将簇号转变为扇区号=============================================================
  84. //有些2G及2G以下SD卡物理0扇区与逻辑0扇区重合、此时DBR_address=0
  85. //4G卡及有MBR数据表的SD卡物理0扇区偏移DBR_address后为逻辑0扇区
  86. u32 fatClustToSect(u32 cluster)
  87. {
  88.     return RootAddress+(DWORD)(cluster-2)*(DWORD)ClusterSize;
  89. }
  90. //====在根目录下查找文件函数(成功返回0)========================================
  91. //只适合查找以小于256的数字为文件名称的文件
  92. u8 FAT_SearchFile(u8* file_num)
  93. {
  94.     u8 j;
  95.     u32 staSec=0;
  96.     u16 i;
  97.     FileCluster = 0;         //目标文件所在的簇号
  98.     FileSize = 0;                //目标文件的大小
  99.     staSec=fatClustToSect(RootDirClu); //根据根目录簇号计算根目录扇区号
  100.     for(j=0;;j++)        //一直搜、直到搜到为止
  101.     {
  102.         SD_ReadSingleBlock(staSec+j, FatBuffer);        //读取根扇区,得到文件信息,文件名、大
  103.         for(i=0; i<16; i++)         //在当前的根目录扇区搜索文件、文件命名规则为数字“001”-“999”
  104.         {

  105.             if( (FatBuffer[32*i]   == (*file_num))&&
  106.                 (FatBuffer[32*i+1] == (*(file_num+1)))&&
  107.                 (FatBuffer[32*i+2] == (*(file_num+2)))&&
  108.                 (FatBuffer[32*i+8] == 'B')&&
  109.                 (FatBuffer[32*i+9] == 'M')&&
  110.                 (FatBuffer[32*i+10]== 'P') )
  111.             {
  112.                 FileCluster += (u32)FatBuffer[32*i+0x15]<<24;        //计算目标文件所处的簇号
  113.                 FileCluster += (u32)FatBuffer[32*i+0x14]<<16;
  114.                 FileCluster += (u32)FatBuffer[32*i+0x1B]<<8;
  115.                 FileCluster += (u32)FatBuffer[32*i+0x1A];
  116.                 FileSize = FAT_GetDWord(32*i+0x1C);                         //计算目标文件的大小
  117.                 return(0);
  118.             }
  119.         }
  120.     }
  121.     return(1);        //查找成功返回0、失败返回1
  122. }
复制代码

最后,给出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

打赏

参与人数 3家元 +60 收起 理由
有点不烧 + 20 謝謝分享
人艰不拆了 + 20
kkdkj + 20 謝謝分享

查看全部打赏

发表于 2020-11-24 22:06:59 来自手机浏览器 | 显示全部楼层
前来围观楼主画图…8a刷一屏多久?
回复 支持 反对

使用道具 举报

发表于 2020-11-25 02:01:18 | 显示全部楼层
:praise::praise:  
新建文件、写入功能
回复 支持 反对

使用道具 举报

 楼主| 发表于 2020-11-25 09:33:13 | 显示全部楼层
触景情伤 发表于 2020-11-24 22:06
前来围观楼主画图…8a刷一屏多久?

屏不大分辨率又很高的彩屏用51玩真的挺鸡肋的,我的这个实验经过不断的优化代码,24M时钟刷一屏848*480的图片需要26秒,感觉时间都浪费在读卡上了,继续优化我就不会玩了。如果你用到SD卡也建议试试我的这个FAT方案,还是挺方便的,我还测试了读.txt也是没问题的,另外我手里现有的三个SD(TF)卡分别是1G、2G、4G-CH都没问题的。
回复 支持 反对

使用道具 举报

 楼主| 发表于 2020-11-25 09:50:54 | 显示全部楼层
devcang 发表于 2020-11-25 02:01
新建文件、写入功能

如果用STM32来玩FAT用原子的例程很爽的,现成的东西也就没啥玩头了,就51单片机而言我感觉能随意的在SD卡中找到文件就可以了,其实,此次实验也是从移植FATS开始的,移植调试好了一个版本,因为没有系统学习过这些东西,应用时总是云里雾里的感觉,最后通过不断的百度才理清了思路并且搞清楚了自己想要的功能才最终简化了代码的。
回复 支持 反对

使用道具 举报

发表于 2020-11-25 09:52:07 来自手机浏览器 | 显示全部楼层
慕名而来 发表于 2020-11-25 09:33
屏不大分辨率又很高的彩屏用51玩真的挺鸡肋的,我的这个实验经过不断的优化代码,24M时钟刷一屏848*480的 ...

不会玩,也没有时间玩了…之前还想着玩玩烂苹果…现在回到家连动电脑焊台的想法都没有了…再加上现在冷的难受更懒得碰了…
回复 支持 反对

使用道具 举报

 楼主| 发表于 2020-11-25 10:08:35 | 显示全部楼层
触景情伤 发表于 2020-11-25 09:52
不会玩,也没有时间玩了…之前还想着玩玩烂苹果…现在回到家连动电脑焊台的想法都没有了…再加上现在冷的 ...

不知道你哪里这种寒冷的室温要持续多久,挺难弄的,大人还好孩子们咋办呀。
回复 支持 反对

使用道具 举报

发表于 2020-11-25 11:10:40 来自手机浏览器 | 显示全部楼层
慕名而来 发表于 2020-11-25 10:08
不知道你哪里这种寒冷的室温要持续多久,挺难弄的,大人还好孩子们咋办呀。 ...

还是广东好,现在都还在穿短袖。坐标——广东沿海。
回复 支持 反对

使用道具 举报

发表于 2020-11-25 13:32:08 来自手机浏览器 | 显示全部楼层
慕名而来 发表于 2020-11-25 10:08
不知道你哪里这种寒冷的室温要持续多久,挺难弄的,大人还好孩子们咋办呀。 ...

一般来说我都会潜意识的不去想之前那么多年怎么过来的…最冷的通常就20-25天左右
回复 支持 反对

使用道具 举报

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

本版积分规则

APP|手机版|小黑屋|关于我们|联系我们|法律条款|技术知识分享平台

闽公网安备35020502000485号

闽ICP备2021002735号-2

GMT+8, 2025-5-13 07:56 , Processed in 0.920402 second(s), 13 queries , Redis On.

Powered by Discuz!

© 2006-2025 MyDigit.Net

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