☰
RK平台调屏:U-Boot与内核DTS共享屏参,显示链路一次搞懂
2026/9/27 1:01:56 网站建设 项目流程

调屏这件事,很多从其它 SoC 平台转过来的工程师,第一反应就是去 U-Boot 源码里翻 DTS,找 panel 节点、找 backlight 节点、找 reset 引脚。但真正在 RK 平台上做过几个项目之后,你会发现一个反直觉的现象:内核 DTS 里的屏参改了、背光节点改了,U-Boot 的 DTS 往往根本不用碰,重新编译内核后屏幕照样点亮;有些场景下连 U-Boot 都不用重新编译,boot logo 也正常出现。这个现象不是运气,而是 RK 的显示链路设计和设备树组织方式决定的,搞懂了它,你调屏的效率会高很多。

这篇文章就围绕这个现象展开。我会先讲清楚 RK 从 VOP 到屏幕的链路,再说为什么 U-Boot 通常不需要单独维护一份显示 DTS,然后以 MIPI DSI 屏为例,把完整调屏流程过一遍,最后聊聊哪些情况下你必须回头动 U-Boot,以及常见的黑屏、花屏问题怎么查。无论你是刚入行调屏的嵌入式工程师,还是被一块屏折腾过好几天的老手,这篇内容都值得存一下。

1. 先掰开 RK 显示链路:图像从 VOP 到屏幕到底经过什么

1.1 接口类型差异:MIPI DSI、eDP、LVDS、RGB 在 DTS 里长啥样

RK 的显示控制器叫 VOP(Video Output Processor),你可以把它理解成一块“画布”,所有要显示的画面最终都在这里合成,然后通过片上集成的各种显示接口把数据送出去。调屏的第一步,就是搞清楚你的屏幕是通过哪种接口接进来的,因为这决定了你在 DTS 里要找哪些节点。

常见的 RK 显示接口有这么几类。

接口典型场景DTS 里常见节点U-Boot 是否参与
MIPI DSI平板、车载、小尺寸消费屏&dsi、&mipi_dsi、route_dsi可选
eDP笔记本、中大尺寸屏&edp、route_edp可选
LVDS工控、车载大屏&lvds、route_lvds可选
RGB/CPU低成本小屏、并口屏&lcdc、route_rgb一般只看背光

不管哪种接口,屏幕本身的描述——分辨率、时序、供电、复位时序、初始化命令——都在 panel 节点里;接口控制器的参数,比如 DSI 的 lane 数、eDP 的 lane 数、时钟频率,则在外设控制器节点里。也就是说,调屏时你改的其实是一堆互相引用的节点,而不是单纯改某一个值。

这里有个很多新手容易混淆的点:U-Boot 和 Linux 内核是两套完全不同的驱动代码,但它们读取的设备树来源却可以是同一份。RK 平台的 U-Boot 并不是像某些老平台那样,在 C 代码里写死一组屏幕参数,而是同样走设备树解析的路子。这就为“不用单独改 U-Boot DTS”埋下了伏笔。

1.2 route 节点:谁在决定“哪条路送画面”

RK 设备树里有一类特殊节点,叫 route 节点,比如 route_dsi、route_edp、route_lvds。它干的事情是把某个 VOP 和某个显示输出接口绑定起来,决定“画布上的画面到底从哪条物理链路送出去”。

看一个典型片段:

&route_dsi { status = "okay"; connect = <&vopb>; };

意思很直白:DSI 这条显示通路要启用,并且画面由 vopb 这个显示控制器送出。RK 一般有 VOPB 和 VOPL,能力不同,VOPB 通常更强一些,支持更高分辨率;VOPL 是低功耗用途。connect 里面绑的如果不是实际接线的那个 VOP,那就会黑屏。

这个节点在排查问题时非常关键。如果你把 route_dsi 的状态改成 disabled,哪怕 DSI 控制器节点本身是 okay,系统也不会往这块屏上送画面。U-Boot 和内核的显示驱动都会读取 route 节点,所以当你发现“接口也对了、panel 也对了、背光也亮了,但就是没画面”的时候,第一件事就该检查 route 节点的状态,而不是一头扎进时序参数里。

