显示驱动基础原理:像素与帧缓冲、RGB与灰度格式、显示接口、刷新率与撕裂效应

做显示驱动,说白了就是让MCU或Linux把内存里的数据,变成屏幕上你能看到的画面。这个过程听起来简单,但里面坑不少。我刚开始接触LVGL时,就因为在像素格式上栽了跟头,折腾了整整两天才把颜色调对。今天咱们就把这些基础概念掰开揉碎,讲清楚。

像素与帧缓冲:屏幕上的每个点

屏幕是由一个个像素点组成的。每个像素点显示什么颜色,由对应的内存数据决定。这块专门存放像素数据的内存区域,就叫帧缓冲(Frame Buffer)。

你可以把帧缓冲想象成一张画布。CPU或GPU往画布上画图,显示控制器则不停地读取画布内容,刷新到屏幕上。LVGL就是往这张画布上绘制UI元素的。

核心概念:帧缓冲 = 像素数据的二维数组。每个像素占用的位数,决定了颜色深度。

举个例子,一个320×240的屏幕,如果每个像素用16位表示,那么帧缓冲大小就是:

320 × 240 × 2 = 153,600 字节 ≈ 150 KB

嗯,这个大小在MCU上还算能接受。但如果是1024×600的屏幕,同样是16位色,帧缓冲就变成了1.2 MB。很多MCU内部RAM根本装不下,这时候就得用外部SDRAM,或者干脆用Linux的方案。

RGB与灰度格式:颜色是怎么编码的

颜色编码格式,是驱动开发里最容易出问题的地方。我见过不少新手,明明代码逻辑都对,但屏幕显示的颜色就是不对——红变蓝,绿变紫。十有八九是格式没配对。

常见的RGB格式

格式 总位数 R/G/B分配 典型场景
RGB565 16位 5/6/5 MCU小屏,最常用
RGB888 24位 8/8/8 Linux大屏,真彩色
ARGB8888 32位 8/8/8/8 带透明度,LVGL常用
L8(灰度) 8位 单通道 墨水屏、低端屏

RGB565 是MCU上的主力格式。为什么?因为它每个像素只占2字节,省内存,而且颜色效果对大多数嵌入式应用来说足够了。我曾经在一个温控器项目里用RGB565显示曲线和图标,用户根本看不出和真彩色的区别。

RGB888 和 ARGB8888 是Linux上的主流。LVGL内部默认使用ARGB8888,因为要处理透明度混合。如果你的屏幕只支持RGB565,LVGL会在底层做格式转换——但这会消耗CPU时间。

我的建议:在MCU上优先选RGB565,在Linux上优先选ARGB8888。如果屏幕硬件支持RGB888,但LVGL配置成ARGB8888,记得检查驱动里的像素格式转换函数,否则颜色会错位。

灰度格式

灰度格式在彩色屏时代用得少了,但在墨水屏、单色OLED、或者某些工业仪表上仍然常见。LVGL支持1位、2位、4位、8位灰度。说白了就是每个像素用更少的位来表示亮度等级。

我记得有个做电子价签的项目,屏幕是黑白双色的,用1位灰度就够了。帧缓冲大小直接降到RGB565的1/16,刷新速度也快了不少。

显示接口:数据怎么送到屏幕

帧缓冲准备好了,接下来就是通过物理接口把数据发给屏幕。不同的接口,速度、引脚数、适用场景都不一样。

SPI接口

SPI是最常见的MCU小屏接口。它只需要4根线(SCK、MOSI、MISO、CS),引脚少,布线简单。但速度是瓶颈——SPI通常是串行传输,时钟频率一般几十MHz。

举个例子,320×240的屏幕,RGB565格式,一帧数据是150KB。如果SPI时钟是40MHz,理论传输时间大约是:

150KB × 8位 / 40MHz ≈ 30ms

嗯,30ms刷新一帧,也就是33fps。看起来还行,但别忘了这还没算命令开销和等待时间。实际能做到20fps就不错了。

