从一张PNG到单片机屏幕:手把手带你玩转 LCD Image Converter
你有没有过这样的经历?
UI设计师甩来一个精美的.png图标,说:“这个要显示在设备屏幕上。”
你打开Keil,一脸茫然——图片怎么放进代码里?难道要一个像素一个像素地写数组?
别慌。LCD Image Converter就是为解决这个问题而生的。
它不是什么神秘黑科技,也不是必须精通C++才能驾驭的重型工具。相反,它是嵌入式图形开发中最实用、最接地气的一环。今天我们就抛开术语堆砌,用“人话”讲清楚:这玩意儿到底怎么用?每一步背后又在干什么?
为什么单片机不能直接“看懂”图片?
我们先搞明白一件事:你在电脑上看到的logo.png,和你的STM32看到的logo[]数组,完全是两码事。
- 你的电脑有操作系统、显卡驱动、GPU加速……一切为你“所见即所得”服务。
- 而你的MCU呢?没有文件系统,没有图像解码库,甚至连动态内存都抠着用。
所以当你说“把这张图显示出来”,对单片机来说,等价于:
“请把我Flash里一段连续的16位数字,按顺序一个个写进LCD控制器的GRAM地址中。”
换句话说,你要做的,不是“加载图片”,而是提前把图片变成一堆数据常量,烧进芯片。
而这,正是LCD Image Converter的核心任务:
👉 把视觉资产 → 编译期静态数据 → MCU能直接读取的C语言数组。
它到底是个啥?一句话说清
LCD Image Converter是一款专用于嵌入式系统的离线图像转换工具。
它长得像个小画图软件,但干的是“翻译官”的活:
输入:一张常见的 PNG/BMP/JPEG
输出:两个标准C文件(.c+.h),里面装着像素数组和尺寸宏定义
这些输出可以直接拖进STM32CubeIDE、Keil或IAR工程里,配合LCD驱动函数使用,实现“图标上屏”。
常见版本来自:
- ST官方提供的 LCD Image Converter(配套STM32 HAL + DSI/LTDC)
- Segger emWin 自带的 ImageConverterPro
- 其他GUI框架如LittlevGL也有类似工具链
虽然界面略有差异,但逻辑完全一致。本文以ST版为例,带你走通全流程。
操作六步走:像搭积木一样简单
别被“专业技术剖析”吓到。其实整个流程就跟用微信发图差不多——只是这里的接收方是你的单片机。
第一步:新建项目,准备开工
打开软件后点击File → New Project,创建一个新工程。
建议勾选保存项目文件(.icv格式)。这很重要!
以后你想换颜色模式、改透明色,不用重做原图处理,打开这个文件就能继续编辑。
💡 类比理解:这就像是PSD源文件 vs JPG成品图。保留.icv = 留下可修改的权利。
第二步:导入图片,支持哪些格式?
点击Add Image或直接拖拽图片进来。
支持的输入格式包括:
- ✅ BMP(无压缩,解析快)
- ✅ PNG(带Alpha通道,适合图标)
- ✅ JPEG(体积小,但不支持透明)
- ❌ GIF动画不行(只能取第一帧)
⚠️ 提醒:尽量提前裁剪好尺寸!
比如你要显示一个48×48的home图标,就别导入一张2000×2000的大图再缩放。不仅浪费时间,还容易因插值失真。
推荐做法:用Photoshop/Figma/Sai先把图处理成目标分辨率,再来转换。
第三步:关键设置!别让颜色出错
这才是最容易翻车的地方。来看看右侧配置面板的核心选项:
| 参数 | 常见设置 | 说明 |
|---|---|---|
| Color Format | RGB565 | 最常用!节省空间,兼容ILI9341/ST7789等主流屏 |
| GrayScale 8bpp | 文字、灰度进度条可用 | |
| Monochrome 1bpp | 极简黑白图标,极致省空间 | |
| RGB888 | 高质量显示,但占内存大(3字节/像素) | |
| Output Type | C Array | 直接生成C代码,嵌入工程最方便 |
| Binary (.bin) | 外部Flash存储时可用,运行时读取 | |
| Variable Name | img_home_icon | 自定义变量名,别叫image1这种鬼名字 |
| Transparent Color | 启用 + 设置Key色(如0xFF00FF) | 实现“挖空背景”的关键 |
| Dithering | 开启 | 在低色深下模拟更多色彩过渡,防止色带 |
📌 特别强调:Color Format 必须与你的LCD控制器匹配!
举个例子:
- 你用的是ILI9341,它支持 RGB565 输入 → 工具里就必须选 RGB565。
- 如果你误选了RGB888,生成的数据长度不对,写进去就是花屏!
🔍 小技巧:不确定硬件支持什么格式?查数据手册里的“GRAM Data Format”章节,或者看例程中LCD_DrawPixel()函数参数类型。
第四步:预览!看看效果对不对
点击顶部的Preview标签页,你会看到这张图在目标色深下的真实表现。
重点关注以下几个问题:
- 🔍 文字是否模糊或锯齿严重?
- 🎨 是否出现明显色块、条纹(色带 banding)?
- 👁️ 透明区域是否正确剔除?
- 📏 图形轮廓有没有畸变?
如果发现颜色太脏,可以尝试开启Dithering(抖动)。
虽然实际像素还是16位,但它通过相邻像素的颜色微调,让人眼“感觉”更平滑。
✅ 正确姿势:预览没问题 → 才能生成代码。
❌ 错误做法:跳过预览 → 烧进板子才发现偏色 → 回头再来改 → 白忙一场。
第五步:生成代码,得到真正的“资源包”
确认无误后,点击Generate Code。
工具会自动生成两个文件:
// image_data.c const uint16_t img_home_icon[] = { 0xF800, 0xF800, 0xF81F, ... };// image_data.h extern const uint16_t img_home_icon[]; #define IMG_HOME_ICON_WIDTH 48 #define IMG_HOME_ICON_HEIGHT 48看到了吗?这就是你能直接用的东西!
.c文件:包含图像数据本体,编译后存在Flash里.h文件:声明变量和宽高宏,方便你在其他模块引用
🎯 注意事项:
- 默认生成的是const uint16_t[],适用于RGB565
- 若选RGB888,则是const uint8_t[]或const uint32_t[]
- 单色图会生成位图(bit-packed),访问需逐位操作
第六步:集成进工程,让图真正“亮起来”
把这两个文件复制到你的MDK/STM32CubeIDE工程目录下,并添加到编译列表中。
然后在主程序中包含头文件,调用显示函数即可:
#include "lcd_driver.h" #include "image_data.h" void show_home_icon(void) { LCD_DrawBitmap(10, 10, img_home_icon, IMG_HOME_ICON_WIDTH, IMG_HOME_ICON_HEIGHT); }其中LCD_DrawBitmap()是你自己写的底层函数,大致逻辑如下:
void LCD_DrawBitmap(uint16_t x, uint16_t y, const uint16_t *pBitmap, uint16_t w, uint16_t h) { for (int dy = 0; dy < h; dy++) { for (int dx = 0; dx < w; dx++) { uint16_t color = pBitmap[dy * w + dx]; // 如果启用透明色且当前像素等于Key色,跳过绘制 if (transparent_enabled && color == transparent_color) continue; LCD_WritePixel(x + dx, y + dy, color); } } }这样,你的图标就会精准出现在屏幕上。
常见坑点与避坑指南
工具虽好,但也有人踩了不少雷。以下是高频问题及解决方案:
❌ 问题1:图像显示花屏、颜色诡异
原因:颜色格式不匹配!
例如工具输出RGB565,但你当成RGB888去读,每个像素只读了一个字节。
🔧 解法:
- 检查工具中的 Color Format 设置
- 查阅LCD控制器文档,确认其GRAM接收格式
- 在代码中统一数据类型(uint16_t*对应 RGB565)
❌ 问题2:Flash爆了!一张图吃掉100KB+
原因:全屏背景图(240×320)用RGB565存储 = 240×320×2 = ~150KB!对于小容量MCU简直是灾难。
🔧 解法:
- 改用灰度图(8bpp)→ 减半
- 使用RLE压缩(若GUI支持)→ 可压缩30%~70%
- 分块加载:只缓存当前可视区域
- 存外部SPI Flash + DMA搬运
💡 进阶建议:复杂UI考虑使用GUI中间件(如emWin、TouchGFX),它们自带资源管理机制。
❌ 问题3:设置了透明色,但没生效!
你以为勾了“Transparent Color”就万事大吉?错!
这只是告诉工具:“遇到这个颜色,标记为透明”。
但最终要不要跳过绘制,还得看你自己的DrawBitmap函数有没有判断逻辑。
🔧 解法:
- 在绘图函数中加入条件判断
- 或者使用GUI库自带的支持透明的API(如GUI_DrawBitmap())
❌ 问题4:加载慢、卡顿严重
特别是从SPI Flash读取大图时,速度跟不上。
🔧 解法:
- 工具中启用压缩选项(如RLE)
- 显示时先解压到SRAM缓冲区,再批量刷屏
- 利用DMA+FSMC/FSPI异步传输,避免CPU阻塞
高效开发实践:老鸟都在用的小技巧
掌握基础之后,再升级一下工作流,效率翻倍:
✅ 统一资源管理
建立/assets/images/src/和/generated/lcd/目录结构,区分原始图与产出物。
✅ 命名规范
采用清晰命名规则,例如:
-img_bg_splash_320x240_rgb565.c
-icon_wifi_24x24_mono1.c
一眼就知道这是啥、多大、什么格式。
✅ 批量处理
一次导入多个图标(如一整套UI icon set),统一设置参数,一键导出全部C数组。
再也不用手动一个个点了!
✅ 保留.icv项目文件
每次修改原图后,打开.icv重新生成即可,无需重复配置。
✅ 脚本化自动化(高手向)
部分工具提供命令行版本,可写Makefile或Python脚本自动执行转换:
lcd_image_converter -i input.png -f rgb565 -t c_array -o output.c结合CI/CD流水线,实现设计稿更新 → 自动打包资源 → 下载测试板验证。
总结:它不只是工具,更是开发思维的转折点
当你第一次成功把一张PNG变成能在OLED上显示的图标时,你会意识到:
嵌入式图形开发,从来不是“能不能显示”,而是“如何高效、可靠、可维护地显示”。
LCD Image Converter的意义,远不止于省了几百行手动编码的时间。它代表了一种现代嵌入式开发范式:
- 设计与实现分离
- 资源预处理前置
- 数据驱动显示
- 工程化而非手工拼凑
未来随着AIoT发展,我们可能会见到更多智能化工具:
- Figma插件一键导出适配多种分辨率的资源包
- AI自动优化图标色深与压缩率
- 云端协同转换平台
但在当下,熟练掌握这款看似简单的工具,依然是每一个想做出漂亮HMI的工程师必备技能。
如果你正在做智能家居面板、工业HMI、穿戴设备界面……不妨现在就打开LCD Image Converter,试着把你桌面上那张Logo转化一下。
也许下一秒,它就会出现在你亲手点亮的屏幕上。
欢迎在评论区分享你的第一次“上屏”体验!