不同 SDK 对 route 节点的命名有一点差异,有的叫 route_mipi,有的叫 route_dsi,老平台可能还有 route_lvds。遇到不清楚的,直接在 SDK 自带的 dts 文件里搜 route 就能找到本平台的命名规范。调屏前把这条链路自己在 dts 里追一遍,能省下大量在群里问“为什么黑屏”的时间。

2. U-Boot 的显示体系和内核不在一个频道,但吃的是同一锅饭

2.1 U-Boot 显示 logo 的典型流程

U-Boot 为什么要管显示?主要就一个目的:开机时显示 logo,或者在 Android 方案的 recovery 模式、fastboot 模式下给用户一个交互画面。它的显示初始化流程和内核 DRM 驱动的流程很像,但简化得多。

大致是这样的:U-Boot 启动后,如果 defconfig 里打开了显示驱动,相关驱动会去解析 DTS 里的 VOP 节点、route 节点、panel 节点和 backlight 节点;然后初始化 VOP 的时钟和控制器;再根据 panel 节点里的时序参数配置对应的显示接口,比如 DSI 的 lane 数、时钟频率;接着初始化 GPIO 完成屏幕复位上电,打开背光;最后往 framebuffer 里写入 logo 内容。

注意一个细节:U-Boot 的显示驱动功能上比内核精简很多,尤其对 MIPI DSI 屏幕,有些屏需要一长串厂商初始化命令,U-Boot 的驱动可能只执行了其中一部分,甚至有些 SDK 版本根本不执行,而是依赖屏硬件上电后的默认状态。这就导致同样一块屏,在内核里正常,在 U-Boot 里却花屏或者不亮。遇到这种现象,不要急着改 U-Boot DTS,先确认 U-Boot 驱动到底有没有执行你屏需要的初始化序列。

2.2 同一个 DTS 怎么同时服务两套驱动

回到核心问题:为什么调屏时不用改 U-Boot DTS?

真正的技术关键在于,RK SDK 里 U-Boot 的板级 DTS 文件,通常会 include 内核的板级 DTS 文件。举个例子,SDK 的 u-boot/arch/arm/dts/ 目录下有一份 rk3568-evb.dts,文件内容往往不是从零开始写的,而是像这样:

#include "rk3568-evb.dts"

把它自己需要的一些 U-Boot 专属属性追加进去,比如 chosen 节点、u-boot,boot0 标识、bootph-all 属性等。这意味着你在内核 dts 里写的 panel 节点、backlight 节点、route 节点,U-Boot 在编译时也能看到。两套驱动吃的是同一棵设备树,只是站在不同角度去解析而已。

所以更准确的说法是:不是“不用改 U-Boot DTS”,而是“U-Boot DTS 不需要单独维护一份屏参”。你在内核 DTS 里改的屏参,本质上已经是 U-Boot 能看到的参数了。调屏时你真正要做的是修改共享的那份 DTS,然后重新编译 U-Boot,让新的设备树打包进 uboot.img。要是你的调试流程是单独加载内核 dtb、不走 U-Boot 显示,那连重编 U-Boot 这一步都可以省掉。这就是网上很多教程说“不用改 U-Boot”的真相。

2.3 defconfig 才是 U-Boot 显示的“总开关”

很多时候你觉得自己“没改 U-Boot 就亮了”,其实是因为 SDK 默认的 U-Boot defconfig 里已经打开了显示驱动。U-Boot 的显示能力不是靠 DTS 决定是否编译的,而是靠 Kconfig 配置。

在 U-Boot 的 defconfig 里,通常会有类似这样的配置项:

CONFIG_ROCKCHIP_DRM_DISPLAY=y CONFIG_ROCKCHIP_DRM_DSI=y CONFIG_ROCKCHIP_DRM_EDP=y CONFIG_ROCKCHIP_BACKLIGHT=y

不同 SDK 版本的宏名会有些区别,但思路一致。这些配置决定了 U-Boot 会不会把显示相关驱动编译进固件。如果某个平台把显示驱动整体裁剪掉了,那你把 DTS 改成花也没用,U-Boot 根本不会去解析显示节点。反过来,如果这些宏默认是打开的,那么 U-Boot 只要能在 DTS 里找到合法节点,就会尝试初始化。

