(资料图片仅供参考)
一、项目介绍项目是基于STM32设计的数码相册,能够通过LCD显示屏解码显示主流的图片,支持bmp、jpg、gif等格式。用户可以通过按键或者触摸屏来切换图片,同时还可以旋转显示,并能够自适应居中显示,小尺寸图片居中显示,大尺寸图片自动缩小显示(超出屏幕范围)。图片从SD卡中获取。
二、设计思路2.1 硬件设计本项目所需的主要硬件:
STM32F103ZET6LCD屏幕SD卡模块按键和触摸屏2.2 软件设计(1)解码图片在STM32芯片中,解码图片需要将读取到的数据存入图形缓冲区中,以便进行图画显示。常用的解码算法有JPEG解码和BMP解码。
(2)图片显示为了更好的实现图片旋转和缩放功能,在显示图片时需对其进行矩阵运算。通过左右翻转和上下翻转,可实现图片的旋转功能。通过计算图片与显示屏幕之间的比例关系并进行缩放,实现自适应居中和图片的缩放功能。
(3)SD卡SD卡模块可通过SPI接口与STM32芯片进行通信,读取SD卡中的图片数据,实现对图片的加载和显示。
(4)按键和触摸屏在使用过程中,用户可以通过按键和触摸屏对图片进行切换、旋转和缩放等操作。通过设置中断处理函数,响应用户的操作并及时更新显示屏幕上的图片。
2.3 图片播放流程图2.4 显示效果三、代码设计3.1 主函数#include "stm32f10x.h" #include "led.h" #include "delay.h" #include "key.h" #include "usart.h" #include < string.h > #include < stdio.h > #include "sd.h" //SD卡 #include "ff.h" //文件系统 #include "bmp.h" //文件系统 #include "iic.h" #include "at24c02.h" #include "xpt2046.h" #include "lcd.h" FATFS fs; // 用户定义的文件系统结构体 int main() { DIR dir_dp; FILINFO file_info; u32 sd_size; //存放SD卡返回的容量 BeepInit(); //蜂鸣器初始化 LedInit(); //LED灯初始化 UsartInit(USART1,72,115200); KeyInit(); //按键初始化 IICInit(); LcdInit(); TOUCH_Init(); //TOUCH_ADJUST(); //触摸屏校准 printf("串口工作正常!\\r\\n"); if(SDCardDeviceInit()) { printf("SD卡初始化失败!\\r\\n"); } sd_size=GetSDCardSectorCount(); //检测SD卡大小,返回值右移11位得到以M为单位的容量 printf("SD卡Sizeof:%d\\r\\n",sd_size >>11); f_mount(&fs,"0:",1); // 注册文件系统工作区,驱动器号 0,初始化后其他函数可使用里面的参数 LcdClear(0xFFFF); //f_mkdir("0:/目录创建测试!"); //测试OK //f_unlink("0:/123"); //删除目录,注意只能删除空目录 //f_unlink("0:/1.bmp");//删除文件 //printf("%d\\r\\n",Show_BMP("1.bmp")); if(f_opendir(&dir_dp,"0:/bmp")!=FR_OK)printf("目录打开失败!\\r\\n"); //循环读取目录 while(f_readdir(&dir_dp,&file_info)==FR_OK) { if(file_info.fname[0]==0)break; //判断目录跳出条件,表示目录已经读取完毕 if(strstr(file_info.fname,".bmp")) //过滤目录 { printf("文件名称: %s,文件大小: %ld 字节\\r\\n",file_info.fname,file_info.fsize); }else printf("文件名称: %s,文件大小: %ld 字节\\r\\n",file_info.fname,file_info.fsize); } if(f_closedir(&dir_dp)!=FR_OK)printf("目录关闭失败!\\r\\n"); while(1) { LED1=!LED1; DelayMs(100); } }
3.2 BMP图片解码#include "bmp.h" unsigned short RGB888ToRGB565(unsigned int n888Color) { unsigned short n565Color = 0; // 获取RGB单色,并截取高位 unsigned char cRed = (n888Color & RGB888_RED) > > 19; unsigned char cGreen = (n888Color & RGB888_GREEN) > > 10; unsigned char cBlue = (n888Color & RGB888_BLUE) > > 3; // 连接 n565Color = (cRed < < 11) + (cGreen < < 5) + (cBlue < < 0); return n565Color; } unsigned int RGB565ToRGB888(unsigned short n565Color) { unsigned int n888Color = 0; // 获取RGB单色,并填充低位 unsigned char cRed = (n565Color & RGB565_RED) > > 8; unsigned char cGreen = (n565Color & RGB565_GREEN) > > 3; unsigned char cBlue = (n565Color & RGB565_BLUE) < < 3; // 连接 n888Color = (cRed < < 16) + (cGreen < < 8) + (cBlue < < 0); return n888Color; } /* 函数功能:实现截图功能 参 数: char filename:文件名称 返 回 值:0表示成功,1表示失败 */ u8 C_BMP(const char *filename,u32 Width,u32 Height) { FIL file; // 用户定义的文件系统结构体 u8 res; // 保存文件操作的返回值 BITMAPFILEHEADER BmpHead; //保存图片文件头的信息 BITMAPINFOHEADER BmpInfo; //图片参数信息 char *p; u32 cnt,c_32; int x,y; u16 c_16; //存放16位的颜色 /*1. 创建一张BMP图片*/ res = f_open(&file,filename, FA_OPEN_ALWAYS | FA_WRITE); if(res!=0)return 1; /*2. 创建BMP的图片头参数*/ memset(&BmpHead,0,sizeof(BITMAPFILEHEADER)); //将指定空间赋值为指定的值 p=(char*)&BmpHead.bfType; //填充BMP图片的类型 *p="B"; *(p+1)="M"; //BmpHead.bfType=0x4d42;//"B""M" //0x4d42 BmpHead.bfSize=Width*Height*3+54; //图片的总大小 BmpHead.bfOffBits=54; //图片数据的偏移量 res =f_write(&file,&BmpHead,sizeof(BITMAPFILEHEADER),&cnt); if(res!=0)return 1; /*3. 创建BMP图片的参数*/ memset(&BmpInfo,0,sizeof(BITMAPINFOHEADER)); BmpInfo.biSize=sizeof(BITMAPINFOHEADER); //当前结构体大小 BmpInfo.biWidth=Width; BmpInfo.biHeight=Height; BmpInfo.biPlanes=1; BmpInfo.biBitCount=24; res =f_write(&file,&BmpInfo,sizeof(BITMAPINFOHEADER),&cnt); if(res!=0)return 1; /*4. 读取LCD屏的颜色数据,用于创建BMP图片*/ for(y=Height-1;y >=0;y--) { for(x=0;x< Width;x++) { c_16=LcdReadPoint(x,y); //读取LCD屏上一个点的颜色 c_32=RGB565ToRGB888(c_16); //颜色的转换 res =f_write(&file,&c_32,3,&cnt); if(res!=0)return 1; } } /*5. 关闭文件*/ f_close(&file); } /* 函数功能:BMP图片显示功能 参 数: char filename:文件名称 返 回 值:0表示成功,1表示失败 */ u8 Show_BMP(const char *filename) { FIL file; // 用户定义的文件系统结构体 u8 res; // 保存文件操作的返回值 BITMAPFILEHEADER BmpHead; //保存图片文件头的信息 BITMAPINFOHEADER BmpInfo; //图片参数信息 char *p; u32 cnt,c_24; int x,y; u16 c_16; //存放16位的颜色 /*1. 打开一张BMP图片*/ res = f_open(&file,filename,FA_READ); if(res!=0)return 1; /*2. 读取BMP的图片头参数*/ res =f_read(&file,&BmpHead,sizeof(BITMAPFILEHEADER),&cnt); if(res!=0)return 1; /*3. 读取BMP图片的参数*/ res =f_read(&file,&BmpInfo,sizeof(BITMAPINFOHEADER),&cnt); if(res!=0)return 1; /*4.显示BMP图片*/ f_lseek(&file,BmpHead.bfOffBits); //移动到RGB数据的存放位置 //后期的优化:读取一行的数据,再显示一行。 for(y=0;y< BmpInfo.biHeight;y++) { for(x=0;x< BmpInfo.biWidth;x++) { res =f_read(&file,&c_24,3,&cnt); if(res!=0)return 1; c_16=RGB888ToRGB565(c_24); //转换颜色 LcdDrawPoint(x,y,c_16); } } /*5. 关闭文件*/ f_close(&file); }
3.3 jpeg图片解码#include "piclib.h" #include "nt35310_lcd.h" _pic_info picinfo; //图片信息 _pic_phy pic_phy; //图片显示物理接口 /* 函数功能: 划横线函数,需要自己实现 */ void Picture_DrawLine(u16 x0,u16 y0,u16 len,u16 color) { NT35310_Fill(x0,y0,x0+len-1,y0,color); } /* 函数功能: 矩形填充颜色 函数参数: x,y:起始坐标 width,height:宽度和高度。 color:颜色数组 */ void Picture_FillColor(u16 x,u16 y,u16 width,u16 height,u16 *color) { NT35310_DrawRectangle(x,y,x+width-1,y+height-1,*color); } /* 函数功能: 画图初始化,在画图之前,必须先调用此函数 函数参数: 指定画点/读点 */ void Picture_DisplayInit(void) { pic_phy.draw_point=NT35310_DrawPoint; //画点函数实现 pic_phy.fill=NT35310_Fill; //填充函数实现,仅GIF需要 pic_phy.draw_hline=Picture_DrawLine; //画线函数实现,仅GIF需要 pic_phy.fillcolor=Picture_FillColor; //颜色填充函数实现,仅TJPGD需要 picinfo.lcdwidth=Lcd_Width; //得到LCD的宽度像素 picinfo.lcdheight=Lcd_Height; //得到LCD的高度像素 picinfo.ImgWidth=0; //初始化宽度为0 picinfo.ImgHeight=0;//初始化高度为0 picinfo.Div_Fac=0; //初始化缩放系数为0 picinfo.S_Height=0; //初始化设定的高度为0 picinfo.S_Width=0; //初始化设定的宽度为0 picinfo.S_XOFF=0; //初始化x轴的偏移量为0 picinfo.S_YOFF=0; //初始化y轴的偏移量为0 picinfo.staticx=0; //初始化当前显示到的x坐标为0 picinfo.staticy=0; //初始化当前显示到的y坐标为0 } /* 函数功能: 初始化智能画点 说明: 内部调用 */ void Picture_PointInit(void) { float temp,temp1; temp=(float)picinfo.S_Width/picinfo.ImgWidth; temp1=(float)picinfo.S_Height/picinfo.ImgHeight; if(temp< temp1)temp1=temp;//取较小的那个 if(temp1 >1)temp1=1; //使图片处于所给区域的中间 picinfo.S_XOFF+=(picinfo.S_Width-temp1*picinfo.ImgWidth)/2; picinfo.S_YOFF+=(picinfo.S_Height-temp1*picinfo.ImgHeight)/2; temp1*=8192;//扩大8192倍 picinfo.Div_Fac=temp1; picinfo.staticx=0xffff; picinfo.staticy=0xffff;//放到一个不可能的值上面 } /* 函数功能: 判断这个像素是否可以显示 函数参数: (x,y) :像素原始坐标 chg :功能变量. 返回值:0,不需要显示.1,需要显示 */ u8 Picture_is_Pixel(u16 x,u16 y,u8 chg) { if(x!=picinfo.staticx||y!=picinfo.staticy) { if(chg==1) { picinfo.staticx=x; picinfo.staticy=y; } return 1; }else return 0; } extern u8 jpg_decode(const u8 *filename); /* 函数功能: 绘制图片 函数参数: FileName:要显示的图片文件 BMP/JPG/JPEG/GIF x,y,width,height:坐标及显示区域尺寸 fast:使能jpeg/jpg小图片(图片尺寸小于等于液晶分辨率)快速解码,0,不使能;1,使能. 函数说明: 图片在开始和结束的坐标点范围内显示 */ u8 Picture_DisplayJPG(const u8 *filename,u16 x,u16 y,u16 width,u16 height,u8 fast) { u8 res;//返回值 //显示的图片高度、宽度 picinfo.S_Height=height; picinfo.S_Width=width; //显示的开始坐标点 picinfo.S_YOFF=y; picinfo.S_XOFF=x; //解码JPG/JPEG res=jpg_decode(filename); //解码JPG/JPEG return res; }
3.4 gif图片解码#include "piclib.h" #include < stm32f10x.h > #include "gif.h" #include "ff.h" #include "delay.h" #include < string.h > const u16 _aMaskTbl[16] = { 0x0000, 0x0001, 0x0003, 0x0007, 0x000f, 0x001f, 0x003f, 0x007f, 0x00ff, 0x01ff, 0x03ff, 0x07ff, 0x0fff, 0x1fff, 0x3fff, 0x7fff, }; const u8 _aInterlaceOffset[]={8,8,4,2}; const u8 _aInterlaceYPos []={0,4,2,1}; u8 gifdecoding=0;//标记GIF正在解码. //检测GIF头 //返回值:0,是GIF89a/87a;非零,非GIF89a/87a u8 gif_check_head(FIL *file) { u8 gifversion[6]; u32 readed; u8 res; res=f_read(file,gifversion,6,(UINT*)&readed); if(res)return 1; if((gifversion[0]!="G")||(gifversion[1]!="I")||(gifversion[2]!="F")|| (gifversion[3]!="8")||((gifversion[4]!="7")&&(gifversion[4]!="9"))|| (gifversion[5]!="a"))return 2; else return 0; } //将RGB888转为RGB565 //ctb:RGB888颜色数组首地址. //返回值:RGB565颜色. u16 gif_getrgb565(u8 *ctb) { u16 r,g,b; r=(ctb[0] >>3)&0X1F; g=(ctb[1] >>2)&0X3F; b=(ctb[2] >>3)&0X1F; return b+(g< <5)+(r< <11); } //读取颜色表 //file:文件; //gif:gif信息; //num:tbl大小. //返回值:0,OK;其他,失败; u8 gif_readcolortbl(FIL *file,gif89a * gif,u16 num) { u8 rgb[3]; u16 t; u8 res; u32 readed; for(t=0;t< num;t++) { res=f_read(file,rgb,3,(UINT*)&readed); if(res)return 1;//读错误 gif- >colortbl[t]=gif_getrgb565(rgb); } return 0; } //得到逻辑屏幕描述,图像尺寸等 //file:文件; //gif:gif信息; //返回值:0,OK;其他,失败; u8 gif_getinfo(FIL *file,gif89a * gif) { u32 readed; u8 res; res=f_read(file,(u8*)&gif- >gifLSD,7,(UINT*)&readed); if(res)return 1; if(gif- >gifLSD.flag&0x80)//存在全局颜色表 { gif- >numcolors=2< <(gif- >gifLSD.flag&0x07);//得到颜色表大小 if(gif_readcolortbl(file,gif,gif- >numcolors))return 1;//读错误 } return 0; } //保存全局颜色表 //gif:gif信息; void gif_savegctbl(gif89a* gif) { u16 i=0; for(i=0;i< 256;i++)gif- >bkpcolortbl[i]=gif- >colortbl[i];//保存全局颜色. } //恢复全局颜色表 //gif:gif信息; void gif_recovergctbl(gif89a* gif) { u16 i=0; for(i=0;i< 256;i++)gif- >colortbl[i]=gif- >bkpcolortbl[i];//恢复全局颜色. } //初始化LZW相关参数 //gif:gif信息; //codesize:lzw码长度 void gif_initlzw(gif89a* gif,u8 codesize) { memset((u8 *)gif- >lzw, 0, sizeof(LZW_INFO)); gif- >lzw- >SetCodeSize = codesize; gif- >lzw- >CodeSize = codesize + 1; gif- >lzw- >ClearCode = (1 < < codesize); gif- >lzw- >EndCode = (1 < < codesize) + 1; gif- >lzw- >MaxCode = (1 < < codesize) + 2; gif- >lzw- >MaxCodeSize = (1 < < codesize) < < 1; gif- >lzw- >ReturnClear = 1; gif- >lzw- >LastByte = 2; gif- >lzw- >sp = gif- >lzw- >aDecompBuffer; } //读取一个数据块 //gfile:gif文件; //buf:数据缓存区 //maxnum:最大读写数据限制 u16 gif_getdatablock(FIL *gfile,u8 *buf,u16 maxnum) { u8 cnt; u32 readed; u32 fpos; f_read(gfile,&cnt,1,(UINT*)&readed);//得到LZW长度 if(cnt) { if (buf)//需要读取 { if(cnt >maxnum) { fpos=f_tell(gfile); f_lseek(gfile,fpos+cnt);//跳过 return cnt;//直接不读 } f_read(gfile,buf,cnt,(UINT*)&readed);//得到LZW长度 }else //直接跳过 { fpos=f_tell(gfile); f_lseek(gfile,fpos+cnt);//跳过 } } return cnt; } //ReadExtension //Purpose: //Reads an extension block. One extension block canconsist of several data blocks. //If an unknown extension block occures, the routine failes. //返回值:0,成功; // 其他,失败 u8 gif_readextension(FIL *gfile,gif89a* gif, int *pTransIndex,u8 *pDisposal) { u8 temp; u32 readed; u8 buf[4]; f_read(gfile,&temp,1,(UINT*)&readed);//得到长度 switch(temp) { case GIF_PLAINTEXT: case GIF_APPLICATION: case GIF_COMMENT: while(gif_getdatablock(gfile,0,256) >0); //获取数据块 return 0; case GIF_GRAPHICCTL://图形控制扩展块 if(gif_getdatablock(gfile,buf,4)!=4)return 1; //图形控制扩展块的长度必须为4 gif- >delay=(buf[2]< <8)|buf[1]; //得到延时 *pDisposal=(buf[0] >>2)&0x7; //得到处理方法 if((buf[0]&0x1)!=0)*pTransIndex=buf[3]; //透明色表 f_read(gfile,&temp,1,(UINT*)&readed); //得到LZW长度 if(temp!=0)return 1; //读取数据块结束符错误. return 0; } return 1;//错误的数据 } //从LZW缓存中得到下一个LZW码,每个码包含12位 //返回值:< 0,错误. // 其他,正常. int gif_getnextcode(FIL *gfile,gif89a* gif) { int i,j,End; long Result; if(gif- >lzw- >ReturnClear) { //The first code should be a clearcode. gif- >lzw- >ReturnClear=0; return gif- >lzw- >ClearCode; } End=gif- >lzw- >CurBit+gif- >lzw- >CodeSize; if(End >=gif- >lzw- >LastBit) { int Count; if(gif- >lzw- >GetDone)return-1;//Error gif- >lzw- >aBuffer[0]=gif- >lzw- >aBuffer[gif- >lzw- >LastByte-2]; gif- >lzw- >aBuffer[1]=gif- >lzw- >aBuffer[gif- >lzw- >LastByte-1]; if((Count=gif_getdatablock(gfile,&gif- >lzw- >aBuffer[2],300))==0)gif- >lzw- >GetDone=1; if(Count< 0)return -1;//Error gif- >lzw- >LastByte=2+Count; gif- >lzw- >CurBit=(gif- >lzw- >CurBit-gif- >lzw- >LastBit)+16; gif- >lzw- >LastBit=(2+Count)*8; End=gif- >lzw- >CurBit+gif- >lzw- >CodeSize; } j=End >>3; i=gif- >lzw- >CurBit >>3; if(i==j)Result=(long)gif- >lzw- >aBuffer[i]; else if(i+1==j)Result=(long)gif- >lzw- >aBuffer[i]|((long)gif- >lzw- >aBuffer[i+1]< <8); else Result=(long)gif- >lzw- >aBuffer[i]|((long)gif- >lzw- >aBuffer[i+1]< <8)|((long)gif- >lzw- >aBuffer[i+2]< <16); Result=(Result >>(gif- >lzw- >CurBit&0x7))&_aMaskTbl[gif- >lzw- >CodeSize]; gif- >lzw- >CurBit+=gif- >lzw- >CodeSize; return(int)Result; } //得到LZW的下一个码 //返回值:< 0,错误(-1,不成功;-2,读到结束符了) // >=0,OK.(LZW的第一个码) int gif_getnextbyte(FIL *gfile,gif89a* gif) { int i,Code,Incode; while((Code=gif_getnextcode(gfile,gif)) >=0) { if(Code==gif- >lzw- >ClearCode) { //Corrupt GIFs can make this happen if(gif- >lzw- >ClearCode >=(1<