
简介本资源是一款面向单片机初学者与课程设计、毕业设计开发者的BMP图像转RGB565格式转换工具专为嵌入式图像显示场景优化——解决单片机内存受限下无法直接加载标准BMP文件的痛点显著简化GUI界面、OLED/LCD屏显等项目中的图像数据预处理流程。压缩包共3个文件约7KB包含核心Python转换脚本实现BMP头解析、像素读取与RGB888→RGB565位域重映射、依赖清单requirements.txt保障跨环境可运行及详尽README.md含使用示例、参数说明与常见问题。目前已有40人学习下载提供开箱即用的轻量级图像格式适配能力无需掌握底层图像编码原理即可快速生成单片机可直读的RGB565数组大幅降低嵌入式图像应用开发门槛。1. BMP转RGB565为什么单片机开发离不开它做单片机图形界面开发尤其是TFT彩屏项目绕不开一个很现实的问题图片资源怎么进单片机。直接放BMP那基本上是不现实的一方面容量撑不住另一方面屏幕驱动芯片要的也不是这种格式。RGB565才是最常见的折中方案。今天要聊的这个工具就是干这件事的把BMP图片批量转成RGB565格式的十六进制数据最终以C语言数组的形式输出直接嵌进单片机工程里用。如果你最近在做LCD屏显示、开机logo、菜单图标这类东西这个工具能省掉你大量手工换算的力气。1.1 从一张图片到单片机屏幕中间经历了什么先说个真实场景。你在电脑上做好了一张240x320的开机图保存成BMP文件大小可能在200KB左右。双击打开挺正常但扔到STM32F103上就不行了片内Flash总共才512KB一张图就占掉快一半剩下代码都没地方放。就算你有外部FlashBMP那种逐像素原始存储方式对显示驱动也不太友好。这里要理解一个关键点BMP文件本身是“为PC显示服务的”它保存的是完整的RGB颜色信息通常是24位或32位也就是说一个像素占3到4个字节。而RGB565是用16位2个字节表示一个像素红色占5位绿色占6位蓝色占5位。人眼对绿色最敏感所以绿色多给1位。整体下来同样一张240x320图片RGB565格式只要150KB比BMP小了大约四分之一而且这个格式几乎被所有主流TFT屏驱动芯片原生支持比如ILI9341、ST7735、ST7789这些直接写寄存器就能刷像素。单片机屏幕显示图片的流程大概是这样的图片先变成C语言数组const uint16_t image[]数组里每一个元素就是一个像素点的RGB565颜色值然后通过SPI或并口把数组里的数据按顺序发给屏幕驱动芯片芯片再控制液晶翻转颜色。这个过程中最麻烦的就是第一步把图片从电脑能看的格式变成单片机认识的数组。手工去像素里取颜色值再换算成十六进制一张图几百个点眼睛早花了。这就是BMP转RGB565工具存在的意义。1.2 RGB565与ARGB8888、BMP的对比先把三种常见格式摆在一起看一轮你就明白为什么RGB565是单片机项目的首选。格式每像素字节240x320尺寸说明BMP(24bit)3字节约230KB含文件头存储冗余大不适合嵌入式ARGB88884字节约307KB多一个透明度通道适合带alpha混合的GUI但吃容量的怪兽RGB5652字节约153KB颜色精度略损失但肉眼几乎看不出性价比最高我这里还要提一句很多人印象里“BMP是无压缩的”这个说法不严谨。BMP支持RLE压缩但绝大多数工具保存的BMP都是无压缩的BI_RGB格式也就是每个像素老老实实地把颜色写一遍。所以BMP转RGB565这个工具处理的也正是这种最常见的无压缩24位BMP。如果你做的是像TouchGFX、LVGL这种带GUI框架的项目内部图像资源很多也要求是RGB565因为它能直接映射到底层framebuffer刷屏不卡顿。再往深了说RGB565的存储顺序还要区分大端还是小端比如MCU是小端模式数组里一个像素的低字节要放前面高字节放后面这个细节后面实操部分我会重点讲很多人在这一步栽跟头。2. 工具整体工作流程与转换原理要搞清楚这个工具为什么好用最好先弄明白它内部是怎么工作的。你不一定要自己写一个转换程序但理解了原理遇到奇怪问题的时候才不会一脸懵比如颜色偏绿、图像倒立、边缘花掉这些其实都是转换过程中的细节没处理对。2.1 BMP文件结构拆解BMP文件的结构比很多人想象中规整得多它就是三个主要部分文件头、信息头、像素数据。文件头的固定14个字节开头两个字节一定是“BM”两个ASCII字符十六进制就是0x42 0x4D这也是判断一个文件是不是BMP的最快办法。紧跟着的4个字节是整个文件大小再往后4字节是保留字段通常是0最后4字节是像素数据的起始偏移量也就是从文件开头算起第几个字节开始是真正的颜色数据。信息头有40个字节里面最关键的信息包括图像宽度、图像高度、每个像素的位数通常24或32、压缩方式0表示无压缩、颜色表数量。这里有一个很多人不注意的坑图像高度如果是个正数表示像素数据从下往上存bottom-up也就是说文件里的第一行像素实际上是图像的最后一行如果高度是负数才是从上往下存。很多转换工具默认按从上往下处理就直接把图片转倒了后面实操部分我会细说怎么处理。像素数据部分对于24位BMP每个像素按照B、G、R的顺序存3个字节。注意是蓝绿红不是红绿蓝。这一点在颜色转换时一定要格外小心搞反了颜色就会偏蓝或偏红。32位BMP则多一个Alpha字节通常是0或255转换时一般可以直接忽略。2.2 RGB565的编码方式与像素排列RGB565的核心就是把24位颜色压缩成16位。具体换算规则如下红色取高5位绿色取高6位蓝色取高5位。假设一个像素的RGB888值分别是R200, G100, B50那么转换成RGB565的计算过程是R被压缩到5位200除以8取整等于25G被压缩到6位100除以4取整等于25B被压缩到5位50除以8取整等于6。然后组合成一个16位数(25 11) | (25 5) | 6算出来等于0xCE66这就是这个像素点存进单片机数组里的最终值。代码写出来就这一行uint16_t rgb565 ((r 0xF8) 8) | ((g 0xFC) 3) | (b 3);在C语言里之所以用按位与运算而不是直接除以8或除以4是为了告诉编译器“我只关心高位低位直接丢掉”性能更好语义也更明确。换成位运算后r的高5位左移11位就是真正的高5位。g的高6位左移5位b的高5位放在最低位。关于存储顺序MCU如果是小端模式比如STM32默认就是小端内存里存储一个uint16_t数值0xCE66时低地址字节是0x66高地址字节是0xCE。如果你要把数组通过SPI发送给屏幕必须保证屏幕端解析的时候也能正确识别哪边是高字节哪边是低字节。不同驱动芯片的要求不同有的先收高字节有的先收低字节所以转换工具最好能提供“字节交换”选项让用户在命令行或GUI里手动切换。2.3 工具的处理流程与判定逻辑BMP转RGB565工具的内部处理流程其实就四步读取文件头、解析信息头、读取像素数据并转换、输出目标文件。但这里面有一些细节是工具体验好坏的分水岭。第一步读取文件头验证是不是BMP。如果是继续如果不是直接报错。好的工具还会检查文件是否被截断防止后面读取越界。第二步解析信息头拿到宽度height、宽度width、位数bitCount、压缩方式compression。这里有个关键分支如果bitCount不是24或32要如何处理比如8位或4位BMP带调色板处理逻辑会复杂很多很多简单工具直接不支持报一个“不支持的BMP格式”。你如果有特殊需求建议先用PS或画图工具把图片另存为24位BMP再交给转换工具省得一报错就干瞪眼。第三步读取像素数据。这一步要注意对齐问题。BMP规定每一行像素的字节数必须是4的倍数如果不是就在行尾补0。比如一张宽为10像素的24位BMP一行的原始数据是10*330字节但实际存储会补到32字节。很多自己写转换脚本的人就是漏了这个对齐导致每一行都偏移2个字节图像边缘全是斜的花纹。市面上的成熟转换工具基本都处理好了这一点但如果你碰上转换结果左右偏移八成就是这里出了问题。第四步生成输出文件。输出格式一般有两种一种是直接输出一个.c文件里面是一个const uint16_t数组另一种是输出纯十六进制文本文件方便你自己在代码里拼接。此外很多工具还支持输出BMP宽度、高度等宏定义以及生成一个可以直接包含的头文件。3. 动手实操从BMP到C语言数组的完整流程下面进入正题我来演示一遍用这类工具把一张BMP图片转成C语言数组的完整过程。我以Windows环境为例因为这个工具通常是Windows下的小软件双击就能用。3.1 环境准备与工具运行解压压缩包后你会看到一个exe文件可能是带GUI窗口的也可能是命令行工具。GUI工具一般长这样一个“打开文件”按钮一个输出格式下拉框一两个复选框如“字节交换”“垂直翻转”以及一个“转换”按钮。如果是命令行工具通常用法是BmpToRGB565.exe input.bmp output.c -swap在你还没习惯命令行之前建议先用GUI版能直接预览转换结果至少看到参数选项不会慌。命令行工具适合后面做批量转换用for循环或脚本一跑就完事。打开工具第一步是加载BMP文件。这时候你可以顺便看一眼软件界面显示的图片信息宽、高、位数。如果显示“24位”放心转如果是“32位”也支持如果显示“8位”或“1位”找工具版本是否支持调色板格式实在不行就用画图工具另存为24位BMP再试。3.2 关键参数设置与转换选项这里我重点讲三个常见参数特别是第一次用的话一定要搞懂它们分别影响什么。第一个是“输出格式”。有的工具输出为C数组有的输出为BIN文件还有的可以输出为ASM汇编文件。C数组最通用我绝大多数情况都选它。数组元素类型可能是uint16_t也可能是uint8_t每个像素拆成两个字节这两个是有区别的。uint16_t数组在C代码里可以直接用指针访问uint8_t数组发送给屏幕时更直观看你自己驱动代码怎么写的。第二个参数是“字节交换”Byte Swap。刚才提过MCU小端模式下RGB565高低字节的存储顺序和屏幕芯片接收顺序可能相反。ILI9341的数据手册里写的是18位或16位颜色配置如果是16位模式SPI发送时通常先发高字节再发低字节。所以如果你的屏幕显示出来颜色块错乱或者颜色像打了马赛克大概率就是这里的字节序反了重新勾选/取消勾选字节交换转换一次就好。第三个参数是“垂直翻转”。前面说过BMP的高度正负决定了像素数据的排列方向。很多工具会自动处理但如果转换后图像上下颠倒说明工具没有正确解析BMP的bottom-up存储或者你选的图片本身就是反的。这时候手动开启垂直翻转或者关闭来回试一次就能解决。虽然看起来是个不起眼的选项但它能救命。3.3 输出文件解读与直接移植转换完成后打开生成的.c文件内容大概是这样的#include image.h const uint16_t image_logo[240][320] { {0xCE66, 0xFFFF, 0x0000, ...}, ... };或者一维数组const uint16_t image_logo[76800] { 0xCE66, 0xFFFF, 0x0000, ... };我个人更喜欢一维数组因为屏幕显示接口函数通常按一维数据流操作。比如ILI9341的写像素函数一般就是先设置窗口坐标然后连续发送一段数组。一维数组可以直接传进去二维数组反而不方便。拿到这个数组后怎么把它显示出来举个STM32 SPI屏的典型代码片段void LCD_ShowImage(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) { SPI_Send16(img[i]); // 根据驱动芯片要求调整字节序 } }这里特别提醒一点如果你的屏是SPI接口刷一张240x320全屏图直接用一个for循环逐点发速度和效果都还行但如果你刷很大的UI界面比如LVGL的多个控件背景这会占用大量CPU时间。后面进阶部分我会讲怎么配合DMA优化。4. 常见问题排查与避坑指南用这个工具的过程中我踩过不少坑也见过很多人在群里问各种怪问题这里把高频问题整理成一张表再有类似情况直接对着排查。问题现象可能原因解决方案转换后颜色偏绿/偏紫RGB通道顺序错乱检查源文件是否为24位BMP确认工具是否把G/B通道搞混图像上下颠倒BMP bottom-up未处理开启/关闭垂直翻转图像左右错位边缘花纹BMP行地址4字节对齐未处理确认工具版本是否支持规范BMP换一个成熟工具显示颜色像花屏字节序错误勾选/取消字节交换数据量太大编辑不了数组过大用离线方式生成文件不要在IDE里直接打开转换后屏幕上有黑点/亮点图片存在透明通道或异常像素用32位BMP时主动处理Alpha通道工具直接把BMP删了/文件损坏杀毒软件误报压缩包先解压再运行加白名单4.1 颜色异常发紫、发绿、偏色这是最常见的问题而且原因往往不是转换工具坏了而是你对BMP存储顺序理解反了。24位BMP在文件里按BGR存如果工具没有正确处理直接把第一个字节当作红色第二个字节当绿色第三个字节当蓝色就会导致整体颜色严重偏蓝。反过来如果你是拿RGB565的数组去调试屏幕屏幕显示出来偏绿那可能是驱动芯片把RGB565当成了BGR565处理。排查颜色问题我的经验是先用一个纯色图测试。比如用画图工具做一个纯红色255,0,0的BMP转换后数组第一个值应该是0xF800。如果你看到的不是0xF800而是0x001F说明颜色通道反了。这种“小步快跑”的测试方法能让你在项目早期就发现颜色编码差异而不是等整张UI图嵌进去之后才出各种偏色问题。4.2 图像颠倒、错位或偏移底部置顶图像问题通常和BMP高度符号有关。BMP文件里如果信息头中的高度字段是正数说明图片方向是bottom-up也就是文件开头存的是图片最后一行。很多转换工具会忽略这个细节直接用从上到下的顺序处理结果屏幕显示出来就是倒的。错位问题则更多是行对齐Stride没处理好。你在Windows画图软件里另存为BMP时保存的位图行字节数一定是对齐到4的倍数但如果你从某些专业软件导出可能行字节数是紧密排列的这时候转换工具如果误判就会一行错位导致“斜纹”。4.3 数据量过大Flash放不下这不是转换工具能帮你解决的但却是每个项目都会撞上的墙。一张320x240的RGB565图片数组大小约150KB。如果你的MCU内部Flash只有256KB放两三张图就没空间了。看看你的项目里有多少张这样的图片算一下总大小图片总大小 图片宽度 x 图片高度 x 2 x 图片数量比如你有10张320x240的图片总大小就是320x240x2x101.5MB这显然不是内部Flash能承受的。几个常用方案一是把图片压缩成JPEG运行时解码代价是占用CPU和内存而且解码库也得吃Flash二是用外部SPI Flash存储图片把图片数组生成多个C文件编译成bin之后用烧录器写入外部Flash然后运行时从外部Flash读取再刷屏三是尽量减少全屏图片用UI颜色少量图标位图的方式。4.4 小心处理BMP的扫描行对齐这个坑主要坑的是自己写转换脚本的人。如果你打算用Python写一个脚本来自动生成RGB565数组一定要记得处理BMP每一行的4字节对齐。我直接给个Python示例这是我在实际项目中验证过没问题的版本import struct def bmp_to_rgb565(filepath): with open(filepath, rb) as f: data f.read() if data[0:2] ! bBM: raise ValueError(Not a BMP file) pixel_offset struct.unpack(I, data[10:14])[0] width struct.unpack(i, data[18:22])[0] height struct.unpack(i, data[22:26])[0] bit_count struct.unpack(H, data[28:30])[0] compression struct.unpack(I, data[30:34])[0] if compression ! 0 or bit_count ! 24: raise ValueError(Only 24-bit uncompressed BMP supported) row_size ((width * 3 3) // 4) * 4 pixels [] # BMP height 0 bottom-up for row in range(abs(height)): src_row (abs(height) - 1 - row) if height 0 else row row_offset pixel_offset src_row * row_size for col in range(width): b, g, r data[row_offset col * 3:row_offset col * 3 3] rgb565 ((r 0xF8) 8) | ((g 0xFC) 3) | (b 3) pixels.append(rgb565) return width, abs(height), pixels这段代码用r 0xF8和g 0xFC做掩码直接把低位移除效率高。如果你是小端MCU输出数组时建议直接写入uint16_t类型值编译器会按小端处理。5. 进阶使用与嵌入式显示项目实战经验基础转换流程跑通之后再用这类工具你会发现它不只是“转换器”还是“嵌入式显示项目的基础设施”。在这个基础上做一点延伸很多麻烦事都能提前规避。5.1 批量转换与脚本化一个项目里往往不止一张图片按钮、图标、背景、logo加起来几十张很正常。逐个打开GUI工具点转换不仅浪费时间还容易漏参数。我建议做两件事第一整理好统一的源图目录和输出目录比如source_image/和output/第二利用工具的命令行模式写一个简单的批处理脚本。如果你用的是Windows可以写个bat脚本echo off for %%f in (source_image\*.bmp) do ( BmpToRGB565.exe %%f output\%%~nf.c -swap )如果你用的是Linux环境下的命令行工具也可以用bash循环。图片命名建议统一格式项目名_功能_尺寸.bmp比如menu_icon_settings_48x48.bmp这样生成出来的C数组名也规范不会出现image1、image2这种无法维护的名字。5.2 优化单片机端显示代码拿到转换工具生成的数组下一步就是如何高效地刷到屏幕上。如果你直接写一个循环每个像素都调用一次SPI发送函数速度很慢因为每次都要等待SPI发送完成。更合理的做法是开启SPI的DMA传输把整个图像数组丢给DMA处理CPU同时去干别的事。以STM32为例方式大概是void LCD_ShowImage_DMA(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); HAL_SPI_Transmit_DMA(hspi1, (uint8_t *)img, w * h * 2); }这里我强烈建议你在转换工具里就选择好字节序这样SPI发送时可以原样发送。如果工具输出数组和你屏幕芯片需要的字节序不一致你在DMA传输前还得做一次字节交换数据量一大这个浪费就非常明显。另外如果显示图像尺寸不大比如32x32图标因为总数据量小直接阻塞发送也没问题。对于满屏图才值得用DMA。还有一个经验尽量把 const 数组放在外部Flash或内部Flash中不要放在RAM里尤其像STM32F103这种RAM只有64KB的芯片一张全屏图就占150KB RAM根本放不下。5.3 显示效果调试小技巧最后分享几个我在实际调试显示效果时的小习惯。第一先在PC端做视觉验证。转换工具如果是GUI版最好能提供一个“预览窗口”直接看到转换后的RGB565效果。如果没有我可以把转换后的C数组再写回成一个BMP在电脑上打开对比。这个逆向过程不复杂就相当于把RGB565再解成24位BMP但能帮你一眼发现通道顺序、颜色精度的问题。第二屏幕上测试时不要一上来就刷全图。先用一张16x16的小图验证颜色、方向、字节序确认无误后再正式刷整个界面。小图片排错成本低我见过好几个人直接刷全图结果花屏排查了好久才发现是字节序问题。第三注意不同屏幕的初始化配置。ST7735和ILI9341的RGB565颜色格式虽然一致但SPI时序、写命令序列不一样。你在调色时如果偏色还要考虑屏幕的电源状态、背光亮度甚至偏压设置。这些跟BMP转RGB565工具本身没关系但实际项目中偏色往往是屏幕初始化没配好倒不是转换数据有问题。第四如果你做的GUI用到LVGL这类框架建议把图像资源转成C数组后直接用LVGL的图片解码接口加载而不是自己写SPI发送逻辑。LVGL支持RGB565格式的图片数据通过lv_img_set_src绑定数组刷新动画时由LVGL内部调度效率比自己写高很多。转换工具的输出在LVGL项目里照样能用只是需要把数组声明为LV_IMG_DECLARE或者用转换器生成对应的img descriptor。我个人在实际操作中的体会是这类“小工具”往往被低估。你花十分钟把BMP转RGB565的原理吃透再把转换工具用熟后面做任何带屏幕的项目都能省出大量调试时间。不要觉得转换数据就是点一下按钮的事里面涉及的字节序、通道顺序、扫描顺序每个细节都直接影响最终显示效果。希望这篇整理能帮你在嵌入式显示项目里少踩几个坑把精力放到更值得优化的地方去。本文还有配套的精品资源点击获取