1、课程导论:显示子系统全景图、DRM/KMS框架简介、设备树基础概念、开发板选型与资料准备

各位同学,欢迎来到《嵌入式Linux显示驱动移植与设备树配置实战》的第一章。

做嵌入式Linux开发,尤其是显示驱动这块,很多人一开始会觉得无从下手。为什么?因为显示子系统涉及的东西太多了——从最底层的硬件接口(比如MIPI DSI、HDMI、LVDS),到内核里的DRM/KMS框架,再到上层的应用层(比如Wayland、X11),最后还要跟设备树打交道。说实话,我早年刚接触时也懵过一阵子。

但别怕。这一章,我们就先把整个显示子系统的“地图”画出来。你知道了全局,后面再深入每个细节,心里就有底了。

1.1 显示子系统全景图

嵌入式Linux的显示子系统,说白了就是一条“数据流水线”。应用程序生成图像数据,经过层层处理,最终在屏幕上显示出来。这条流水线大致可以分为三层:

  • 应用层:比如Qt、GTK、Wayland Compositor。它们负责绘制窗口、处理用户交互。
  • 内核层:核心就是DRM/KMS框架。它管理显存、控制显示控制器、处理显示时序。
  • 硬件层:包括GPU(图形处理器)、Display Controller(显示控制器)、Encoder(编码器)、Connector(连接器)、Panel(面板)。

我习惯把这三层的关系想象成一个“工厂流水线”:应用层是“设计部门”,画好图纸;内核层是“生产调度”,安排资源、控制机器;硬件层就是“车间里的机器”,负责实际输出。

下面这张图,就是整个显示子系统的核心结构。你把它看懂了,后面学起来会轻松很多。

嵌入式Linux显示子系统全景图 嵌入式Linux显示子系统全景图 应用层 (Application Layer) Qt / GTK / Wayland Compositor / X11 内核层 (Kernel Layer) DRM (Direct Rendering Manager) / KMS (Kernel Mode Setting) 硬件层 (Hardware Layer) GPU Display Controller Encoder / Connector Panel (LCD/HDMI) 数据流方向:应用层 → 内核层 → 硬件层 → 屏幕显示

1.2 DRM/KMS框架简介

DRM(Direct Rendering Manager)和KMS(Kernel Mode Setting)是Linux内核中显示子系统的核心框架。我当年第一次看到这两个缩写时,还以为是什么高深莫测的东西。其实说白了:

  • DRM:负责管理显存、同步访问、提供用户空间接口(比如/dev/dri/card0)。它让多个程序可以安全地共享显示资源。
  • KMS:负责设置显示模式——分辨率、刷新率、像素时钟这些。它直接操作显示控制器硬件。

DRM/KMS框架里,有几个核心概念你必须记住:

概念 说明 类比
CRTC 显示控制器,负责生成显示时序、扫描显存输出 流水线上的“传送带”
Plane 图像层,可以叠加多个图层(如视频层、UI层) 流水线上的“物料托盘”
Encoder 编码器,将数字信号转为特定接口格式(如HDMI、LVDS) 流水线上的“转换器”
Connector 物理连接器,代表实际的显示接口 流水线的“出口”
FB (Framebuffer) 帧缓冲,存储一帧图像数据的内存区域 流水线上的“产品”

我的经验:调试显示驱动时,最常用的命令就是 modetest。它可以直接操作DRM/KMS,查看当前有哪些CRTC、Plane、Connector。我习惯在移植完驱动后,先用 modetest -M <driver_name> 验证框架是否正常工作。

1.3 设备树基础概念

设备树(Device Tree)是嵌入式Linux中描述硬件信息的“说明书”。它告诉内核:你的板子上有哪些外设、它们怎么连接、需要什么配置。

为什么需要设备树?因为ARM架构不像x86那样有自动枚举硬件的机制。你想想看,一个SoC可以搭配不同的板子,外设连接方式千差万别。如果没有设备树,每换一块板子就得重新编译内核——这谁受得了?

设备树的核心语法很简单,就是一堆节点和属性:

/dts-v1/;

/ {
    model = "My Board";
    compatible = "vendor,my-board";

    chosen {
        stdout-path = &uart0;
    };

    lcd_panel: display@0 {
        compatible = "my,lcd-panel";
        pinctrl-0 = <&lcd_pins>;
        backlight = <&backlight>;
        port {
            panel_in: endpoint {
                remote-endpoint = <&display_out>;
            };
        };
    };
};

这里有几个关键点:

  • compatible:驱动匹配的关键字。内核通过它找到对应的驱动程序。
  • reg:寄存器地址或设备地址。
  • pinctrl:引脚复用配置。
  • port/endpoint:描述设备之间的数据连接关系。

避坑指南:我曾经在一个项目里,因为设备树里 compatible 字符串写错了一个字母,导致驱动死活加载不上。查了两天才发现是拼写问题。所以,写设备树时一定要仔细核对 compatible 的值,最好从驱动源码里复制过来。

1.4 开发板选型与资料准备

选开发板,说白了就是选SoC。不同的SoC,显示控制器、GPU、接口都不一样。我个人建议初学者从这几款入手:

开发板 SoC 显示接口 适合场景
Raspberry Pi 4 BCM2711 HDMI 2.0, DSI 入门、社区资源丰富
i.MX8M Mini EVK i.MX8M Mini MIPI DSI, LVDS, HDMI 工业级、文档齐全
STM32MP157 STM32MP157 MIPI DSI, LTDC 教学、双核A7+M4
RK3399 Rockchip RK3399 HDMI, MIPI DSI, eDP 高性能、Android/Linux双用

选好板子后,你需要准备以下资料:

  1. SoC数据手册(Datasheet):显示控制器、时钟、GPIO的寄存器描述。
  2. 参考手册(Reference Manual):比数据手册更详细,包含编程指南。
  3. 原理图(Schematic):知道显示接口连到了SoC的哪些引脚。
  4. 内核源码:建议用LTS版本,比如5.10或6.1。
  5. 设备树绑定文档:内核源码里 Documentation/devicetree/bindings/display/ 目录下。

我的习惯:拿到一块新板子,我第一件事就是看原理图,找到显示接口的引脚连接。然后对照SoC数据手册,确认这些引脚对应的功能。最后再去看内核里已有的设备树文件,看看有没有现成的配置可以参考。这样一套下来,心里就有数了。

嗯,第一章的内容就到这里。你先把这张“地图”记在脑子里,后面我们就要开始动手了——从编译内核、配置设备树,到点亮第一块屏幕。