1. 双缓冲基础:帧缓冲区的工作模型

各位同学,咱们今天聊点实在的。双缓冲这个概念,说白了就是「两个画板轮流用」。你想想看,如果你只有一个画板,画一笔显示一笔,观众看到的就是你从白纸到成品的全过程——中间那些半成品、擦掉的线条、涂改的痕迹,全都会被看到。这在嵌入式UI里,就是屏幕闪烁、撕裂的根源。

TouchGFX为什么需要双缓冲?我直接告诉你答案:为了画面不闪、不撕裂、不掉帧。你做一个产品,用户一划屏就看到画面中间有条横线劈开,上半张是旧画面、下半张是新画面,这产品谁敢用?

核心定义:双缓冲是指使用两个帧缓冲区(Front Buffer 和 Back Buffer),一个用于当前显示,一个用于后台绘制。绘制完成后交换角色,保证用户始终看到完整的帧。

1.1 单缓冲的痛点

我先说说单缓冲的问题。早年我做一个小型温控器项目,MCU是STM32F103,屏幕分辨率320x240,16位色。当时图省事,只开了一个缓冲区。结果呢?

  • 每次更新温度数值,数字从「25.0」变成「25.1」的过程中,屏幕会闪一下
  • 如果刚好在刷新中间读取了触摸,画面会出现水平撕裂
  • 动画效果?别想了,拖影严重到客户直接退货

为什么会这样?因为单缓冲模式下,CPU/LTDC(LCD控制器)在同一块内存上打架。LTDC在逐行扫描屏幕时,CPU在改写同一块内存。你改到一半,LTDC读到一半,画面就裂了。

对比项 单缓冲 双缓冲
内存占用 低(1个帧缓冲区) 高(2个帧缓冲区)
画面撕裂 严重 无
闪烁 明显 无
动画流畅度 差 好
实现复杂度 简单 中等

1.2 双缓冲的工作模型

双缓冲的模型其实很直观。我画个图给你看:

双缓冲工作模型 Front Buffer 当前显示 LTDC 读取 Back Buffer 后台绘制 CPU/GPU 写入 VSync 触发交换 LTDC 扫描过程(逐行读取 Front Buffer) 扫描期间 Back Buffer 可自由写入,互不干扰 帧 n 显示 帧 n+1 绘制 帧 n+1 显示

流程其实就三步:

  1. 绘制阶段:CPU/GPU 在 Back Buffer 里画下一帧。这时候 Front Buffer 还在显示上一帧,互不干扰。
  2. 交换阶段:等到垂直消隐期(VBlank),也就是屏幕扫完最后一行、准备回扫第一行的时候,把 Front Buffer 和 Back Buffer 的指针交换。
  3. 显示阶段:LTDC 开始从新的 Front Buffer 读取数据,逐行刷新屏幕。而原来的 Front Buffer 变成了新的 Back Buffer,供下一帧绘制。

个人经验:我在做一款智能家居中控屏时,发现如果交换操作不在 VBlank 内完成,屏幕顶部会出现一条旧画面的残留。后来我直接在 LTDC 的 VBlank 中断里做指针交换,问题彻底解决。记住:交换时机比交换本身更重要。

1.3 TouchGFX 中的帧缓冲区配置

TouchGFX 支持三种帧缓冲区策略,我列个表给你对比:

策略 缓冲区数量 内存需求 适用场景
Single Buffer 1 低 静态界面、无动画
Double Buffer 2 中 常见动画、滑动、切换
Triple Buffer 3 高 高帧率、复杂动画、视频播放

在 TouchGFX 的 TouchGFXConfiguration.cpp 里,你可以这样配置双缓冲:

// 双缓冲配置示例
static uint16_t frontBuffer[FRAME_BUFFER_WIDTH * FRAME_BUFFER_HEIGHT];
static uint16_t backBuffer[FRAME_BUFFER_WIDTH * FRAME_BUFFER_HEIGHT];

void touchgfx_init() {
    HAL& hal = touchgfx_generic_init<TouchGFXHAL>(
        frontBuffer,           // 初始 Front Buffer
        FRAME_BUFFER_WIDTH,
        FRAME_BUFFER_HEIGHT,
        0                      // 0 表示使用双缓冲
    );
    
    // 注册第二个缓冲区
    hal.setFrameBufferBackBuffer(backBuffer);
}

嗯,这里要注意:setFrameBufferBackBuffer 这个函数必须在 touchgfx_init 之后调用。我见过有人把它写在初始化之前,结果程序跑起来直接 HardFault。

3.4 避坑指南

我曾经踩过的坑:

  • 缓冲区对齐问题:有些 LTDC 控制器要求帧缓冲区地址按 32 字节对齐。我一开始没注意,结果画面显示出来是歪的。后来加了 __attribute__((aligned(32))) 才解决。
  • 内存带宽瓶颈:双缓冲意味着内存占用翻倍。在 STM32F429 上跑 800x480 的 24 位色,两个缓冲区就要 800×480×3×2 ≈ 2.3MB。内部 SRAM 根本不够,必须用外部 SDRAM。但 SDRAM 的带宽有限,如果帧率上不去,先查查 SDRAM 时钟配置。
  • 交换标志位:TouchGFX 内部用 FrameBufferAllocator 管理缓冲区状态。如果你手动操作缓冲区,记得调用 hal.lockFrameBuffer() 和 hal.unlockFrameBuffer(),否则会出现竞态条件。

1.5 小结

双缓冲不是什么高深的技术,说白了就是「空间换时间」。用一块额外的内存,换来画面的稳定和流畅。在 TouchGFX 里,双缓冲是默认推荐的工作模式——除非你的内存实在紧张到连一个缓冲区都挤不出来。