2. 嵌入式GUI基础

做嵌入式GUI,说白了就是在跟一块内存打交道。这块内存,就是显存(VRAM)。而帧缓冲(Framebuffer),则是我们操作这块内存的“视图”。

我记得刚入行那会儿,总以为GUI就是画点、画线、画圆。后来被一个闪烁的界面折磨了三天,才明白——显存和帧缓冲才是GUI的根基。不懂它们,你连画面为什么闪都搞不清楚。

显存(VRAM)是什么?

显存,全称Video Random Access Memory。你可以把它想象成一块专用的画板。

CPU把要显示的数据写进去,显示控制器(Display Controller)定时从里面读出来,转换成屏幕上的像素点。这个过程是硬件自动完成的,不需要CPU干预。

核心要点:显存是CPU和显示设备之间的“共享缓冲区”。CPU写,显示控制器读,两者并行工作。

我做过一个项目,MCU只有64KB RAM,但屏幕分辨率是320×240,颜色深度16位。算一下:320×240×2 = 153,600字节,约150KB。64KB根本装不下!

怎么办?要么换MCU,要么用外部SRAM或SDRAM。这就是显存带来的第一个现实问题——容量。

帧缓冲(Framebuffer)的概念

帧缓冲,是显存在软件层面的抽象。它就是一个内存区域,里面按顺序存放着每个像素的颜色值。

举个例子:一个320×240、16位色的屏幕,帧缓冲就是一块320×240×2 = 153,600字节的连续内存。

像素的排列方式通常是从左到右、从上到下。第0个字节对应屏幕左上角,第319个字节对应第一行最右边,以此类推。

我的习惯:在项目初期,我会先画一张“帧缓冲布局图”。把每个像素的偏移地址标出来,这样写驱动时心里有底,不会搞错坐标映射。

帧缓冲的核心作用就两个:

  • CPU写入:应用程序修改帧缓冲中的数据
  • 硬件读取:显示控制器按固定频率扫描帧缓冲,刷新屏幕

这两个操作是异步的。如果CPU在写入时,显示控制器正好在读同一块区域,就会出现“撕裂”——上半屏是旧画面,下半屏是新画面。

避坑指南:我曾经在一个工控项目里忽略了撕裂问题。结果客户反馈屏幕偶尔闪一下,查了三天才发现是帧缓冲读写冲突。后来加了双缓冲才解决。

像素格式与颜色深度

像素格式,决定了每个像素用多少位来表示。颜色深度,就是这些位的数量。

常见的颜色深度有:

颜色深度 每像素位数 颜色数 典型格式 适用场景
1位 1 2 黑白 电子纸、低端LCD
8位 8 256 索引色(调色板) 低端彩屏、资源受限系统
16位 16 65536 RGB565 / RGB555 嵌入式GUI最常用
24位 24 1677万 RGB888 高端显示、HMI
32位 32 1677万+Alpha ARGB8888 带透明效果的GUI

嵌入式里最常用的是RGB565。为什么?

  • 16位,每个像素占2字节,内存占用适中
  • 颜色数65536,人眼基本看不出色阶
  • 硬件支持广泛,大部分LCD控制器原生支持

RGB565的位布局:

位:  15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
     |   R4 R3 R2 R1 R0 | G5 G4 G3 G2 G1 G0 | B4 B3 B2 B1 B0 |
     红色5位         绿色6位         蓝色5位

你想想看,红色和蓝色各5位,绿色6位。为什么绿色多1位?因为人眼对绿色最敏感。这是硬件设计者的小心思。

实际经验:我在一个项目里用过RGB888(24位),效果确实好,但帧缓冲大小是RGB565的1.5倍。MCU的RAM吃紧,DMA传输时间也变长。后来权衡了一下,还是换回了RGB565。除非你的应用需要显示照片级图片,否则RGB565足够了。

帧缓冲与像素格式的关系

帧缓冲的大小,由分辨率 × 每像素字节数决定。

举个例子:

  • 分辨率:320×240
  • 像素格式:RGB565(2字节/像素)
  • 帧缓冲大小:320 × 240 × 2 = 153,600字节

如果换成RGB888(3字节/像素):

  • 帧缓冲大小:320 × 240 × 3 = 230,400字节

差了76.8KB。在嵌入式系统里,这可能是半个MCU的RAM总量。

我的建议:选像素格式时,先算帧缓冲大小,再对比MCU的RAM容量。留出至少20%的余量给堆栈和变量。否则后期优化会非常痛苦。

显存与帧缓冲的典型架构

下面这张图,是我做项目时经常参考的结构。它把显存、帧缓冲、显示控制器之间的关系画清楚了。

显存与帧缓冲架构图 CPU 帧缓冲(显存) RGB565 / 320×240 显示控制器 屏 双缓冲:两个帧缓冲交替使用 一个被CPU写,一个被控制器读 撕裂:读写同一块缓冲导致 上半屏旧画面,下半屏新画面

从这张图可以看得很清楚:

  • CPU 负责往帧缓冲里写数据
  • 显示控制器 负责从帧缓冲里读数据
  • 屏幕 只是被动接收显示控制器的信号

帧缓冲是CPU和显示控制器之间的桥梁。这座桥的设计好坏,直接决定了GUI的流畅度和稳定性。

实际项目中的选择策略

我总结了一套选型思路,分享给你:

  1. 先定分辨率:根据屏幕硬件确定
  2. 再算RAM:分辨率 × 每像素字节数 = 帧缓冲大小
  3. 对比MCU:帧缓冲大小不能超过MCU可用RAM的70%
  4. 选像素格式:在RAM允许范围内,选颜色深度最高的
  5. 考虑双缓冲:如果需要防撕裂,帧缓冲大小要翻倍

我曾经踩过的坑:在一个项目里,我选了RGB565,但没注意MCU的DMA传输宽度。DMA只支持32位传输,而RGB565是16位。结果每次传输都要做位对齐,效率直接砍半。后来我换成了RGB888(24位),用32位DMA传输,虽然帧缓冲大了,但速度反而更快。

嗯,这里要注意:硬件特性会影响软件选择。选像素格式时,不光要看RAM,还要看DMA、总线宽度、显示控制器的支持情况。

显存和帧缓冲,是嵌入式GUI的基石。搞懂了它们,你就能理解为什么有些界面会闪、为什么有些颜色不对、为什么内存总是不够用。下一层,我们就要开始动手操作帧缓冲了。