简介:这是一套面向单片机初学者与课程设计、毕业设计学生的轻量级图像格式转换工具,专为解决嵌入式平台图像显示难题而开发:BMP图像结构清晰但体积大,而RGB565仅需2字节/像素,在资源受限的单片机上可显著提升图像加载与显示效率。压缩包共3个文件(7KB),包含核心Python转换脚本(实现BMP头解析、像素数据读取与RGB888→RGB565位域重映射)、依赖清单requirements.txt(确保跨环境可运行)及详尽README.md(含使用示例、参数说明与常见问题)。已有40人学习下载,适合电子类专业学生快速集成图像资源到STM32、ESP32等MCU项目中,无需从零编写图像解析逻辑,直接获得可烧录的RGB565数组或二进制数据,大幅降低GUI界面、OLED/LCD屏显等实践环节的技术门槛。 做单片机项目,想在TFT屏上显示一张BMP图片,第一反应就是找工具把它转成RGB565数组。这个“BMP转RGB565单片机工具.zip”就是干这个的,解压出来一个小软件,把bmp喂进去,出来一个C语言数组,直接复制进工程就能跑。这篇文章就围绕这个工具,从原理、操作、代码到踩坑,完整过一遍,适合正在做单片机显示、或者准备往屏幕上放图片的朋友参考。我先说结论:工具虽然小,但用好的话,能省下大量手动取模的时间,更重要的是能让你彻底搞懂图片在单片机里到底是怎么表达的。
1. 为什么BMP图片不能直接在单片机上用:RGB565产生的技术背景
1.1 BMP格式与单片机存储的冲突
BMP(Bitmap)是Windows时代留下的位图格式,最常见的是24位真彩色。所谓24位,就是每个像素用3个字节表示,分别存红色(R)、绿色(G)、蓝色(B)的亮度值,每个通道的取值范围是0到255。一张320×240分辨率的BMP图片,数据量是多少?320×240×3 = 230400字节,也就是225KB左右。
225KB放在今天的PC上当然不算什么,但放到单片机里就非常尴尬。STM32F103C8T6的Flash只有64KB,连一张320×240的BMP都放不下,更别说还有程序代码和字库。就算是用外部Flash或者SD卡,读取一张24位BMP到屏幕显示,还得处理BMP的文件头、信息头、调色板,边缘有对齐字节,还要应对自顶向下或自底向上的像素存储顺序。对单片机这种资源受限的环境来说,直接解析BMP完全是吃力不讨好。
更重要的是,市面上绝大多数彩色TFT屏幕模块,驱动IC(比如ST7735、ILI9341)原生支持的颜色格式就是RGB565,你告诉屏幕“这个像素是0xF800”,它就知道这是红色;直接给它0x00FF0000这种RGB888三段,反而要先在驱动里做格式转换,既浪费带宽又拖慢刷新。
所以实际项目里通用的做法是:在PC端先把BMP转换成RGB565格式的数组,单片机只负责搬运像素数据到屏幕,不参与任何格式换算。这个转换工具,做的就是把“人看的BMP”变成“单片机直接能喂给屏幕的RGB565字节流”。
1.2 RGB565的编码原理:为什么绿色要占6位
RGB565是一种16位颜色编码,每个像素用两个字节(16位)表示。具体分配是:最高5位给红色,中间6位给绿色,最低5位给蓝色。这就是“565”的由来。
为什么不是5-5-5然后留一位不用,也不是6-6-4?因为人眼对绿色最敏感,人眼的视锥细胞里,感知绿色的细胞数量最多,对绿色光强的分辨率也最高。所以在有限的位数里,绿色多分到1位,整体视觉上的色彩过渡就明显更细腻。这也是行业长期实践出来的折中方案,不只是单片机,很多视频压缩格式和显示面板也是这么干的。
RGB565能表示的总颜色数是2的16次方,也就是65536种颜色,日常显示照片、图标完全够用。从RGB888转RGB565的公式也很简单:
- 红色:取RGB888的高5位,即R565 = (R888 >> 3) & 0x1F
- 绿色:取高6位,G565 = (G888 >> 2) & 0x3F
- 蓝色:取高5位,B565 = (B888 >> 3) & 0x1F
最后拼成16位整数:RGB565 = (R565 << 11) | (G565 << 5) | B565。
如果你用工具转出一张红色渐变的图,你会发现数组里低5位的值从0到31变化,这就是蓝色通道被压缩后的痕迹。转换工具本质上是遍历BMP文件的每一个像素,做这个压缩和移位运算,然后按照屏幕需要的扫描顺序,输出成C语言数组。
1.3 转换工具的核心价值:省事与可批量
有人会说,我自己用Python写个脚本转也行。确实可以,但是对一个只做硬件、不想折腾脚本的人来说,“BMP转RGB565单片机工具.zip”这种小工具的价值在于:打开软件,选图,设参数,点生成,搞定。工具内部帮你处理了BMP文件头解析、像素对齐、颜色深度转换、扫描方向调整、大端小端输出格式选择这些琐碎事。对一个“只想快点在屏幕上看到图片”的开发者来说,这种开箱即用是最高效的。
另外,很多此类工具还支持批量转换,把几十张图片一次性生成一个多张图集成的头文件,这在做菜单界面、图标切换时特别有用。
2. 工具实测:BMP转RGB565的操作流程与参数选择
2.1 解压后的软件组成与运行环境
“BMP转RGB565单片机工具.zip”解压后一般是一个exe文件,可能是单文件,也可能带着一个说明文档。这类工具基本都是Windows下运行的,双击就能打开,不需要安装依赖。我用的版本界面比较简单,左侧是图片预览区域,中间是各种参数设置,右侧是输出代码预览。
有一点要注意,如果你的系统是Win10以后的高分屏,界面可能有点模糊,兼容性设置为“替代高DPI缩放行为”可以解决,这个不影响功能。工具本身不需要专门的驱动或额外库,杀毒软件偶尔会误报,加个白名单就好。
2.2 核心参数怎么设置:分辨率和图片格式
实际操作时,关键参数有这么几个:
- 图片缩放方式:工具通常会提供“直接使用源图分辨率”和“设置最大宽度/高度”两种模式。如果你直接拿一张1920×1080的BMP转进去,生成的数组大得吓人,单片机根本放不下,所以必须设置目标分辨率。比如屏幕是128×160,你就填128×160,工具会帮你缩放。这里建议用“按比例缩放”而不是“强制拉伸”,不然图片会变形。
- 颜色格式:目标必须选RGB565,有的工具会写“RGB565 16位”、“RGB565 高字节在前”等。
- 输出格式:常见的有C数组、C文件、Bin文件。单片机用一般选“C数组”,会生成类似
const unsigned int image[240][320] = {...}或者一维数组。有的工具还能直接输出成 .h 头文件,方便直接#include。
比较关键的是“扫描方式”或者叫“取模方式”,这个决定像素点从哪个角开始排列。后面会说,这里先提一句:如果生成的图像在屏幕上左右颠倒或上下颠倒,回到工具改这个选项,重转即可。
2.3 生成C数组并集成到工程
假设你用的是STM32和Keil或者STM32CubeIDE,生成完数组之后,一般有两种用法:
第一种是工具直接生成一个.h文件,里面有完整的数组定义,比如:
#ifndef _IMAGE_DATA_H #define _IMAGE_DATA_H const unsigned int logo_img[80][80] = { 0x0000, 0x0000, 0x0000, ... }; #endif你把这个头文件放到工程目录下,在屏幕上显示图片的地方#include "image_data.h"就行。注意这个数组带有const,所以会被编译器放到Flash区域,不会占用RAM。如果你没有给数组加const,它会被复制到RAM,STM32F103只有20KB RAM,一张稍微大点的图就会爆掉。
第二种是工具只输出一段数组文本,你得自己新建一个.c文件贴进去。我习惯的做法是单独建images.c和images.h,把所有图片数组都放在images.c里,然后images.h里用extern声明,这样逻辑清晰,多张图片时也方便管理。
2.4 实际测试:把一张BMP火锅底料图显示到TFT上
为了验证工具好用,我做了一次完整测试。拿一张32×32的彩色BMP,转换参数为:RGB565、大端模式、C数组。输出的数组大概是这样的:
const unsigned char img[] = { 0x00, 0x00, 0xF8, 0x00, ... };有的工具输出的是unsigned char数组,两个字节表示一个像素,有的输出unsigned int数组,一个元素表示一个像素。用法上略有差别,后面代码部分我会说明。跟直接用屏幕画点函数相比,用数组显示图片的速度提升了几个量级,因为省去了RGB888到RGB565的转换过程,SPI只需要不断地推数据就行。
3. 单片机端显示RGB565图像的完整代码与硬件接线
3.1 屏幕驱动IC与SPI接线
我这里拿最常见的ST7735 1.8寸TFT屏举例(128×160),屏幕的驱动IC是ST7735S,走SPI接口。接线表很简单:
| 屏幕引脚 | 单片机引脚 | 说明 |
|---|---|---|
| VCC | 3.3V | 电源 |
| GND | GND | 地 |
| CS | PB12 | 片选,低电平有效 |
| RESET | PB13 | 复位,低电平有效 |
| DC/RS | PB14 | 数据/命令选择 |
| SDA | PB15 | SPI主出从入(MOSI) |
| SCL | PB13 | SPI时钟(SCLK) |
如果用硬件SPI,通常还要配置SPI的中断或者DMA,不过初学阶段用软件模拟SPI也能跑,帧率不高但能看。我建议优先用硬件SPI,代码更简洁,速度也更快。
3.2 RGB565数组显示函数
核心显示函数其实就两步:设置窗口区域,然后连续写入像素数据。
以ST7735为例,设置窗口是告诉屏幕“我要在这块矩形区域里写像素”,然后发送像素数据。下面是一个典型的drawImage函数:
void LCD_DrawImage(uint16_t x, uint16_t y, uint16_t w, uint16_t h, const unsigned char *img) { uint32_t i; LCD_SetWindow(x, y, x + w - 1, y + h - 1); // 设置显示窗口 LCD_CS_Clr(); LCD_DC_Set(); // 数据模式 for (i = 0; i < w * h * 2; i++) { LCD_SPI_WriteByte(img[i]); // 逐字节发送 } LCD_CS_Set(); }这个函数假设img数组是按“高字节在前、低字节在后”的方式存放。如果你的工具输出的是uint16_t数组,那么代码里就需要先发送高字节,再发送低字节:
for (i = 0; i < w * h; i++) { LCD_SPI_WriteByte((uint8_t)(img[i] >> 8)); LCD_SPI_WriteByte((uint8_t)(img[i] & 0xFF)); }为什么是这个顺序?因为ST7735的像素数据接口是8位的,你必须按照屏幕IC约定的字节序发送。常见的“RGB565 大端模式”就是高字节先发,比如红色0xF800,拆成0xF8和0x00,先发0xF8,再发0x00。如果你发现颜色不对,红色显示成蓝色,那就把高位低位反一下再发。
3.3 用DMA传输提高刷新率
上面那个函数用SPI逐字节发送,如果图片只有32×32,问题不大,但如果全屏128×160,那就是20480个像素,每次要发送40960字节。用普通的SPI阻塞发送,大概要几十毫秒,肉眼能明显感觉从上到下慢慢刷出来,撕帧感很强。
更好的做法是用SPI的DMA传输。发送数据的循环交给DMA,CPU可以去做别的。下面是一个简单示意:
void LCD_DrawImage_DMA(uint16_t x, uint16_t y, uint16_t w, uint16_t h, const unsigned char *img) { LCD_SetWindow(x, y, x + w - 1, y + h - 1); LCD_CS_Clr(); LCD_DC_Set(); HAL_SPI_Transmit_DMA(&hspi1, (uint8_t *)img, w * h * 2); HAL_Delay(1); // 简易等待,实际可以使用DMA完成中断标志 LCD_CS_Set(); }这里有个坑:DMA传输很快,但不能在最后一次传输完成前拉高CS,否则数据会丢。严谨的做法是注册DMA传输完成回调,在回调里拉高CS。初学者可能图省事用HAL_Delay,但实际速度优势会被强行拖慢。真正的做法应该是:
volatile uint8_t dma_tx_done = 0; void HAL_SPI_TxCpltCallback(SPI_HandleTypeDef *hspi) { if (hspi == &hspi1) { dma_tx_done = 1; } }在发送前清零标志,发送后等待标志置1,再置高CS。这样安全且快。不过,如果工具生成的数组是const存在Flash里,DMA发送Flash数据是没问题的,因为Flash和RAM都是总线可访问的。如果数组是零时生成的动态数据,要保证数据在DMA传输结束前不被释放。
3.4 显示坐标与图片裁剪
显示函数带的x、y参数是图片在屏幕上的起始坐标。如果你想显示多张图片拼接成界面,每次调用时传入不同的起始坐标就行。这里要注意的是,LCD_SetWindow内部有坐标范围限制,如果超了屏幕最大坐标,驱动IC可能会忽略或者异常,所以图片大小和坐标要提前算好。
有的工具生成数组时不带透明通道,如果是做UI图标,很多朋友希望“去掉背景色”露出屏幕底色。这时可以在显示函数里加一个“透明色跳过”逻辑:如果某个像素值等于你指定的背景色(比如0xFFFF白色),就发送和背景一样的颜色,从效果看就像透明了。这个做法会稍微拖慢速度,但做菜单常用。
4. 最容易翻车的五个细节:取模方向、大小端、分辨率与空间
4.1 取模方向不对导致图像翻转或镜像
这是使用这类工具最容易踩的坑,排到的概率极大。BMP文件本身有一个特点:像素数据通常是从图片的最后一行开始存储的,也就是说BMP文件里第一行数据对应的是图片的最下面一行,这叫“自底向上存储”(bottom-up)。如果你在Windows上用画图存了一张32×32的BMP,刚好它的数据是从最后还是从开头,和具体位深度、压缩方式都有关,很多BMP格式的存储顺序确实是从左下角到右上角。
而TFT屏幕的坐标是左上角为原点,x向右增加,y向下增加。所以如果工具不处理这个顺序,你直接拿BMP像素顺序去显示,图片会上下颠倒。
解决的方法是在工具里找“取模方向”或“扫描顺序”选项。常见的有“水平扫描、垂直扫描”“自左向右、自下向上”等。如果显示出来上下颠倒,就把扫描顺序改成“自顶向下”;如果左右颠倒,就设置水平反向。用“BMP转RGB565单片机工具”有一点好,它会在代码预览旁边生成一个小预览图,你翻转后可以直观地看到当前坐标朝向。没有预览的工具,就只能烧录测试了。
我个人经验:生成前先选一个“左上角起点”的扫描方向,测试图是张有明显上下结构、左右对称的图,比如字母L或者一个箭头,一眼就能看出来方向对不对。别用纯色或对称图案做测试图,不然翻了也看不出来。
4.2 大小端模式导致颜色错乱
RGB565的16位数据在传输时有两个字节。如果你生成的是“大端模式”,数组中每个像素的高字节在前,低字节在后;如果是“小端模式”,则反过来。
单片机侧如果按字节发送,比如上面的LCD_DrawImage直接逐字节发img数组,那么工具输出是什么顺序,屏幕就按什么顺序解释。如果工具是“大端”,而你的屏幕IC内部默认“小端”,或者你的显示函数用了uint16_t按16位发且SPI是8位模式,颜色就会出现R/B互换、偏色很怪的现象。
判断偏色的规律通常是:
- 红色变成蓝色,蓝色变成红色——典型的R/B通道交换,说明字节序反了。
- 颜色整体发绿或者发暗——可能是RGB565的位序不对,比如你把第5位和最后一个字节搞混了。
- 图片像打了马赛克,有明显的竖条纹——说明扫描方向和窗口大小不匹配。
遇到颜色不对,第一件事不是去调屏幕驱动,而是把数组里的某个像素手动对照一下。比如生成一张左上角一个红色像素的图,数组中那个像素的十六进制应该是0xF800(大端字节序就是 0xF8 0x00)。如果看到第一像素是0x00F8,那高低位反了,写个循环交换一下,或者重新在工具里选择大小端。
4.3 分辨率超出屏幕显示范围
有时候你只是想把屏幕显示区域的一小块刷一张图,但工具默认按源图比例缩放,导致转换后的图片分辨率不是整数,代码里的w、h传的参数跟数组实际宽度不一致,屏幕就会花屏。
我建议转图前先明确目标屏幕尺寸。如果你的屏幕是128×160,想显示一张32×32的图标,直接在工具里把“目标宽度”设为32,“目标高度”设为32,不要用百分比。如果工具支持自定义缩放算法,选“最近邻”还是“双线性”会影响清晰度。对于小分辨率的单片机屏幕,最近邻缩放能保留锐利边缘,双线性缩放会让小字变模糊。这里没有绝对好坏,看具体用途。显示照片用双线性更平滑,显示文字图标用最近邻更清晰。
4.4 位图深度和压缩格式不支持
“BMP转RGB565单片机工具”这类小工具一般只支持24位非压缩BMP。如果你在Windows画图里“另存为BMP”,它默认是24位;但如果你用截图工具或者PS存的是32位带透明通道的BMP,或者干脆是8位索引色BMP,很多工具会直接报错“文件格式不支持”。
解决办法是:先检查源图格式。在Windows资源管理器里看属性,点“详细信息”,看位深度。如果不是24位或32位,用画图打开后重新另存为“24位位图”。截图的PNG想转为BMP?直接用画图打开PNG,另存BMP即可。当然有些工具也支持PNG输入,但为了稳定,建议先转成24位BMP再喂给工具。
如果是压缩过的BMP(RLE压缩),小工具可能解析错误,Windows画图保存的BMP都是不压缩的,所以不常遇到。但用过一些在线压缩工具导出的BMP,就得注意了。
4.5 烧录空间估算:一张图能吃掉多少Flash
很多人在电脑边转完图,直接复制数组到工程,编译时发现Flash超了。其实数组大小很好估算:一张W×H的RGB565图片,占用字节数是 W × H × 2。
- 32×32的图标:2048字节,很小。
- 128×160全屏图:40960字节,约40KB。
- 320×240全屏图:153600字节,约150KB。
如果你的单片机是STM32F103C8T6(64KB Flash),放满128×160全屏图(40KB)加上基本的驱动代码(10KB以上),就已经很紧了,再来一张就完蛋。更别说51单片机,很多只有8KB Flash,连一张像样的图都放不下。
所以在工程规划阶段就要算好:程序本身需要多少Flash,能留给图片多少。如果不够,有几个思路:
- 降低分辨率显示,比如只显示一个区域不全屏。
- 在运行时从SD卡读取图片数据,缺点是速度慢,并且需要文件系统。
- 把图片压缩成JPEG或PNG放在外部Flash或者SD卡,显示时解码,但这对单片机的运算压力很大,需要专门的解码库,不推荐新手做。
对大多数项目来说,宁可牺牲图片数量,也不要让Flash掉进去。工具生成数组时,尽量使用高质量且简洁的代码,避免不必要的调试信息。
4.6 一个隐藏的坑:PIL/Pillow保存的BMP行对齐
这个坑即使你后面的脚本方案也会遇到,但现成工具一般已经帮你处理了,不过为了深入理解还是值得提一下。BMP文件有个特性:每一行的数据字节数必须对齐到4字节。比如一张宽度为25像素的24位BMP,每行原始数据是25×3=75字节,但BMP会填充一个字节变成76字节(使行大小为4的倍数)。转换工具如果没跳过填充字节,生成的数组就会从第25个像素开始出现错位,图像整体向右下方偏移,颜色像彩虹一样花掉。好工具都会处理这层填充,所以用工具转图片时基本不操心。如果你自己写脚本或者打开BMP文件手动解析,这个对齐字节一定要记得跳过。
我见过某个工具因为行对齐没处理好,生成的数组一放到大屏幕上就出现规律的斜条纹。最后就是逐行Debug发现行末尾多了填充。这也是我们后面要自己写脚本时必须处理的细节,读者如果遇到类似花屏,记得检查BMP行对齐。
5. 从复制数组到批量成阵:自写脚本生成多张图片的C头文件
5.1 为什么还要自己写脚本
“BMP转RGB565单片机工具.zip”面对单张图很方便,但项目做到后面,比如做菜单界面时要切换十几张图标,每张都靠鼠标点选、另存、复制代码,不仅慢还容易出错。另一个痛点是很多现成工具不支持“自动缩放并居中”,也没有提供“生成坐标结构体”之类的功能。
这时候自己写一个转换脚本就很有必要了。不用太复杂,用Python加Pillow库就能轻松替代大多数GUI工具。脚本的优势是:批量处理、可以嵌入代码生成流程、支持自定义输出格式。
5.2 Python核心代码:BMP转RGB565并输出C数组
下面给一个可用的Python脚本,它能把目录下的所有BMP/PNG/JPG统一缩放至指定宽高,并生成一个包含所有图片的.h文件。
from PIL import Image import os def rgb888_to_rgb565(r, g, b): return ((r & 0xF8) << 8) | ((g & 0xFC) << 3) | (b >> 3) def convert_image(path, target_w, target_h): img = Image.open(path).convert("RGB") img = img.resize((target_w, target_h), Image.LANCZOS) # 高质量缩放 width, height = img.size pixels = list(img.getdata()) data = [] for i in range(height): for j in range(width): r, g, b = pixels[i * width + j] data.append(rgb888_to_rgb565(r, g, b)) return width, height, data def generate_header(image_files, target_w, target_h, output_path): with open(output_path, "w", encoding="utf-8") as f: f.write("#ifndef _IMAGES_DATA_H\n") f.write("#define _IMAGES_DATA_H\n\n") for idx, img_file in enumerate(image_files): w, h, data = convert_image(img_file, target_w, target_h) f.write(f"const unsigned int img_{idx}[{h}][{w}] = {{\n") for y in range(h): line = "" for x in range(w): line += f"0x{data[y*w+x]:04X}, " f.write(" " + line + "\n") f.write("};\n\n") f.write("#endif\n") # 使用时填写你的图片路径列表 if __name__ == "__main__": images = ["icon1.bmp", "icon2.bmp", "icon3.png"] generate_header(images, 32, 32, "image_data.h")这段脚本有几个点值得说明:
rgb888_to_rgb565函数用掩码法,与工具内部的处理方式一致。Image.LANCZOS缩放质量比NEAREST好,适合照片和图标。- 输出为二维数组
[高][宽],直观且方便显示函数索引。 - 文件名列表可以改成
os.listdir批量扫描目录,自动识别图片扩展名。
这个脚本默认生成的是uint16_t风格的数组,但以unsigned int存储,注意在你的单片机上unsigned int是16位还是32位。在C51里,int是16位,刚刚好;在STM32里,int是32位,但数组元素按int存会浪费Flash空间,因为0xFFFF本来只用2字节,你按4字节存会白白多出一倍体积。所以如果用在ARM类单片机上,最好把输出改成unsigned short或者uint16_t:
const uint16_t img_0[32][32] = { ... };同时显示函数也要对应改成const uint16_t *img。
5.3 两种方案的取舍:何时用工具,何时用脚本
给个建议:
- 使用工具:临时用一次、电脑上没有Python环境、想快速预览结果。对小白来说,GUI工具上手零成本,不容易出错。
- 使用脚本:需要批量多张图、图片尺寸需要统一、输出格式要定制、需要自动生成文件名和坐标表。项目中期之后,脚本的效率优势非常明显。
我个人现在做项目,初期会用GUI工具验证图片方向,后面批量出图都是用脚本,因为脚本可以把“转图”和“生成代码”合并到一步,还不会漏图。
6. 工具之外的真实经验:让图像在单片机上跑得更顺的几点建议
6.1 先分类明白,再谈显示
转RGB565前,先把图像按用途分类:
- 全彩色照片类:适合RGB565,色彩尽可能丰富,内存代价高。
- 图标按钮类:很多是单色或者少量颜色,用RGB565也能做,但如果你只用黑、白、红三色,可以考虑转成1位色深或调色板,体积缩小到1/16。
- 文字类:不建议转图片,直接使用字库数组,清晰又省资源。
工具只负责“把BMP变成RGB565”,没有帮你做图片抠图、配色、压缩这些事。所以最好的流程是:先在PC上用图像编辑工具处理好图片,再交给转换工具或者脚本。
6.2 关于取模方向的个人建议
在做界面时,我倾向于在工具里统一采用“从左上角开始,横向从左到右,纵向从上到下”的扫描方式,也就是俗称的“水平扫描、左上起点”。这样生成的数组顺序和屏幕坐标完全一致,调试时看着不容易晕。如果用了“垂直扫描”,也就是从上到下、再从左到右,显示函数里的坐标映射逻辑会变得非常绕,除非你屏幕硬件本身是竖屏或者特殊旋转,否则不建议用非水平扫描。
6.3 使用多个图片时的命名与索引约定
当你转完10张图,生成的数组名是乱糟糟的img_0、img_1,显示代码里很快就会失控。建议在工具里或脚本里,将生成数组名和图片语义关联起来。比如:
#define IMG_LOGO_INDEX 0 #define IMG_MENU_INDEX 1 #define IMG_BACK_INDEX 2然后建立一个索引表:
const unsigned int *const image_table[] = { img_0, img_1, img_2 };显示时通过索引访问:
LCD_DrawImage(0, 0, 32, 32, (const uint8_t *)image_table[IMG_LOGO_INDEX]);这样以后换图、加图,就不用来回改代码里数组名了。工具没做这个,你自己补上,能省不少事。
6.4 抗锯齿和透明背景的处理
彩色图片在浅色背景上常常显出一条深色边框,这是因为图片在缩放时产生了残留的半透明像素。尤其是从截图转出来的小图标。解决办法是在转图前处理干净,把背景统一成纯色,或者用Pillow加一个“去黑边”步骤:把接近透明的像素的RGB值替换成目标背景色。对于RGB565而言,没有alpha通道,所以类似“阴影”“磨砂”效果很多情况下都得放弃,硬做只会显得脏。
如果确实需要透明效果,一个替代方案是:在显示图片前用“颜色键”把特定颜色当作透明。这个“透明色”最好不是图片中实际使用的颜色,常见选0x07E0(纯绿),但正式产品里更多人选黑色或洋红色作为色键。单片机上实现透明显示时,逐像素判断,效率会明显下降,所以只适合小尺寸图标。
6.5 关于屏幕选择和刷新率的实际感受
如果你的项目要高频更新全屏图像,比如做动画、视频播放,那老老实实选带显存且支持RGB接口或者MIPI DSI的高分辨率屏幕模块,而不是SPI屏。SPI屏幕哪怕能用DMA,全屏刷新一帧也要几十毫秒,帧率根本跑不上去。用RGB565数组显示静态菜单、开机logo是完全够用的,但别指望它做流畅动画。定位清楚,工具选得就越准,你也不会在SPI屏上白白浪费时间。
7. 最后补充一点:工具适配的常见单片机平台
“BMP转RGB565单片机工具.zip”不挑具体MCU,它生成的数组,在STM32、GD32、新唐、NXP、51单片机、ESP32上都能用,前提是屏幕驱动IC接受RGB565数据。最常见的组合就是51单片机挂ST7735、ILI9341,或者STM32挂SSD1963等大屏。数组是没有平台转换概念的——它只是一段数据,由你的驱动代码决定怎么送到屏幕。
不过要留意,51单片机的C编译器(Keil C51)中,默认int是16位,所以用uint16_t数组非常合适。但如果屏幕驱动是你自己写的,且你用的MCU是16位的Hi-Tech PICC,或8位AVR,一定要确认数组定义方式与编译器匹配。举个例子,在Keil C51里,数组上限和内存限制比ARM平台严苛,太大的数组会编译报错,尤其不要把图片数组定义成局部变量,一定要用code关键字放到程序存储区。常规工具有些会把数组输出为const unsigned char image[],这样在C51里需要加code修饰符才能放到Flash,否则默认放RAM,51的RAM往往只有256字节,肯定装不下。
我看到过有人问“为什么工具生成的数组编译时RAM溢出”,多半就是老51编译器没有自动把const数组分配到code区。这种情况下,手动改数组声明为:
code unsigned char image[] = { ... };就能解决。
8. 动手实践:用最小系统做个图片显示Demo
最后给一个完整的最小演示路径,方便你跑通整个流程:
- 准备一张彩色图片,最好控制在96×96以内,方便调试。
- 用Windows画图打开,另存为24位BMP。
- 打开“BMP转RGB565单片机工具.zip”解压出的exe,加载图片。
- 设置目标宽度96,目标高度96,颜色格式RGB565,字节序大端,扫描方式为水平左上。
- 生成代码,复制数组到你的工程。
- 调用3.2节中的绘制函数,传入起点坐标和宽高。
- 下载到单片机,观察显示效果。如果上下颠倒,回到工具改“扫描方向”为竖直反向;如果颜色异常,调整大小端设置。
- 一切正常后,再用脚本方式批量生成你需要的其他图片。
这个方法我从大学参加电子设计竞赛开始,一直用到现在,已经成了“肌肉记忆”。每次换屏幕、换MCU平台,路径都一模一样。你只要跟着走一次,基本就能掌握。
如果你手头没有“BMP转RGB565单片机工具.zip”,也可以用免费的取模软件替代,市面上还能找到像Image2Lcd、WinHex配合手工提取,但那些要么界面老旧,要么流程繁琐。相比之下,这个极简zip工具胜在直接和聚焦,没有广告,也没有一堆用不上的高级功能。把图片拖进去,选几个必要参数,点按钮,代码就出来了,这本身就解决了“把图放到单片机屏幕”这个具体问题。在做单片机显示的路上,它会是一个非常顺手的起点。
本文还有配套的精品资源,点击获取