LCD显示图片
206.3.5 LCD显示图片
/*
* 本节目标:把一张自选图片转换为 LCD 可直接接收的 RGB565 字节数组,并在屏幕左上角显示。
* 收录边界:只列出视频最终实际保留的业务代码,以及 pic.h 中生成数组的声明和首行数据。
* 数据说明:gImage_bmp4 的完整初始化器有 235200 字节,属于工具生成的图片资源,正文不机械展开全部数据。
* 版本边界:代码保留视频结束时的状态;i、j、t 虽已不再使用但仍留在工程中,因此会产生 3 条未使用变量警告。
*/
/* main.c:本节使用的三个 LCD 业务头文件。 */
#include "lcd_init.h" /* 引入 LCD_Init()、屏幕尺寸和底层初始化声明。 */
#include "lcd.h" /* 引入 LCD_ShowPicture() 等显示接口。 */
#include "pic.h" /* 引入 Image2Lcd 生成的 gImage_bmp4 图片数组。 */
/* pic.h:以下是最终数组的真实声明和第一行数据;其余 235184 个生成字节不在笔记中重复展开。 */
const unsigned char gImage_bmp4[235200] = { /* 元数据注释为 0X10,0X10,0X01,0X2C,0X01,0X88,0X01,0X1B。 */
0XFF, 0XFF, 0XFF, 0XFF, 0XFF, 0XFF, 0XFF, 0XFF, /* 图片开头的 8 个真实字节。 */
0XFF, 0XFF, 0XFF, 0XFF, 0XFF, 0XFF, 0XFF, 0XFF /* 图片开头的后 8 个真实字节;后续数据由工具生成。 */
/* ……其余图片数据见课程工程 pic.h;此处只是源码摘录,不能单独替代完整数组。 */
}; /* 图片数组摘录结束。 */
/* main.c / main():视频结束时 USER CODE 相关部分的有效代码。 */
u8 i, j; /* 原演示的双重循环变量;调用被注释后本节未再使用。 */
float t = 0; /* 原浮点显示变量;相关显示调用被注释后本节未再使用。 */
HAL_Init(); /* 初始化 HAL、Flash 接口和系统滴答定时器。 */
SystemClock_Config(); /* 配置系统时钟。 */
MX_GPIO_Init(); /* 初始化 LCD 片选、复位、背光等 GPIO。 */
MX_SPI1_Init(); /* 初始化向 LCD 发送数据的 SPI1。 */
MX_USART1_UART_Init(); /* 初始化工程原有的 USART1;本节图片显示没有使用它。 */
LCD_Init(); /* 复位并初始化 LCD 控制器。 */
LCD_ShowPicture(0, 0, 300, 392, gImage_bmp4); /* 从 (0,0) 开始显示 300×392 的最终 RGB565 图片。 */
while (1) /* 初始化和图片写入结束后进入无限循环。 */
{ /* 主循环开始。 */
/* 本节循环体为空,LCD 显存会保持已经写入的图像。 */
} /* 返回循环顶部,程序不会正常退出。 */
一、取模图片
本节不再修改 LCD 底层驱动,而是完成一条完整的图片资源制作链:把普通 PNG 图片缩放并另存为 BMP,再用 Image2Lcd v2.9 转换成按 RGB565、高字节在前排列的 C 字节数组,粘贴进 pic.h,最后由 LCD_ShowPicture() 发送到 LCD。
这里的“取模”不是压缩图片,也不是让 STM32 运行时解析 BMP 文件,而是提前在电脑上把每个像素换算成显示控制器需要的字节。固件烧入 Flash 后,单片机只看到 const unsigned char 数组,不再知道原文件是 PNG 还是 BMP。
1.1 初始代码与调试准备
上一节的 main.c 会依次执行全屏填色、整屏图片、清屏、文字和小企鹅平铺等多项演示。为了让本节的现象只受一张图片影响,视频先把无关调用全部注释掉。
最先停用的是:
// LCD_Fill(0, 0, LCD_W, LCD_H, GRED);
随后只保留一个 LCD_ShowPicture() 调用。这样做便于判断尺寸、颜色格式或数组内容是否正确,也避免后续清屏代码立即覆盖刚显示的图片。
视频采用注释而非删除,因此 u8 i, j; 和 float t = 0; 仍然存在,但它们原来的使用代码都已停用。Keil 最终给出 3 条 #177-D 警告,分别提示 i、j、t 已声明但未引用;这不是图片显示失败的原因。
1.2 代码优化与图片显示
视频把原来第 101~118 行的延时、清屏、文字、数值以及双重循环图片显示代码全部注释,只保留当时位于第 100 行的图片调用。开始时保留的是原有全屏图片:
LCD_ShowPicture(0, 0, 320, 480, gImage_2);
LCD_ShowPicture(x, y, length, width, pic) 中各参数的实际含义是:
| 参数 | 本驱动中的含义 |
|---|---|
x、y | 图片左上角在 LCD 上的坐标 |
length | 水平方向像素数,也就是通常所说的图片宽度 |
width | 垂直方向像素数,也就是通常所说的图片高度;原函数命名容易让人误解 |
pic | 已按显示顺序排好的原始 RGB565 字节数组 |
驱动会设置从 (x, y) 到 (x + length - 1, y + width - 1) 的窗口,并发送 length × width × 2 字节。参数必须同时满足:
[ x+length\le LCD_W,\qquad y+width\le LCD_H ]
而数组的有效字节数至少应为:
[ N=length\times width\times2 ]
1.3 编译与投屏展示
注释其他演示代码后,视频重新编译和烧录,先验证工程能够只显示一张原有图片。此时目标不是修改驱动,而是把 gImage_2 换成自己的图片数组。
配套资料位于第三个专题的 ST7796 模块资料中。本地课程包的准确名称是 03-LCD取模教程.zip,其中包含:
LCD图片取模教程.docx;LCD汉字取模教程.docx;LCD_ASCII码取模教程.docx;Image2Lcd 2.9;PCtoLCD2002。
本节只使用图片取模教程和 Image2Lcd v2.9。汉字与 ASCII 字模不是本课重点。
1.4 解压与打开教程文件
视频把 03-LCD取模教程.zip 解压到当前文件夹,再进入解压后的目录。原笔记写“第三个压缩包”只是在描述网盘中的排列位置;真正辨认资料时应以文件名为准,以免网盘排序变化后选错。
解压后先打开 LCD图片取模教程.docx,再启动 Image2Lcd 2.9(破解版) 文件夹中的 Img2Lcd.exe。课程资料已经自带该工具,本节无需另外联网下载。
1.5 取模教程概览
图片取模的核心是让以下四组参数彼此一致:
- 输入图片的像素尺寸;
- Image2Lcd 实际输出的像素尺寸;
- RGB565 的颜色和字节排列方式;
LCD_ShowPicture()中填写的宽、高及数组名。
其中任何一项不匹配,都可能出现颜色异常、画面错位、图像被拉伸,甚至从数组末尾继续读出无关 Flash 数据。视频连续两次失败,正好说明不能只看软件输入框中的数字,必须检查窗口底部显示的实际输出尺寸和生成数组的长度。
1.6 取模软件介绍与操作
软件的准确名称是 Image2Lcd v2.9,不是原笔记中的 “imagine to LCD”。启动后点击“打开”,选择要转换的输入图片。
该版本的打开对话框按教程使用 BMP 文件,因此视频先把课程提供的图片转成 BMP。需要强调:BMP 只是取模软件的输入格式;生成的是 C 数组,STM32 不会在运行时读取 BMP 文件头、调色板或压缩数据。
最终设置中,软件右侧显示的是转换预览,点击“保存”会出现 Save as C file 对话框,保存类型为 C files (*.c;*.h)。生成内容是普通文本形式的 C 数组,可能被文本编辑器打开,但它不是必须保存成 .txt 文件。
1.7 图片格式转换与取模准备
课程工程 uint3code 目录中的原图实际为 图片1.png。本地文件信息和视频一致:分辨率为 404×529。原笔记把输入笼统写成“当前文件夹中的 PNG”,精校后明确到真实文件和尺寸。
Image2Lcd 的课程操作流程是:
图片1.png → Windows 画图缩放 → 图片2.bmp → Image2Lcd → gImage_bmp4[]
PNG、JPEG、BMP 是文件封装格式;RGB565 是像素颜色编码,二者不是同一层概念。把文件另存为 BMP 并不会自动保证输出数组就是 RGB565,仍需在 Image2Lcd 中选择 16 位真彩色和正确的字节顺序。
1.8 使用画图软件转换图片格式
视频右键原图,选择“打开方式 → 画图”,随后使用“文件 → 另存为 → BMP 图片”。在保存前先从文件属性的“详细信息”页确认原分辨率为 404×529。
屏幕当前为竖屏模式,LCD_W = 320、LCD_H = 480。404×529 超过显示区域,若直接按原尺寸调用显示函数,地址窗口会越过有效坐标;因此必须先缩放。
Windows 画图保存出来的 BMP 可以是软件支持的常见位深,不要求 BMP 文件本身必须是 16 位。真正决定固件数组为 RGB565 的,是 Image2Lcd 的“16 位真彩色”和 R5/G6/B5 设置。原笔记中“另存 BMP 时选择 16 位颜色”的说法与视频操作不符。
1.9 调整图片大小与分辨率
视频在画图中选择“重设大小”,单位改为“像素”,把水平尺寸改为 300,保持纵横比后垂直尺寸为 392。两者都小于当前屏幕的 320×480,因此可以从 (0,0) 显示。
缩放后的像素数为:
[ 300\times392=117600\text{ 个像素} ]
转换为 RGB565 后,每像素 16 位、即 2 字节,因此数组应为:
[ 117600\times2=235200\text{ 字节} ]
这正是最终 gImage_bmp4[235200] 的长度。该数组约占 229.7 KiB Flash,所以旧图片数组不应反复粘贴、同时保持引用;视频也会撤销上一版数组后再粘贴新数据。
1.10 取模软件设置与参数调整
打开 图片2.bmp 后,视频依据教程配置 Image2Lcd。最终有效设置如下:
| 设置项 | 最终值 | 作用 |
|---|---|---|
| 输出数据类型 | C语言数组(*.c) | 生成可粘贴到工程中的字节数组 |
| 扫描模式 | 水平扫描 | 按行排列像素,匹配当前 LCD 地址递增方式 |
| 输出灰度 | 16位真彩色 | 每个像素生成 16 位颜色数据 |
| 颜色位数 | R:5bits, G:6bits, B:5bits | 使用 RGB565,共 65536 种编码 |
| 最大宽度和高度 | 300、392 | 指定期望输出尺寸 |
| 高位在前 | 勾选 MSB First | 每个 RGB565 像素先放高字节,再放低字节 |
| 包含图像头数据 | 不勾选 | 数组只保存原始像素,避免头数据被当成屏幕像素 |
| 其他扫描/反序选项 | 不勾选 | 避免左右、上下或字节顺序被额外翻转 |
“高位在前”尤其重要。上一节已经验证,LCD 的 8 位 SPI 数据路径需要先发送 RGB565 高字节。LCD_ShowPicture() 会按数组原顺序发送,所以取模工具必须预先把每个像素排成高字节、低字节;这样图片不会重现 LCD_Fill() 中红蓝颠倒的问题。
1.11 生成取模数据与保存
输入最大宽度和高度后,还必须点击数字右侧的三角按钮,使设置真正应用到输出图像。判断是否生效不能只看两个输入框,而要看 Image2Lcd 窗口底部:最终必须显示:
输入图像:图片2.bmp (300,392)
输出图像:(300,392)
确认输出尺寸、16 位彩色和高位在前均正确后,再点击“保存”,给生成文件命名。视频先后生成 bmp2、bmp3,最终成功版本是 bmp4,工具据此生成数组名 gImage_bmp4。
生成数组声明旁的元数据注释包含 0x012C 和 0x0188,分别是十六进制的 300 和 392;更直接的校验仍是数组长度:
gImage_bmp4[235200] = 300 × 392 × 2
1.12 将取模数据复制到代码中
保存后,生成的 C 文件以文本形式打开。视频用 Ctrl+A 全选、Ctrl+C 复制,再切换到工程的 pic.h,在头文件保护宏内部粘贴数组。
图片数组应使用 const,使数据作为只读资源存放在程序 Flash,而不是在启动时占用约 230 KiB SRAM。最终工程的声明为:
const unsigned char gImage_bmp4[235200] = { /* 后接完整 RGB565 字节数据 */ };
数组体是取模工具根据图片生成的数据,不需要也不应手工逐字节修改。人工需要核对的是数组名、长度、颜色格式、输出尺寸和主程序调用参数。
1.13 替换数组名与坐标调整
粘贴数组后,视频复制数组标识符,在 main.c 中把原来的 gImage_2 替换为最终的 gImage_bmp4,并把尺寸参数改为 300、392:
LCD_ShowPicture(0, 0, 300, 392, gImage_bmp4);
这里前两个 0 是图片左上角坐标,不是图片尺寸。第三、第四个实参必须和 Image2Lcd 的实际输出尺寸一致。最后一个实参必须与 pic.h 中数组名完全相同,否则会在编译阶段出现未定义标识符。
使用编辑器的 Go To Definition 可以从调用跳到数组定义,这是检查当前调用究竟引用哪一版数据的快捷方法。
1.14 编译与效果验证
最终 bmp4 版本编译、烧录后,LCD 正确显示“码上未来 / CODE WITH YOU”图片。视频构建输出为 0 个错误、3 个警告;这 3 个警告来自已经失去用途的 i、j、t,不是图片数组问题。
构建信息中的 RO-data 为 235580 字节,与图片数组的 235200 字节非常接近,说明绝大多数只读数据空间都被这张图片占用。图片资源会显著增加固件体积和烧录时间,这是把未压缩 RGB565 数据直接存入内部 Flash 的自然结果。
二、问题发现
2.1 例题1: 图片取模设置问题及解决
视频第一次生成的是:
const unsigned char gImage_bmp2[87840] = { /* ... */ };
但 main.c 却调用:
LCD_ShowPicture(0, 0, 300, 392, gImage_bmp2);
87840 ÷ 2 = 43920 个像素,而 183 × 240 = 43920。数组声明旁的元数据也包含 0x00B7(183)和 0x00F0(240),视频中的 Image2Lcd 状态栏明确显示“输出图像:(183,240)”。这证明第一版数组实际只有 183×240,而函数按 300×392 需要读取 235200 字节。
结果是函数在发送完数组的 87840 字节后,还会继续从其后的 Flash 地址读取 147360 字节:
[ 235200-87840=147360\text{ 字节} ]
这是数组越界读取,C 语言行为未定义,屏幕自然可能显示旧图、错位图或杂乱数据。视频随后重新选择 16 位真彩色,并生成 gImage_bmp3,但它仍是 87840 字节,因此问题没有彻底解决。
真正成功的步骤是:选择 16 位真彩色后,把最大宽高设为 300、392,并点击三角按钮应用,使状态栏从 (183,240) 变为 (300,392)。再次保存后得到 gImage_bmp4[235200],数组长度终于和函数参数一致。
2.2 例题2: 图片格式与软件设置问题
排查图片显示问题时,建议依次检查以下不变量:
- 输入文件能被 Image2Lcd 正常打开,课程流程使用 BMP。
- 状态栏输出宽高与
LCD_ShowPicture()的第三、第四个参数一致。 - 数组长度等于
宽 × 高 × 2;不满足时禁止继续烧录验证。 - 输出为 16 位真彩色,颜色位数选择 R5/G6/B5。
- 使用水平扫描,并勾选高位在前
MSB First。 - 不包含图像头数据,因为当前驱动会把数组第一个字节直接当成像素数据。
- 数组名与调用实参一致,旧数组应撤销或删除,避免浪费 Flash。
- 坐标和尺寸不能越过
LCD_W × LCD_H边界。
这比只凭肉眼反复烧录更可靠。尤其是“数组长度 = 宽 × 高 × 2”能够在编译前直接发现视频前两次尝试中的尺寸错误。
2.3 例题3: 图片显示问题
最终图片尺寸为 300×392,而屏幕是 320×480,并且图片从 (0,0) 开始写入。因此只有左上角 117600 个像素被刷新,屏幕总像素数为 153600,还有:
[ 320\times480-300\times392=36000\text{ 个像素} ]
没有被本次图片覆盖。右侧 20 像素宽区域和下方剩余区域可能继续显示此前留在 LCD 显存中的内容,也可能在重新上电后呈现控制器复位后的未定义状态。视频把它称为未刷新的“底色/乱码”,断电再上电后旧内容消失,属于当前只更新局部窗口时的正常现象,并不说明数组末尾还有背景数据。
若希望每次显示结果都确定,应在画局部图片前先全屏清成指定背景色,或者生成恰好 320×480 的整屏图片。课程为了专注验证取模过程,已经把 LCD_Fill() 注释掉,所以本节保留局部区域之外不刷新的现象。