龙空技术网

「正点原子STM32Mini板资料连载」第三十六章 图片显示实验

正点原子日常 168

前言:

如今我们对“stm32图片缩放显示算法”大体比较着重,你们都想要学习一些“stm32图片缩放显示算法”的相关知识。那么小编在网摘上搜集了一些对于“stm32图片缩放显示算法””的相关文章,希望我们能喜欢,你们一起来了解一下吧!

1)实验平台:正点原子STM32mini开发板

2)摘自《正点原子STM32 不完全手册(HAL 库版)》关注官方微信号公众号,获取更多资料:正点原子

第三十六章 图片显示实验

在开发产品的时候,很多时候,我们都会用到图片解码,在本章中,我们将向大家介绍

如何通过 STM32 来解码 BMP/JPG/JPEG/GIF 等图片,并在 LCD 上显示出来。本章分为如

下几个部分:

36.1 图片格式简介

36.2 硬件设计

36.3 软件设计

36.4 下载验证

36.1 图片格式简介

我们常用的图片格式有很多,一般最常用的有三种:JPEG(或 JPG)、BMP 和 GIF。其中

JPEG(或 JPG)和 BMP 是静态图片,而 GIF 则是可以实现动态图片。下面,我们简单介绍一

下这三种图片格式。

首先,我们来看看 BMP 图片格式。BMP(全称 Bitmap)是 Window 操作系统中的标准图

像文件格式,文件后缀名为“.bmp”,使用非常广。它采用位映射存储格式,除了图像深度可

选以外,不采用其他任何压缩,因此,BMP 文件所占用的空间很大,但是没有失真。BMP 文

件的图像深度可选 lbit、4bit、8bit、16bit、24bit 及 32bit。BMP 文件存储数据时,图像的扫描

方式是按从左到右、从下到上的顺序。

典型的 BMP 图像文件由四部分组成:

1, 位图头文件数据结构,它包含 BMP 图像文件的类型、显示内容等信息;

2, 位图信息数据结构,它包含有 BMP 图像的宽、高、压缩方法,以及定义颜色等信息

3, 调色板,这个部分是可选的,有些位图需要调色板,有些位图,比如真彩色图(24 位

的 BMP)就不需要调色板;

4, 位图数据,这部分的内容根据 BMP 位图使用的位数不同而不同,在 24 位图中直接使

用 RGB,而其他的小于 24 位的使用调色板中颜色索引值。

关于 BMP 的详细介绍,请参考光盘的《BMP 图片文件详解.pdf》。接下来我们看看 JPEG

文件格式。

JPEG 是 Joint Photographic Experts Group(联合图像专家组)的缩写,文件后辍名为“.jpg”

或“.jpeg”,是最常用的图像文件格式,由一个软件开发联合会组织制定,同 BMP 格式不同,

JPEG 是一种有损压缩格式,能够将图像压缩在很小的储存空间,图像中重复或不重要的资料

会被丢失,因此容易造成图像数据的损伤(BMP 不会,但是 BMP 占用空间大)。尤其是使用过

高的压缩比例,将使最终解压缩后恢复的图像质量明显降低,如果追求高品质图像,不宜采用

过高压缩比例。但是 JPEG 压缩技术十分先进,它用有损压缩方式去除冗余的图像数据,在获

得极高的压缩率的同时能展现十分丰富生动的图像,换句话说,就是可以用最少的磁盘空间得

到较好的图像品质。而且 JPEG 是一种很灵活的格式,具有调节图像质量的功能,允许用不同

的压缩比例对文件进行压缩,支持多种压缩级别,压缩比率通常在 10:1 到 40:1 之间,压缩

比越大,品质就越低;相反地,压缩比越小,品质就越好。比如可以把 1.37Mb 的 BMP 位图

文件压缩至 20.3KB。当然也可以在图像质量和文件尺寸之间找到平衡点。JPEG 格式压缩的

主要是高频信息,对色彩的信息保留较好,适合应用于互联网,可减少图像的传输时间,可以

支持 24bit 真彩色,也普遍应用于需要连续色调的图像。

JPEG/JPG 的解码过程可以简单的概述为如下几个部分:

1、从文件头读出文件的相关信息。

JPEG 文件数据分为文件头和图像数据两大部分,其中文件头记录了图像的版本、长宽、

采样因子、量化表、哈夫曼表等重要信息。所以解码前必须将文件头信息读出,以备

