4、帧缓冲(Framebuffer)机制:单缓冲与双缓冲、帧缓冲地址配置、DMA2D加速、帧率计算与优化
帧缓冲,说白了就是显存。你在屏幕上看到的每一帧画面,数据都来自内存里的一块区域。这块区域怎么分配、怎么切换、怎么搬运,直接决定了你的界面是丝滑还是卡顿。
我刚开始做 TouchGFX 移植时,觉得帧缓冲不就是一块内存嘛,随便给个地址就行。结果画面撕裂、闪屏、帧率上不去,折腾了两周才发现是缓冲模式选错了。嗯,这章咱们就把帧缓冲的坑一个一个填平。
核心要点:帧缓冲的配置是 TouchGFX 性能的基石。选错模式、配错地址、没用 DMA2D,都会让界面体验大打折扣。
4.1 单缓冲 vs 双缓冲:你选对了吗?
TouchGFX 支持两种缓冲模式:单缓冲(Single Buffer)和双缓冲(Double Buffer)。它们的区别,我画了张图帮你理解。
单缓冲模式下,CPU 和 LTDC(显示控制器)共用同一块内存。CPU 正在画图时,LTDC 可能正好读到一半的数据,画面就撕裂了。双缓冲模式用两块内存,一块给 CPU 画,一块给 LTDC 读,VSync 信号到来时瞬间切换,完美避开撕裂。
| 对比项 | 单缓冲 | 双缓冲 |
|---|---|---|
| 内存占用 | 1 × 帧缓冲大小 | 2 × 帧缓冲大小 |
| 画面撕裂 | 可能发生 | 不会发生 |
| 实现复杂度 | 低 | 中 |
| 适用场景 | 低分辨率、静态界面 | 动画、视频、高帧率 |
我的建议:只要内存够用,无脑选双缓冲。我曾经在一个 480×272 的项目里为了省 300KB RAM 用了单缓冲,结果客户反馈界面闪得眼睛疼。后来换成双缓冲,问题全解决了。
4.2 帧缓冲地址配置:别踩这些雷
帧缓冲的地址配置,看起来就是填个宏定义的事。但这里面的坑,我踩过不止一次。
TouchGFX 在 TouchGFXConfiguration.cpp 或 STM32TouchController.cpp 里定义帧缓冲地址。关键参数有两个:
- 帧缓冲起始地址:必须是 4 字节对齐的
- 帧缓冲大小:宽度 × 高度 × 每像素字节数
// 以 480×272 分辨率、RGB565 格式为例
// 每像素 2 字节,总大小 = 480 × 272 × 2 = 261,120 字节
// 错误示范:地址未对齐
#define FRAMEBUFFER_ADDR 0xC0000001 // ❌ 非 4 字节对齐
// 正确示范:地址 4 字节对齐
#define FRAMEBUFFER_ADDR 0xC0000000 // ✅ 4 字节对齐
// 双缓冲时,两个缓冲连续排列
#define FRAME_BUFFER_0 (uint16_t*)0xC0000000
#define FRAME_BUFFER_1 (uint16_t*)(0xC0000000 + 480 * 272 * 2)
避坑指南:我曾经在 STM32F429 上把帧缓冲地址放在 DTCM RAM(0x20000000 区域),结果 LTDC 根本访问不到。因为 LTDC 只能通过 AHB 总线访问,而 DTCM 是挂在 CPU 内核总线上的。帧缓冲必须放在 AHB SRAM 或 SDRAM 中。
常见的帧缓冲存放区域:
| 内存区域 | 地址范围 | 是否可用 | 说明 |
|---|---|---|---|
| DTCM RAM | 0x20000000 - 0x2001FFFF | ❌ 不可用 | LTDC 无法访问 |
| SRAM1/SRAM2 | 0x20020000 - 0x2003FFFF | ✅ 可用 | 容量有限,适合小分辨率 |
| SDRAM | 0xC0000000 - 0xC3FFFFFF | ✅ 推荐 | 容量大,适合高分辨率 |
| FMC NAND | 0x80000000 - 0x8FFFFFFF | ⚠ 慎用 | 速度慢,不推荐 |
4.3 DMA2D 加速:让帧率翻倍的秘密
DMA2D 是 STM32 上的专用图形 DMA 控制器。它能独立于 CPU 完成图像拷贝、颜色填充、像素格式转换等操作。说白了,就是给 CPU 减负的。
TouchGFX 默认支持 DMA2D 加速。你只需要在 TouchGFXConfiguration.cpp 里启用它:
// 启用 DMA2D 加速
void touchgfx_init()
{
// 实例化 DMA2D 接口
static TouchGFXHAL hal;
// 配置帧缓冲地址
hal.setFrameBufferStartAddresses(
(uint16_t*)FRAME_BUFFER_0,
(uint16_t*)FRAME_BUFFER_1,
(uint16_t*)FRAME_BUFFER_0
);
// 启用 DMA2D
hal.setDMA2DUsage(true);
// 初始化 TouchGFX
touchgfx::HAL::getInstance()->init();
}
DMA2D 能加速哪些操作?我列了个清单:
- 块拷贝(Blit):把一张图片从内存拷贝到帧缓冲
- 颜色填充(Fill):用纯色填充一块区域
- Alpha 混合(Alpha Blending):透明图层的叠加
- 像素格式转换:比如从 RGB888 转 RGB565
性能对比(实测数据):在 STM32F746 上,480×272 全屏填充操作,CPU 直接写需要 2.3ms,DMA2D 只需要 0.4ms,快了将近 6 倍。
不过要注意,DMA2D 不是万能的。它只能处理连续内存块的操作。如果你的界面用了大量不规则形状的裁剪区域,DMA2D 的优势就发挥不出来了。这时候 CPU 绘制反而更快。
4.4 帧率计算与优化:从 30fps 到 60fps
帧率(FPS)是衡量界面流畅度的核心指标。TouchGFX 的帧率受三个因素制约:
- 绘制时间:CPU 或 DMA2D 完成一帧绘制所需的时间
- 传输时间:数据从内存到 LCD 接口的传输时间
- 刷新时间:LCD 面板本身的刷新周期
帧率的计算公式很简单:
FPS = 1 / (绘制时间 + 传输时间 + 刷新时间)
// 举个例子:
// 绘制时间 = 8ms
// 传输时间 = 5ms(LTDC 从 SDRAM 读取)
// 刷新时间 = 4ms(LCD 面板 60Hz 刷新率)
// 总时间 = 17ms
// FPS = 1 / 0.017 ≈ 58.8 fps
我遇到过最典型的性能瓶颈,是帧缓冲放在 SDRAM 里但没开 Cache。LTDC 每次读取都要等好几个时钟周期,传输时间直接翻倍。后来在 SDRAM 区域开启了 MPU 的 Cache 策略,帧率从 32fps 提到了 55fps。
优化技巧:
- 帧缓冲放在 SDRAM 时,开启 MPU 的 Write-Through 或 Write-Back 策略
- 减少每帧的绘制区域,只更新变化的部分(TouchGFX 的 Partial Buffer 机制)
- 使用 RGB565 格式代替 RGB888,每像素少 1 字节,带宽节省 33%
- 降低 LCD 刷新率,从 60Hz 降到 50Hz,如果界面动画不多的话
还有一个容易被忽略的点:中断优先级。LTDC 的 VSync 中断如果优先级太低,会被其他中断打断,导致帧切换延迟。我习惯把 LTDC 中断优先级设为最高(0),确保帧切换不被干扰。
避坑指南:我曾经在一个项目里发现帧率忽高忽低,查了两天发现是 DMA2D 中断和 LTDC 中断互相抢占。两个中断都用了同一个优先级,导致 DMA2D 传输完成时 LTDC 正在切换缓冲,画面就卡了一下。解决方案是把 DMA2D 中断优先级设得比 LTDC 低一级。
最后,给你一个帧率优化的检查清单:
| 检查项 | 优化方法 | 预期提升 |
|---|---|---|
| 帧缓冲位置 | 从 DTCM 移到 SDRAM | 10-20% |
| DMA2D 启用 | 开启 DMA2D 加速 | 30-50% |
| MPU Cache | 配置 SDRAM 区域 Cache 策略 | 20-40% |
| 像素格式 | RGB888 → RGB565 | 15-25% |
| 中断优先级 | LTDC 最高,DMA2D 次之 | 5-10% |
| Partial Buffer | 只更新变化区域 | 30-60% |