2、帧缓冲深度剖析:单缓冲、双缓冲、三缓冲的优缺点对比,以及如何根据屏幕尺寸和色深计算帧缓冲大小
帧缓冲,说白了就是显存在嵌入式世界里的名字。你屏幕上每一帧的画面,都得先画到这块内存里,再由硬件推出去显示。很多刚接触 TouchGFX 的朋友,上来就问我:“为什么我的 UI 动起来会撕裂?”或者“为什么我加了动画,内存就爆了?”
嗯,答案基本都出在帧缓冲上。今天我们就把它彻底拆开看看。
2.1 什么是帧缓冲?
帧缓冲就是一块连续的内存区域,里面存着屏幕上每个像素的颜色值。MCU 的 LTDC(LCD 控制器)或者 DMA2D 会直接从这里读取数据,刷新到屏幕上。
举个例子:如果你的屏幕分辨率是 480×272,色深是 RGB565(每个像素 2 字节),那一个帧缓冲的大小就是:
480 × 272 × 2 = 261,120 字节 ≈ 255 KB
听起来不大?但你要知道,很多 MCU 的 SRAM 总共才 512KB 甚至 256KB。一个帧缓冲就吃掉一半,你还得跑系统、存图片、放字体……所以,帧缓冲的配置直接决定了你的项目能不能跑起来。
核心公式:
帧缓冲大小(字节)= 屏幕宽度(像素)× 屏幕高度(像素)× 每像素字节数
每像素字节数取决于色深:RGB565 = 2 字节,RGB888 = 3 字节,ARGB8888 = 4 字节。
2.2 单缓冲、双缓冲、三缓冲,到底怎么选?
这三种模式,我习惯叫它们“画画的三种方式”。你想想看,如果你只有一块画板,画一笔就得给人看一笔,那画面肯定不连贯。这就是单缓冲的问题。
2.2.1 单缓冲(Single Buffer)
系统只有一块帧缓冲。MCU 往里面画图的同时,LTDC 也在读它去显示。
- 优点:内存占用最小,只占一份帧缓冲大小。
- 缺点:画面撕裂严重。因为你在擦写的时候,硬件可能正在读同一块区域,结果就是上半屏是新的,下半屏还是旧的。
- 适用场景:静态界面、几乎没有动画的菜单、或者对显示质量要求极低的产品。
我在一个低成本的温控器项目里用过单缓冲。说实话,翻页时那个撕裂感,我自己都看不下去。但客户预算就那么多,RAM 只有 64KB,没办法。
注意:单缓冲下,如果你用 TouchGFX 的 Application::invalidate() 局部刷新,撕裂会稍微好一点,但无法完全避免。我个人建议,除非 RAM 实在不够,否则别用单缓冲。
2.2.2 双缓冲(Double Buffer)
系统有两块帧缓冲:一块叫“前台缓冲”(正在显示),一块叫“后台缓冲”(正在绘制)。绘制完成后,通过交换指针或者复制内容,把后台缓冲推到前台。
- 优点:彻底消除撕裂。用户永远看不到绘制过程。
- 缺点:内存占用翻倍。你需要两份帧缓冲的大小。
- 适用场景:绝大多数 TouchGFX 项目,尤其是带动画、滑动、过渡效果的 UI。
双缓冲是 TouchGFX 的默认配置,也是我最推荐的起点。我记得第一次在 STM32F429 上跑双缓冲,480×272 的屏幕,RGB565,帧缓冲占了 255KB × 2 = 510KB。那块芯片的 SRAM 是 256KB,根本放不下。后来我用了外部 SDRAM 才搞定。
小技巧:双缓冲不一定要用两份完全独立的内存。如果你用的是支持“行交替”或“部分刷新”的硬件,可以只分配一块大缓冲,通过地址偏移来模拟双缓冲。但 TouchGFX 的标准做法还是两块独立区域。
2.2.3 三缓冲(Triple Buffer)
三块帧缓冲:一块显示,一块绘制,一块等待。绘制线程和显示线程可以完全异步,互不等待。
- 优点:帧率最稳定,绘制和显示完全解耦。即使绘制偶尔超时,也不会影响显示。
- 缺点:内存占用是单缓冲的三倍。对 MCU 的带宽和内存控制器压力也更大。
- 适用场景:高性能需求,比如视频播放、60fps 的流畅动画、或者多任务下 UI 绘制时间不确定的场景。
三缓冲在嵌入式里其实用得不多。为什么?因为大部分 MCU 的 RAM 根本扛不住。我曾经在一个需要播放简单动画的项目里试过三缓冲,480×272 的 RGB888 屏幕,每帧 480×272×3 = 391KB,三份就是 1.17MB。最后我只能用双缓冲加局部刷新来妥协。
2.3 三种缓冲模式的对比
| 特性 | 单缓冲 | 双缓冲 | 三缓冲 |
|---|---|---|---|
| 内存占用 | 1 × 帧缓冲 | 2 × 帧缓冲 | 3 × 帧缓冲 |
| 画面撕裂 | 有 | 无 | 无 |
| 帧率稳定性 | 低 | 中 | 高 |
| 绘制延迟 | 低 | 中(需等待 VSync) | 低(异步) |
| 适用场景 | 静态界面、极低 RAM | 大多数 UI 项目 | 高性能动画、视频 |
2.4 如何计算帧缓冲大小?
这个其实不难,但有几个坑要注意。我直接给你一个完整的计算流程。
- 确定屏幕分辨率:比如 800×480。
- 确定色深:
- RGB565:2 字节/像素。颜色质量一般,但省内存。
- RGB888:3 字节/像素。颜色细腻,但内存占用多 50%。
- ARGB8888:4 字节/像素。带透明度通道,适合复杂叠加效果。
- 计算单帧大小:宽 × 高 × 每像素字节数。
- 乘以缓冲数量:单缓冲 ×1,双缓冲 ×2,三缓冲 ×3。
- 考虑对齐:有些 MCU 要求帧缓冲地址按 32 字节或 64 字节对齐,否则 DMA 传输会出问题。对齐后实际占用可能会多几个字节,但通常可以忽略。
实战计算示例:
屏幕:480 × 272,色深:RGB565,双缓冲。
单帧大小 = 480 × 272 × 2 = 261,120 字节 ≈ 255 KB。
双缓冲总大小 = 255 KB × 2 = 510 KB。
如果你的 MCU 内部 SRAM 只有 256 KB,那就必须用外部 SDRAM 或者选择更小的屏幕/更低的色深。
2.5 帧缓冲与内存布局的关系
帧缓冲放在哪里,直接影响性能。我见过不少项目,帧缓冲放在内部 SRAM 里,结果 LTDC 和 CPU 抢带宽,UI 卡成幻灯片。
- 内部 SRAM:速度快,但容量小。适合小分辨率屏幕(如 320×240 以下)。
- 外部 SDRAM:容量大,但延迟高。需要开启 Cache 和 MPU 来保证一致性。
- FMC/FSMC 并行接口:适合中分辨率屏幕,但布线复杂。
- QSPI 或 Octal SPI PSRAM:新兴方案,容量和速度折中,但需要硬件支持。
我个人习惯:如果屏幕分辨率超过 480×272,或者色深用 RGB888,我直接上外部 SDRAM。内部 SRAM 留给关键数据和堆栈。
避坑指南:我曾经在一个项目里把帧缓冲放在外部 SDRAM,但没有配置 MPU 的 Cache 策略。结果 LTDC 读到的数据总是旧的,画面出现奇怪的“鬼影”。后来加了 MPU_Region_InitTypeDef,把 SDRAM 区域设为“非缓存、非缓冲”才解决。嗯,这个坑我印象很深。
2.6 知识体系总览
下面这张图,帮你把帧缓冲的核心逻辑串起来。从屏幕参数到缓冲模式,再到内存布局,一条线走通。
2.7 小结
帧缓冲的选择,本质上是在“内存”和“显示质量”之间做 trade-off。单缓冲省内存但画面撕裂,双缓冲是主流选择,三缓冲留给高性能场景。
计算帧缓冲大小很简单,但别忘了考虑 MCU 的内存布局和带宽。我建议你拿到一个新项目,第一件事就是算一下帧缓冲要占多少内存,再决定用哪种模式、放哪里。
嗯,这一章就到这里。记住:帧缓冲是 UI 性能的基石,这块搞不定,后面全是白搭。