显示驱动基础:接口类型与帧缓冲原理
做嵌入式GUI移植,第一关就是搞懂显示接口。我见过不少工程师,上来就调代码,结果发现屏幕死活不亮——多半是接口类型没选对,或者帧缓冲的配置出了问题。这一章,咱们就把这些基础打牢。
显示接口类型:选对接口,事半功倍
嵌入式设备里的显示屏,接口五花八门。我个人习惯先按带宽和复杂度把它们分成四类:SPI、RGB、MIPI DSI、LVDS。你想想看,选错了接口,就像用吸管喝奶茶和用消防水管喝水的区别——流量完全不是一个量级。
| 接口类型 | 带宽 | 引脚数 | 典型应用 | 驱动复杂度 |
|---|---|---|---|---|
| SPI | 低(~10Mbps) | 4~6 | 小尺寸OLED、低端LCD | 低 |
| RGB | 中(~100Mbps) | 16~24 | 中尺寸TFT、MCU直驱 | 中 |
| MIPI DSI | 高(~1Gbps) | 2~4(差分对) | 手机屏、平板、高端HMI | 高 |
| LVDS | 高(~1Gbps) | 4~8(差分对) | 工控屏、车载屏、大尺寸 | 高 |
SPI接口:小屏利器
SPI接口最简单,四根线(SCLK、MOSI、MISO、CS)就能驱动一块小屏幕。我在项目里用STM32驱动128x64的OLED,SPI时钟跑到18MHz,刷新率勉强够用。但你要注意,SPI是串行协议,像素数据要一位一位地传。320x240的屏,16位色深,一帧数据就是320×240×2=153600字节。SPI跑10Mbps,一帧要122ms,帧率只有8fps——说白了,SPI只适合小尺寸或静态画面。
RGB接口:MCU直驱的黄金选择
RGB接口是并行总线,R、G、B各5~8位,加上HSYNC、VSYNC、DCLK、DE,总共20多根线。数据直接写入显存,MCU只需要把像素值往总线上一放,屏自己就刷新了。我建议做中端HMI产品时优先考虑RGB接口——它不需要额外的显示控制器,MCU内置的LTDC或FMC就能搞定。
MIPI DSI:高速差分,手机屏标配
MIPI DSI用差分信号传输,一对差分线就能跑到1Gbps。它分D-PHY和C-PHY两种物理层,D-PHY最常见。DSI有命令模式和视频模式两种工作方式。命令模式需要屏端有FrameBuffer,视频模式则靠主机持续送数据。嗯,这里要注意:很多MCU不带MIPI DSI控制器,得外挂桥接芯片,比如东芝的TC358870。我在一个项目里用过这颗芯片,把RGB信号转成MIPI DSI,驱动一块5.5寸的手机屏,效果不错,但成本也上去了。
LVDS:工控和车载的常青树
LVDS也是差分传输,但它是点对点的,不像DSI那样有复杂的协议栈。LVDS通常用于大尺寸屏(7寸以上),抗干扰能力强,传输距离远。我调试工控屏时遇到过一个问题:LVDS线长了,屏闪。后来发现是共模电感没选对,换了个型号就好了。说白了,LVDS的硬件设计比软件更关键。
帧缓冲(Framebuffer)原理:显存就是画布
帧缓冲,说白了就是一块内存区域,里面存着屏幕上每个像素的颜色值。GUI库往这块内存里写数据,显示控制器定时把它刷到屏上。你想想看,这就像画家在画布上作画,画布就是帧缓冲,画家就是CPU,而观众的眼睛就是显示控制器。
常见的像素格式有:
- RGB565:16位,R占5位,G占6位,B占5位。MCU最常用,省内存。
- RGB888:24位,每个颜色8位。颜色丰富,但占内存大。
- ARGB8888:32位,多一个Alpha通道。适合透明效果,但内存翻倍。
举个例子,一块480×272的屏,用RGB565格式,帧缓冲大小是:480×272×2 = 261120字节,约255KB。如果用RGB888,就是480×272×3 = 391680字节,约382KB。我建议在MCU上优先用RGB565,省下来的内存可以给GUI库做缓存。
显存管理:双缓冲与撕裂
显存管理最头疼的问题就是撕裂(Tearing)。什么是撕裂?就是屏幕正在刷新的时候,CPU往帧缓冲里写数据,结果一帧画面里上半部分是旧数据,下半部分是新数据,中间有一条明显的分界线。我在一个项目里遇到过这个问题,画面滚动时总有一条横线在闪,排查了半天才发现是撕裂。
解决撕裂的常用方法是双缓冲:
- 前端缓冲:显示控制器正在读取并刷到屏幕上的缓冲区。
- 后端缓冲:GUI库正在绘制的缓冲区。
- 交换机制:后端绘制完成后,通过VSYNC信号触发交换,把后端变成前端。
双缓冲的代码实现大致如下:
// 双缓冲交换示例
typedef struct {
uint16_t *front; // 前端缓冲地址
uint16_t *back; // 后端缓冲地址
uint16_t width;
uint16_t height;
} framebuffer_t;
void framebuffer_swap(framebuffer_t *fb) {
// 等待VSYNC
while(!(LCD->SR & LCD_SR_VSYNC));
// 交换指针
uint16_t *tmp = fb->front;
fb->front = fb->back;
fb->back = tmp;
// 更新显示控制器地址
LCD->FB_ADDR = (uint32_t)fb->front;
}
显存管理的另一个问题是内存分配。MCU的RAM通常只有几百KB到几MB,帧缓冲可能占掉一大半。我建议:
- 如果屏分辨率不高(如320×240),用单缓冲+撕裂检测就够了。
- 如果分辨率高(如800×480),必须用双缓冲,但可以把后端缓冲放在外部SDRAM里。
- 如果内存实在不够,可以考虑压缩帧缓冲,比如用RLE(游程编码)或LZ4,但会增加CPU开销。
知识体系总览
下面这张图把本章的核心逻辑串起来了。你可以看到,从接口选择到帧缓冲管理,每一步都环环相扣。
从这张图你可以看到,接口类型决定了数据怎么从MCU传到屏上,帧缓冲决定了数据怎么存,显存管理决定了画面会不会撕裂。这三层选对了,GUI移植就成功了一半。