【正点原子探索者STM32F407开发板例程连载+教学】第46章 图片显示实验

2019-07-21 04:20发布

第四十六章 图片显示实验

  [mw_shl_code=c,true]1.硬件平台:正点原子探索者STM32F407开发板 2.软件平台:MDK5.1 3.固件库版本:V1.4.0[/mw_shl_code]

在开发产品的时候,很多时候,我们都会用到图片解码,在本章中,我们将向大家介绍如何通过STM32F4来解码BMP/JPG/JPEG/GIF等图片,并在LCD上显示出来。本章分为如下几个部分: 46.1 图片格式简介 46.2 硬件设计 46.3 软件设计 46.4 下载验证  

46.1 图片格式简介

我们常用的图片格式有很多,一般最常用的有三种:JPEG(或JPG)、BMPGIF。其中JPEG(或JPG)和BMP是静态图片,而GIF则是可以实现动态图片。下面,我们简单介绍一下这三种图片格式。 首先,我们来看看BMP图片格式。BMP(全称Bitmap)是Window操作系统中的标准图像文件格式,文件后缀名为“.bmp”,使用非常广。它采用位映射存储格式,除了图像深度可选以外,不采用其他任何压缩,因此,BMP文件所占用的空间很大,但是没有失真。BMP文件的图像深度可选lbit4bit8bit16bit24bit32bitBMP文件存储数据时,图像的扫描方式是按从左到右、从下到上的顺序。 典型的BMP图像文件由四部分组成: 1,  位图头文件数据结构,它包含BMP图像文件的类型、显示内容等信息; 2,  位图信息数据结构,它包含有BMP图像的宽、高、压缩方法,以及定义颜 {MOD}等信息 1,  调 {MOD}板,这个部分是可选的,有些位图需要调 {MOD}板,有些位图,比如真彩 {MOD}图(24位的BMP)就不需要调 {MOD}板; 2,  位图数据,这部分的内容根据BMP位图使用的位数不同而不同,在24位图中直接使用RGB,而其他的小于24位的使用调 {MOD}板中颜 {MOD}索引值。 关于BMP的详细介绍,请参考光盘的《BMP图片文件详解.pdf》。接下来我们看看JPEG文件格式。 JPEGJoint Photographic Experts Group(联合图像专家组)的缩写,文件后辍名为“.jpg”或“.jpeg”,是最常用的图像文件格式,由一个软件开发联合会组织制定,同BMP格式不同,JPEG是一种有损压缩格式,能够将图像压缩在很小的储存空间,图像中重复或不重要的资料会被丢失,因此容易造成图像数据的损伤(BMP不会,但是BMP占用空间大)。尤其是使用过高的压缩比例,将使最终解压缩后恢复的图像质量明显降低,如果追求高品质图像,不宜采用过高压缩比例。但是JPEG压缩技术十分先进,它用有损压缩方式去除冗余的图像数据,在获得极高的压缩率的同时能展现十分丰富生动的图像,换句话说,就是可以用最少的磁盘空间得到较好的图像品质。而且 JPEG是一种很灵活的格式,具有调节图像质量的功能,允许用不同的压缩比例对文件进行压缩,支持多种压缩级别,压缩比率通常在101401之间,压缩比越大,品质就越低;相反地,压缩比越小,品质就越好。比如可以把137MbBMP位图文件压缩至203KB。当然也可以在图像质量和文件尺寸之间找到平衡点。JPEG格式压缩的主要是高频信息,对 {MOD}彩的信息保留较好,适合应用于互联网,可减少图像的传输时间,可以支持24bit真彩 {MOD},也普遍应用于需要连续 {MOD}调的图像。 JPEG/JPG的解码过程可以简单的概述为如下几个部分: 1、从文件头读出文件的相关信息。 JPEG 文件数据分为文件头和图像数据两大部分,其中文件头记录了图像的版本、长宽、采样因子、量化表、哈夫曼表等重要信息。所以解码前必须将文件头信息读出,以备 图像数据解码过程之用。 2、从图像数据流读取一个最小编码单元(MCU) ,并提取出里边的各个颜 {MOD}分量单元。 3、将颜 {MOD}分量单元从数据流恢复成矩阵数据。 使用文件头给出的哈夫曼表,对分割出来的颜 {MOD}分量单元进行解码,把其恢复成8×8 的数据矩阵。 48×8 的数据矩阵进一步解码。 此部分解码工作以8×8 的数据矩阵为单位, 其中包括相邻矩阵的直流系数差分解码、使用文件头给出的量化表反量化数据、反Zig- zag 编码、隔行正负纠正、反向离散余弦变换等5 个步骤, 最终输出仍然是一个8×8 的数据矩阵。 5、颜 {MOD}系统YCrCb RGB 转换。 将一个MCU的各个颜 {MOD}分量单元解码结果整合起来,将图像颜 {MOD}系统从YCrCb RGB 转换。 6、排列整合各个MCU 的解码数据。 不断读取数据流中的MCU 并对其解码,直至读完所有MCU 为止,将各MCU 解码后的数据正确排列成完整的图像。     JPEG的解码本身是比较复杂的,这里FATFS的作者,提供了一个轻量级的JPG/JPEG解码库:TjpgDec,最少仅需3KBRAM3.5KBFLASH即可实现JPG/JPEG解码,本例程采用TjpgDec作为JPG/JPEG的解码库,关于TjpgDec的详细使用,请参考光盘:6,软件资料图片编解码TjpgDec技术手册 这个文档。 BMPJPEG这两种图片格式均不支持动态效果,而GIF则是可以支持动态效果。最后,我们来看看GIF图片格式。 GIF(Graphics Interchange Format)CompuServe公司开发的图像文件存储格式,1987年开发的GIF文件格式版本号是GIF87a1989年进行了扩充,扩充后的版本号定义为GIF89a GIF图像文件以数据块(block)为单位来存储图像的相关信息。一个GIF文件由表示图形/图像的数据块、数据子块以及显示图形/图像的控制信息块组成,称为GIF数据流(Data Stream)。数据流中的所有控制信息块和数据块都必须在文件头(Header)和文件结束块(Trailer)之间。 GIF文件格式采用了LZW(Lempel-Ziv Walch)压缩算法来存储图像数据,定义了允许用户为图像设置背景的透明(transparency)属性。此外,GIF文件格式可在一个文件中存放多幅彩 {MOD}图形/图像。如果在GIF文件中存放有多幅图,它们可以像演幻灯片那样显示或者像动画那样演示。 一个GIF文件的结构可分为文件头(File Header)GIF数据流(GIF Data Stream)和文件终结器(Trailer)三个部分。文件头包含GIF文件署名(Signature)和版本号(Version)GIF数据流由控制标识符、图象块(Image Block)和其他的一些扩展块组成;文件终结器只有一个值为0x3B的字符(';')表示文件结束。 关于GIF的详细介绍,请参考光盘GIF解码相关资料。图片格式简介,我们就介绍到这里。

