显示驱动原理:像素与颜色深度、帧缓冲机制、显示接口详解

各位同学,今天我们来聊聊嵌入式GUI最底层的东西——显示驱动。说白了,就是屏幕怎么亮起来、怎么把图像画上去的。我刚开始做嵌入式开发时,总觉得显示驱动是硬件工程师的事,直到自己调一块LCD屏调了三天没点亮……嗯,从那以后我老实了,老老实实把原理啃了一遍。

像素与颜色深度

先问个问题:屏幕上的一个点,怎么描述它?

答案很简单——用数字。每个像素点由红、绿、蓝三个子像素组成,这就是我们常说的RGB模型。每个子像素用多少位二进制数来表示,就是颜色深度。

常见颜色深度对照表

颜色深度 总颜色数 每像素字节数 典型应用
1 bpp 2 0.125 单色OLED、电子纸
8 bpp 256 1 低端彩屏、调色板模式
16 bpp (RGB565) 65536 2 嵌入式LCD最常用
24 bpp (RGB888) 1677万 3 高清屏、手机屏
32 bpp (ARGB8888) 1677万+Alpha 4 带透明效果的GUI

我个人习惯在资源受限的MCU上用RGB565。为什么?因为每个像素只占2字节,320×240的屏幕,帧缓冲才150KB,大部分MCU扛得住。RGB888虽然颜色更细腻,但3字节对齐在32位总线上反而尴尬,你想想看。

避坑指南

我曾经在一个项目里用了24位色,结果DMA传输时因为字节对齐问题,画面一直有条纹。后来改成32位(浪费一个字节做填充),问题就解决了。嗯,有时候浪费也是一种优化。

帧缓冲(FrameBuffer)机制

帧缓冲是什么?说白了就是一块内存区域,里面存着屏幕上每个像素的颜色值。CPU往这块内存里写数据,显示控制器就自动把数据刷到屏幕上。

我画了一张图,帮你理解这个流程:

帧缓冲工作机制 CPU / GPU 写入像素数据 帧缓冲 SDRAM / SRAM 像素数据矩阵 显示控制器 DMA读取+时序生成 LCD 关键点: • 帧缓冲是CPU和屏幕之间的"中间人" • 双缓冲技术:一个缓冲用于显示,一个用于绘制,避免撕裂 Buffer A(显示中) → 发送到屏幕 Buffer B(绘制中) ← CPU写入新内容 VSync交换

帧缓冲的核心优势是什么?CPU只管往内存里写数据,不用管屏幕的刷新时序。显示控制器(LTDC或DSI Host)会自动从帧缓冲里取数据,按行、按帧发送给屏幕。

注意:帧缓冲的大小 = 屏幕宽度 × 屏幕高度 × 每像素字节数。320×240的RGB565屏,帧缓冲 = 320 × 240 × 2 = 153,600字节 ≈ 150KB。如果你的MCU只有64KB RAM,那就得用分块刷新或者外挂SDRAM。

显示接口详解

帧缓冲里的数据怎么送到屏幕上?这就靠显示接口了。嵌入式领域最常见的三种接口:SPI、RGB、MIPI DSI。我一个个说。

SPI接口

SPI接口,说白了就是串行通信。用MOSI、MISO、SCK、CS四根线,外加DC(数据/命令选择)线。速度慢,但引脚少。

SPI LCD驱动示例(伪代码)

// 写命令
void LCD_WriteCmd(uint8_t cmd) {
    DC_LOW();          // DC=0 表示命令
    CS_LOW();          // 片选使能
    SPI_Transmit(cmd); // 发送一个字节
    CS_HIGH();         // 片选释放
}

// 写数据
void LCD_WriteData(uint8_t data) {
    DC_HIGH();         // DC=1 表示数据
    CS_LOW();
    SPI_Transmit(data);
    CS_HIGH();
}

