3. 色彩深度与像素格式:RGB565、RGB888、ARGB8888 的内存占用差异
做嵌入式 GUI 移植,第一个绕不开的坎就是选像素格式。说白了,你选什么颜色格式,直接决定了你的显存要多大、刷屏要多快、画面好不好看。这三者之间,永远是个三角博弈。
我个人习惯,在项目启动的第一天就把这个定死。因为一旦代码写起来,中间再改像素格式,那简直是给自己挖坑。今天我们就掰开揉碎,把 RGB565、RGB888、ARGB8888 这三兄弟的内存账算清楚。
3.1 三种主流格式的「内存账单」
先看一张表,把底裤扒干净。
| 像素格式 | 每像素位数 (bpp) | 每像素字节数 | 颜色通道 | 总色数 | 典型显存占用 (320×240) |
|---|---|---|---|---|---|
| RGB565 | 16 bpp | 2 字节 | R:5, G:6, B:5 | 65,536 | 150 KB |
| RGB888 | 24 bpp | 3 字节 | R:8, G:8, B:8 | 16,777,216 | 225 KB |
| ARGB8888 | 32 bpp | 4 字节 | A:8, R:8, G:8, B:8 | 16,777,216 + 256 级透明 | 300 KB |
看到没?同样是 320×240 的屏幕,RGB565 只要 150KB,ARGB8888 直接翻倍到 300KB。你想想看,如果你的 MCU 只有 512KB 的 SRAM,一个显存就吃掉一半多,剩下的还要跑系统、堆栈、缓冲区……压力山大。
3.2 为什么 RGB565 是嵌入式「默认首选」?
我在项目中遇到过很多次,产品经理拿着手机说「你看这个渐变色有断层」。嗯,这确实是 RGB565 的硬伤——绿色只有 6 位,红色和蓝色只有 5 位,总共才 6 万多种颜色。人眼对绿色最敏感,所以绿色通道多给 1 位,这是有讲究的。
但 RGB565 的好处太明显了:
- 内存减半:相比 ARGB8888,直接省一半显存
- 刷屏快:16 位数据总线一次传输一个像素,不用拼接
- 硬件友好:绝大多数低成本 LCD 控制器原生支持 16 位模式
3.3 RGB888:色彩党的「折中方案」
RGB888 每像素 3 字节,比 RGB565 多了 50% 的内存,但颜色数量从 6 万直接飙升到 1600 万。说白了,人眼基本分不出 1600 万色和 1670 万色的区别,但 6 万色和 1600 万色的差距,一眼就能看出来。
什么时候该用 RGB888?
- 屏幕分辨率较低(比如 240×240),显存总量可控
- 需要显示照片、渐变背景、医疗影像等色彩丰富的场景
- 你的 MCU 有足够的内存带宽(比如带 TFT 控制器的 Cortex-M7)
3.4 ARGB8888:功能最全,代价最大
ARGB8888 多了一个 Alpha 通道,也就是透明度。每像素 4 字节,显存直接起飞。但它的价值在于:
- 窗口叠加:比如一个半透明的弹窗浮在主界面上
- 抗锯齿字体:文字边缘平滑,没有毛刺
- UI 动效:淡入淡出、滑动渐变,都需要 Alpha 混合
我曾经在一个智能家居面板项目上,客户非要实现「毛玻璃效果」。嗯,ARGB8888 是唯一选择。但代价是显存从 150KB 涨到 300KB,而且每次刷屏都要做 Alpha 混合运算,CPU 占用率直接飙到 60%。
3.5 如何平衡色彩质量与内存?——我的「三板斧」
好,理论说完了,来点实战的。我总结了一套选型流程,你可以直接套用。
- 第一步:算显存预算
先看 MCU 还剩多少 RAM。比如总 RAM 256KB,系统占 80KB,堆栈占 20KB,留给 GUI 的只有 156KB。那 RGB565(150KB)就是天花板。 - 第二步:看 UI 需求
有没有透明叠加?有没有照片级图片?有没有抗锯齿字体?如果都没有,直接 RGB565 走起。 - 第三步:考虑「混合模式」
这是我最常用的技巧——主显存用 RGB565,只有需要透明效果的窗口用 ARGB8888 单独分配缓冲区。说白了,就是「按需分配」,不搞一刀切。
3.6 一张图看懂选型逻辑
下面这张 SVG 图,是我自己总结的「像素格式选型决策树」。每次新项目开始,我都会拿出来过一遍。
3.7 代码层面的「内存对齐陷阱」
最后说一个我踩过的坑。RGB565 每像素 2 字节,但很多 MCU 的 DMA 要求源地址 4 字节对齐。如果你把一张 RGB565 的位图放在一个奇数地址上,DMA 传输会直接挂掉。
解决办法很简单:
// 错误示例:位图可能未对齐
const uint16_t image[] = { 0xF800, 0x07E0, 0x001F };
// 正确示例:强制 4 字节对齐
__attribute__((aligned(4)))
const uint16_t image[] = { 0xF800, 0x07E0, 0x001F };
嗯,这个坑我当年查了整整两天。后来养成了习惯,所有位图、字体、调色板数组,一律加 __attribute__((aligned(4)))。不管编译器默认对齐策略是什么,我自己先锁死。