避坑指南:我曾经在一个项目里用SPI驱动320×480的屏幕,发现刷新动画时有明显的闪烁。后来一查,是SPI DMA没有配置好,CPU在逐字节发送数据,浪费了大量时间。改用DMA后,帧率从12fps提升到了28fps。

8080并行接口

8080接口(也叫MCU 8080或Intel 8080)是并行传输的,一次可以传8位或16位数据。速度比SPI快得多,但占用的引脚也多——至少需要8根数据线加上若干控制线。

8080接口在STM32F4、F7系列上很常见。我记得有个项目用8080 16位接口驱动480×272的屏幕,刷新率能做到60fps,动画非常流畅。

MIPI DSI接口

MIPI DSI是Linux大屏上的主流接口。它使用差分信号传输,速度快、抗干扰强。MIPI DSI可以跑几百MHz甚至上GHz的时钟,轻松驱动1080p甚至4K屏幕。

但MIPI DSI的驱动复杂度也高得多。你需要配置PLL、D-PHY、DSI控制器等多个模块。在Linux上,通常使用DRM/KMS框架来管理MIPI DSI屏幕。

接口类型 速度 引脚数 适用平台 典型分辨率
SPI 低(~40MHz) 4~6 MCU 320×240以下
8080 中(~100MHz) 12~20 MCU 800×480以下
MIPI DSI 高(~1GHz) 2~10 Linux 1920×1080以上

刷新率与撕裂效应

刷新率就是屏幕每秒更新画面的次数,单位是Hz。60Hz意味着每16.6ms刷新一帧。但这里有个问题——如果CPU在屏幕正在刷新的时候修改了帧缓冲,会发生什么?

你想想看,屏幕从上到下逐行扫描。如果扫描到一半时,你改了帧缓冲的数据,那么上半屏是旧画面,下半屏是新画面。这个现象就叫撕裂(Tearing)。

撕裂在快速滚动的场景下特别明显。比如显示一个滚动的文字列表,撕裂会让文字看起来像被撕开了一样。

怎么解决撕裂?

常用的方法有两种:

  1. 双缓冲(Double Buffering):准备两个帧缓冲。一个用于屏幕显示(前台缓冲),一个用于CPU绘制(后台缓冲)。绘制完成后,通过VSync信号交换缓冲。这样屏幕永远只看到完整的帧。
  2. VSync同步:在垂直消隐期(VBlank)更新帧缓冲。屏幕扫描完最后一行的下一行之前,有一段空闲时间,这时候修改数据不会导致撕裂。

关键点:LVGL本身不直接处理撕裂,它依赖底层驱动提供正确的帧缓冲切换机制。在Linux上,DRM/KMS的Page Flip机制天然支持双缓冲和VSync。在MCU上,你需要自己实现。

我记得有个项目,用MCU驱动800×480的屏幕,没有用双缓冲,结果用户滑动菜单时撕裂非常严重。后来我加了一个简单的双缓冲方案——两个帧缓冲轮流切换,配合DMA传输,撕裂问题就解决了。

知识结构图

下面这张图总结了本章的核心概念和它们之间的关系:

显示驱动基础原理结构图 帧缓冲 像素格式 显示接口 刷新率 RGB565/888 ARGB8888 灰度格式 SPI接口 8080接口 MIPI DSI 撕裂效应 双缓冲 VSync同步 帧缓冲是核心,像素格式决定数据布局,显示接口决定传输方式,刷新率与撕裂影响用户体验 四者环环相扣,驱动开发时需要整体考虑

小结

这一章我们讲了显示驱动的四个基础概念:

  • 帧缓冲:像素数据的内存映像,大小由分辨率和颜色深度决定
  • RGB与灰度格式:RGB565适合MCU,ARGB8888适合Linux,灰度格式用于特殊场景
  • 显示接口:SPI省引脚但慢,8080快但占引脚,MIPI DSI是Linux大屏标配
  • 刷新率与撕裂:双缓冲+VSync是解决撕裂的标准方案