开头先交代背景。我这段时间一直在搞一块基于 RK3568 的商显方案,主芯片选了瑞芯微 RK3568,系统基于开源鸿蒙 OpenHarmony 标准系统做整机定制。需求倒不复杂:一台设备要同时带一块 HDMI 大屏做广告/信息发布,一块 MIPI 触摸屏给操作员本地交互,后面还想通过 BT1120 往外送一路视频给后级处理设备。说白了,就是要把 RK3568 的多路显示能力在 OpenHarmony 上真正“跑起来、亮起来、各干各的”。这类需求在工业 HMI、自助终端、会议平板、充电桩、楼宇对讲上都特别常见。
这篇文章我会按实际移植顺序来写:先把 RK3568 显示子系统和 OpenHarmony 显示架构的对应关系理清楚,再讲内核 Kconfig 和设备树怎么配,然后到 OpenHarmony 侧的多屏适配,最后是联调和问题排查。全程会带上我实际操作的命令、改过的节点和踩过的坑,尽量做到你拿一台 RK3568 开发板就能照着复现。
这次移植适合谁看?主要是做 OpenHarmony 系统适配、内核/设备树开发的工程师,其次是做整机方案的软硬件工程师,他们需要知道多屏时序、带宽、引脚复用这些硬约束。如果你是刚接触 OpenHarmony 的嵌入式开发者,也可以从这里入手,因为显示子系统是上手操作系统移植最好的“骨架”之一。
1. 需求拆解:RK3568+OpenHarmony多路显示移植到底在移什么
1.1 先搞清楚RK3568能输出哪几路画面
RK3568 是瑞芯微一颗很典型的四核 A55 平台,它的显示控制器不是单个 VOP 出口,而是分成了两个独立的 Video Output Processor,在 Rockchip 内核驱动里通常叫 VOPB 和 VOPL。这两个 VOP 可以独立使能、独立配时序,各自挂不同的显示接口,这是“多路显示”的硬件基础。
RK3568 对外引出的显示接口大致有:
| 接口类型 | 典型分辨率 | 常见应用场景 |
|---|---|---|
| HDMI 2.0 | 最高 4K@60 | 广告机、会议屏、电视机 |
| eDP 1.3 | 最高 2560x1600 | 笔记本模组、一体机、高端 HMI |
| MIPI DSI(两路) | 每路常见 1080P@60 | 车载屏、门禁、平板 |
| LVDS | 1080P@60 | 工控屏、电梯面板 |
| BT1120/并行接口 | 由外部转接芯片决定 | 视频采集、工业相机、后级处理 |
这也就意味着,一块 RK3568 单芯片做“HDMI + MIPI DSI + BT1120”的三路输出,在硬件管脚上是完全可以支撑的。关键是系统侧要能同时枚举出三个 connector,并且在 OpenHarmony 的图形合成器里正确识别它们。
多路显示通常分“同显”和“异显”两种模式。同显就是把同一画面复制到多个屏幕,适合展览展示;异显是每个屏幕显示不同内容,适合“主屏放业务界面、副屏放操作面板”的整机设计方案。我在这个项目里要的是异显:HDMI 出去的是面向客户的大屏内容,MIPI 屏是本地运维操作界面,BT1120 那一路则是给后端图像处理设备的原始视频源。
1.2 从系统框架看移植的三个层级
在 OpenHarmony 上做显示移植,不是只改一个 dts 文件就完事。整个图形链路从底层到上层大致是:内核 DRM 驱动 -> HDF 显示适配层 -> RenderService/Composer 合成 -> 应用侧多屏接口。任何一个层级没对齐,表现出的症状都是“屏幕不亮”或者“亮但没内容”,但排查方向完全不同。
内核 DRM 层是最先要打通的。VOPB/VOPL 驱动要能初始化,HDMI、DSI、BT1120 这些 encoder/connector 要通过设备树被正确 probe,最终在 /dev/dri/card0 上挂出对应的 DRM device。这一层如果没做好,上层图片渲染得再好也送不出去。
HDF(Hardware Driver Foundation)显示适配层是 OpenHarmony 特有的。它相当于把内核 DRM 的能力封装成标准的 Display HDI 接口,供系统的 RenderService 调用。RK3568 在 OpenHarmony 生态里比较成熟,官方和一些开发板厂商已经把这层的通用驱动做好了,我们移植的重点通常是确认它读到了哪个 connector、把哪一路作为主屏。
最上层是 RenderService/Composer 的多屏管理。OpenHarmony 从 3.2 版本开始对多屏的支持逐渐完善,系统可以通过 DisplayManager 的接口枚举所有显示设备,设置主屏、副屏、扩展模式或镜像模式。做系统适配时,我们一般会在 init 或者开机脚本里把默认主屏顺序固定住,避免每次升级后 HDMI 和 MIPI 的显示顺序随机变化。
1.3 平台选择与版本基线
我这次用的基线是 OpenHarmony 4.1 Release 对应的 RK3568 标准系统代码,内核是 Linux 5.10。选这个版本的原因很直接:RK3568 在这条分支上被验证得最多,Rockchip 官方 SDK 里关于 display 的 dts 和 Kconfig 可以直接参考,踩坑资料也相对好找。
如果你刚拿到一块新的 RK3568 开发板,建议先别急着定制,先把官方镜像烧进去,确认单屏显示正常,再去动设备树做多屏。多屏移植的难度不是“加起来”,而是“排列组合”。HDMI 单独能亮、MIPI 单独能亮,不代表两个同时开就能各显各的,这和 VOP 分配、时钟、带宽、GPIO 复用都有关系。我后面会专门讲这些联动问题。
2. 环境准备:内核Kconfig与显示驱动依赖一个都不能漏
2.1 编译环境与代码基线确认
OpenHarmony 的编译环境和普通嵌入式 Linux 不太一样,它走的是一套基于 illumos/clang+GN 的构建系统。我习惯用一台 32GB 内存以上的 Ubuntu 22.04 服务器做全量编译,RK3568 标准系统首次编译大概需要半小时到一小时,和机器磁盘性能关系很大。
代码拉取可以按 OpenHarmony 官方 release 文档操作,用 repo 把 manifest 拿到手,重点确认三个仓库:vendor下的对应开发板工程、device/board下的板级配置、kernel/linux分支对应的内核版本。这三个仓库必须和同一个版本基线匹配,否则很容易出现“内核起来了但 HDF 驱动版本对不上”的问题。
一个很实用的验收习惯:先不改任何代码,直接编译烧录官方镜像,用 HDMI 接一台 1080P 显示器,确认系统能正常开机进桌面。如果这一步都起不来,说明你的环境/烧录流程还有问题,不要带着未知因素去改显示配置。
2.2 显示相关的内核Kconfig逐项核对
RK3568 显示移植最先踩的坑,往往不是设备树写错,而是内核 .config 里根本没编译对应的显示驱动。移植前,请先确认下面这些配置项是打开的:
| 配置项 | 作用 | 漏配的现象 |
|---|---|---|
| CONFIG_DRM_ROCKCHIP | Rockchip DRM 主驱动 | /dev/dri 不出现 |
| CONFIG_ROCKCHIP_DW_HDMI | HDMI 控制器驱动 | HDMI 无输出、dmesg 无 HDMI 相关日志 |
| CONFIG_ROCKCHIP_DW_MIPI_DSI | MIPI DSI 控制器驱动 | DSI 屏黑屏、无 DSI 日志 |
| CONFIG_ROCKCHIP_ANALOGIX_DP | eDP 控制器驱动 | eDP 屏不亮 |
| CONFIG_DRM_PANEL_SIMPLE | 通用 panel 支持 | dts 里的 simple-panel 节点无法 probe |
| CONFIG_BACKLIGHT_PWM | PWM 背光驱动 | 屏幕能出画面但背光不亮 |
修改方式有几种,最干净的是直接改内核 defconfig。以 RK3568 为例,路径通常在kernel/arch/arm64/configs/rockchip_linux_defconfig,加上对应配置后重新编译内核。改完编译后,务必在 ./out 内核产物里检查一遍.config,确认配置真正生效了,不要只看你改了 defconfig 就以为一定进了。
我见过一些同事漏了 CONFIG_BACKLIGHT_PWM,结果 HDMI 屏正常,MIPI 屏亮度一直为 0,黑漆漆一片。这个配置很容易被忽略,因为表现上像背光硬件坏了。
2.3 先跑通一组基线显示配置
在配置全开之后,我建议先做一次“最小显示验证”。不碰多屏,不改复杂 panel,先用默认 dts 把开发板原装的那块屏点亮。验证方法很简单:
- 烧录编译好的 boot 和系统镜像;
hdc shell连接开发板;- 执行
ls /sys/class/drm/,确认能看到类似card0-HDMI-A-1或card0-DSI-0的节点; - 执行
dmesg | grep -iE "drm|hdmi|dsi",看有没有报错和链路建立日志。
这一步通过后,再进入多屏设备树改造。不要试图一步到位把三路显示同时配好,那样出了问题根本不知道是哪个节点的问题。
3. 设备树配置:三路显示节点从零到亮屏
3.1 先理清RK3568显示硬件的拓扑关系
RK3568 的设备树里,显示相关的节点拓扑可以用一句话概括:display_subsystem是总目录,下面挂 VOPB/VOPL 两个显示控制器,每个 VOP 通过 port 与不同的 encoder 连接,encoder 再接对应的 connector。以官方 Linux SDK 为例,常见的节点有vopb、vopl、hdmi、dsi0、dsi1、edp、bt1120,还有一大堆route_hdmi、route_dsi0这类路由节点。
很多移植失败的根因,是只开了&hdmi { status = "okay"; },却没开对应的&route_hdmi。这在 Rockchip 的驱动设计里是专门的控制开关:route_xxx节点决定某个显示接口和哪个 VOP 绑在一起,status = "okay"表示这个显示通路要被激活。二者缺一不可。
不同 SDK 版本之间,节点命名会有差异。比如有的版本是&hdmi_in_vp0,有的直接是&route_hdmi { connect = <&vp0_out_hdmi>; };。我建议以你手头 SDK 里arch/arm64/boot/dts/rockchip/下现成的 evb dts 文件为参照,不要盲目照抄社区里别的版本的写法。
3.2 HDMI输出节点配置参考
下面这段是我在这台设备上使用的 HDMI 相关节点配置,基于常见 RK3568 SDK 整理,节点名以你实际 SDK 为准:
&hdmi { status = "okay"; }; &hdmi_in_vp0 { status = "okay"; }; &route_hdmi { status = "okay"; connect = <&vp0_out_hdmi>; }; &hdmi_sound { status = "okay"; };这段配置的含义是:把 HDMI 控制器挂到 VP0 上,并激活 HDMI 路由。hdmi_sound开不开取决于你有没有用 HDMI 音频,我用到的场景不需要音频,但保留也没问题。
如果 HDMI 插上后分辨率不对,或者始终以低分辨率输出,常见原因是显示器的 EDID 读取失败。可以在内核日志里看drm相关输出,确认有没有EDID读取记录。也可以在 dts 里给 HDMI 节点加上固定的display-timings或者允许驱动使用video=参数强制指定分辨率,不过这是我后面推荐的“最后手段”,正常情况应该优先让 EDID 自动识别。
3.3 MIPI DSI屏的配置与背光控制
MIPI DSI 屏的配置比 HDMI 复杂,因为面板的时序、初始化序列、背光、复位 GPIO 全都要在设备树里描述清楚。我的 MIPI 屏是一块常见的 1080P IPS 屏,驱动 IC 本身不需要复杂的初始化序列,所以我直接用 simple-panel 节点加 display-timings:
/ { backlight: backlight { compatible = "pwm-backlight"; pwms = <&pwm4 0 25000 0>; brightness-levels = <0 20 40 80 120 160 200 255>; default-brightness-level = <4>; power-supply = <&vcc3v3_lcd0_n>; status = "okay"; }; panel: panel { compatible = "simple-panel"; backlight = <&backlight>; enable-gpios = <&gpio0 RK_PC6 GPIO_ACTIVE_HIGH>; display-timings { native-mode = <&timing0>; timing0: timing0 { clock-frequency = <148500000>; hactive = <1920>; hback-porch = <148>; hfront-porch = <88>; hsync-len = <44>; vactive = <1080>; vback-porch = <36>; vfront-porch = <4>; vsync-len = <5>; hsync-active = <0>; vsync-active = <0>; de-active = <0>; pixelclk-active = <0>; }; }; status = "okay"; }; }; &dsi0 { status = "okay"; panel@0 { compatible = "simple-panel"; reg = <0>; backlight = <&backlight>; }; ports { port@1 { reg = <1>; dsi0_out: endpoint { remote-endpoint = <&vp0_out_dsi0>; }; }; }; };这里提两个容易出错的细节。第一,pwm4的25000是 PWM 周期,单位是纳秒,对应 40kHz。背光频率太低会听到电流声,太高又可能对触摸屏产生干扰,我用 20kHz 到 40kHz 之间比较稳,具体看你屏厂推荐值。第二,enable-gpios这个复位/使能引脚的时序非常重要,如果驱动 GPIO 被复用成了别的功能,屏幕就会出现“初始化了但一直黑”的情况。
VOP 的分配这里也要注意:如果 HDMI 已经占了 VP0,DSI 那一路建议挂到 VP1,否则两路共用一个 VOP,带宽和扫描时序容易互相干扰。不同内核版本的 route 写法不一样,我用的是&vp0_out_dsi0这种 endpoint 连接方式,你那边可能是&dsi0_in_vp1之类的节点,原理是一样的。
3.4 eDP屏与BT1120的补充思路
eDP 屏的配置和 DSI 很像,也需要 panel 节点、背光节点、eDP 控制器节点以及路由节点。区别在于 eDP 有 Link Rate、Lane Count 这些参数,以及有时需要用edp-panel的 compatible 让驱动去读 eDP 屏的 DPCD 信息来自动协商链路。如果你手持的是一块 eDP 笔记本屏模组,不知道详细参数,优先走自动协商,不要死填 timing。
BT1120 这路比较特殊。它不是接常规 LCD 的,而是把 VOP 输出的数字视频信号以 BT.1120 并行格式送出去,给外部视频处理芯片或采集设备用。配置思路同样是三件套:控制器节点、输入路由节点、路由节点状态。但实际调通后你会发现,BT1120 本身不含像素时钟恢复,对后端设备的同步要求更高,需要和后端联调水平/垂直同步信号。
我没有在这台设备的 BT1120 上做复杂的内容合成,只是把其中一路 VOP 的画面直接送出去。如果你也想这么干,务必确认后端设备要求的色彩空间和时序,BT1120 通常是 YUV 信号,不是普通 LCD 用的 RGB,转换没做对的话画面会整体偏色或者绿屏。
4. OpenHarmony侧适配:主屏顺序与多屏策略落地
4.1 HDF显示设备注册与Composer识别顺序
设备树配置好、内核能枚举出多个 connector 之后,OpenHarmony 的 HDF 显示驱动会在系统起来的时候扫描 DRM device,创建对应的显示设备。多数 RK3568 的 OpenHarmony 移植包已经带好了这块逻辑,但你会遇到一个新问题:到底哪块屏是主屏?
主屏顺序直接决定了开机 Logo 显示在哪、RenderService 默认把桌面绘制到哪。它不是一个运行时的“自动行为”,而是由 HDF 驱动扫描 connector 的顺序决定的,扫描顺序又和 dts 中节点的注册顺序、内核枚举顺序有关。最可靠的做法是在系统侧通过配置文件固定下来。
具体做法看你的 vendor 包怎么组织。有些 RK3568 工程里,显示相关的 HDF 配置在/vendor/etc/display_config/下,里面会有类似display_device_config的文件,可以指定屏幕尺寸、刷新率、方向;有些则是在 init 脚本里通过属性控制。我建议先跑一条命令看当前系统识别的显示设备:
hdc shell hidumper -ls在列出的服务里找到显示相关服务,再 dump 具体信息。不同版本服务名不一样,所以我没法给你一个固定的命令参数。你只要确认“系统确实看到了两个屏幕”,这一步就算完成了。
4.2 主屏、副屏、异显与同显的落地方式
模式的选择要看你的产品定义。我这个项目里,HDMI 是“对外展示面”,MIPI 是“本地操作面”,二者显示内容完全不同,属于异显。
OpenHarmony 系统级有没有提供“直接配置异显”的命令行开关?说实话,这个能力在不同版本里成熟度差异很大,4.0 之前主要靠应用层代码调多屏接口来实现,4.1 之后系统能力完善了很多,但也还没有一个像 Androidwm set-user-rotation那样通用统一的命令。所以我会分两条腿走路:
一是在系统底层的 init 配置里确认两个显示设备都能被正常枚举和打开,保证整机不会因为“副屏没准备好”导致主界面卡顿或者渲染异常;
二是在业务应用层通过 DisplayManager 接口动态创建虚拟屏或者把不同 UIAbility 投到不同屏幕。对系统移植工程师而言,你要做的最重要的事情就是保证底层把两块屏都“亮出来、显示正常”,上层的业务分流是应用工程师的事。
如果你的产品只需要“同显”,那简单很多,系统通常默认支持镜像模式,你只要在底层保证两个屏都能出画面即可。异显则要关注两个屏幕的分辨率和刷新率差异,如果一块 4K 一块 800x480,合成器把内容从 4K 缩放到小屏上,容易出现锯齿和布局错乱。
4.3 开机亮度、屏幕方向与触摸校准
多屏设备还有一个绕不开的细节:副屏的方向和触摸坐标。设备树里 VOP 输出的画面顺序和面板物理安装方向如果不一致,就会出现“开机画面横的、触摸坐标竖的”的诡异问题。
方向修正一般在 HDF 显示配置里做,有些 RK3568 工程支持在display_device_config里配置旋转角度。触摸坐标的映射则复杂一些,因为触摸 IC 驱动报出来的是原始坐标,指示的是触摸面板的物理方向,系统在不做任何处理时,默认认为触摸方向和显示方向一致。你把屏幕旋转了 90 度,但触摸没跟着转,就会出现点 A 出 B 的错乱。
这种情况我会在 HDF 或内核 input 层面做坐标变换。注意,触摸和显示是两条独立的链路,一定分别验证:先用系统的触屏测试页面画线,看笔画是否跟手、方向是否正确,再调整坐标映射参数。我见过太多工程师改了一下午显示方向,最后发现问题根本在触摸方向没同步。
5. 联调实战与排查:黑屏、花屏、顺序错乱一次讲透
5.1 多路显示从设备树到开机亮屏的完整联调步骤
三路显示都配好后,我按下面的顺序做了联调,每步都有明确的验证手段,强烈建议你也按这个顺序来:
- 确认设备树编译通过,烧录后
dmesg | grep -iE "drm|hdmi|dsi|edp"没有任何 fatal error; - 查看
/sys/class/drm/,确认 HDMI、DSI 等 connector 节点都枚举出来,status是connected; - 先断开 MIPI 屏,只留 HDMI,确认 HDMI 单独能出画面且分辨率正确;
- 再断开 HDMI,只留 MIPI 屏,确认 MIPI 能起来,背光亮度正常,触摸可用;
- 双屏同时接上,观察主屏是哪一个,确认开机 Logo 和桌面在预定的主屏上;
- 最后接 BT1120 后端设备,确认第三路视频信号有稳定的同步信号和画面帧。
第 5 步最容易出问题。两个屏都接上后,如果主屏不是你想要的那块,先别急着改代码,用cat /sys/class/drm/card0-HDMI-A-1/status这类命令确认每个 connector 的连接状态,再看内核日志里 VOP 绑定的打印顺序。排查思路是“先确认设备树绑定的 VOP 对不对,再调整 HDF 或 init 侧的主屏优先级”。
5.2 高频问题速查表
下面这张表是我这次移植和以往给 RK 平台做显示适配时,总结出来的最高频问题集合,每一行都对应一个真实踩过的坑:
| 现象 | 可能原因 | 排查命令/手段 | 处理建议 |
|---|---|---|---|
| 开机后所有屏黑屏 | 内核 .config 缺少 DRM/显示驱动 | ls /dev/dri | 核对 Kconfig,重编内核 |
| HDMI 有信号但分辨率很低 | EDID 读取异常 | dmesg 搜 edid | 检查 HDMI 线材/座子焊接,或用 video= 强制分辨率 |
| MIPI 屏黑但背光亮 | panel 初始化时序不对 | dmesg 搜 dsi | 核对 enable/reset GPIO 时序,确认 init 序列 |
| 屏亮但显示花屏 | 时序参数不对或像素时钟误差 | 对比屏规格书 timing | 核对 hactive/hback-porch 等参数 |
| 双屏同时开只有一个亮 | VOP 绑定冲突或带宽不够 | dmesg 搜 vop/dclk | 检查 route 是否重复指向同一 VOP |
| 副屏显示主屏内容,异显失败 | 系统层未做多屏模式设置 | 应用层查 DisplayManager | 应用侧调用多屏接口,或确认系统版本支持异显 |
| 屏幕方向不对 | 面板安装方向与 VOP 输出不一致 | 旋转配置测试 | 在 HDF 显示配置里做旋转 |
| 触摸坐标与画面方向不一致 | 触摸驱动未做坐标变换 | 触屏画线测试 | 调整 input 坐标映射 |
| 屏幕偶尔闪一下 | PWM 背光频率偏低或干扰 | 观察闪烁频率 | 调整背光 PWM 频率 |
其中“双屏同时开只有一个亮”是我在所有 RK 多屏项目上遇到最多的问题。不只是 RK3568,RK3588、RK3399 上同样存在。多数情况是route_xxx节点的connect属性写重复了,两个接口都指向了同一个 VOP 的同一个 port,导致另一个 VOP 没有可用的数据源。你可以用 dmesg 搜索vop相关的 bind 日志,确认每个 VOP 到底绑了哪个显示接口。
5.3 容易忽略的硬件与资源冲突
设备树改到“看起来全对”,但屏幕就是不亮,这类问题十有八九出在硬件资源冲突上。RK3568 的不少引脚是复用的,一个 GPIO 可能既支持 I2C 又支持 PWM 又支持 GPIO 中断,一旦某个外设驱动先 claim 了这个引脚,显示链路再去申请同一个引脚就会失败。
比如,我这次调试时发现 BT1120 的某个数据引脚和板子上一个 LED 的 GPIO 冲突,导致 BT1120 一直初始化失败。这类问题查起来特别费时间,因为软件日志不会直接告诉你“引脚被占了”,只会显示一个模棱两可的 timeout。排查时试着把不相关的外设节点先 disable 一批,逐步二分定位。
另外,多路显示对电源的瞬态功耗要求更高。两块屏同时点亮的时候,瞬间电流可能比单屏大很多,如果板子的 3.3V/5V 电源余量不足,会出现“单屏没问题、双屏总有一颗不亮”的诡异现象。遇到这种问题先量电压,不要一上来就怀疑软件。
最后再提醒一点,多路显示联调时,确认每块屏的复位 GPIO 要有足够的延时。我的经验是上电时序硬性要求:先供屏电源,再拉复位,再发初始化序列,最后打开背光。顺序反了很伤屏,也会导致随机黑屏。
写在最后
多路显示移植这件事,本质上是在硬件能力、内核驱动、系统框架三个层面之间找平衡。RK3568 的硬件能力非常强,OpenHarmony 的显示架构也足够现代,双方接驳的难点反而不是某一项技术的“深水区”,而是那些细碎的节点名、配置项、时序参数、引脚复用问题。你只要按“先单屏、再多屏、再异显”的顺序一步步来,大多数问题都能在日志里找到答案。
我个人的一个体会是:在做系统移植时,别急着把全部功能一次性打开,先把一条最小路径跑通,再逐步叠加。以显示为例,就是先让一块屏完美点亮,然后增加第二块、第三块。每增加一路,只改一个变量,验证一个变量,这样就算踩坑也能迅速定位。如果你在 RK3568 的 OpenHarmony 多屏移植上也遇到类似问题,欢迎按我上面整理的排查思路去试,多数情况下问题都能锁定在设备树和资源冲突这两个方向。