3. 显示驱动基础

单缓冲,说白了就是整个系统只有一块显存。CPU 往里面写数据,显示控制器从里面读数据,读写都对着同一块内存。听起来简单,对吧?我刚开始做嵌入式 GUI 时,用的就是这种方案。那时候觉得,一块缓冲区够用了,何必搞复杂?

但实际用起来,问题就来了。

3.1 单缓冲的工作原理

先看看它的基本流程。你想想看,一个典型的单缓冲系统是这样工作的:

  1. CPU 把要显示的内容,逐像素写入帧缓冲区(Frame Buffer)
  2. 显示控制器(Display Controller)按照固定的刷新率,从同一个帧缓冲区读取数据
  3. 读取的数据经过 LCD 接口,最终显示在屏幕上

整个过程就像只有一个黑板。老师(CPU)在上面写板书,学生(显示控制器)同时在看。老师写的时候,学生也在看——这就是问题的根源。

核心要点:单缓冲模式下,CPU 和显示控制器共享同一块内存区域,没有中间缓存层。

我画了一张流程图,帮你理清这个关系:

单缓冲工作机制流程图 CPU(写入) 绘制像素数据 帧缓冲区 单块内存区域 显示控制器 读取并刷新 写入 读取 ⚠ 撕裂发生的条件 当 CPU 正在写入帧缓冲区时,显示控制器恰好开始读取同一区域 屏幕上半部分显示新帧,下半部分显示旧帧 → 画面撕裂 典型时序冲突 VSync 信号到来 → 显示控制器开始扫描 → 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 帧会出现撕裂。这个概率相当高。

那有没有办法缓解?有,但治标不治本。

  1. 等待 VSync:在 VSync 信号到来之后才开始绘制,确保控制器从帧头开始扫描。但这只能保证一帧完整,如果绘制时间超过一个刷新周期,下一帧还是会撕裂。
  2. 提高绘制速度:用 DMA、硬件加速、减少绘制区域。但受限于硬件性能,提升有限。
  3. 降低刷新率:把刷新率降到 30Hz,刷新周期变成 33ms,撕裂概率降低。但画面会变卡顿。

这些方法我都试过。说实话,在资源受限的平台上,单缓冲能做到「基本可用」就已经不错了。如果你要做流畅的动画交互,我建议直接上双缓冲。下一章我会详细讲。

嗯,这里再补充一点:单缓冲并不是一无是处。在以下场景中,它反而是最优解:

  • 设备只显示静态文本或图标,不涉及动画
  • MCU 的 RAM 小于 64KB,放不下双缓冲
  • 屏幕分辨率极低(比如 128x64 的单色屏)
  • 对成本极度敏感,能省一个显示控制器就省一个

我做过一个温湿度传感器项目,用的就是 128x64 单色 OLED,单缓冲。因为只显示数字和几个图标,没有动画需求。代码不到 200 行,稳定跑了三年没出过问题。所以,选什么方案,关键看需求。