这里就出现了一个调屏时很常见的误区:有人改了 DTS 里所有显示参数,U-Boot 仍然不出 logo,于是怀疑是 DTS 哪里写错了,折腾半天。其实问题可能在 defconfig 的开关上,或者在 U-Boot 的板级文件有没有正确 include 那颗内核 DTS。遇到 U-Boot 显示问题,先查“开关”再查“参数”,不要一上来就改屏参。

2.4 内核接管后,U-Boot 的显示工作就结束了

还有一个容易让人困惑的点:U-Boot 已经把屏点亮了,为什么内核起来后又要重新初始化一遍?

因为 U-Boot 和内核是两套独立的驱动,U-Boot 初始化完的寄存器状态,在跳转到内核后并不会被完整继承。内核的 DRM 驱动会重新 probe 硬件,重新配置 VOP、重新发 DSI 初始化序列、重新调背光。所以 U-Boot 阶段的点亮,本质上只是“早班员工先开了灯”,内核起来后会再开一次。只要两边读的 DTS 参数一致,这个过程用户无感;如果两边参数不一致,就会出现 logo 正常但内核起来黑屏,或者 logo 花屏但内核显示正常这类奇葩现象。

RK 为了让这个交接更顺滑,在 U-Boot 和内核之间还留了一些 framebuffer 交接机制,本质上也是靠 DTS 里的 reserved-memory 配置来对齐地址。但不管机制多复杂,源头都是同一份设备树。所以调屏时把内核 DTS 作为“唯一权威版本”,让 U-Boot 跟着走,是最不容易出错的姿势。

3. 调屏实操:以 MIPI DSI 屏为例,主要工作量其实在内核 DTS

3.1 开工前必须从屏厂拿到的参数清单

调屏不是拿到一块屏就开写 DTS,没参数就是盲人摸象。我一般开工前会找屏厂要一份屏规格书,并且重点核对下面这些东西。

  • 分辨率:hactive、vactive,这个不用说。
  • 时序:hback-porch、hfront-porch、hsync-len、vback-porch、vfront-porch、vsync-len,以及 clock-frequency。
  • DSI 配置:几 lane,数据格式是 RGB888 还是 RGB666,DSI 时钟频率。
  • 供电:屏需要的 VCC、IOVCC、VCI 这些电压值,以及上电顺序。
  • 复位时序:reset 脚是低有效还是高有效,拉低多久,然后再拉高等多久才能发起操作。
  • 初始化命令:有些 MIPI 屏上电后需要主控通过 DSI 发一串初始化命令,这些命令序列屏厂通常会提供十六进制数组。
  • 背光:背光 PWM 频率、使能脚电平、默认亮度。

特别强调一下复位时序。不同屏差距非常大,有的只要 1ms,有的要 10ms。曾经遇到一块屏,reset 拉低时间不够,导致屏偶尔白屏,改长时序后问题消失。这种问题是 DTS 参数不好解决的,往往要在驱动里加延时,或者在上电时序里处理好。

3.2 修改 Panel、背光、route 节点的步骤

以 MIPI DSI 屏为例,典型的内核 DTS 修改包含三个地方。首先是 DSI 控制器下的 panel 节点。

&dsi { status = "okay"; panel@0 { compatible = "panel-mipi-dsi"; reg = <0>; backlight = <&backlight>; reset-gpios = <&gpio3 RK_PB6 GPIO_ACTIVE_LOW>; pinctrl-names = "default"; pinctrl-0 = <&lcd_panel_reset>; dsi,lanes = <4>; dsi,format = <MIPI_DSI_FMT_RGB888>; display-timings { timing0 { clock-frequency = <140000000>; hactive = <1080>; vactive = <1920>; hback-porch = <40>; hfront-porch = <40>; hsync-len = <8>; vback-porch = <10>; vfront-porch = <28>; vsync-len = <4>; }; }; }; };

其次是背光节点。背光常见方案是 PWM 背光,也有 GPIO 开关背光的。PWM 频率要注意,一般屏厂会推荐 20k 到 200k 之间,太低会有可闻噪声,太高可能影响亮度调节线性度。下面是一个典型配置。

