2、显示驱动基础:LCD/OLED工作原理、像素与分辨率、色彩深度、帧缓冲概念

做嵌入式GUI,说白了就是跟屏幕打交道。你写的每一行代码,最终都要变成屏幕上的一个像素点。这一章我们就把显示驱动的基础打牢,后面讲架构时才不会发虚。

2.1 LCD与OLED:两种主流屏幕的工作原理

先聊聊屏幕本身。现在嵌入式设备里,LCD和OLED是绝对的主流。我刚开始做项目时,也分不清它们到底差在哪,直到有一次在户外项目上选错了屏,大太阳底下啥都看不清……嗯,从那以后我就认真研究了一回。

LCD(液晶显示器)

LCD自己不发光,它靠背后的背光源照亮。液晶分子像一个个小百叶窗,通过电压控制扭转角度,决定光线能不能透过去。再加上RGB滤色片,就形成了彩色像素。

  • 背光常亮:即使显示全黑画面,背光也开着,所以黑色不够纯,有点发灰。
  • 功耗较高:背光一直亮着,耗电相对大。
  • 成本低:技术成熟,大尺寸便宜,适合工控、家电。

OLED(有机发光二极管)

OLED每个像素自己发光,不需要背光。电压一加,有机材料就亮了。黑色就是直接关掉像素,所以黑色是纯黑,对比度极高。

  • 自发光:每个像素独立控制,黑色区域完全不耗电。
  • 响应快:刷新速度比LCD快很多,适合动态画面。
  • 寿命问题:蓝色像素衰减快,长时间显示固定界面容易烧屏。我在做智能手表项目时就踩过这个坑,后来加了像素偏移算法才解决。
我的建议:如果项目对功耗和对比度要求高,选OLED;如果对成本和寿命更敏感,选LCD。没有绝对的好坏,只有合不合适。

2.2 像素与分辨率:屏幕的“最小颗粒”

像素是屏幕的最小显示单元。每个像素由R、G、B三个子像素组成。分辨率就是横向和纵向的像素数量,比如320×240,就是横向320个像素,纵向240个像素。

分辨率越高,画面越细腻。但代价是什么?显存占用和刷新压力都变大。我见过有人在小MCU上硬跑800×480的屏,结果帧率只有个位数,UI卡得像幻灯片。

分辨率 常见场景 像素总数 RGB565显存占用
128×64 小OLED、手环 8,192 16 KB
320×240 工控屏、MP3 76,800 150 KB
800×480 智能家居、导航 384,000 750 KB
1024×600 平板、HMI 614,400 1.2 MB
注意:选分辨率时一定要算好MCU的RAM和总线带宽。我曾经在一个STM32F103上硬上480×272的屏,结果DMA刷屏时CPU几乎被占满,按键响应都延迟了。

2.3 色彩深度:RGB565与RGB888

色彩深度决定了每个像素用多少bit来表示颜色。说白了,就是颜色够不够“真”。

RGB565

每个像素用16位:R占5位,G占6位,B占5位。总共能显示2^16=65536种颜色。

  • 优点:省显存,传输快。一个像素只占2字节。
  • 缺点:颜色过渡有断层,尤其是渐变和肤色,能看出条纹。
  • 适用:低端MCU、小尺寸屏、对颜色要求不高的场景。

RGB888

每个像素用24位:R、G、B各占8位。总共2^24=1677万色,也就是真彩色。

  • 优点:颜色细腻,过渡平滑,显示照片和图标效果很好。
  • 缺点:一个像素占3字节,显存和带宽需求大。
  • 适用:高性能MCU、带LCD控制器的芯片、高端HMI。

我的经验:在资源紧张的MCU上,RGB565是主流选择。但如果你的芯片有硬件DMA和足够的RAM,RGB888带来的视觉提升非常明显。我做过一个对比测试,同样的UI界面,RGB888的观感比RGB565好一个档次,尤其是圆角和阴影部分。

2.4 帧缓冲(FrameBuffer)概念

帧缓冲,说白了就是显存里的一块区域,专门用来存放一帧画面的像素数据。CPU或GPU把要显示的内容画到这块缓冲区里,然后显示控制器定期从缓冲区读取数据,刷新到屏幕上。

