3、帧缓冲基础:帧缓冲(FrameBuffer)概念、显存映射、像素格式(RGB565/RGB888)
好,咱们今天聊点实在的。帧缓冲,英文叫 FrameBuffer,说白了就是一块专门用来存图像数据的内存区域。你想想看,游戏机要显示画面,总得有个地方先把画面数据放好,然后让显示控制器去读,对吧?这个“放数据的地方”,就是帧缓冲。
核心理解:帧缓冲 = 显存 + 映射机制 + 像素格式。三者缺一不可。
3.1 帧缓冲到底是什么?
我个人的理解很简单:帧缓冲就是一块内存,里面每个字节都对应屏幕上某个像素的颜色。CPU 往这块内存里写数据,显示控制器(比如 LCD 控制器、HDMI 发送器)就自动把数据刷到屏幕上。
嗯,这里要注意:帧缓冲不是显卡的专利。在嵌入式系统里,我们经常用 MCU 内部的 SRAM 或者外挂的 SDRAM 来充当帧缓冲。说白了,只要你的 CPU 能读写,显示控制器能读取,这块内存就能当帧缓冲用。
我在项目中遇到过一件事:有个同事把帧缓冲定义在内部 SRAM 里,结果分辨率一高,内存不够用了。后来改成外挂 SDRAM,但时序没调好,画面出现撕裂。嗯,这就是典型的帧缓冲设计没考虑周全。
3.2 显存映射:CPU 怎么找到像素?
显存映射,其实就是把帧缓冲的物理地址告诉 CPU 和显示控制器。CPU 通过地址总线访问这块内存,显示控制器通过 DMA 方式读取。
举个例子,假设你的屏幕分辨率是 320x240,像素格式是 RGB565(每个像素 2 字节)。那么帧缓冲的大小就是:
帧缓冲大小 = 320 × 240 × 2 = 153,600 字节 ≈ 150 KB
你需要在内存中开辟一块连续的 150KB 空间。然后告诉显示控制器:起始地址是 0x20000000,长度是 150KB。这样,CPU 往 0x20000000 写数据,屏幕左上角第一个像素就会变化。
我的经验:显存映射最好用 4KB 对齐的地址。很多显示控制器对非对齐地址支持不好,我曾经在这上面浪费了两天时间。
为什么会这样?因为很多嵌入式显示控制器内部用的是行缓冲(line buffer)机制,地址不对齐会导致行数据错位。你想想看,一个像素偏移,整行颜色都乱了。
3.3 像素格式:RGB565 vs RGB888
像素格式决定了每个像素用多少位来表示颜色。嵌入式系统里最常见的两种:RGB565 和 RGB888。
| 格式 | 位数 | 颜色深度 | 内存占用 | 典型场景 |
|---|---|---|---|---|
| RGB565 | 16 位 | 65,536 色 | 2 字节/像素 | 低成本 MCU、小屏幕 |
| RGB888 | 24 位 | 16,777,216 色 | 3 字节/像素 | 高性能 SoC、大屏幕 |
3.3.1 RGB565 详解
RGB565 用 16 位表示一个像素:红色 5 位、绿色 6 位、蓝色 5 位。为什么绿色多 1 位?因为人眼对绿色最敏感,多给 1 位能让颜色过渡更自然。
数据排列方式(小端模式):
位: 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
在 C 语言里,我习惯这样构造 RGB565 颜色值:
// 将 R、G、B(0-255)转换为 RGB565
uint16_t rgb565(uint8_t r, uint8_t g, uint8_t b) {
return ((r >> 3) << 11) | ((g >> 2) << 5) | (b >> 3);
}
注意看,我把 8 位的 R 右移 3 位,变成 5 位;G 右移 2 位,变成 6 位;B 右移 3 位,变成 5 位。这样就把 24 位颜色压缩到了 16 位。
避坑指南:我曾经在 RGB565 的字节序上栽过跟头。有些显示控制器要求高字节在前(大端),有些要求低字节在前(小端)。如果你的画面颜色完全不对,先检查字节序!
3.3.2 RGB888 详解
RGB888 就简单多了,每个颜色分量占 8 位,一共 24 位。数据排列通常是:
字节偏移: +0 +1 +2
R G B
在内存中,我习惯用结构体来表示:
typedef struct {
uint8_t r; // 红色 0-255
uint8_t g; // 绿色 0-255
uint8_t b; // 蓝色 0-255
} rgb888_t;
RGB888 的好处是颜色精度高,做渐变、抗锯齿效果更好。但代价是内存占用多了 50%。对于 1024x768 的屏幕,RGB565 只需要 1.5MB,RGB888 需要 2.25MB。
3.4 实际项目中的选择建议
我个人建议这样选:
- MCU 驱动小屏幕(320x240 以下):用 RGB565。内存省,速度也快。
- Linux 或 RTOS 驱动大屏幕(800x480 以上):用 RGB888。颜色表现好,而且现代 SoC 的内存带宽足够。
- 特殊场景(如电子墨水屏):可能只需要 1 位色深,连 RGB565 都用不上。
你想想看,如果你的游戏机只有 2MB 内存,用 RGB888 的话,一个 800x480 的帧缓冲就占掉 1.15MB,剩下的内存还要跑游戏逻辑、音频、输入处理,够呛。这时候 RGB565 就香多了。
3.5 帧缓冲的初始化流程
最后,我分享一下帧缓冲初始化的标准步骤。这是我多年总结出来的,照着做基本不会出问题:
- 分配内存:在 SDRAM 或 SRAM 中分配连续空间,地址对齐到 4KB。
- 清零:把整个帧缓冲填 0(黑色),防止上电时出现花屏。
- 配置显示控制器:告诉控制器帧缓冲的起始地址、宽度、高度、像素格式。
- 使能显示:打开 LCD 或 HDMI 输出,此时屏幕应该是全黑。
- 写入测试图案:比如画一个红色矩形,验证颜色和位置是否正确。
// 帧缓冲初始化示例(伪代码)
void fb_init(void) {
// 1. 分配内存
uint32_t fb_addr = sdram_alloc(320 * 240 * 2); // RGB565
// 2. 清零
memset((void*)fb_addr, 0, 320 * 240 * 2);
// 3. 配置显示控制器
lcd_set_framebuffer(fb_addr);
lcd_set_resolution(320, 240);
lcd_set_pixel_format(PIXEL_FORMAT_RGB565);
// 4. 使能
lcd_enable(1);
// 5. 画测试图案
draw_rect(10, 10, 100, 100, rgb565(255, 0, 0)); // 红色矩形
}
嗯,到这里帧缓冲的基础就讲完了。下一节我们会深入双缓冲和撕裂问题的处理,那才是真正考验功力的地方。