3. 显示驱动基础
单缓冲,说白了就是整个系统只有一块显存。CPU 往里面写数据,显示控制器从里面读数据,读写都对着同一块内存。听起来简单,对吧?我刚开始做嵌入式 GUI 时,用的就是这种方案。那时候觉得,一块缓冲区够用了,何必搞复杂?
但实际用起来,问题就来了。
3.1 单缓冲的工作原理
先看看它的基本流程。你想想看,一个典型的单缓冲系统是这样工作的:
- CPU 把要显示的内容,逐像素写入帧缓冲区(Frame Buffer)
- 显示控制器(Display Controller)按照固定的刷新率,从同一个帧缓冲区读取数据
- 读取的数据经过 LCD 接口,最终显示在屏幕上
整个过程就像只有一个黑板。老师(CPU)在上面写板书,学生(显示控制器)同时在看。老师写的时候,学生也在看——这就是问题的根源。
核心要点:单缓冲模式下,CPU 和显示控制器共享同一块内存区域,没有中间缓存层。
我画了一张流程图,帮你理清这个关系:
3.2 单缓冲的优缺点
优点很明显,缺点也很致命。我列个表,你一看就明白:
| 维度 | 优点 | 缺点 |
|---|---|---|
| 内存占用 | 只需一块帧缓冲区,内存开销最小 | 无法通过增加缓冲区来优化性能 |
| 实现复杂度 | 代码简单,没有同步问题(指缓冲区切换) | 需要手动处理 VSync 同步,否则撕裂严重 |
| 延迟 | 写入即显示,理论延迟最低 | 写入期间画面不稳定,实际体验差 |
| 适用场景 | 静态画面、低刷新率、资源极度受限 | 动画、视频、游戏等动态场景几乎不可用 |
我个人习惯把单缓冲比作「手写板书」。你写一个字,学生看一个字。写快了字迹潦草,写慢了学生等着。但如果你只是挂一张静态的课程表,那完全够用。
3.3 单缓冲下的撕裂现象分析
撕裂(Tearing)是单缓冲最头疼的问题。为什么会这样?
我给你拆解一下。显示控制器是按行扫描的。它从帧缓冲区的起始地址开始,一行一行地读取像素数据,送到屏幕。这个过程是连续的、周期性的。假设屏幕刷新率是 60Hz,那每 16.67 毫秒,控制器就会从头到尾扫一遍。
现在问题来了:如果 CPU 在控制器扫描到一半时,修改了帧缓冲区的内容,会发生什么?
撕裂现象:屏幕上会出现一条水平分割线。分割线以上是新帧的内容,分割线以下是旧帧的内容。两个半帧拼在一起,画面就像被撕开了一样。
我曾经在一个智能家居面板项目上吃过这个亏。面板显示一个时钟秒针动画,单缓冲,60Hz 刷新。秒针转动时,屏幕中间总有一条横线在跳。客户说「你们这个屏幕是不是坏了?」——其实不是坏了,是撕裂。
我后来用逻辑分析仪抓了时序,发现每次撕裂都发生在 VSync 信号之后 5~8 毫秒。因为 CPU 在 VSync 之后才开始绘制,但绘制速度跟不上扫描速度。控制器扫到第 300 行时,CPU 才画完第 200 行。结果就是:前 200 行是新画面,后 120 行是旧画面。
避坑指南:我曾经在低端 MCU 上做单缓冲,发现撕裂最严重的时候不是绘制最复杂的时候,而是绘制时间刚好超过一个刷新周期一半的时候。因为控制器扫描到中间位置时,CPU 刚好完成一半绘制。这个「半程碰撞」是最难看的。
要量化分析撕裂,我们可以看一个关键指标:
- 绘制时间(Draw Time):CPU 完成一帧绘制所需的时间
- 刷新周期(Refresh Period):1 / 刷新率,比如 60Hz 就是 16.67ms
- 撕裂概率:当 Draw Time > 0 时,撕裂概率 = (Draw Time) / (Refresh Period)
举个例子:如果你的绘制时间是 10ms,刷新周期是 16.67ms,那撕裂概率大约是 60%。也就是说,每 10 帧里有 6 帧会出现撕裂。这个概率相当高。
那有没有办法缓解?有,但治标不治本。
- 等待 VSync:在 VSync 信号到来之后才开始绘制,确保控制器从帧头开始扫描。但这只能保证一帧完整,如果绘制时间超过一个刷新周期,下一帧还是会撕裂。
- 提高绘制速度:用 DMA、硬件加速、减少绘制区域。但受限于硬件性能,提升有限。
- 降低刷新率:把刷新率降到 30Hz,刷新周期变成 33ms,撕裂概率降低。但画面会变卡顿。
这些方法我都试过。说实话,在资源受限的平台上,单缓冲能做到「基本可用」就已经不错了。如果你要做流畅的动画交互,我建议直接上双缓冲。下一章我会详细讲。
嗯,这里再补充一点:单缓冲并不是一无是处。在以下场景中,它反而是最优解:
- 设备只显示静态文本或图标,不涉及动画
- MCU 的 RAM 小于 64KB,放不下双缓冲
- 屏幕分辨率极低(比如 128x64 的单色屏)
- 对成本极度敏感,能省一个显示控制器就省一个
我做过一个温湿度传感器项目,用的就是 128x64 单色 OLED,单缓冲。因为只显示数字和几个图标,没有动画需求。代码不到 200 行,稳定跑了三年没出过问题。所以,选什么方案,关键看需求。