
最先开始做彩屏项目时我花了整整一晚上折腾一个 128x160 的小屏幕所有代码都写完了图片就是不显示。后来才发现问题根本不在驱动而是图片转 RGB565 数组这一步没做对。也就是从那时候起Bmp2RGB 这个工具成了我电脑里常年不删的小插件。如果你也正在做 TFT-LCD、小型游戏机、嵌入式 GUI 这类彩屏项目早晚会碰到同一件事怎么把一张图片转成 C 语言数组塞进单片机里。这篇就围绕 Bmp2RGB 工具把 16 位 RGB565 数组的生成、校验、接入方法和常见问题完整过一遍帮你少走几小时弯路。1. RGB565 格式背后的原理与选型逻辑1.1 为什么屏幕上的像素要精简成 16 位电脑上常见的彩图是 RGB888也叫 24 位真彩色一个像素由红、绿、蓝三个通道组成每个通道占 8 位取值 0 到 255。这种方式显示效果好但对单片机来说却非常奢侈。算一笔账就很清楚320x240 分辨率的屏幕如果用 RGB888 刷一帧完整画面需要的字节数是 320 乘以 240 再乘以 3等于 230400 字节约 225KB。很多入门级 MCU 的 RAM 总共才 20KB 到 64KB连一帧画面都装不下。RGB565 把每个像素压缩到 2 字节同样的分辨率下一帧数据降到约 150KB存储压力和传输带宽都砍掉三分之一。人眼对颜色的分辨率其实没有想象中那么高。尤其是 2.4 寸、3.5 寸这种小屏16 位色深和 24 位色深带来的视觉差异很小除非显示细腻的渐变色或照片否则肉眼几乎分不出来。这也是为什么几乎所有中低端 TFT-LCD 屏幕都默认走 RGB565 的原因。1.2 位与位之间是怎么安排的RGB565 这个名字直接暴露了位分配方式红色取高 5 位绿色取高 6 位蓝色取高 5 位加起来正好 16 位。从 24 位颜色值转到 RGB565 的公式是固定的uint16_t RGB888_TO_RGB565(uint8_t r, uint8_t g, uint8_t b) { uint16_t r5 r 3; // 高5位 uint16_t g6 g 2; // 高6位 uint16_t b5 b 3; // 高5位 return (r5 11) | (g6 5) | b5; }这里有个细节值得注意绿色比红蓝多了 1 位不是因为绿光更重要而是人眼对绿色最敏感多给绿色 1 位能显著减少色彩断层。你在看调色板时如果发现屏幕偏绿能力最强就是这个原因。几个典型颜色可以自己验证一下改成常量之后正好是像素值的经典写法颜色RGB888RGB565 值纯红0xFF00000xF800纯绿0x00FF000x07E0纯蓝0x0000FF0x001F白色0xFFFFFF0xFFFF黑色0x0000000x00001.3 RGB565、RGB555、RGB888 怎么选不少新手看到 RGB555 也会犯迷糊以为和 RGB565 只是差 1 位关系不大。实际上 RGB555 的 16 位中只有 15 位有效最高位要么空着要么用作透明标志颜色数量比 RGB565 少一半。格式位深每像素字节红色位数绿色位数蓝色位数常见场景RGB88824 位3888电脑显示、图像处理RGB56516 位2565TFT-LCD、单片机 GUIRGB55515 位2555老式显卡、部分 LCD结论很简单对绝大多数单片机彩屏项目来说直接用 RGB565别犹豫。除非你用的屏幕控制器或驱动库明确指定 RGB555否则 RGB565 就是默认最优解。2. 转换前必须完成的图片预处理2.1 先把图片缩放到 LCD 分辨率很多人在这一步偷懒直接在工具里把大图转出来结果不是变形就是锯齿严重。Bmp2RGB 这类工具的核心功能是格式转换不是图像处理它内部的缩放逻辑很简单指望它把 1920x1080 的照片缩到 128x160 还能保持清晰度基本不可能。正确做法是先准备图像素材。Windows 自带的画图其实够用了打开图片后用调整大小功能把像素改成目标分辨率比如 128x160。注意画图的缩放默认是保持纵横比的如果原图比例和屏幕不一致先裁剪再缩放别直接拉伸否则人物或者图标看上去像被压扁了似的。如果你需要批量处理图片可以用 ImageMagick一条命令搞定magick input.png -resize 128x160! -type TrueColor output.bmp注意命令里的感叹号表示强制拉伸为 128x160不用感叹号则是等比缩放。2.2 保存成 24 位 BMP避开压缩和调色板Bmp2RGB 这个工具叫 Bmp2RGB第一步就限定了输入格式只能处理 BMP。这一点最容易被忽略很多人拿 PNG 或 JPG 直接改扩展名为 .bmp然后抱怨工具打不开。改扩展名只是改了文件名文件内部格式没变工具照样读不了。保存 BMP 时还要注意位深。Windows 画图里另存为 BMP 图片时会弹出位深度选项常见的有 24 位位图、256 色位图、16 色位图和单色位图。一定要选 24 位位图。256 色位图带调色板Bmp2RGB 需要额外解析调色板容易出错16 色和单色就更不用说了颜色信息严重不足。还有一个隐藏坑是 RLE 压缩。BMP 标准里存在一种用游程编码压缩的 BMP 格式主要在 Windows 3.x 时代出现。如果遇到 Bmp2RGB 打不开、或者打开后画面花成一团的情况先别怀疑工具坏了把图片重新另存为不带压缩的 24 位 BMP 再试。2.3 检查图片方向与 BGR 存储顺序BMP 文件内部存储像素时颜色顺序是 BGR 而不是 RGB而且第一行数据对应的是图片底部不是顶部。这两个特性很容易让新手在后续步骤里遇到颜色不对图像颠倒的问题。关于颜色顺序大多数转换工具会自动处理。但不同版本的 Bmp2RGB 行为不一致有的工具默认不交换 R 和 B需要手动勾选。所以我在预处理阶段建议你先做一张纯红色的测试图比如 32x32 的纯红色 BMP用它走一遍完整流程看最终显示出来是不是纯红。如果是红蓝互换再去工具里找颜色交换选项这比直接拿复杂图片测试高效得多。图片方向问题也一样工具里通常有扫描方向选项后面会专门讲。预处理阶段只需要做一件事明确你的屏幕上画面是从上往下还是从下往上这个和 LCD 控制器的扫描方向有关不同屏幕差异很大最好翻一下手头屏幕的数据手册确认。3. Bmp2RGB 工具参数逐项设置与实际操作3.1 Bmp2RGB 能做什么它是不是万能工具Bmp2RGB 是一个非常小的绿色软件不需要安装打开就能用。它的核心能力就是读取 BMP 图片按你指定的格式输出一个 C 语言数组。它不擅长图像缩放不擅长特效处理唯一擅长的就是把像素数据精确地翻译成数组。同类工具还有 Img2Lcd、LCD Image Converter 等参数逻辑基本一致。如果你拿到的 Bmp2RGB 版本界面和我描述的有细微差别不用慌核心参数含义不会变。我以最常见版本为例把每个参数的设置原则讲清楚你就知道到了一个新工具面前该怎么看参数。3.2 核心参数表与设置建议Bmp2RGB 的参数界面看起来复杂实际上只需要关注下面这几项参数推荐设置设置理由输出类型C 语言数组方便直接复制进工程选纯二进制则无法识别颜色格式RGB565与 16 位真彩屏匹配最常用最大宽度屏幕宽度与 LCD 分辨率一致如 128最大高度屏幕高度与 LCD 分辨率一致如 160扫描方向水平扫描或垂直扫描取决于 LCD 控制器的扫描顺序行对齐4 字节或 1 字节建议 1 字节否则宽度不满足会填充无效字节数组类型unsigned char 或 unsigned short避免 char 类型有符号导致数据被判负数输出文件名自定义如 image.h方便工程管理这些参数里最影响显示效果的是扫描方向。BMP 数据第一行是图片的顶部但 LCD 屏幕的扫描起点不一定是左上角。有些屏幕从右上角开始扫有些从右下角。选错扫描方向最常见的表现就是上下颠倒、左右镜像严重情况下会出现蛇形走位式的错乱画面。行对齐的问题也常被忽略。BMP 标准规定每一行的字节数必须是 4 的倍数如果你的图片宽度是 126 像素RGB565 每像素 2 字节每行就是 252 字节252 对 4 取余正好是 0没问题。但如果是 125 像素每行 250 字节250 除以 4 余 2BMP 文件就会在每行末尾补 2 个无效字节。工具如果默认按 4 字节对齐数组里就会混入这些垃圾数据显示时画面就会每隔一行错开一点。3.3 实操流程五步走以 Bmp2RGB 为例完整转换流程可以拆成五步第一步打开工具载入提前处理好的 BMP 文件。这一步如果提示格式错误回到上一节检查图片是不是 24 位位图。第二步按上面的表格把参数设好。颜色格式选 RGB565最大宽度和高度填屏幕分辨率扫描方向先选水平扫描。第三步点击输出或者转换按钮生成 C 语言文件。生成的文件通常是一个 .h 头文件里面是一个大数组。第四步用文本编辑器打开生成的 .h 文件确认数组确实存在且数组元素值不是乱码或全 0。如果编辑器打开后是乱码检查输出类型是不是选成了二进制文件。第五步做合法性校验。用数组长度除以图片分辨率再除以 2应该得到 1。比如 128x160 的图数组字节数应该是 128×160×240960。如果长度不对说明工具设置有问题不要急着接入代码。生成后的数组大概是这个样子// 128x160 RGB565 图片共 40960 字节 const unsigned char image_data[40960] { 0x00, 0xF8, 0x00, 0xF8, 0xE0, 0x07, 0x1F, 0x00, // ... 后边还有上万行 };看完这个数组你会明白手写这些数据完全不现实工具的价值在这里体现得淋漓尽致。4. 数组接入单片机及显示验证的完整链路4.1 把数组封装到 Flash 而不是 RAM生成好的数组放到工程里首先要定义一个足够大的存储空间。这里最关键的认知是图片数据是常量应该放进 Flash而不是 RAM。以 STM32 为例如果你写const unsigned char image_data[40960] { ... };编译器会把这段数据放在 Flash 区不占 RAM。但如果漏掉 constunsigned char image_data[40960] { ... };编译器会试图把它放进 RAM对于内部 RAM 只有 20KB 的芯片直接编译不过。数组定义方式有两种主流习惯。一种是直接用 unsigned char 数组每个像素占 2 字节方便按字节写入 LCD另一种是用 unsigned short 数组每个元素直接代表一个 RGB565 颜色值代码可读性更好。// 方式一字节数组 const unsigned char image_byte[40960] { 0x00, 0xF8, 0xE0, 0x07 }; // 方式二16位数组 const uint16_t image_word[20480] { 0xF800, 0x07E0 };两种方式在刷图时的写法略有不同差异点主要在字节序上后面排查部分会展开。4.2 LCD 底层刷图与临时屏显验证拿到数组后第一步不是往 UI 框架里塞而是先写一个临时的验证函数确保底层能够正确刷图。以常见的 ST7735 或 ILI9341 控制器为例刷一张完整的 RGB565 图片核心操作是两步设置显示窗口然后连续写入像素数据。void LCD_DrawImage(uint16_t x, uint16_t y, uint16_t w, uint16_t h, const uint16_t *img) { LCD_SetWindow(x, y, x w - 1, y h - 1); for (uint32_t i 0; i w * h; i) { LCD_WriteData(img[i]); } LCD_SetWindow(0, 0, LCD_WIDTH - 1, LCD_HEIGHT - 1); }这里有个容易出错的地方调用完显示窗口设置后如果后续还要画其他内容必须把显示窗口重置为整个屏幕范围否则后续的绘制全都局限在刚才的窗口里显示的内容会错乱。写 UI 程序时这个问题特别隐蔽因为单张图片显示是正常的一旦连续绘制两个控件就会出问题。如果你的图片数组是 unsigned char 类型刷图时要自己拼像素void LCD_DrawImageByte(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); for (uint32_t i 0; i w * h; i) { uint16_t color (img[i * 2] 8) | img[i * 2 1]; LCD_WriteData(color); } LCD_SetWindow(0, 0, LCD_WIDTH - 1, LCD_HEIGHT - 1); }这段代码里的 img[i * 2] 是高字节还是低字节取决于工具生成数组时的字节序。如果你把大小端搞反画面就会出现红蓝互换、颜色整体偏色的现象。4.3 先拿纯色图踩点再上复杂图很多初学者喜欢直接拿一张照片或者一张游戏截图去验证刷图流程结果屏幕上一片花哨的乱码根本分不清是工具参数问题、数组字节序问题还是 LCD 驱动时序问题。这种排错方式效率极低。我的建议很简单在画图工具里生成一张纯红色 128x160 的 BMP用同样的参数转成数组刷到屏幕上。理想情况下屏幕应该是纯红色。如果没有显示出红色但能看到有明显的色块变化说明刷图流程基本通了问题出在颜色格式或字节序上。如果连色块都没有屏幕全黑或者只亮一部分说明显示窗口设置、图片尺寸或者数组长度有严重问题。纯绿色和纯蓝色也可以做类似的验证。三张测试图都通过了再去转真实图片成功率会高很多。这个方法我几乎在每个彩屏项目里都会用它能把一个大问题拆成几个独立的小问题逐个击破大幅降低排错难度。5. 高发问题排查从花屏偏色到编译失败5.1 花屏与错位先看扫描方向和行对齐花屏是图片显示中出现概率最高的问题表现形式也五花八门大致可以分为三类。第一类是上下颠倒或左右镜像通常是因为 BMP 存储方向与 LCD 扫描方向不一致。BMP 文件里第一行是图片顶部但你的屏幕可能是从底部开始扫描的。解决办法是在 Bmp2RGB 工具的扫描方向参数里切换水平扫描和垂直扫描或者在画图工具里把图片翻转后再转换。第二类是斜切式错行画面看起来像每隔一行就偏移了若干像素图像整体呈锯齿状。这大概率是 BMP 行对齐的锅。前面提到BMP 每行字节数要 4 的倍数如果图片宽度不满足每行末尾会有填充字节。解决方法是把图片宽度改成 4 的整数倍或者调整工具里的对齐选项图像宽度尽量用 120、128、160、240、320 这些常见分辨率。第三类是局部花屏比如左上角正常右侧和底部有杂色条纹。这种往往是显示窗口设置错了检查调用 LCD_SetWindow 时传入的宽度和高度是否与数组实际分辨率一致。比如数组是 128x160但窗口设置成了 120x160屏幕右侧就会拼接上不该显示的显存数据看起来像花屏。5.2 偏色与红蓝互换RGB 与 BGR、字节序的问题对比花屏来说偏色问题更隐蔽因为图片内容能看清只是颜色不对很多人会误以为是屏幕本身偏色。常见的偏色有两种一种是大面积红蓝互换比如红色草地、绿色天空。这是因为 Bmp2RGB 没有正确执行 BGR 到 RGB 的通道交换或者你的 LCD 控制器像素格式被配置成了 RGB666 等特殊模式。解决方法是到工具设置里找 Swap R/B、RGB/BGR 切换的选项打开重新生成数组。另一种是整体色调偏暗特别是渐变色区域有明显色阶断裂。这个大概率是 RGB565 和 RGB555 混用导致的。如果你生成数组时选了 RGB555但 LCD 初始化配置用的是 RGB565那么每个像素的最高位被丢弃颜色等级直接少一半画面自然偏暗。反过来配置也会出现类似问题。检查 LCD 初始化代码ILI9341 这类控制器一般有一个像素格式寄存器0x3A 写成 0x55 代表 16 位0x66 代表 18 位务必确认和你生成的数组格式一致。字节序的问题也要单独说一下。如果你的数组是 unsigned char 类型那么 0xF800 这个红色像素在大端格式下存储为 0xF8 0x00在小端格式下存储为 0x00 0xF8。两者没有谁对谁错关键是要跟 LCD 写入数据的顺序匹配。遇到红蓝互换以外的偏色比如黄色和青色互换先检查你的拼像素代码是高字节在前还是低字节在前。5.3 编译报错数组太大、Flash 放不下接入工程后最常见的两个编译错误一个是 region FLASH overflowed一个是 relocation truncated to fit核心都是同一个问题图片数组太大单片机存不下。遇到这种情况先确认数组定义有没有加 const。如果加了 const 还是超容量说明这张图超出了芯片 Flash 的剩余空间。我自己常用的处理手段是问题原因解决手段图片分辨率过大缩小到屏幕实际分辨率不额外放大颜色格式浪费确认生成的是 RGB565不是 RGB888 形式单图数据量T 华语把大图拆成多个小图需要时再显示不同时占 FlashFlash 实在不够改用外部 SPI Flash 或 SD 卡存储图片运行时读图RAM 占用过大确认数组定义有 const且 LCD 显示用 DMA 减少 CPU 搬运我曾经在一个只有 64KB Flash 的芯片上放过一张 240x320 的图片一张图占掉 150KB明显放不下。后来把图片拆成四块每块 120x160大小约 37.5KB再按需组合显示才勉强塞进去。如果项目对图片量需求很大最彻底的方案还是外部存储配合 FATFS 文件系统Bmp2RGB 生成的数组就不再是唯一选择了。5.4 生成的数据文件打开乱码有时候转换完了打开生成的 .h 文件看见的不是整齐的 0x00、0xF8 这种十六进制数字而是一堆乱码符号。这不是转换失败大概率是输出格式选错了。Bmp2RGB 这类工具的输出类型一般有 C 源码、纯二进制、汇编、文本文件等。如果选成纯二进制生成的 .h 文件里保存的就是原始像素字节文本编辑器打开当然是一团乱麻。解决方法是回到工具设置里把输出类型换成 C Source File 或者 Text File重新生成一次。还有一种情况是你用 Windows 记事本打开文件发现中文注释是乱码但数组数据正常。这是文件编码问题不用理会把文件拖到 VS Code 或 Notepad 里看就能正常显示不影响编译使用。5.5 带透明效果的图标出现黑边RGB565 数组本身是不支持透明度信息的因为 16 位全部被颜色占用。如果你用 PNG 转 BMP再转 RGB565 数组透明区域会被填充成黑色。在浅色背景上显示时图标周围会出现一圈明显黑边非常难看。处理方案有两种。第一种是在画图工具里把透明区域改造成目标背景色只适合背景色固定的场景比如做一个蓝色背景上的白色图标直接把透明区域涂成蓝色。第二种是用支持 alpha 通道解析的工具比如 LCD Image Converter 可以直接把带透明信息的 PNG 转成带透明色分配的数组但这种方式对底层绘图逻辑要求更高需要每像素判断是否透明。如果只是给 LCD 菜单做小图标我通常直接用第一种办法简单可靠不需要改绘图代码。如果图片的透明信息非常重要那就放弃 Bmp2RGB改用支持 alpha 输出的工具或者直接使用带透明通道的更高位深格式。最后说一个我自己习惯的小技巧把常用尺寸的纯色测试图比如 128x160 或 240x320 的红色、绿色、蓝色 BMP统一放到一个测试目录里备份。每次拿到新屏幕或者新版本的转换工具先花两分钟跑一遍测试图确认颜色格式和扫描方向正确再转正式图片。这个习惯帮我省下的排错时间远比转换工具本身节约的时间多得多。