&backlight { status = "okay"; pwms = <&pwm1 0 20000 0>; enable-gpios = <&gpio1 RK_PA1 GPIO_ACTIVE_HIGH>; pinctrl-names = "default"; pinctrl-0 = <&bl_en>; brightness-levels = <0 255>; default-brightness-level = <200>; };

最后是 route 节点,决定 VOP 和 DSI 接口的绑定关系。

&route_dsi { status = "okay"; connect = <&vopb>; };

这里必须要注意:compatible 字符串要以你 SDK 自带的面板驱动模板为准,我示例里的 panel-mipi-dsi 并不一定适用于所有内核版本。另外,如果屏需要发初始化命令序列,不同 SDK 的写法差别很大,有的放在 panel 节点的 init-sequence 里,有的用 rockchip,cmd 这种自定义字段,还有的直接在驱动里做 switch 分支。别光看网上教程抄,去 SDK 里搜一个现成量产的屏幕节点,照它的格式改,最稳妥。

3.3 U-Boot 侧真正要做的三件小事

内核 DTS 改完后,如果你希望 U-Boot 也能正常显示 logo,U-Boot 侧真正要做的事情其实很少,通常就三件。

第一,确认 U-Boot 的 defconfig 里对应显示接口的驱动宏已经打开。这个前面说过,是总开关。

第二,确认 U-Boot 板级 DTS 包含了内核 DTS。如果你的项目是基于 SDK 默认板子改的,一般已经 include 了;如果是自己新做的板子,要检查 u-boot/arch/arm/dts 下的文件是否引用了你正在改的那颗内核 dts 文件。这一步被很多人忽略,结果内核侧怎么改都正常,U-Boot 永远老样子。

第三,重新编译 U-Boot 并烧写 uboot.img。这里说的“重编 U-Boot”不是改代码,而是让 U-Boot 拿到最新生成的 dtb。如果你是调试阶段通过 fastboot 单独加载内核 dtb,U-Boot 自带 dtb 不参与显示流程,那确实连这一步都不需要。这也是为什么很多帖子里说“调屏不用动 U-Boot”——人家是在单独的调试加载模式下说的,量产烧一体的镜像时还是得把 U-Boot 同步编译一遍。

3.4 如果你想让 U-Boot 阶段也能过 logo,还要多做什么

如果你想在 U-Boot 阶段就把 logo 打出来,并且对显示效果有要求,光靠 DTS 参数同步还不够。需要确认 U-Boot 的显示驱动执行了屏的初始化流程。有些 MIPI 屏的初始化序列很长,U-Boot 的驱动可能没有执行全,这种情况你就得去 U-Boot 的 panel 驱动里补充初始化命令,或者调整驱动的执行逻辑。

另外,logo 的格式和位置也有讲究。RK U-Boot 的 logo 一般放在 resource.img 分区,内核起来后,如果 DTS 里配置了同样的 logo 资源,可以做到从 U-Boot logo 平滑切换到内核 logo,不闪黑屏。如果你发现 logo 显示后有短暂黑屏再亮,多半是内核 drm 重新初始化的过程中清空了 framebuffer,而新的 logo 资源没有及时加载。这时候别急着找 U-Boot 的麻烦,先检查内核侧 logo 资源有没有配好。

4. 什么情况必须动 U-Boot DTS:四个典型场景

4.1 开机 logo 本身就是产品需求

有些产品要求一上电就要看到品牌 logo,并且从按 power 键到出 logo 的时间被死死卡住,比如必须 300ms 内。这时候 U-Boot 显示是必须工作的,U-Boot DTS 里关于 VOP、DSI、panel、backlight 的配置就都必须正确。但这里要分清:绝大多数情况下,你需要的不是“改 U-Boot 专用屏参”,而是“确保 U-Boot 能拿到内核 DTS 里已经改好的屏参”。如果你手头 SDK 的 U-Boot 板级 DTS 没有正确 include 内核 DTS,那你确实需要改 U-Boot DTS 的 include 关系,否则 U-Boot 看不到屏节点。

4.2 有些屏必须由 U-Boot 完成上电复位