图像数据解码过程之用。

2、从图像数据流读取一个最小编码单元(MCU) ,并提取出里边的各个颜色分量单元。

3、将颜色分量单元从数据流恢复成矩阵数据。

使用文件头给出的哈夫曼表,对分割出来的颜色分量单元进行解码,把其恢复成 8×8

的数据矩阵。

4、8×8 的数据矩阵进一步解码。

此部分解码工作以 8×8 的数据矩阵为单位,

其中包括相邻矩阵的直流系数差分解码、使用文件头给出的量化表反量化数据、反 Zig- zag 编码、隔行正负纠正、反向离散余弦变

换等 5 个步骤, 最终输出仍然是一个 8×8 的数据矩阵。

5、颜色系统 YCrCb 向 RGB 转换。

将一个 MCU 的各个颜色分量单元解码结果整合起来,将图像颜色系统从 YCrCb 向

RGB 转换。

6、排列整合各个 MCU 的解码数据。

不断读取数据流中的 MCU 并对其解码,直至读完所有 MCU 为止,将各 MCU 解码

后的数据正确排列成完整的图像。

JPEG 的解码本身是比较复杂的,这里 FATFS 的作者,提供了一个轻量级的 JPG/JPEG 解

码库:TjpgDec,最少仅需 3KB 的 RAM 和 3.5KB 的 FLASH 即可实现 JPG/JPEG 解码,本例程

采用 TjpgDec 作为 JPG/JPEG 的解码库,关于 TjpgDec 的详细使用,请参考光盘:6,软件资料

\图片解码\TjpgDec 技术手册 这个文档。

BMP 和 JPEG 这两种图片格式均不支持动态效果,而 GIF 则是可以支持动态效果。最后,

我们来看看 GIF 图片格式。

GIF(Graphics Interchange Format)是 CompuServe 公司开发的图像文件存储格式,1987 年开

发的 GIF 文件格式版本号是 GIF87a,1989 年进行了扩充,扩充后的版本号定义为 GIF89a。

GIF 图像文件以数据块(block)为单位来存储图像的相关信息。一个 GIF 文件由表示图形/图

像的数据块、数据子块以及显示图形/图像的控制信息块组成,称为 GIF 数据流(Data Stream)。

数据流中的所有控制信息块和数据块都必须在文件头(Header)和文件结束块(Trailer)之间。

GIF 文件格式采用了 LZW(Lempel-Ziv Walch)压缩算法来存储图像数据,定义了允许用户为

图像设置背景的透明(transparency)属性。此外,GIF 文件格式可在一个文件中存放多幅彩色图

形/图像。如果在 GIF 文件中存放有多幅图,它们可以像演幻灯片那样显示或者像动画那样演示。

一个 GIF 文件的结构可分为文件头(File Header)、GIF 数据流(GIF Data Stream)和文件终结

器(Trailer)三个部分。文件头包含 GIF 文件署名(Signature)和版本号(Version);GIF 数据流由控

制标识符、图象块(Image Block)和其他的一些扩展块组成;文件终结器只有一个值为 0x3B 的字

符(';')表示文件结束。

关于 GIF 的详细介绍,请参考光盘 GIF 解码相关资料。图片格式简介,我们就介绍到这里。

36.2 硬件设计

本章实验功能简介:开机的时候先检测字库,然后检测 SD 卡是否存在,如果 SD 卡存在,

则开始查找 SD 卡根目录下的 PICTURE 文件夹,如果找到则显示该文件夹下面的图片文件(支

持 bmp、jpg、jpeg 或 gif 格式),循环显示,通过按 KEY0 和 KEY1 可以快速浏览下一张和上

一张,WK_UP 按键用于暂停/继续播放,DS1 用于指示当前是否处于暂停状态。如果未找到

PICTURE 文件夹/任何图片文件,则提示错误。同样我们也是用 DS0 来指示程序正在运行。

所要用到的硬件资源如下:

1) 指示灯 DS0 和 DS1

2) KEY0、KEY1 和 WK_UP 三个按键

3) 串口

4) TFTLCD 模块

5) SD 卡

6) SPI FLASH

这几部分,在之前的实例中都介绍过了,我们在此就不介绍了。需要注意的是,我们在 SD

卡根目录下要建一个 PICTURE 的文件夹,用来存放 JPEG、JPG、BMP 或 GIF 等图片。36.3 软件设计