46.2 硬件设计

本章实验功能简介:开机的时候先检测字库,然后检测SD卡是否存在,如果SD卡存在,则开始查找SD卡根目录下的PICTURE文件夹,如果找到则显示该文件夹下面的图片文件(支持bmp、jpg、jpeg或gif格式),循环显示,通过按KEY0和KEY2可以快速浏览下一张和上一张,KEY_UP按键用于暂停/继续播放,DS1用于指示当前是否处于暂停状态。如果未找到PICTURE文件夹/任何图片文件,则提示错误。同样我们也是用DS0来指示程序正在运行。 所要用到的硬件资源如下: 1)  指示灯DS0DS1  2)  KEY0KEY2KEY_UP三个按键 3)  串口 4)  TFTLCD模块 5)  SD 6)  SPI FLASH 这几部分,在之前的实例中都介绍过了,我们在此就不介绍了。需要注意的是,我们在SD卡根目录下要建一个PICTURE的文件夹,用来存放JPEGJPGBMPGIF等图片。

46.3 软件设计

打开本章实验工程目录可以看到,我们在工程根目录下面新建了一个PICTURE文件夹。在该文件夹里面新建了bmp.cbmp.htjpgd.ctjpgd.hinteger.hgif.cgif.hpiclib.cpiclib.h9个文件。打开实验工程可以看到,我们在工程中新建了PICTURE分组,添加了相关源文件到工程,同时将PICTURE文件夹加入头文件包含路径。 对于这些文件,其中bmp.cbmp.h用于实现对bmp文件的解码;tjpgd.ctjpgd.h用于实现对jpeg/jpg文件的解码;gif.cgif.h用于实现对gif文件的解码;这几个代码太长了,所以我们在这里不贴出来,请大家参考光盘本例程的源码,我们打开piclib.c,代码如下: _pic_info picinfo;        //图片信息 _pic_phy pic_phy;         //图片显示物理接口     //lcd.h没有提供划横线函数,需要自己实现 void piclib_draw_hline(u16 x0,u16 y0,u16 len,u16 color) {        if((len==0)||(x0>lcddev.width)||(y0>lcddev.height))return;        LCD_Fill(x0,y0,x0+len-1,y0,color);     } //填充颜 {MOD} //x,y:起始坐标 //widthheight:宽度和高度。 //*color:颜 {MOD}数组 void piclib_fill_color(u16 x,u16 y,u16 width,u16 height,u16 *color)        LCD_Color_Fill(x,y,x+width-1,y+height-1,color);      } //画图初始化,在画图之前,必须先调用此函数 //指定画点/读点 void piclib_init(void) {        pic_phy.read_point=LCD_ReadPoint;         //读点函数实现,BMP需要        pic_phy.draw_point=LCD_Fast_DrawPoint; //画点函数实现        pic_phy.fill=LCD_Fill;                              //填充函数实现,GIF需要        pic_phy.draw_hline=piclib_draw_hline;     //画线函数实现,GIF需要        pic_phy.fillcolor=piclib_fill_color;            //颜 {MOD}填充函数实现,TJPGD需要        picinfo.lcdwidth=lcddev.width;                   //得到LCD的宽度像素        picinfo.lcdheight=lcddev.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 } //快速ALPHA BLENDING算法. //src:源颜 {MOD} //dst:目标颜 {MOD} //alpha:透明程度(0~32) //返回值:混合后的颜 {MOD}. u16 piclib_alpha_blend(u16 src,u16 dst,u8 alpha) {        u32 src2;u32 dst2;         //Convert to 32bit |-----GGGGGG-----RRRRR------BBBBB|        src2=((src<<16)|src)&0x07E0F81F;        dst2=((dst<<16)|dst)&0x07E0F81F;          //Perform blending R:G:B with alpha in range 0..32        //Note that the reason that alpha may not exceed 32 is that there are only        //5bits of space between each R:G:B value, any higher value will overflow        //into the next component and deliver ugly result.        dst2=((((dst2-src2)*alpha)>>5)+src2)&0x07E0F81F;        return (dst2>>16)|dst2;  } //初始化智能画点 //内部调用 void ai_draw_init(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 is_element_ok(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; } //智能画图 //FileName:要显示的图片文件  BMP/JPG/JPEG/GIF //x,y,width,height:坐标及显示区域尺寸 //fast:使能jpeg/jpg小图片(图片尺寸小于等于液晶分辨率)快速解码,0,不使能;1,使能. //图片在开始和结束的坐标点范围内显示 u8 ai_load_picfile(const u8 *filename,u16 x,u16 y,u16 width,u16 height,u8 fast) {            u8    res;//返回值        u8 temp;         if((x+width)>picinfo.lcdwidth)return PIC_WINDOW_ERR;     //x坐标超范围了.        if((y+height)>picinfo.lcdheight)return PIC_WINDOW_ERR;    //y坐标超范围了        //得到显示方框大小                    if(width==0||height==0)return PIC_WINDOW_ERR; //窗口设定错误        picinfo.S_Height=height;        picinfo.S_Width=width;        //显示区域无效        if(picinfo.S_Height==0||picinfo.S_Width==0)        {               picinfo.S_Height=lcddev.height;               picinfo.S_Width=lcddev.width;               return FALSE;          }        if(pic_phy.fillcolor==NULL)fast=0;//颜 {MOD}填充函数未实现,不能快速显示        //显示的开始坐标点        picinfo.S_YOFF=y;        picinfo.S_XOFF=x;        //文件名传递                temp=f_typetell((u8*)filename);   //得到文件的类型        switch(temp)        {                                                                                           case T_BMP:                      res=stdbmp_decode(filename);                   //解码bmp                                   break;               case T_JPG:               case T_JPEG:                      res=jpg_decode(filename,fast);                   //解码JPG/JPEG                                 break;               case T_GIF:                      res=gif_decode(filename,x,y,width,height);  //解码gif                              break;               default:                     res=PIC_FORMAT_ERR;                         //非图片格式!!!                       break;        }                                                                                     return res; } //动态分配内存 void *pic_memalloc (u32 size)                   {        return (void*)mymalloc(SRAMIN,size); } //释放内存 void pic_memfree (void* mf)              {        myfree(SRAMIN,mf); } 此段代码总共9个函数,其中,piclib_draw_hlinepiclib_fill_color函数因为LCD驱动代码没有提供,所以在这里单独实现,如果LCD驱动代码有提供,则直接用LCD提供的即可。 piclib_init 函数,该函数用于初始化图片解码的相关信息,其中_pic_phy是我们在piclib.h里面定义的一个结构体,用于管理底层LCD接口函数,这些函数必须由用户在外部实现。_pic_info则是另外一个结构体,用于图片缩放处理。 piclib_alpha_blend函数,该函数用于实现半透明效果,在小格式(图片分辨率小于LCD分辨率)bmp解码的时候,可能被用到。 ai_draw_init函数,该函数用于实现图片在显示区域的居中显示初始化,其实就是根据图片大小选择缩放比例和坐标偏移值。 is_element_ok函数,该函数用于判断一个点是不是应该显示出来,在图片缩放的时候该函数是必须用到的。 ai_load_picfile函数,该函数是整个图片显示的对外接口,外部程序,通过调用该函数,可以实现bmpjpg/jpeggif的显示,该函数根据输入文件的后缀名,判断文件格式,然后交给相应的解码程序(bmp解码/jpeg解码/gif解码),执行解码,完成图片显示。注意,这里我们用到一个f_typetell的函数,来判断文件的后缀名,f_typetell函数在exfuns.c里面实现,具体请参考光盘本例程源码。 最后,pic_memallocpic_memfree分别用于图片解码时需要用到的内存申请和释放,通过调用mymallocmyfreee来实现。 接下来我们看看头文件piclib.h代码如下: #ifndef __PICLIB_H #define __PICLIB_H                     ……//圣罗头文件引入 #define PIC_FORMAT_ERR        0x27       //格式错误 #define PIC_SIZE_ERR                     0x28       //图片尺寸错误 #define PIC_WINDOW_ERR       0x29       //窗口设定错误 #define PIC_MEM_ERR              0x11              //内存错误 #ifndef TRUE #define TRUE    1 #endif #ifndef FALSE #define FALSE   0 #endif //图片显示物理层接口  //在移植的时候,必须由用户自己实现这几个函数 typedef struct {        u16(*read_point)(u16,u16); //u16 read_point(u16 x,u16 y)      读点函数        void(*draw_point)(u16,u16,u16); //void draw_point(u16 x,u16 y,u16 color)画点函数       void(*fill)(u16,u16,u16,u16,u16);        ///void fill(u16 sx,u16 sy,u16 ex,u16 ey,u16 color)单 {MOD}填充函数             void(*draw_hline)(u16,u16,u16,u16);         //void draw_hline(u16 x0,u16 y0,u16 len,u16 color) 画水平线函数       void(*fillcolor)(u16,u16,u16,u16,u16*);      //void piclib_fill_color(u16 x,u16 y,u16 width,u16 height,u16 *color) 颜 {MOD}填充 }_pic_phy; extern _pic_phy pic_phy; //图像信息 typedef struct {                   u16 lcdwidth;         //LCD的宽度        u16 lcdheight;        //LCD的高度        u32 ImgWidth;      //图像的实际宽度和高度        u32 ImgHeight;        u32 Div_Fac;       //缩放系数 (扩大了8192倍的)          u32 S_Height;       //设定的高度和宽度        u32 S_Width;         u32  S_XOFF;             //x轴和y轴的偏移量        u32 S_YOFF;        u32 staticx;           //当前显示到的xy坐标        u32 staticy;                                                                                                     }_pic_info; extern _pic_info picinfo;//图像信息 void piclib_fill_color(u16 x,u16 y,u16 width,u16 height,u16 *color); void piclib_init(void);                                              //初始化画图 u16 piclib_alpha_blend(u16 src,u16 dst,u8 alpha);      //alphablend处理 void ai_draw_init(void);                                                 //初始化智能画图 u8 is_element_ok(u16 x,u16 y,u8 chg);                      //判断像素是否有效 u8 ai_load_picfile(const u8 *filename,u16 x,u16 y,u16 width,u16 height,u8 fast);//智能画图 void *pic_memalloc (u32 size);    //pic申请内存 void pic_memfree (void* mf);      //pic释放内存 #endif 这里基本就是我们前面提到的两个结构体的定义以及一些函数的申明,相信大家很容易明白。最后我们看看main.c文件内容如下: //得到path路径下,目标文件的总个数 //path:路径                //返回值:总有效文件数 u16 pic_get_tnum(u8 *path) {              u8 res; u16 rval=0;       DIR tdir;             //临时目录        FILINFO tfileinfo; //临时文件信息            u8 *fn;                                                                        res=f_opendir(&tdir,(const TCHAR*)path);        //打开目录       tfileinfo.lfsize=_MAX_LFN*2+1;                      //长文件名最大长度        tfileinfo.lfname=mymalloc(SRAMIN,tfileinfo.lfsize);//为长文件缓存区分配内存        if(res==FR_OK&&tfileinfo.lfname!=NULL)        {               while(1)//查询总的有效文件数               {                res=f_readdir(&tdir,&tfileinfo);                    //读取目录下的一个文件                if(res!=FR_OK||tfileinfo.fname[0]==0)break;      //错误了/到末尾了,退出                  fn=(u8*)(*tfileinfo.lfname?tfileinfo.lfname:tfileinfo.fname);                                        res=f_typetell(fn);                       if((res&0XF0)==0X50) rval++;//取高四位,是否图片文件?是则加1               }         }        return rval; } int main(void) {               u8 res; u8 t; u16 temp;       DIR picdir;                 //图片目录        FILINFO picfileinfo;//文件信息        u8 *fn;                       //长文件名        u8 *pname;                   //带路径的文件名        u16 totpicnum;             //图片文件总数        u16 curindex;                //图片当前索引        u8 key;                         //键值        u8 pause=0;                  //暂停标记        u16 *picindextbl;          //图片索引表        NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//设置系统中断优先级分组2        delay_init(168);  //初始化延时函数        uart_init(115200);         //初始化串口波特率为115200        LED_Init();                         //初始化LED        usmart_dev.init(84);             //初始化USMART       LCD_Init();                         //LCD初始化        KEY_Init();                         //按键初始化         W25QXX_Init();                  //初始化W25Q128        my_mem_init(SRAMIN);     //初始化内部内存池        my_mem_init(SRAMCCM);  //初始化CCM内存池        exfuns_init();                       //fatfs相关变量申请内存        f_mount(fs[0],"0:",1);          //挂载SD       f_mount(fs[1],"1:",1);          //挂载FLASH.        POINT_COLOR=RED;             while(font_init())                //检查字库        {                       LCD_ShowString(30,50,200,16,16,"Font Error!"); delay_ms(200);               LCD_Fill(30,50,240,66,WHITE); delay_ms(200);//清除显示           }            Show_Str(30,50,200,16,"Explorer STM32F4开发板",16,0);            Show_Str(30,70,200,16,"图片显示程序",16,0);                                           Show_Str(30,90,200,16,"KEY0:NEXT KEY2REV",16,0);                                      Show_Str(30,110,200,16,"WK_UPAUSE",16,0);                                             Show_Str(30,130,200,16,"正点原子@ALIENTEK",16,0);                                                Show_Str(30,150,200,16,"2014515",16,0);       while(f_opendir(&picdir,"0:/PICTURE"))//打开图片文件夹       {                       Show_Str(30,170,240,16,"PICTURE文件夹错误!",16,0); delay_ms(200);               LCD_Fill(30,170,240,186,WHITE); delay_ms(200);//清除显示           }         totpicnum=pic_get_tnum("0:/PICTURE"); //得到总有效文件数       while(totpicnum==NULL)//图片文件为0                 {                       Show_Str(30,170,240,16,"没有图片文件!",16,0); delay_ms(200);               LCD_Fill(30,170,240,186,WHITE); delay_ms(200);//清除显示           }       picfileinfo.lfsize=_MAX_LFN*2+1;                                 //长文件名最大长度        picfileinfo.lfname=mymalloc(SRAMIN,picfileinfo.lfsize); //长文件缓存区分配内存       pname=mymalloc(SRAMIN,picfileinfo.lfsize);    //为带路径的文件名分配内存       picindextbl=mymalloc(SRAMIN,2*totpicnum);   //申请内存,用于存放图片索引       while(picfileinfo.lfname==NULL||pname==NULL||picindextbl==NULL)//分配出错       {                       Show_Str(30,170,240,16,"内存分配失败!",16,0); delay_ms(200);               LCD_Fill(30,170,240,186,WHITE); delay_ms(200);//清除显示        }           //记录索引     res=f_opendir(&picdir,"0:/PICTURE"); //打开目录        if(res==FR_OK)        {               curindex=0;//当前索引为0               while(1)//全部查询一遍               {                      temp=picdir.index;                                    //记录当前index                res=f_readdir(&picdir,&picfileinfo);        //读取目录下的一个文件                if(res!=FR_OK||picfileinfo.fname[0]==0)break;//错误了/到末尾了,退出                   fn=(u8*)(*picfileinfo.lfname?picfileinfo.lfname:picfileinfo.fname);                       res=f_typetell(fn);                       if((res&0XF0)==0X50)//取高四位,看看是不是图片文件                       {                             picindextbl[curindex]=temp;//记录索引                             curindex++;                      }                       }        }          Show_Str(30,170,240,16,"开始显示...",16,0);        delay_ms(1500);        piclib_init();                                                                  //初始化画图                  curindex=0;                                                                   //0开始显示      res=f_opendir(&picdir,(const TCHAR*)"0:/PICTURE");   //打开目录        while(res==FR_OK)//打开成功        {                   dir_sdi(&picdir,picindextbl[curindex]);                      //改变当前目录索引                res=f_readdir(&picdir,&picfileinfo);                    //读取目录下的一个文件         if(res!=FR_OK||picfileinfo.fname[0]==0)break;         //错误了/到末尾了,退出           fn=(u8*)(*picfileinfo.lfname?picfileinfo.lfname:picfileinfo.fname);                              strcpy((char*)pname,"0:/PICTURE/");                      //复制路径(目录)               strcat((char*)pname,(const char*)fn);                              //将文件名接在后面              LCD_Clear(BLACK);              ai_load_picfile(pname,0,0,lcddev.width,lcddev.height,1);//显示图片                  Show_Str(2,2,240,16,pname,16,1);                          //显示图片名字               t=0;               while(1)               {                      key=KEY_Scan(0);                     //扫描按键                      if(t>250)key=1;                   //模拟一次按下KEY0                         if((t%20)==0)LED0=!LED0;//LED0闪烁,提示程序正在运行.                      if(key==KEY2_PRES)         //上一张                      {                             if(curindex)curindex--;                             else curindex=totpicnum-1;                             break;                      }else if(key==KEY0_PRES)//下一张                      {                             curindex++;                                            if(curindex>=totpicnum)curindex=0;//到末尾的时候,自动从头开始                             break;                      }else if(key==WKUP_PRES) { pause=!pause; LED1=!pause;}//暂停?                      if(pause==0)t++;                      delay_ms(10);               }                                                   res=0;         }                                                                                     myfree(SRAMIN,picfileinfo.lfname);//释放内存               myfree(SRAMIN,pname);                   //释放内存                              myfree(SRAMIN,picindextbl);            //释放内存         } 此部分除了mian函数,还有一个pic_get_tnum的函数,用来得到path路径下,所有有效文件(图片文件)的个数。在mian函数里面我们通过索引(图片文件在PICTURE文件夹下的编号),来查找上一个/下一个图片文件,这里我们需要用到FATFS自带的一个函数:dir_sdi,来设置当前目录的索引(因为f_readdir只能沿着索引一直往下找,不能往上找),方便定位到任何一个文件。dir_sdiFATFS下面被定义为static函数,所以我们必须在ff.c里面将该函数的static修饰词去掉,然后在ff.h里面添加该函数的申明,以便main函数使用。 其他部分就比较简单了,至此,整个图片显示实验的软件设计部分就结束了。该程序将实现浏览PICTURE文件夹下的所有图片,并显示其名字,每隔3s左右切换一幅图片。

46.4 下载验证

在代码编译成功之后,我们下载代码到ALIENTEK探索者STM32F4开发板上,可以看到LCD开始显示图片(假设SD卡及文件都准备好了),如图46.4.1所示:
 46.4.1 图片显示实验显示效果KEY0KEY2可以快速切换到下一张或上一张,KEY_UP按键可以暂停自动播放,同时DS1亮,指示处于暂停状态,再按一次KEY_UP则继续播放。同时,由于我们的代码支持gif格式的图片显示(注意尺寸不能超过LCD屏幕尺寸),所以可以放一些gif图片到PICTURE文件夹,来看动画了。
  实验详细手册和源码下载地址:http://www.openedv.com/posts/list/41586.htm  正点原子探索者STM32F407开发板购买地址http://item.taobao.com/item.htm?id=41855882779