3、帧缓冲区(FrameBuffer)详解:显存映射、颜色深度(RGB565/RGB888)与数据格式
帧缓冲区(FrameBuffer)是 LCD 显示驱动中连接 CPU/GPU 与显示面板的核心内存区域。它本质上是一块连续的物理内存,用于存储一帧完整的图像数据。显示控制器通过 DMA 或专用硬件接口,以固定刷新率(如 60Hz)从该区域逐像素读取数据,并转换为 LCD 面板所需的时序信号。理解帧缓冲区的内存映射、颜色深度与数据排列格式,是优化显示数据搬运效率的前提。
3.1 显存映射:物理地址与虚拟地址
在嵌入式系统中,帧缓冲区通常位于系统的 SDRAM 或专用的 SRAM 区域。CPU 通过内存管理单元(MMU)将物理地址映射到进程的虚拟地址空间,使得应用程序可以像操作普通数组一样读写显存。
dma_alloc_coherent() 或 ioremap_wc() 分配连续物理内存并建立映射,确保 CPU 与 DMA 对同一物理地址的访问一致性。
3.2 颜色深度:RGB565 与 RGB888
颜色深度决定了每个像素占用的位数,直接影响帧缓冲区的大小与数据传输带宽。常见的 LCD 面板支持 RGB565(16位)或 RGB888(24位/32位)格式。
| 颜色深度 | 每像素位数 | 颜色总数 | 典型帧缓冲区大小(1024×600) | 带宽需求(60Hz) |
|---|---|---|---|---|
| RGB565 | 16 bpp | 65,536 | 1024 × 600 × 2 = 1,228,800 字节 ≈ 1.17 MB | 1.17 MB × 60 ≈ 70.3 MB/s |
| RGB888 | 24 bpp | 16,777,216 | 1024 × 600 × 3 = 1,843,200 字节 ≈ 1.76 MB | 1.76 MB × 60 ≈ 105.5 MB/s |
| ARGB8888 | 32 bpp | 16,777,216 + Alpha | 1024 × 600 × 4 = 2,457,600 字节 ≈ 2.34 MB | 2.34 MB × 60 ≈ 140.6 MB/s |
3.3 数据格式:像素排列与字节序
帧缓冲区中的像素数据必须按照 LCD 控制器期望的格式排列。常见的排列方式包括:
- RGB565 小端模式: 每个像素占用 2 字节,低字节存储 G[2:0] + R[4:0],高字节存储 B[4:0] + G[5:3]。
- RGB888 连续模式: 每个像素占用 3 字节,按 R、G、B 顺序连续排列。
- ARGB8888 对齐模式: 每个像素占用 4 字节(Alpha、R、G、B),便于 32 位总线对齐访问。
3.4 帧缓冲区与 DMA 传输的配合
DMA 控制器在搬运帧缓冲区数据时,通常以行为单位进行传输。每行像素数据在内存中连续排列,行与行之间可能存在 行间距(stride/pitch),用于对齐或预留边界。
/* 帧缓冲区分配示例:RGB565 格式,1024x600,行间距 2048 字节 */
#define LCD_WIDTH 1024
#define LCD_HEIGHT 600
#define BPP 2 /* RGB565: 2 bytes per pixel */
#define STRIDE 2048 /* 行间距,对齐到 32 字节 */
uint8_t *framebuffer = (uint8_t *)malloc(STRIDE * LCD_HEIGHT);
/* 像素访问宏 */
#define PIXEL(x, y) (*(uint16_t *)(framebuffer + (y) * STRIDE + (x) * BPP))
/* 设置像素颜色 (RGB565) */
void set_pixel_rgb565(int x, int y, uint16_t color) {
PIXEL(x, y) = color;
}
/* DMA 传输配置:源地址 = framebuffer,目的地址 = LCD 控制器 FIFO */
dma_config_t dma_cfg = {
.src_addr = (uint32_t)framebuffer,
.dst_addr = (uint32_t)LCD_FIFO_ADDR,
.transfer_size = STRIDE * LCD_HEIGHT, /* 总字节数 */
.burst_size = 32, /* 32 字节突发 */
.src_inc = DMA_ADDR_INC, /* 源地址递增 */
.dst_inc = DMA_ADDR_FIXED, /* 目的地址固定 */
};
3.5 多缓冲与双缓冲机制
为避免屏幕撕裂(tearing)和闪烁,现代显示驱动通常采用 双缓冲(Double Buffering) 或 三缓冲(Triple Buffering) 机制。CPU 在后缓冲区(Back Buffer)绘制,DMA 从前缓冲区(Front Buffer)读取并发送到 LCD。
| 缓冲策略 | 内存占用 | 撕裂风险 | 适用场景 |
|---|---|---|---|
| 单缓冲 | 1 × FB | 高 | 低刷新率、静态图像 |
| 双缓冲(Page Flip) | 2 × FB | 低(需 VSync 同步) | 视频播放、UI 动画 |
| 三缓冲 | 3 × FB | 极低 | 高帧率游戏、实时渲染 |
3.6 帧缓冲区对齐与缓存一致性
CPU 与 DMA 同时访问帧缓冲区时,必须处理 缓存一致性(Cache Coherency) 问题。CPU 的写操作可能仅更新 Cache,而 DMA 直接从物理内存读取,导致数据不一致。
- 方案一: 将帧缓冲区内存区域设置为 非缓存(Non-cacheable) 或 写合并(Write-combining),避免 Cache 干扰。
- 方案二: 在 CPU 写入后手动执行 Cache 刷新(Clean/Flush),在 DMA 读取前执行 Cache 失效(Invalidate)。
- 方案三: 使用硬件 I/O 一致性(I/O Coherency) 总线,如 ARM 的 ACE/CHI 协议,自动维护一致性。
/* Linux 帧缓冲区缓存一致性处理示例 */
#include <linux/dma-mapping.h>
/* 分配一致性内存(非缓存) */
dma_addr_t phys_addr;
void *virt_addr = dma_alloc_coherent(dev, size, &phys_addr, GFP_KERNEL);
/* 或使用普通内存 + 手动刷新 */
void *virt_addr = kmalloc(size, GFP_KERNEL);
dma_addr_t phys_addr = virt_to_phys(virt_addr);
/* CPU 写入后刷新 Cache */
dma_sync_single_for_device(dev, phys_addr, size, DMA_TO_DEVICE);
/* DMA 读取后使 Cache 失效 */
dma_sync_single_for_cpu(dev, phys_addr, size, DMA_FROM_DEVICE);
3.7 本章小结
帧缓冲区是 LCD 显示驱动的数据中枢,其内存映射方式、颜色深度选择、数据排列格式以及缓存一致性策略,直接决定了显示系统的性能与图像质量。在实际项目中,应根据 LCD 面板规格、系统总线带宽和 CPU 处理能力,综合权衡 RGB565 与 RGB888 的选用,并合理设计双缓冲与 DMA 传输的配合机制。