2. 显示基础

在把嵌入式 Linux 显示驱动从 FBDEV 迁移到 DRM/KMS 之前,必须先建立一套“显示链路”的底层认知。DRM 并不是凭空出现的抽象层,它只是把传统显示控制器中本来就存在的像素格式、时序、显存、扫描输出等概念,用更规范的对象模型重新组织了一遍。理解这些基础概念,后续看 drm_mode、drm_framebuffer、drm_crtc 等结构体时才不会觉得突兀。

一、像素格式:显存里每个像素如何被解释

像素格式(Pixel Format / DRM_FORMAT_*)描述的是“显存中的一段二进制数据,如何被解释成颜色”。它至少包含三个维度:颜色分量排列顺序、每个分量的位宽、以及是否带 Alpha 通道。例如 DRM_FORMAT_XRGB8888 表示每像素 32 位,其中 R/G/B 各 8 位,高 8 位忽略;DRM_FORMAT_ARGB8888 则把高 8 位用作 Alpha。

嵌入式平台常见的格式还有 RGB565(16bpp,适合低带宽屏)、YUV420 系列(视频层常用,带色度抽样)、以及带预乘 Alpha 的 ARGB8888。像素格式选错,最直观的现象就是颜色偏色、红蓝互换或整屏发绿,而不是“黑屏”。

排查颜色异常时,先确认 framebuffer 的 pixel_format 与显示控制器输出格式是否一致,再检查是否发生了 RGB/BGR 字节序交换。

二、分辨率、刷新率与显存带宽

分辨率指一帧图像的有效像素行列数,如 1920×1080。刷新率(Refresh Rate)指显示控制器每秒向屏幕输出完整帧的次数,单位 Hz。二者与像素格式共同决定了扫描输出所需的显存带宽:

带宽 ≈ 水平总像素 × 垂直总行数 × 刷新率 × 每像素字节数

例:1920×1080@60Hz,ARGB8888(4B/px)
有效像素带宽 = 1920 × 1080 × 60 × 4 ≈ 497 MB/s
若含消隐区(2200×1125),则 ≈ 594 MB/s

注意公式里用的是“总像素”而非“有效像素”,因为消隐区同样要占用扫描时间。嵌入式 SoC 的显示控制器、DDR 控制器和总线带宽有限,高分辨率 + 高刷新率 + 多图层叠加时,带宽很容易成为瓶颈,表现为撕裂、闪烁或掉帧。

三、时序参数:让屏幕“对上拍”

并行 RGB、MIPI DSI、LVDS 等接口都依赖一套时序参数来同步扫描。核心参数包括:

  • Hactive / Vactive:有效显示区域的行像素数与行数。
  • HFP / HBP / HSYNC:水平前肩、后肩、同步脉冲宽度。
  • VFP / VBP / VSYNC:垂直前肩、后肩、同步脉冲宽度。
  • Pixel Clock:像素时钟频率,决定每个像素的输出节奏。

这些参数通常来自屏幕数据手册(Datasheet)的 Timing Table。DRM 中用 struct drm_display_mode 描述,字段名如 hdisplay、hsync_start、hsync_end、htotal 等。时序不匹配时,屏幕可能黑屏、偏移、抖动或出现彩色条纹。

扫描输出:从显存到屏幕的时序映射 显存 Framebuffer ARGB8888 / RGB565 stride = width × bpp 起始地址 + 偏移 显示控制器 / CRTC 按 Pixel Clock 逐像素读取 叠加图层 / 缩放 / 色彩转换 生成 HSYNC / VSYNC / DE 显示面板 Panel 1920×1080 @ 60Hz MIPI DSI / LVDS / RGB 按行扫描点亮像素 读取 输出 水平时序(一行) HBP HSYNC Hactive = 1920 像素(有效显示) HFP Htotal = HBP + HSYNC + Hactive + HFP 垂直时序(一帧) VBP VSYNC Vactive = 1080 行 VFP

四、显存、扫描输出与 DRM 对象映射

显存(Framebuffer)是扫描输出的数据源。传统 FBDEV 用 /dev/fb0 暴露一块连续显存,应用直接 mmap 后写像素;而 DRM/KMS 把显存抽象为 drm_framebuffer,并引入 GEM/DMA-BUF 来管理缓冲区生命周期。扫描输出(Scanout)则是 CRTC 按当前 mode 从 framebuffer 中逐行读取并送往编码器/连接器的过程。

在 DRM 中,这条链路被拆成清晰的对象:

概念 DRM 对象 职责
显存缓冲 drm_framebuffer + GEM object 存放像素数据,描述格式、宽高、stride、修饰符
扫描控制器 drm_crtc 按 mode 读取 framebuffer,产生时序信号
显示模式 drm_display_mode 分辨率、刷新率、H/V 前后肩与同步宽度
图层 drm_plane 把 framebuffer 叠加、缩放、裁剪后送入 CRTC
物理接口 drm_encoder + drm_connector 把并行信号转换为 MIPI/LVDS/HDMI 并连接面板

五、案例:一次“颜色发绿”的排查

某嵌入式板卡从 FBDEV 切到 DRM 后,屏幕能亮但整体偏绿。排查步骤如下:

  1. 用 modetest -M xxx -p 查看当前 CRTC 绑定的 framebuffer 格式,发现是 DRM_FORMAT_RGB565。
  2. 检查应用侧写入的像素格式,实际按 ARGB8888 填充,导致高低字节错位。
  3. 确认显示控制器输出格式寄存器配置为 RGB565,与 framebuffer 一致。
  4. 修正应用侧格式或改用 DRM_FORMAT_XRGB8888,颜色恢复正常。
像素格式不一致不一定黑屏,更多时候表现为偏色、花屏或颜色错位。移植时务必让应用、framebuffer、CRTC、面板四者的格式与位宽保持一致。

六、常见误区与复盘

  • 只看有效分辨率,忽略消隐区:带宽和 Pixel Clock 计算必须用 Htotal/Vtotal,否则会低估负载。
  • 把刷新率当成“应用帧率”:刷新率是扫描输出的固定节奏,应用帧率受 VSYNC 和合成能力约束。
  • 认为 stride 等于 width×bpp:很多控制器要求行对齐,stride 可能大于理论值,必须按驱动返回的 pitch 使用。
  • 忽略显存对齐与缓存一致性:DMA 扫描输出要求物理连续或 IOMMU 映射,CPU 写入后需正确 flush/sync。

把像素格式、分辨率、刷新率、时序参数、显存与扫描输出这六个概念串成一条链路,就得到了 DRM/KMS 的“物理底座”。后续配置 drm_mode、提交 atomic commit、调试 panel 时序时,所有问题几乎都能回溯到这条链路上的某一环。