// 设置像素区域(矩形裁剪)
void LCD_SetWindow(uint16_t x, uint16_t y, uint16_t w, uint16_t h) {
    LCD_WriteCmd(0x2A); // 列地址设置
    LCD_WriteData(x >> 8); LCD_WriteData(x & 0xFF);
    LCD_WriteData((x+w-1) >> 8); LCD_WriteData((x+w-1) & 0xFF);
    
    LCD_WriteCmd(0x2B); // 行地址设置
    LCD_WriteData(y >> 8); LCD_WriteData(y & 0xFF);
    LCD_WriteData((y+h-1) >> 8); LCD_WriteData((y+h-1) & 0xFF);
    
    LCD_WriteCmd(0x2C); // 开始写内存
}

// 刷一帧数据(320x240 RGB565)
void LCD_Refresh(uint16_t *fb) {
    LCD_SetWindow(0, 0, 320, 240);
    DC_HIGH();
    CS_LOW();
    for (int i = 0; i < 320 * 240; i++) {
        SPI_Transmit(fb[i] >> 8);  // 高字节
        SPI_Transmit(fb[i] & 0xFF); // 低字节
    }
    CS_HIGH();
}

SPI接口的瓶颈很明显——速度。SPI时钟一般20-40MHz,刷一帧320×240的RGB565图,需要传输320×240×2=153,600字节。算下来:153600 × 8 / 20MHz ≈ 61ms,也就是16帧/秒。再快就吃力了。

我的经验:SPI接口适合小屏(2.8寸以下)或者静态界面。如果你要做动画或者视频播放,别用SPI,会卡到你怀疑人生。我曾经用SPI驱动一个320×480的屏做菜单滑动,帧率只有12fps,用户体验极差。

RGB接口

RGB接口是并行的。R、G、B各用5-8根数据线,加上HSYNC(行同步)、VSYNC(帧同步)、DOTCLK(像素时钟)、DE(数据使能)。

说白了,RGB接口就是直接把帧缓冲里的像素数据,一个像素一个像素地并行发送给屏幕。速度极快,但引脚多——16位RGB需要至少18根线,24位需要26根。

三种接口对比

特性 SPI RGB MIPI DSI
引脚数 4-6 18-26 2-4(差分对)
最大速度 ~40MHz ~100MHz ~1Gbps/Lane
传输方式 串行 并行 高速串行差分
典型分辨率 320×240以下 800×480以下 1920×1080以上
是否需要帧缓冲 需要(MCU端) 需要(MCU端) 需要(屏端或MCU端)
典型应用 小尺寸彩屏、OLED 中尺寸TFT 手机、平板、高清屏

RGB接口的时序要求很严格。每个像素时钟DOTCLK都要精确,HSYNC和VSYNC的脉冲宽度、前后肩都要配好。我调RGB接口时,最常干的事就是拿逻辑分析仪抓时序,看HSYNC的周期对不对。

MIPI DSI接口

MIPI DSI是手机和平板上最常用的接口。它用差分信号传输,一对差分线就能跑几百Mbps到1Gbps。4对差分线(4 Lane)加起来,带宽足够驱动4K屏。

DSI有两种模式:

  • 命令模式(Command Mode):类似SPI,MCU发命令和数据给屏幕,屏幕自带帧缓冲和控制器。适合低功耗场景。
  • 视频模式(Video Mode):类似RGB接口,实时传输像素流,屏幕没有帧缓冲。适合高刷新率场景。

注意:MIPI DSI的物理层是差分信号,PCB布线要等长、阻抗匹配(100Ω差分阻抗)。我见过一个项目,DSI线走了10cm没做等长,结果画面有雪花点。嗯,高频信号就是这么娇气。

接口选择决策树

我根据经验总结了一个简单的选择逻辑:

显示接口选择决策树 屏幕分辨率多大? ≤ 320×240 SPI接口 320×240 ~ 800×480 RGB接口 ≥ 800×480 MIPI DSI 补充说明: • 引脚资源紧张时,即使分辨率低也优先考虑SPI • 需要高刷新率(>30fps)时,RGB或DSI是必须的 • 低功耗场景(手表、手环),DSI命令模式+屏端帧缓冲更优

好了,这一章的内容就这些。像素深度决定了颜色质量,帧缓冲是CPU和屏幕之间的桥梁,显示接口决定了传输速度和引脚开销。理解这三层,你就能看懂市面上绝大多数嵌入式显示方案了。