打开上一章的工程,首先在 HARDWARE 文件夹所在的文件夹下新建一个 PICTURE 的文

件夹。在该文件夹里面新建 bmp.c、bmp.h、tjpgd.c、tjpgd.h、integer.h、gif.c、gif.h、piclib.c 和

piclib.h 等 9 个文件。并将 PICTURE 文件夹加入头文件包含路径。

其中 bmp.c 和 bmp.h 用于实现对 bmp 文件的解码;tjpgd.c 和 tjpgd.h 用于实现对 jpeg/jpg 文

件的解码;gif.c 和 gif.h 用于实现对 gif 文件的解码;这几个代码太长了,所以我们在这里不贴

出来,请大家参考光盘本例程的源码,我们打开 piclib.c,在里面输入如下代码:

#include "piclib.h"

#include "lcd.h"

_pic_info picinfo;

//图片信息

_pic_phy pic_phy;

//图片显示物理接口

//LCD 驱动部分,没有提供划横线函数,需要自己实现

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);

}

//填充颜色

//x,y:起始坐标

//width,height:宽度和高度。

//*color:颜色数组

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;

//读点函数实现

pic_phy.draw_point=LCD_Fast_DrawPoint; //画点函数实现

pic_phy.fill=LCD_Fill;

//填充函数实现

pic_phy.draw_hline=piclib_draw_hline; //画线函数实现

pic_phy.fillcolor=piclib_fill_color;

//颜色填充函数实现

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:源颜色

//dst:目标颜色

//alpha:透明程度(0~32)

//返回值:混合后的颜色.

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;//颜色填充函数未实现,不能快速显示

//显示的开始坐标点

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(size);

}

//释放内存

void pic_memfree (void* mf)

{

myfree(mf);

}

此段代码总共 9 个函数,其中,piclib_draw_hline 和 piclib_fill_color 函数因为 LCD 驱动代

码没有提供,所以在这里单独实现,如果 LCD 驱动代码有提供,则直接用 LCD 提供的即可。

piclib_init 函数,该函数用于初始化图片解码的相关信息,其中_pic_phy 是我们在 piclib.h

里面定义的一个结构体,用于管理底层 LCD 接口函数,这些函数必须由用户在外部实现。

_pic_info 则是另外一个结构体,用于图片缩放处理。

piclib_alpha_blend 函数,该函数用于实现半透明效果,在小格式(分辨率小于 240*320)

bmp 解码的时候,可能被用到。

ai_draw_init 函数,该函数用于实现图片在显示区域的居中显示初始化,其实就是根据图片

大小选择缩放比例和坐标偏移值。

is_element_ok 函数,该函数用于判断一个点是不是应该显示出来,在图片缩放的时候该函

数是必须用到的。

ai_load_picfile 函数,该函数是整个图片显示的对外接口,外部程序,通过调用该函数,可

以实现 bmp、jpg/jpeg 和 gif 的显示,该函数根据输入文件的后缀名,判断文件格式,然后交给

相应的解码程序(bmp 解码/jpeg 解码/gif 解码),执行解码,完成图片显示。注意,这里我们用

到一个 f_typetell 的函数,来判断文件的后缀名,f_typetell 函数在 exfuns.c 里面实现,具体请参

考光盘源码。

最后,pic_memalloc 和 pic_memfree 分别用于图片解码时需要用到的内存申请和释放,通

过调用 mymalloc 和 myfreee 来实现。

保存 piclib.c,然后在工程里面新建一个 PICTURE 的分组,将 bmp.c、gif.c、tjpgd.c 和 piclib.c

等 4 个 c 文件加入到 PICTURE 分组下。然后打开 piclib.h,在该文件输入如下代码:

#ifndef __PICLIB_H

#define __PICLIB_H

#include "sys.h"

#include "lcd.h"

#include "malloc.h"

#include "ff.h"

#include "exfuns.h"

#include "bmp.h"

#include "tjpgd.h"

#include "gif.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) 单色填充函数

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) 颜色填充

}_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_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

这里基本就是我们前面提到的两个结构体的定义以及一些函数的申明,保存 piclib.h。最后

