1.3寸LCD屏幕 显示图片
测试平台:STM32F103RFT6
库版本:官方标准库3.5.0版本
屏幕:中景园1.3寸24Pin接插式LCD屏幕
分辨率:240*240像素
驱动芯片:ST7789
驱动方式:4线SPI
stm32【 1.3寸LCD屏幕(1)】
上一篇文章介绍了LCD屏幕的基础配置以及实现全屏颜色显示的功能
本文会介绍一些基础的显示函数和显示图片的实现方式
注: 文中首次出现的代码块会标注[xxx.c]或[xxx.h],表明该代码是属于对应的文件,未标注的即为重复出现的
1、基础显示函数
画点和画线函数▼
[lcd.c]
/********************************************************************************
* @brief 在指定位置画点
* @param (x,y) 坐标
* @param color 颜色
* @retval none
*******************************************************************************/
void LCD_Draw_Point(u16 x,u16 y,u16 color)
{
LCD_SetRegion(x, y, x, y);
LCD_WR_Data16(color);
}
/********************************************************************************
* @brief LCD画线函数,使用Bresenham算法
* @param (x1,y1) 起点
* @param (x1,y1) 终点
* @param PixcelColor 点颜色
* @retval none
*******************************************************************************/
void LCD_Draw_Line(u16 x1, u16 y1, u16 x2, u16 y2, u16 PixcelColor)
{
u16 t;
int xerr = 1,yerr = 1;
int delta_x, delta_y, distance;
int incx, incy, uRow, uCol;
delta_x = x2 - x1; //计算坐标增量
delta_y = y2 - y1;
uRow = x1;
uCol = y1;
if(delta_x > 0)incx = 1; //设置单步方向
else if(delta_x == 0)incx = 0; //垂直线
else {incx = -1; delta_x = -delta_x;}
if(delta_y > 0)incy = 1;
else if(delta_y == 0)incy = 0; //水平线
else{incy = -1; delta_y = -delta_y;}
if(delta_x > delta_y)distance = delta_x;//选取基本增量坐标轴
else distance = delta_y;
for(t = 0; t <= distance; t++ ){ //画线输出
LCD_Draw_Point(uRow,uCol,PixcelColor);//画点
xerr += delta_x ;
yerr += delta_y ;
if(xerr > (distance>>1)){ // 等于(distance/2)
xerr -= distance;
uRow += incx;
}
if(yerr > distance){
yerr -= distance;
uCol += incy;
}
}
}
画线函数就不解释了,这个Bresenham算法网上有很多很详细的介绍
2、图片显示
要在屏幕上显示图片,有两个步骤
1、图片取模,生成数组或者bin文件
如果生成数组,需要创建一个 [xxx.h] 文件来存储该数组,直接烧录到单片机内部,需要时调用数组即可
如果生成bin文件,则需要直接写入外部存储空间,例如外部flash芯片或者SD卡,但是flash烧写不太方便,这里推介使用SD卡的,搭配FATFS文件操作系统的话,可以直接将bin文件通过电脑拷贝进SD卡,再读使用文件取文件,那么更换图片就很方便了。
第二种方法过于复杂,涉及到SDIO和FATFS文件系统,可以单独开两篇文章来介绍了,以后有时间再写,这里介绍第一种方式来显示图片
图片取模
驱动芯片ST7789第8.12章节的图表▼
可以看见,注意(DDRAM)这列图片,无论屏幕显示方向如何改变,数据的方向始终是【从左到右,从上到下】
在8.4.2小节里介绍到SPI模式下写入指令时提到:需要先发送高位▼
在上一篇文章的【2.4章节LCD初始化】配置中,色彩模式的配置是5-6-5的RGB色彩
因此,无论使用哪款图片取模软件,取模设置都需要符合以下条件
(1)取模方向【从左到右,从上到下】
(3)数据结构【高位在前,MSB first】
(2)色彩格式【5-6-5的RGB比例】
▼这里使用【Image2lcd】这款软件,3.2版本(该版本可以批量转换图片,可以用来制作可播放的视频文件)
输出类型【C语言数组】
扫描模式【水平扫描】,上方有数据生成方向的预览图,正好符合需要的取模方向
输出灰度【16位真彩色】在上一篇文章中提到色彩配置是65K色彩
//颜色数据格式,见8.8和9.1.33章节
LCD_WR_Cmd(0x3A);
LCD_WR_Data8(0x05);
16位真彩色:2^16正好是65536
宽度和高度【xxx】这个就是根据需要显示的大小来填写了
不过输入(240,240)输出的图片格式并不是(240,240),而是按照宽高比例输出,在上图最底下的一栏文字,这里显示了原始图片的分辨率和输出图片的分辨率(240,230)
接下来的是一些勾选的选项:
包含图像头数据: 该选项会在颜色数据前面附加一些文件信息,关于这些信息的解读可以参考【帮助】-【用法说明】-【图像头数据结构】
根据该数据结构可以对图像信息进行解读,其中比较重要的是w和h,也就是图片的宽度和高度,在图片显示函数中会使用到
当然你也可以不包含数据头信息,只是这样显示图片的话需要手动输入图片宽高,不太方便。
字节内像素数据反序 这个没有使用过,也勾选不了,估计大概是跟计算机数据存储的大端模式和小端模式有关,在这里就不介绍了,感兴趣的可以自行了解
自右自左扫描
自底自顶扫描 这两个配合扫描模式可以改变数据扫描方向
高位在前(MSB First) 这个很重要,必须勾选,生成数据时高八位在前
在预览图的下方是色彩格式的选择,这里选【16位彩色】,颜色位数选【5-6-5】,符合第三个条件
这是我的最终配置▼
这里选择了16色真彩色
配置完成点击保存就可以生成一个C文件,这样就完成了图片的取模
打开生成的文件,可以看到有一个数组,这里截取开头部分说明下该数组应怎么使用,为后边的编程做铺垫
const unsigned char gImage_my_logo[110408] = { 0X10,0X10,0X00,0XF0,0X00,0XE6,0X01,0X1B,
0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,
0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,
0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,
0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,
0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,
首先可以知道的是该数组长度为【110408】,这是可以计算出来的
前面提到,实际输出的图像分辨率为(240,230)
那么240 x 230 = 5520
由于一个像素点有2byte数据,那么这张图片的数据量就是:55200 x 2 = 110400
在image2lcd软件的【帮助】中有16位真彩色信息头的结构体▼
“4096色/16位真彩色/18位真彩色/24位真彩色/32位真彩色”的图像数据头如下:
typedef struct _HEADCOLOR
{
unsigned char scan;
unsigned char gray;
unsigned short w;
unsigned short h;
unsigned char is565;
unsigned char rgb;
}HEADCOLOR;
也就是信息头大小是8byte
那么加起来就是110400 + 8 = 110408
数组的[0]~[7]就是图片信息头▼
0X10,0X10,0X00,0XF0,0X00,0XE6,0X01,0X1B,
第3-6个是宽度和高度,都是2byte组成
宽度:0x00 F0 转换成十进制即240
高度:0x00 E6 转换成十进制即230
就是图片输出的分辨率(240,230),在编程的时候把这4byte数据解析出来就能知道图片的大小了
其他的具体含义不解释,有兴趣的可以自己去看看
显示函数
显示图片的话首先做个准备工作:
上一篇文章中设置显示区域函数,需要设置起始坐标和结束坐标,这里写一个新的,设置起始点和宽度高度,方便图片显示▼
[lcd.c]
/********************************************************************************
* @brief 设置图片显示位置
* @param xy起点和区域大小(图片长宽)
* @retval none
*******************************************************************************/
void LCD_SetRegion_Image(u16 x, u16 y, u16 w, u16 h)
{
#if USE_HORIZONTAL == 1
y = y + 80; //Y轴中心点偏移80格
#elif USE_HORIZONTAL == 3
x = x + 80; //X轴中心点偏移80格
#endif
LCD_WR_Cmd(0x2a);//列地址设置
LCD_WR_Data16(x);
LCD_WR_Data16(x + w - 1);
LCD_WR_Cmd(0x2b);//行地址设置
LCD_WR_Data16(y);
LCD_WR_Data16(y + h - 1);
LCD_WR_Cmd(0x2c);//储存器写
}
然后是根据上节提到的信息头来编写显示函数了
[lcd.c]
/*
API使用参考
图片数据存放在主控芯片的flash
通过指针*p传递数据地址
图片取模软件:"Image2LCD"
注意图片取模参数,软件 “帮助” 中有图片信息头说明
图片信息头包含:扫描的方向、灰度值、图片宽度和高度等
*/
/********************************************************************************
* @brief 定点显示图片,注意图片取模的信息头
* @param (x,y) 图片起始点
(Wide,Height) 图片分辨率(最大240x240)
*pdata 图片取模数组
取模方式:水平扫描 从左到右 高位在前
* @retval none
*******************************************************************************/
void Showimage_1(u32 x, u32 y, u8 *pdata)
{
u8 *u_pdata;
u32 i;
u16 Wide;
u16 Height;
u32 data_cnt;
u_pdata = pdata;
//提取图片信息头
Wide = (u16)u_pdata[2] << 8; //高八位在前
Wide += (u16)u_pdata[3]; //低八位在后
Height = (u16)u_pdata[4] << 8; //高八位在前
Height += (u16)u_pdata[5]; //低八位在后
u_pdata += 8;
LCD_SetRegion_Image(x, y, Wide, Height); //坐标设置
data_cnt = Wide * Height*2;
for(i = 0; i < data_cnt; i++){
LCD_WR_Data8(u_pdata[i]);
}
}
没啥好解释的,获取宽度和高度信息后,设定显示区域,把指针【*u_pdata】往后移8位,然后开始刷数据就行了,注意数据量是点阵数的2倍
把生成C文件改成头文件,然后添加进工程中【#include “my_logo.h”】
在main函数里调用该函数即可显示图片
Showimage_1(0,0,gImage_my_logo);
3、总结
这是实物展示▼
图片越大,烧写越久,而且要注意所选的单片机是否有足够的空间来存储该数组,这张图片足足需要110K,这也是开头提到的第二种方法里为啥不推荐使用外部flash来存储,烧写太麻烦了,耗时长,当然用来固化一些不再修改的Logo的话,就另当别论了
使用SD卡这样的存储介质的话,可以存储更多的图片,如果把图片一张张放映出来,能达40ms一张的播放速度,到就可以变成小电视了,以后有空再写吧