4、帧缓冲(Framebuffer)机制:单缓冲与双缓冲、帧缓冲地址配置、DMA2D加速、帧率计算与优化

帧缓冲,说白了就是显存。你在屏幕上看到的每一帧画面,数据都来自内存里的一块区域。这块区域怎么分配、怎么切换、怎么搬运,直接决定了你的界面是丝滑还是卡顿。

我刚开始做 TouchGFX 移植时,觉得帧缓冲不就是一块内存嘛,随便给个地址就行。结果画面撕裂、闪屏、帧率上不去,折腾了两周才发现是缓冲模式选错了。嗯,这章咱们就把帧缓冲的坑一个一个填平。

核心要点:帧缓冲的配置是 TouchGFX 性能的基石。选错模式、配错地址、没用 DMA2D,都会让界面体验大打折扣。

4.1 单缓冲 vs 双缓冲:你选对了吗?

TouchGFX 支持两种缓冲模式:单缓冲(Single Buffer)和双缓冲(Double Buffer)。它们的区别,我画了张图帮你理解。

单缓冲 vs 双缓冲 工作流程对比 单缓冲模式 CPU 绘制新帧到缓冲 LTDC 读取同一缓冲 → 屏幕显示 ⚠ 可能产生撕裂(Tearing) 双缓冲模式 Buffer A
CPU 绘制 Buffer B
LTDC 读取
VSync 切换 Buffer A
LTDC 读取
Buffer B
CPU 绘制
VSync 切换 ✅ 无撕裂,画面流畅

单缓冲模式下,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 的帧率受三个因素制约:

  1. 绘制时间:CPU 或 DMA2D 完成一帧绘制所需的时间
  2. 传输时间:数据从内存到 LCD 接口的传输时间
  3. 刷新时间: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%