我们在 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(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;

//图片索引表

HAL_Init();

//初始化 HAL 库

Stm32_Clock_Init(RCC_PLL_MUL9); //设置时钟,72M

delay_init(72);

//初始化延时函数

uart_init(115200);

//初始化串口

usmart_dev.init(84);

//初始化 USMART

LED_Init();

//初始化 LED

KEY_Init();

//初始化按键

LCD_Init();

//初始化 LCD

mem_init();

//初始化内存池

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(60,50,200,16,16,"Font Error!"); delay_ms(200);

LCD_Fill(60,50,240,66,WHITE); delay_ms(200);

}

Show_Str(30,50,200,16,"Mini STM32 开发板",16,0);

Show_Str(30,70,200,16,"图片显示程序",16,0);

Show_Str(30,90,200,16,"KEY0:NEXT KEY1:PREV",16,0);

Show_Str(30,110,200,16,"KEY_UP:PAUSE",16,0);

Show_Str(30,130,200,16,"正点原子@ALIENTEK",16,0);

Show_Str(30,150,200,16,"2019 年 11 月 18 日",16,0);

while(f_opendir(&picdir,"0:/PICTURE"))//打开图片文件夹

{

Show_Str(60,170,240,16,"PICTURE 文件夹错误!",16,0);delay_ms(200);

LCD_Fill(60,170,240,186,WHITE); delay_ms(200);

}

totpicnum=pic_get_tnum("0:/PICTURE"); //得到总有效文件数

while(totpicnum==NULL)//图片文件为 0

{

Show_Str(60,170,240,16,"没有图片文件!",16,0); delay_ms(200);

LCD_Fill(60,170,240,186,WHITE); delay_ms(200);//清除显示

}

picfileinfo.lfsize=_MAX_LFN*2+1;

//长文件名最大长度

picfileinfo.lfname=mymalloc(picfileinfo.lfsize); //为长文件缓存区分配内存

pname=mymalloc(picfileinfo.lfsize);

//为带路径的文件名分配内存

picindextbl=mymalloc(2*totpicnum);//申请 2*totpicnum 个字节内存,用于存放图片索引

while(picfileinfo.lfname==NULL||pname==NULL||picindextbl==NULL)//内存分配出错

{

Show_Str(60,170,240,16,"内存分配失败!",16,0); delay_ms(200);

LCD_Fill(60,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(60,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==KEY1_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;

//暂停的时候 LED1 亮.

}

if(pause==0)t++;

delay_ms(10);

}

res=0;

}

myfree(picfileinfo.lfname); //释放内存

myfree(pname);

//释放内存

myfree(picindextbl);

//释放内存

}

此部分除了 mian 函数,还有一个 pic_get_tnum 的函数,用来得到 path 路径下,所有有效

文件(图片文件)的个数。在 mian 函数里面我们通过索引(图片文件在 PICTURE 文件夹下的

编号),来查找上一个/下一个图片文件,这里我们需要用到 fatfs 自带的一个函数:dir_sdi,来

设置当前目录的索引(因为 f_readdir 只能沿着索引一直往下找,不能往上找),方便定位到任

何一个文件。dir_sdi 在 FATFS 下面被定义为 static 函数,所以我们必须在 ff.c 里面将该函数的

static 修饰词去掉,然后在 ff.h 里面添加该函数的申明,以便 main 函数使用。

其他部分就比较简单了,至此,整个图片显示实验的软件设计部分就结束了。该程序将实

现浏览 PICTURE 文件夹下的所有图片,并显示其名字,每隔 3s 左右切换一幅图片。

36.4 下载验证

在代码编译成功之后,我们下载代码到 ALIENTEK MiniSTM32 开发板上,可以看到 LCD

开始显示图片(假设 SD 卡及图片文件都已经准备好了),如图 36.4.1 所示:

图 36.4.1 图片显示实验显示效果

按 KEY0 和 KEY1 可以快速切换到下一张或上一张,WK_UP 按键可以暂停自动播放,同

时 DS1 亮,指示处于暂停状态,再按一次 WK_UP 则继续播放(DS1 灭)。同时,由于我们的

代码支持 gif 格式的图片显示(注意尺寸不能超过 LCD 屏幕尺寸),所以可以放一些 gif 图片到

PICTURE 文件夹,来看动画了。

本章,同样可以通过 USMART 来测试该实验,将 ai_load_picfile 函数加入 USMART 控制(方法前面已经讲了很多次了),就可以通过串口调用该函数,在屏幕上任何区域显示任何你想要显示的图片了!

标签: #stm32图片缩放显示算法