(資料圖)
項(xiàng)目是基于STM32設(shè)計的數(shù)碼相冊,能夠通過LCD顯示屏解碼顯示主流的圖片,支持bmp、jpg、gif等格式。用戶可以通過按鍵或者觸摸屏來切換圖片,同時還可以旋轉(zhuǎn)顯示,并能夠自適應(yīng)居中顯示,小尺寸圖片居中顯示,大尺寸圖片自動縮小顯示(超出屏幕范圍)。圖片從SD卡中獲取。
二、設(shè)計思路2.1 硬件設(shè)計本項(xiàng)目所需的主要硬件:
STM32F103ZET6LCD屏幕SD卡模塊按鍵和觸摸屏2.2 軟件設(shè)計(1)解碼圖片在STM32芯片中,解碼圖片需要將讀取到的數(shù)據(jù)存入圖形緩沖區(qū)中,以便進(jìn)行圖畫顯示。常用的解碼算法有JPEG解碼和BMP解碼。
(2)圖片顯示為了更好的實(shí)現(xiàn)圖片旋轉(zhuǎn)和縮放功能,在顯示圖片時需對其進(jìn)行矩陣運(yùn)算。通過左右翻轉(zhuǎn)和上下翻轉(zhuǎn),可實(shí)現(xiàn)圖片的旋轉(zhuǎn)功能。通過計算圖片與顯示屏幕之間的比例關(guān)系并進(jìn)行縮放,實(shí)現(xiàn)自適應(yīng)居中和圖片的縮放功能。
(3)SD卡SD卡模塊可通過SPI接口與STM32芯片進(jìn)行通信,讀取SD卡中的圖片數(shù)據(jù),實(shí)現(xiàn)對圖片的加載和顯示。
(4)按鍵和觸摸屏在使用過程中,用戶可以通過按鍵和觸摸屏對圖片進(jìn)行切換、旋轉(zhuǎn)和縮放等操作。通過設(shè)置中斷處理函數(shù),響應(yīng)用戶的操作并及時更新顯示屏幕上的圖片。
2.3 圖片播放流程圖2.4 顯示效果三、代碼設(shè)計3.1 主函數(shù)#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" //文件系統(tǒng) #include "bmp.h" //文件系統(tǒng) #include "iic.h" #include "at24c02.h" #include "xpt2046.h" #include "lcd.h" ? ? FATFS fs; // 用戶定義的文件系統(tǒng)結(jié)構(gòu)體 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(); //觸摸屏校準(zhǔn) 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); // 注冊文件系統(tǒng)工作區(qū),驅(qū)動器號 0,初始化后其他函數(shù)可使用里面的參數(shù) LcdClear(0xFFFF); //f_mkdir("0:/目錄創(chuàng)建測試!"); //測試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"); //循環(huán)讀取目錄 while(f_readdir(&dir_dp,&file_info)==FR_OK) { if(file_info.fname[0]==0)break; //判斷目錄跳出條件,表示目錄已經(jīng)讀取完畢 if(strstr(file_info.fname,".bmp")) //過濾目錄 { printf("文件名稱: %s,文件大小: %ld 字節(jié)\\r\\n",file_info.fname,file_info.fsize); }else printf("文件名稱: %s,文件大小: %ld 字節(jié)\\r\\n",file_info.fname,file_info.fsize); } if(f_closedir(&dir_dp)!=FR_OK)printf("目錄關(guān)閉失敗!\\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; } ? ? ? /* 函數(shù)功能:實(shí)現(xiàn)截圖功能 參 數(shù): char filename:文件名稱 返 回 值:0表示成功,1表示失敗 */ u8 C_BMP(const char *filename,u32 Width,u32 Height) { FIL file; // 用戶定義的文件系統(tǒng)結(jié)構(gòu)體 u8 res; // 保存文件操作的返回值 BITMAPFILEHEADER BmpHead; //保存圖片文件頭的信息 BITMAPINFOHEADER BmpInfo; //圖片參數(shù)信息 char *p; u32 cnt,c_32; int x,y; u16 c_16; //存放16位的顏色 /*1. 創(chuàng)建一張BMP圖片*/ res = f_open(&file,filename, FA_OPEN_ALWAYS | FA_WRITE); if(res!=0)return 1; /*2. 創(chuàng)建BMP的圖片頭參數(shù)*/ 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; //圖片數(shù)據(jù)的偏移量 res =f_write(&file,&BmpHead,sizeof(BITMAPFILEHEADER),&cnt); if(res!=0)return 1; /*3. 創(chuàng)建BMP圖片的參數(shù)*/ memset(&BmpInfo,0,sizeof(BITMAPINFOHEADER)); BmpInfo.biSize=sizeof(BITMAPINFOHEADER); //當(dāng)前結(jié)構(gòu)體大小 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屏的顏色數(shù)據(jù),用于創(chuàng)建BMP圖片*/ for(y=Height-1;y >=0;y--) { for(x=0;x< Width;x++) { c_16=LcdReadPoint(x,y); //讀取LCD屏上一個點(diǎn)的顏色 c_32=RGB565ToRGB888(c_16); //顏色的轉(zhuǎn)換 res =f_write(&file,&c_32,3,&cnt); if(res!=0)return 1; } } /*5. 關(guān)閉文件*/ f_close(&file); } ? ? /* 函數(shù)功能:BMP圖片顯示功能 參 數(shù): char filename:文件名稱 返 回 值:0表示成功,1表示失敗 */ u8 Show_BMP(const char *filename) { FIL file; // 用戶定義的文件系統(tǒng)結(jié)構(gòu)體 u8 res; // 保存文件操作的返回值 BITMAPFILEHEADER BmpHead; //保存圖片文件頭的信息 BITMAPINFOHEADER BmpInfo; //圖片參數(shù)信息 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的圖片頭參數(shù)*/ res =f_read(&file,&BmpHead,sizeof(BITMAPFILEHEADER),&cnt); if(res!=0)return 1; /*3. 讀取BMP圖片的參數(shù)*/ res =f_read(&file,&BmpInfo,sizeof(BITMAPINFOHEADER),&cnt); if(res!=0)return 1; /*4.顯示BMP圖片*/ f_lseek(&file,BmpHead.bfOffBits); //移動到RGB數(shù)據(jù)的存放位置 //后期的優(yōu)化:讀取一行的數(shù)據(jù),再顯示一行。 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); //轉(zhuǎn)換顏色 LcdDrawPoint(x,y,c_16); } } /*5. 關(guān)閉文件*/ f_close(&file); } ? ?
3.3 jpeg圖片解碼#include "piclib.h" #include "nt35310_lcd.h" _pic_info picinfo; //圖片信息 _pic_phy pic_phy; //圖片顯示物理接口 ? /* 函數(shù)功能: 劃橫線函數(shù),需要自己實(shí)現(xiàn) */ void Picture_DrawLine(u16 x0,u16 y0,u16 len,u16 color) { NT35310_Fill(x0,y0,x0+len-1,y0,color); } ? /* 函數(shù)功能: 矩形填充顏色 函數(shù)參數(shù): x,y:起始坐標(biāo) width,height:寬度和高度。 color:顏色數(shù)組 */ void Picture_FillColor(u16 x,u16 y,u16 width,u16 height,u16 *color) { NT35310_DrawRectangle(x,y,x+width-1,y+height-1,*color); } ? /* 函數(shù)功能: 畫圖初始化,在畫圖之前,必須先調(diào)用此函數(shù) 函數(shù)參數(shù): 指定畫點(diǎn)/讀點(diǎn) */ void Picture_DisplayInit(void) { pic_phy.draw_point=NT35310_DrawPoint; //畫點(diǎn)函數(shù)實(shí)現(xiàn) pic_phy.fill=NT35310_Fill; //填充函數(shù)實(shí)現(xiàn),僅GIF需要 pic_phy.draw_hline=Picture_DrawLine; //畫線函數(shù)實(shí)現(xiàn),僅GIF需要 pic_phy.fillcolor=Picture_FillColor; //顏色填充函數(shù)實(shí)現(xiàn),僅TJPGD需要 picinfo.lcdwidth=Lcd_Width; //得到LCD的寬度像素 picinfo.lcdheight=Lcd_Height; //得到LCD的高度像素 ? picinfo.ImgWidth=0; //初始化寬度為0 picinfo.ImgHeight=0;//初始化高度為0 picinfo.Div_Fac=0; //初始化縮放系數(shù)為0 picinfo.S_Height=0; //初始化設(shè)定的高度為0 picinfo.S_Width=0; //初始化設(shè)定的寬度為0 picinfo.S_XOFF=0; //初始化x軸的偏移量為0 picinfo.S_YOFF=0; //初始化y軸的偏移量為0 picinfo.staticx=0; //初始化當(dāng)前顯示到的x坐標(biāo)為0 picinfo.staticy=0; //初始化當(dāng)前顯示到的y坐標(biāo)為0 } ? ? /* 函數(shù)功能: 初始化智能畫點(diǎn) 說明: 內(nèi)部調(diào)用 */ 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; //使圖片處于所給區(qū)域的中間 picinfo.S_XOFF+=(picinfo.S_Width-temp1*picinfo.ImgWidth)/2; picinfo.S_YOFF+=(picinfo.S_Height-temp1*picinfo.ImgHeight)/2; temp1*=8192;//擴(kuò)大8192倍 picinfo.Div_Fac=temp1; picinfo.staticx=0xffff; picinfo.staticy=0xffff;//放到一個不可能的值上面 } ? /* 函數(shù)功能: 判斷這個像素是否可以顯示 函數(shù)參數(shù): (x,y) :像素原始坐標(biāo) 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); ? /* 函數(shù)功能: 繪制圖片 函數(shù)參數(shù): FileName:要顯示的圖片文件 BMP/JPG/JPEG/GIF x,y,width,height:坐標(biāo)及顯示區(qū)域尺寸 fast:使能jpeg/jpg小圖片(圖片尺寸小于等于液晶分辨率)快速解碼,0,不使能;1,使能. 函數(shù)說明: 圖片在開始和結(jié)束的坐標(biāo)點(diǎn)范圍內(nèi)顯示 */ 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; ? //顯示的開始坐標(biāo)點(diǎn) 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;//標(biāo)記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轉(zhuǎn)為RGB565 //ctb:RGB888顏色數(shù)組首地址. //返回值: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];//保存全局顏色. } //恢復(fù)全局顏色表 //gif:gif信息; void gif_recovergctbl(gif89a* gif) { u16 i=0; for(i=0;i< 256;i++)gif- >colortbl[i]=gif- >bkpcolortbl[i];//恢復(fù)全局顏色. } ? //初始化LZW相關(guān)參數(shù) //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; } ? //讀取一個數(shù)據(jù)塊 //gfile:gif文件; //buf:數(shù)據(jù)緩存區(qū) //maxnum:最大讀寫數(shù)據(jù)限制 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); //獲取數(shù)據(jù)塊 return 0; case GIF_GRAPHICCTL://圖形控制擴(kuò)展塊 if(gif_getdatablock(gfile,buf,4)!=4)return 1; //圖形控制擴(kuò)展塊的長度必須為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; //讀取數(shù)據(jù)塊結(jié)束符錯誤. return 0; } return 1;//錯誤的數(shù)據(jù) } ? //從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,讀到結(jié)束符了) // >=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<