某些对时序要求苛刻的屏幕,如果 U-Boot 阶段不做 GPIO 上电和复位,等内核启动到一半才拉屏电源和复位,屏幕就会白屏或者花屏。典型的例子是带 MCU 的 DSI 屏,主控和屏的握手必须在上电后的很短时间内完成,晚了屏可能进入异常状态。

这种屏通常要在 U-Boot 阶段就把电源、复位 GPIO 处理好,甚至要在 U-Boot 的 board 初始化代码里加延时。DTS 层面能做的,是把 panel 节点的 reset-gpios、enable-gpios、pinctrl 配齐,让 U-Boot 驱动在初始化时执行。但如果 SDK 的 U-Boot 驱动本身不解析这些 GPIO,那你就得改 U-Boot 驱动代码,这就超出了“改 DTS”的范畴。遇到这种情况,别死磕 DTS,直接看驱动源码更实际。

4.3 屏参版本分裂:一个屏两套参数

这个场景主要出现在比较老的 RK 平台,比如 RK3288、RK3128 上。早期 SDK 的 U-Boot 里可能还保留着一套独立的 LCDC 面板参数数组,它以结构体或者宏的形式写死在 C 文件里,根本不读 DTS。内核侧则走设备树。两套参数一旦不一致,就会出现 U-Boot logo 正常但内核黑屏,或者反过来。

这种情况下,你不想动 U-Boot 都不行,因为内核 DTS 参数根本传不到 U-Boot 的显示代码里。解决方案有两种:一是把 U-Boot 里的面板参数改成与内核 DTS 一致;二是把旧平台的 U-Boot 显示驱动升级成新平台那套基于 DTS 的 rockchip_display 驱动。具体怎么做取决于项目的改造成本。所以你看,网上那些“RK 调屏不用改 U-Boot”的说法,其实是针对新平台的,老平台该改还得改。

4.4 想裁剪 U-Boot 显示,省启动时间

还有一种反向操作:产品压根不想要 U-Boot logo,希望内核起来后直接出画面。这时候 U-Boot 显示初始化反而成了负担,白白增加几十毫秒启动时间。你可以在 U-Boot DTS 里把 route 节点状态都置为 disabled,或者干脆在 defconfig 里去掉显示驱动宏。这种“动 U-Boot”的本质是关闭显示,不是调屏参。很多 Android 平板方案为了追求开机速度,会做这步裁剪,逻辑上也能明显缩短从上电到 kernel 首帧的时间。

5. 调屏现场常见问题与排查思路

5.1 改了内核 DTS,U-Boot logo 反而不见了

一个常见场景:内核 DTS 改了屏参,U-Boot 原本能显示 logo,重编内核后发现 U-Boot logo 没了。

原因通常是 U-Boot 的 dtb 还是旧参数,或者新 DTS 里 route 节点的状态被改掉了。比如某块屏移除后,你顺手把 dsi 节点 status 改成了 disabled,但 U-Boot 的 dtb 还是老的,它尝试初始化一块已经不存在的屏,初始化失败后直接跳过显示。

排查方法是先看 U-Boot 的串口日志,确认显示驱动有没有探测到 panel;再看 U-Boot dtb 和内核 dtb 的节点状态是否一致。如果确实不需要 U-Boot logo,那就接受现状;如果需要,就重新编译 U-Boot,把最新的 dtb 烧进去。

5.2 U-Boot 有 logo,内核起完黑屏

U-Boot logo 正常,至少说明硬件链路基本是通的,屏和背光都没大问题。内核起来黑屏,问题基本锁定在内核侧:panel 节点的 compatible 没有对应驱动;route_dsi 指向的 VOP 不对;dsi,lanes 与实际接法不一致;或者 DSI 初始化命令没有被内核驱动执行。

在板子上执行:

dmesg | grep -E "rockchip-drm|dsi|panel"

重点看有没有 “failed to find panel” 或者类似的报错。如果有,就说明 panel 驱动没有匹配上,回查 compatible 字符串和内核里 CONFIG_DRM_ROCKCHIP_* 相关配置。如果日志里能识别到 panel,但画面不显示,再查 route 和 vop 的绑定,以及 DSI 的 lane 数。

