3. 帧缓冲管理与配置策略
帧缓冲,说白了就是一块内存区域,专门用来存放要显示到屏幕上的像素数据。TouchGFX 的渲染引擎每画一帧,最终都要往这块内存里写数据。低内存平台上,帧缓冲往往是整个系统里最大的单一内存消费者——我见过不少项目,MCU 的 SRAM 一大半都被它吃掉了。
所以,怎么管好这块内存,怎么配置它,直接决定了你的 UI 能不能跑起来、跑得顺不顺。这一章,我们就来把帧缓冲的底细彻底摸清楚。
3.1 帧缓冲的基本模型
先看一个最简单的模型:单帧缓冲。
MCU 上的 TouchGFX 引擎把渲染好的像素数据写入帧缓冲,然后通过 LTDC、SPI 或 8080 接口,把帧缓冲里的内容刷到 LCD 上。这个过程每 16.67ms(60fps)重复一次。
帧缓冲的大小怎么算?很简单:分辨率 × 每像素字节数。举个例子,480×272 的屏幕,RGB565 格式(每像素 2 字节),那就是 480×272×2 = 261,120 字节,差不多 255KB。嗯,这个数字你最好记在心里,后面选 MCU 的时候经常要用到。
3.2 单缓冲 vs 双缓冲 vs 三缓冲
这里有个经典问题:到底用几个缓冲?我直接说结论——低内存平台上,首选单缓冲。为什么?因为省内存。但单缓冲有个坑,我们慢慢说。
| 缓冲模式 | 内存占用 | 撕裂风险 | 适用场景 |
|---|---|---|---|
| 单缓冲 | 1×帧缓冲 | 有 | 内存极度受限(≤512KB SRAM) |
| 双缓冲 | 2×帧缓冲 | 无 | 内存较充裕,追求画面流畅 |
| 三缓冲 | 3×帧缓冲 | 无 | 高性能平台,极少用于低内存 |
单缓冲模式下,TouchGFX 在渲染下一帧的同时,LTDC 可能正在读取同一块内存刷屏。如果渲染速度赶不上刷新率,屏幕上就会出现上半帧是旧画面、下半帧是新画面的「撕裂」现象。我曾经在一个 STM32F429 项目上踩过这个坑,画面一滚动就撕裂,后来加了双缓冲才解决。
那低内存平台怎么选?我的建议是:先试单缓冲,如果撕裂明显且无法接受,再考虑双缓冲。但双缓冲意味着内存翻倍——255KB 变 510KB,很多 MCU 的 SRAM 直接就爆了。
3.3 帧缓冲的存放位置
帧缓冲放哪儿?这是个好问题。低内存平台上,常见的选项就两个:内部 SRAM 和 外部 SDRAM。
| 存放位置 | 访问速度 | 容量 | 典型 MCU |
|---|---|---|---|
| 内部 SRAM | 极快(~200MHz) | 通常 128KB~1MB | STM32F4/F7/H7 |
| 外部 SDRAM | 较快(~100MHz) | 8MB~64MB | STM32F7/H7 + SDRAM 芯片 |
| 外部 PSRAM | 中等(~50MHz) | 1MB~8MB | ESP32、i.MX RT + PSRAM |
我个人习惯是:能放内部 SRAM 就放内部 SRAM。速度快,延迟低,不需要初始化外部存储器,省事。但内部 SRAM 通常很小,480×272 的 RGB565 帧缓冲就要 255KB,很多 MCU 的 SRAM 总共才 256KB,放完帧缓冲就啥也干不了了。
如果内部 SRAM 实在不够,可以考虑「部分帧缓冲」方案——只在内部 SRAM 里放一个很小的局部缓冲(比如 32×32 像素),每次只渲染和传输屏幕的一小块区域。TouchGFX 4.18+ 开始支持这个特性,但需要配合特定的 LCD 驱动。我在一个 STM32G0 项目上用过,效果还不错。
3.4 TouchGFX 的帧缓冲配置
在 TouchGFX 里,帧缓冲的配置主要在 TouchGFXConfiguration.cpp 里完成。核心代码就几行,但每行都有讲究。
// 帧缓冲配置示例
static uint16_t frameBuffer[480 * 272]; // 单缓冲,RGB565
void touchgfx_init()
{
HAL& hal = touchgfx_generic_init<HAL>(0, 0, 0, 0, 0);
// 设置帧缓冲地址
hal.setFrameBufferStartAddress(
reinterpret_cast<uint8_t*>(frameBuffer),
sizeof(frameBuffer)
);
// 配置显示接口
hal.setDisplayDriver(&displayDriver);
hal.setLCD(480, 272, TFT_RGB565, 60);
}
这里有几个关键点:
- 帧缓冲数组必须全局或静态分配,不能放在栈上。栈空间通常只有几 KB,放不下 255KB 的数组。
- 地址对齐:帧缓冲的起始地址最好 4 字节对齐,有些 DMA 控制器要求 8 字节甚至 16 字节对齐。不对齐的话,DMA 传输可能会出问题。
- 内存对齐宏:可以用
__attribute__((aligned(4)))或ALIGN_32BYTES来强制对齐。
hal.setFrameBufferStartAddress() 的第一个参数是帧缓冲的起始地址,第二个参数是总大小。如果是双缓冲,总大小要传 2×帧缓冲大小。TouchGFX 内部会根据这个大小自动计算第二个缓冲的偏移量。
3.5 双缓冲的配置与同步
如果决定用双缓冲,配置上要改两个地方:
// 双缓冲配置
static uint16_t frameBuffer0[480 * 272];
static uint16_t frameBuffer1[480 * 272];
void touchgfx_init()
{
// 注意:第二个参数传的是两个缓冲的总大小
hal.setFrameBufferStartAddress(
reinterpret_cast<uint8_t*>(frameBuffer0),
sizeof(frameBuffer0) + sizeof(frameBuffer1)
);
// 启用双缓冲
hal.setFrameBufferStrategy(HAL::FRAME_BUFFER_STRATEGY_DOUBLE);
}
双缓冲的核心思想是:渲染引擎往 Buffer0 写,LTDC 从 Buffer1 读。等渲染完成后,交换角色。这样 LTDC 永远在读一个完整的帧,不会出现撕裂。
但这里有个同步问题:什么时候交换缓冲?TouchGFX 提供了两种策略:
- VSync 同步:在垂直消隐期(VBlank)交换缓冲,这是最推荐的方式。LTDC 在 VBlank 期间不读取帧缓冲,交换是安全的。
- 立即交换:渲染完成后立即交换,但可能和 LTDC 的读取冲突,导致撕裂。低内存平台上不推荐。
双缓冲意味着内存占用翻倍。以 480×272 RGB565 为例,单缓冲 255KB,双缓冲 510KB。很多 STM32F4 系列的 SRAM 只有 192KB 或 256KB,根本放不下。这时候要么降分辨率(比如 320×240),要么用外部 SDRAM。
3.6 低内存平台的帧缓冲优化策略
内存实在不够怎么办?我整理了几条实战中验证过的策略:
- 降低色深:RGB565 每像素 2 字节,换成 RGB332(1 字节)或 L8(1 字节索引色),帧缓冲大小直接减半。代价是颜色精度下降,但很多工业 UI 场景完全够用。
- 缩小分辨率:从 480×272 降到 320×240,帧缓冲从 255KB 降到 150KB。如果屏幕支持缩放,效果更明显。
- 使用部分帧缓冲:只渲染屏幕变化的部分。TouchGFX 的 Partial Frame Buffer 功能就是干这个的,但需要 LCD 驱动支持区域刷新。
- 帧缓冲放外部 PSRAM:如果 MCU 支持 PSRAM(比如 ESP32-S3),可以把帧缓冲放在 PSRAM 里,内部 SRAM 留给渲染和系统使用。
- 压缩帧缓冲:用 RLE 或 LZ4 压缩帧缓冲数据,传输到 LCD 前再解压。这个方案实现起来比较麻烦,但确实能省内存。
我记得有一次,一个客户用 STM32F401,内部 SRAM 只有 96KB,屏幕是 320×240 RGB565,帧缓冲就要 150KB。怎么都放不下。后来我帮他改成 RGB332 格式,帧缓冲降到 75KB,再配合部分帧缓冲,最终跑起来了。虽然颜色从 65536 色降到了 256 色,但客户说 UI 看起来差别不大。
3.7 帧缓冲的初始化与测试
配置完成后,一定要做初始化测试。我一般会写一个简单的测试函数:
void frameBufferTest()
{
// 填充帧缓冲为纯红色
uint16_t* fb = reinterpret_cast<uint16_t*>(FRAME_BUFFER_ADDR);
for (int i = 0; i < 480 * 272; i++) {
fb[i] = 0xF800; // RGB565 红色
}
// 等待几帧,观察屏幕是否全红
HAL::getInstance()->vSync();
HAL::getInstance()->vSync();
HAL::getInstance()->vSync();
// 填充为纯绿色
for (int i = 0; i < 480 * 272; i++) {
fb[i] = 0x07E0; // RGB565 绿色
}
// 再等待几帧
HAL::getInstance()->vSync();
HAL::getInstance()->vSync();
}
这个测试能快速验证三件事:
- 帧缓冲地址是否正确(屏幕能正常显示颜色)
- 帧缓冲大小是否匹配(没有花屏或偏移)
- LTDC 或 SPI 接口是否正常工作(颜色能正确传输到屏幕)
如果屏幕显示的颜色不对,或者出现条纹、偏移,八成是帧缓冲地址或大小配置错了。我曾经因为忘记把帧缓冲数组放在 .bss 段,结果数组被编译器优化到了 .data 段,导致初始化时占用了大量 Flash 空间,程序直接跑飞。嗯,这种低级错误,犯过一次就再也不会忘了。
3.8 本章小结
帧缓冲管理是低内存平台移植的核心难点。说白了,就是要在有限的内存里,找到最合适的缓冲模式、存放位置和配置参数。我的经验是:先算清楚内存账,再动手配置。把分辨率、色深、缓冲数量这三个变量调好,大部分问题都能解决。