为什么需要帧缓冲?因为屏幕刷新和CPU绘图是异步的。如果没有缓冲区,CPU画到一半时屏幕刷新了,就会出现撕裂——上半屏是旧画面,下半屏是新画面。

单缓冲 vs 双缓冲

模式 原理 优点 缺点
单缓冲 CPU直接往显示缓冲区画 省内存 容易撕裂,闪烁
双缓冲 CPU画到后台缓冲区,完成后交换到前台 无撕裂,画面流畅 多占一倍显存

双缓冲是嵌入式GUI的标配。我习惯的做法是:在初始化时分配两个缓冲区,一个作为显示缓冲区(由LCD控制器直接读取),另一个作为绘图缓冲区。绘图完成后,通过DMA或指针交换,把新画面推送到屏幕上。

避坑指南:我曾经在双缓冲切换时忘了做垂直同步(VSync)等待,结果画面还是出现了撕裂。后来加了一个等待VSync中断的机制,问题就解决了。记住,缓冲区交换一定要在屏幕刷新间隙完成。

2.5 核心知识体系图

下面这张图把本章的核心概念串起来了。你可以看到,从屏幕硬件到帧缓冲,再到上层GUI,每一层都有明确的职责。

显示驱动核心知识体系 显示驱动核心知识体系 LCD / OLED 屏幕 工作原理与选型 像素与分辨率 显示颗粒度与显存计算 色彩深度 RGB565 / RGB888 帧缓冲(FrameBuffer) 显存中的一帧画面数据 单缓冲 直接绘制,易撕裂 双缓冲 后台绘制,交换显示 上层GUI框架 LVGL / TouchGFX / 自研引擎 帧缓冲是连接底层屏幕硬件与上层GUI框架的桥梁,理解它是入门嵌入式GUI的关键

2.6 代码示例:初始化一个RGB565帧缓冲

下面是一个简单的帧缓冲初始化代码,我用的是STM32平台,但思路是通用的。你可以在任何MCU上套用。

/* 定义帧缓冲结构 */
typedef struct {
    uint16_t width;
    uint16_t height;
    uint16_t *buffer;  /* RGB565像素数据 */
} framebuffer_t;

/* 分配双缓冲 */
static uint16_t fb_front[320 * 240];  /* 前台缓冲区 */
static uint16_t fb_back[320 * 240];   /* 后台缓冲区 */
static framebuffer_t fb;

void framebuffer_init(uint16_t w, uint16_t h) {
    fb.width = w;
    fb.height = h;
    fb.buffer = fb_front;  /* 默认前台显示 */
    
    /* 清空缓冲区 */
    memset(fb_front, 0, w * h * sizeof(uint16_t));
    memset(fb_back, 0, w * h * sizeof(uint16_t));
    
    printf("FrameBuffer initialized: %dx%d, RGB565\n", w, h);
}

/* 交换缓冲区(带VSync等待) */
void framebuffer_swap(void) {
    while(!(LCD->SR & LCD_SR_VSYNC));  /* 等待垂直同步 */
    
    /* 交换指针 */
    uint16_t *tmp = fb.buffer;
    fb.buffer = (fb.buffer == fb_front) ? fb_back : fb_front;
    
    /* 更新LCD控制器起始地址 */
    LCD->LR = (uint32_t)fb.buffer;
}

关键点:framebuffer_swap函数里的VSync等待不能省。我见过有人为了省那几微秒,结果画面撕裂被客户投诉。宁可慢一点,也要保证画面完整。

2.7 本章小结

这一章我们聊了LCD和OLED的区别,像素和分辨率怎么算,RGB565和RGB888怎么选,以及帧缓冲的核心概念。这些都是嵌入式GUI的底层基础,你理解得越深,后面做架构设计时就越有底气。

我个人觉得,帧缓冲是这一章最重要的概念。它就像一块画布,你的GUI框架在上面作画,显示控制器把它呈现给用户。搞懂了它,你就掌握了嵌入式GUI的“数据流”核心。