5.3 屏幕亮但花屏、颜色异常

屏幕能亮,说明电源、背光、VOP 基本工作正常,花屏多半出在数据链路或时序参数上。常见的坑有三个:一是 DSI lane 填错了,屏是 4 lane 接的,你写成 2 lane,后续数据错位;二是颜色格式不对,屏支持 RGB666,你写 RGB888,颜色发白或者偏色;三是时钟频率偏得离谱,导致时序不一致。

排查时先用示波器量 DSI clock 的波形频率,再对照 DTS 里的 clock-frequency。如果是 RGB 接口的屏,花屏大多和 hback-porch、hfront-porch 这些时序参数有关,可以逐项和屏规格书比对。还有一个很容易忽略的问题:U-Boot 和内核两份 dtb 参数不一样,内核重新初始化时会闪一下花屏,然后恢复正常。这种情况严格说不是“花屏”,而是交接过程的数据撕裂,但表现形式很像,也要列入怀疑清单。

5.4 偶发性不亮:多半是时序和 GPIO 默认电平

偶发不亮是最难查的问题,因为很多时候重启一下又好了。我踩过的经验是:优先怀疑复位 GPIO 时序和默认电平。

有些板子用的 GPIO 在上电瞬间是浮空或者低电平,如果 reset 是低有效,可能导致屏在上电瞬间一直处于复位状态,主控初始化时屏根本没准备好。解决办法是在 pinctrl 里给 reset GPIO 配置一个默认状态,比如默认输出高电平,避免上电瞬间抖动。另外,电源到复位的延时、复位释放到 DSI 命令发送的延时,这两个参数在很多屏厂规格书里有明确要求,务必确认驱动里有对应延时。

5.5 日志怎么看:U-Boot 和 kernel 各自的战场

U-Boot 里可以打开 CONFIG_CMD_DM,在串口命令行执行 dm tree,看看 panel 和 backlight 节点有没有被正确绑定。如果驱动报错,一般会在初始化时直接打印在串口,比如 “failed to bind panel” 之类的信息。内核侧看日志就方便多了,dmesg 过滤 drm、dsi、panel、backlight 关键词即可。也可以配合 /sys 节点看背光状态和分辨率。

如果 U-Boot 和内核日志都没有直接报错,但是屏不亮,这时候就得回到硬件层面:先量屏供电,再量 DSI 的 clock 和 data lane 上有没有波形,最后量背光使能脚的电压。软件配置再完美,供电没出来也是白搭。

5.6 常见问题速查表

现象优先排查项可能原因
U-Boot 和内核都黑屏硬件供电、route 节点电源没供上,或 route 被 disabled
U-Boot 不亮,内核亮defconfig、U-Boot dtb显示驱动没编进去,或 dtb 未同步
U-Boot 亮,内核黑屏内核 dmesg、compatible内核 panel 驱动没匹配
花屏DSI lane、clock-frequencylane 数不对或时序参数误差大
偶发不亮reset GPIO 默认电平上电时序、复位时序不满足屏规格
背光不亮enable-gpios、PWM 配置GPIO 默认电平错、PWM 未输出
颜色异常dsi,formatRGB888/RGB666 格式不匹配

我个人干 RK 调屏这几年,最大的体会就是:不要被“U-Boot DTS”这个词吓住。瑞芯微的设计思路是把显示参数的唯一权威版本放在内核 DTS 里,U-Boot 只是搭便车,正常情况下你不需要维护两份屏参。真正让你花掉一个下午的,往往是那些 DTS 之外的东西——GPIO 默认电平、电源时序、屏厂初始化命令能不能在 U-Boot 简化驱动里完整跑完。如果你正在调一块新屏,先把第 3 节那张参数清单拿到手,再按第 5 节的排查顺序走一遍,大概率能少走不少弯路。

最后再分享一个小经验:新项目拿来第一件事,不要急着写自己的 DTS,先在内核源码树里搜一款已经量产的、接口类型和分辨率接近的屏幕节点,复制过来改参数,成功概率远高于从零开始凭感觉拼节点。屏这东西,初始化序列和时序要求非常吃具体型号,找到一个接近的模板,比对着规格书自己猜省太多时间。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询