前阵子在RK3568上调多路显示,从内核设备树一路折腾到OpenHarmony的HAL层,把HDMI、DSI、RGB三路输出全部点亮,顺便解决了同显和异显的问题。今天把整个RK3568平台上的OpenHarmony多路显示移植过程拆开聊聊,希望能给正在做智慧屏、收银机、工业HMI或者任何需要多屏交互设备的开发者一点参考。
先说清楚这篇文章覆盖什么内容:从OpenHarmony显示子系统的整体架构讲起,给你们理清RK3568的多路显示硬件资源,然后按实战顺序讲设备树怎么配置、HDF驱动怎么接入、用户态Display HAL怎么注册多屏,最后梳理调试过程中最容易踩的坑。适合两类人看:一类是从嵌入式Linux转过来做OpenHarmony的人,你对DRM/KMS已经熟了,只缺HDF这层;另一类是刚接触瑞芯微平台的新手,你需要知道多路显示移植不只是改个dts这么简单,从上层窗口管理到下层像素时钟,任何一环没对上,屏就是不亮。
1. 多路显示需求与OpenHarmony显示子系统设计
1.1 为什么多路显示是刚需而不是噱头
很多开发者拿到RK3568开发板,第一件事是跑桌面,第二件事就是接第二块屏。单屏在消费电子领域够用,但一旦进到行业场景,双屏甚至三屏是标配:标准收银机一定是主屏给店员操作、副屏给顾客看价格;广告机和信息发布终端需要主屏播内容、副屏做运维监控;工业HMI设备通常需要一块触摸屏做交互,同时从HDMI输出一份画面到车间大屏。
多路显示按工作模式分三种:同显(Mirror),所有屏幕显示一模一样的内容;异显(Extend),每块屏显示独立的画面;还有一种少见但实用的独立显示模式,每块屏由完全独立的应用驱动,互不干扰。OpenHarmony在这三种模式下都能支持,但底层呈现的方式完全不同。同显最简单,很多平台在硬件合成器层面就能做到;异显需要每个显示设备独立管理和合成,对驱动和窗口管理器都有额外要求;独立显示则对HAL层的display id管理、Multi-screen策略要求更高。
做多路显示移植,本质上就是解决两个问题:第一,让内核里每一条显示链路都稳定点亮,这涉及DRM/KMS驱动的device tree配置和panel驱动初始化;第二,把点亮的链路正确注册到OpenHarmony的用户态Display HAL,让系统把它当成一个合法的Display设备,并能被窗口模块和RenderService调度。这两步拆开来看都不算特别难,但合在一起,坑就成倍增长。
1.2 OpenHarmony显示链路和HDF框架
OpenHarmony的显示链路可以简化理解为一条流水线:应用把UI画到Surface上,RenderService负责合成,合成结果通过Composer接口下发到硬件,硬件再通过DRM/KMS或自定义驱动把画面输出到屏幕。
这套架构里有两个核心的抽象域:一个是native层的Display模块,负责窗口、Surface、合成器、vsync这些逻辑;另一个是内核层的显示驱动。RK3568的OpenHarmony移植,内核用的还是标准的Rockchip DRM驱动,基于Linux内核的drm/rockchip模块实现,这点和普通嵌入式Linux完全一致,你之前会调Linux显示驱动,到了OpenHarmony也一样用得上。
HDF(HarmonyOS Driver Framework)负责连接这两个域。OpenHarmony的显示HAL定义了一组HDI(Hardware Device Interface)接口:Display Layer、Display Composer、Display Gralloc、Display Gfx。这些接口一头对接上层RenderService,一头对接底层内核设备节点。RK3568平台要做的多路显示移植,主要工作就在这层:解析底层DRM枚举出来的多个Connector,为每个Connector创建一个Display设备,提供Layer管理、Buffer申请、硬件合成这些能力。
1.3 RK3568硬件能力盘点
RK3568这颗芯片,做多路显示有它得天独厚的一面。它内部集成了一个VOP2(Video Output Processor v2),这代显示控制器的设计目标很明确——多路独立输出。VOP2内部有多个Video Port(VP),每个VP相当于一条独立的显示通道,具备独立的时钟、独立的图层管理、独立的色彩处理能力。
RK3568的显示接口资源按EVB开发板常见配置来看,通常包含:一路HDMI(HDMI 2.0,支持4K60)、一路eDP或DP、两路MIPI DSI(每路支持4-lane MIPI,常见分辨率可以到1080P或2K)、一路RGB并口(通常用于低成本LCD)。接口集合在三块主要的复用组里,VP的数量决定了你可以同时驱动多少路显示。
注意:VP数量是判断多屏能力的第一指标。RK3568的VOP2版本在标准配置下提供了三个可用的Video Port,这意味着理论上可以同时出三路画面。但要结合具体封装、BSP版本和板级设计,有些复用的MUX在引脚排列上会限制同时使用的接口组合。
一图流理解VOP2的对应关系:VP0再接HDMI,VP1接DSI0,VP2接RGB接口,这是最典型的三屏配置。但你完全可以把VP0接DSI0、VP1接eDP,组合很灵活,关键看设备树route节点怎么连。接口和VP的对应关系由设备树中的route_xxx节点定义,这点后面细说。
2. 移植前准备:环境、源码与显示通路选型
2.1 编译环境与源码准备
做OpenHarmony的RK3568移植,源码和编译环境建议按官方标准流程来。我踩过的第一个坑是环境版本问题——OpenHarmony的编译链非常依赖特定版本的Ubuntu、Python和Node.js,与其自己折腾,不如直接用官方推荐的Docker镜像,或者严格对照官方文档安装环境。
源码准备分几块:
- OpenHarmony主仓代码,里面包含device、vendor、drivers等目录。
- rockchip厂商代码,包含kernel代码、厂商HAL实现和产品配置。
- display组件相关代码,通常位于
drivers/peripheral/display和drivers/hdi/display目录(注意不同版本路径可能有调整)。
编译命令各家版本略有差异,大体是./build.sh --product-name rk3568 --ccache这样的形式。第一次编译RK3568的整套系统,机器配置好点,否则编译时间会劝退你。我的建议是至少16G内存、8核CPU起步,磁盘要留出150G左右,源码加中间产物非常占空间。
2.2 先在Linux SDK上跑通显示链路
这是我想重点强调的经验:在动OpenHarmony之前,先用瑞芯微的官方Linux SDK把显示链路全部跑通。
为什么?因为多路显示的90%问题都出在硬件链路本身——屏幕不亮、花屏、分辨率不对、颜色偏色,这些问题的根源往往是panel初始化时序不对、lane配置错误、像素时钟不稳定,和操作系统的关系不大。如果你直接用OpenHarmony来排查这类问题,你会同时面对两套复杂度:底层硬件的复杂度和系统框架的复杂度。
我的操作路径是:先用Linux SDK的buildroot或Debian镜像启动板子,确认每一块屏在Linux下都能正常点亮、正常显示。然后检查/sys/class/drm/目录,确认有几个connector被正常枚举,例如card0-HDMI-A-1、card0-DSI-1、card0-RGB-1,每个connector的status是否为connected。这一步做完,你就把问题的范围缩小到了OD层——剩下的就是HDF和HAL适配工作。
Linux SDK还有一个好处是驱动版本和OpenHarmony内核对得上。瑞芯微在BSP里对显示驱动做过大量定制,包括VOP2的modeset策略和带宽分配,尽量保持两边内核一致,可以少踩很多莫名其妙的坑。
2.3 显示通路选型规划
在动手改代码前,先做一张表,把需求落到纸面上。多路显示最容易犯的错误是看到哪里改哪里,改了两天发现接口复用冲突,又要推翻重来。
以我做的三屏方案为例,规划是这样的:
| 显示设备 | 接口 | 目标分辨率 | 建议VP | 工作模式 |
|---|---|---|---|---|
| 主屏 | HDMI | 4K30 或 1080P60 | VP0 | 主显示 |
| 副屏 | DSI0 | 1080P60 | VP1 | 异显/扩展显示 |
| 第三屏 | RGB | 720P60 | VP2 | 独立显示 |
规划的时候要考虑几个约束。第一,分辨率越高对VOP输出带宽要求越大,把所有屏都顶到最高分辨率不现实,要根据实际使用场景取舍。第二,有些接口有引脚复用限制,比如eDP和某些DSI可能共用部分引脚,选型时要看板子的原理图确认。第三,OpenHarmony对主显示设备的定义会影响开机动画和系统UI的默认落点,一般建议把连接最大的屏的那路设为主显示,开机效果比较好看。
这张表要在整个移植过程中反复参照,每一项修改都要问一句:这处改动会不会影响我这张表里的规划。
3. 多路显示HDF驱动移植实操
3.1 设备树配置要点
设备树是整个多路显示移植的第一道关口。RK3568的显示设备树配置分布在几个层次:SoC级dtsi定义硬件控制器节点,板级dts里定义接口状态和route节点,panel子节点定义屏幕参数。
主要看这几个部分。
第一是VOP2节点。需要确保&vop2节点状态为okay,并配置对应的输出接口:
&vop2 { status = "okay"; }; &hdmi { status = "okay"; }; &dsi0 { status = "okay"; panel@0 { compatible = "xx,xx-1080p"; reg = <0x0>; backlight = <&backlight0>; reset-gpios = <&gpio0 RK_PB5 GPIO_ACTIVE_LOW>; }; }; &rgb { status = "okay"; };第二是route节点。Rockchip的dts里,route节点定义了VP和输出接口之间的绑定关系,这是多路显示最关键的配置。例如:
&route_hdmi { status = "okay"; connect = <&vp0>; }; &route_dsi0 { status = "okay"; connect = <&vp1>; }; &route_rgb { status = "okay"; connect = <&vp2>; };每个route节点有一个connect属性,指向具体的VP。用这种方式,VP0的输出给到HDMI,VP1给到DSI0,VP2给到RGB,三路同时开启互不干扰。
第三是内存配置。多路显示同时开启时,需要为显示buffer预留足够的内存。Rockchip的drm驱动一般会从CMA或IOMMU分配显存,如果CMA配置太小,三路显示同时申请buffer时可能分配失败,现象就是其中一路无法正常工作。常见做法是在dts里给display子系统预留合适的memory region:
reserved-memory { #address-cells = <2>; #size-cells = <2>; ranges; linux,cma { compatible = "shared-dma-pool"; reusable; size = <0x0 0x4000000>; linux,cma-default; }; };注意,具体size要根据你的显示分辨率和帧数来算。一路1080P60,RGB888格式,单帧需要约1920×1080×4字节≈8MB,双缓冲就需要16MB左右。三路同时跑,再留一些余量给其他DMA用途,128MB到256MB是比较常见的配置。
3.2 Panel驱动与初始化序列
设备树里声明了panel节点,还需要对应的panel驱动来初始化屏幕。如果你用的屏幕比较常见(比如市面上一堆京东方、天马、群创的MIPI屏),内核drm/panel目录下通常有现成的驱动可用,或者厂商BSP里已经适配过。你要做的只是在dts里把compatible和初始化参数改一下。
最麻烦的是新屏幕。MIPI DSI屏的初始化通常包含一组特定的命令序列(init_cmd),包括进入睡眠模式、设置显示参数、打开电源、退出睡眠模式等。这些命令序列都要从屏幕的specification里拿到。写panel驱动的时候,核心是把init_cmd数组按规定的延时填充正确:
static const struct drm_display_mode default_mode = { .clock = 148500, .hdisplay = 1920, .hsync_start = 1920 + 152, .hsync_end = 1920 + 152 + 80, .htotal = 1920 + 152 + 80 + 188, .vdisplay = 1080, .vsync_start = 1080 + 20, .vsync_end = 1080 + 20 + 4, .vtotal = 1080 + 20 + 4 + 40, .flags = DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC, }; static const struct panel_init_cmd dsi_init_cmds[] = { _INIT_CMD_OPC(0x80, 0x00), _INIT_CMD_OPC(0x81, 0x00), ... };参数的每一个数字都对应屏幕时序spec里的一行,hdisplay、htotal、clock这些要按面板厂商给定的值来填,乱填的结果是屏幕能点亮但画面偏移、闪动甚至直接黑屏。
提示:调到新屏的时候,先查内核日志里有没有
panel_simple_probe或对应的probe成功日志。如果panel节点probe都没过,后面一切都是空的。
3.3 用户态Display HAL的多屏注册
内核层面把显示链路都跑通之后,真正让OpenHarmony识别多屏的工作才开始。
OpenHarmony的显示HDI实现,在Rockchip平台的完整路径大致是:HDF驱动加载时读取/dev/dri/card0,通过libdrm枚举CRTC和Connector,每检测到一个connected的Connector就创建一个HDF Display设备。这个Display设备会上报到Display Service,最终展现为一个逻辑显示器。
我在RK3568的OpenHarmony HDI实现里看到的流程大概是这样的:
- 调用
drmModeGetResources获取DRM资源,得到每个Connector的ID。 - 遍历Connector,检查
drmModeConnectorGetPossibleCrtcs和连接状态。 - 对每个connected的Connector创建一个
DisplayDevice实例,分配其对应的CRTC、plane等资源。 - 把多个DisplayDevice加入全局链表,并标记主从关系(PrimaryDisplay / ExtendDisplay)。
如果你发现副屏在OpenHarmony里没有出现,排查方向通常是:底层drm的connector状态是否为connected,HAL枚举逻辑是否只取了第一个connector,以及Display Service侧的显示策略是否把副屏当成合法的display设备。
还要关注hotplug。HDMI这种支持热插拔的接口,拔插时内核会通过uevent事件通知用户态。OpenHarmony的Display HAL需要监听这类事件,实时更新connector状态。如果这部分没实现,会出现开机时没插HDMI,后续插上也不会有反应的情况。监听方法一般是drmHandleEvent里处理DRM_EVENT_HOTPLUG,再重新枚举connector。
3.4 应用层快速验证副屏输出
驱动都适配完了,怎么快速验证副屏能出画面?最直接的方式是写一个简单的OpenHarmony应用,创建一个窗口并指定它的displayId为副屏的ID,然后往这个窗口里画点内容。
OpenHarmony的窗口创建默认落在主屏上,要让窗口显示到副屏,需要设置窗口的display id属性。具体API在不同版本略有差异,大致是通过wm模块创建Window,然后window.setWindowDisplayId(displayId)来指定。副屏的displayId可以通过DisplayManager的getAllDisplays()查询到。
还有一个更轻量的验证路径是跑LVGL。如果你熟悉嵌入式UI开发,可以直接在OpenHarmony上编译一个基于LVGL的Native程序,通过NAPI拉起一个Surface,把LVGL的framebuffer画上去,再指定displayId输出。这个方法对验证屏幕颜色、更新帧率非常直观。LVGL移植到OpenHarmony侧的难点不在显示驱动,而在输入事件接入,但作为纯显示验证,足够用。
4. 显示合成、同显异显与常见调试
4.1 同显与异显的实现思路
多路显示驱动跑通之后,下一步是解决合成模态问题:你要同显、异显还是独立显示?
先说异显,这是RK3568最自然的工作模式。因为VOP2每个VP都独立输出,OpenHarmony只要为每路显示创建独立的Display设备,上层就能往不同屏上渲染不同内容。主屏正常走RenderService,副屏同样独立合成,等于系统里有两条完整的显示流水线。这种模式对驱动改动最小,我推荐所有没有特殊需求的项目默认用异显。
再说同显。如果你希望两块屏显示一模一样的内容,有两个实现思路。第一个思路是在VOP层做镜像输出,但Rockchip的VOP2镜像能力不是所有输出接口都能同时用,而且需要dts里额外配置mirror相关属性,不是所有BSP版本都开放了这套接口。第二个思路是在渲染层做内容复制——OpenHarmony支持一个窗口同时输出到多个Display设备,应用层或窗口管理器把同样的画面内容同步推送到两个display上,这种方式更通用,代价是多占一倍的合成带宽。
独立显示模式最灵活,但OpenHarmony侧需要额外的策略代码。你需要监控每个Display的事件,管理每个Display的应用窗口栈,确保副屏上不会出现主屏的系统UI元素。在行业设备里,独立显示模式通常意味着你要在副屏上跑一个自研的HMI应用,其他系统组件不往这个屏上渲染。这块工作更多在应用层和窗口管理层,驱动侧只要确保多屏枚举和vblank事件正常就行。
4.2 带宽与性能注意事项
三路显示同时跑,对芯片和内存的压力会成倍增加。RK3568的显示通路里,VOP2输出到DSI的带宽受限于MIPI DSI的lane数和时钟,HDMI则受限于TMDS时钟。内存侧,三路显示需要同时从DDR读取帧数据,加上CPU和GPU的访问,DDR带宽会成为系统瓶颈。
实测中比较典型的场景:一路4K HDMI + 一路1080P DSI + 一路720P RGB,此时System UI流畅度还能接受,但一旦三路画面频繁更新(比如同时播放视频),就可能出现掉帧或显示撕裂。排查方法是用cat /proc/memory_info或/sys/kernel/debug/dri/0/下的DRM调试节点观察带宽占用。
优化方向有几个:第一,副屏分辨率尽量不用超过1080P;第二,刷新率可以从60Hz降到50Hz甚至30Hz,对工业HMI场景影响不大;第三,DSI接口能用4-lane就不要用2-lane,lane数减半等于把等效像素时钟也减半;第四,注意显示buffer的内存分配策略,尽量使用物理连续的内存减少IOMMU开销,但也要留意算力成本。
还有一点容易被忽略:多路显示会拉高整机功耗和发热。做产品量产验证时,一定要做长时间拷机,看三路显示同时工作多少个小时候温升是否超标,毕竟显示控制器、DDR、panel背光都是耗电大户。
4.3 常见问题排查实录
把所有我遇到的、朋友遇到的典型问题整理成一张速查表,按症状分门别类:
| 症状 | 可能原因 | 排查方式 | 解决方案 |
|---|---|---|---|
| 某一路屏幕完全不亮 | drm链路未枚举/电源未启 | 查dmesg里的``` | |
| vop2/drm/panel```相关日志 | 确保dts里route、panel、backlight节点状态为okay | ||
| DSI花屏或颜色异常 | lane数配置不对/初始化序列缺失/时钟频率不对 | 对照panel spec查命令序列;用示波器量PCLK | 修正panel时序参数和init_cmd |
| HDMI无信号 | 分辨率超规格/热插拔检测没生效 | 换低分辨率看是否恢复;检查hotplug事件 | 确认HDMI带宽支持范围,补齐hotplug处理 |
| 三屏只枚举出两屏 | VP绑定冲突/connector轮询不到 | dmesg查vop2分配的VP;check drm connector state | 调整route节点的connect属性,确保每个VP独立 |
| 画面撕裂或掉帧 | DDR带宽不足/帧缓冲频繁重绘 | 观察视频播放时系统负载 | 降低副屏刷新率或分辨率;优化buffer回收策略 |
| 系统UI只出现在主屏 | Display Service策略未配置扩展屏 | 用DisplayManager查询display列表 | 在OH的配置中标记副屏并设置窗口displayId |
每个诊断步骤,我的习惯是先看内核日志,再看HAL日志。内核日志可以直接定位到crtc、connector、encoder的注册情况;HAL日志可以看到Display设备是否成功上抛。
4.4 独门的调试技巧
这里分享几个常规文档里看不到的排查技巧。
技巧一:善用DRM调试节点。内核编译时开启CONFIG_DRM_DEBUG和CONFIG_DEBUG_FS之后,可以访问/sys/kernel/debug/dri/0/state查看当前的crtc/plane/connector状态,非常直观。比如:
cat /sys/kernel/debug/dri/0/state能看到每个plane挂在哪条channel上,active状态是什么,这对于确认VP绑定关系极其好使。
技巧二:开局先用单路测试,再叠加多路。多路显示调试最容易出现的问题是不知道哪路影响了哪路。我的流程是先把三路都配置好,但只在dts里打开其中一路,确认它在前、后级都工作正常后,再依次打开其他路。如果有某一路打开导致其他路异常,多半是带宽或IRQ资源冲突,不至于让你在黑暗中猜测。
技巧三:用帧率工具量化性能。OpenHarmony提供了一些Debug工具,配合hidumper -s 显示服务可以查看合成帧率。加上-c参数可以dump各模块的数据。观察多屏场景下的帧率降低,可以快速判断合成是发生在硬件还是软件。硬件合成如果一直掉链子,很可能是Layer数量超过硬件限制,触发了GPU合成回退。
技巧四:多关注vblank中断。RK3568的三路显示都有独立的vblank中断,中断不稳定会导致vsync信号异常,进而让上层渲染频率紊乱。用cat /proc/interrupts | grep vop查看中断计数是否持续递增。中断不跳或跳得不均匀,说明时钟配置有问题。
技巧五:背光也是一个隐藏的错误源。有时候屏其实已经点亮了,只是背光没开,看起来像黑屏。排查供电时序时要先确认背光enable引脚和控制信号都正常,再怀疑显示链路。我的经验是先手动给背光引脚拉高,排除最蠢的可能性之后再去查fault。
4.5 关于摄像头等其他外设的联动影响
项目中如果还有摄像头调试(比如OV5695这类MIPI接口的sensor),要特别注意显示和camera会在MIPI通道和DDR带宽上互相竞争。RK3568的MIPI DSI和MIPI CSI共用部分SoC内部通路,如果同时使用大分辨率屏幕和大分辨率摄像头,可能出现DDR带宽峰值超限。遇到视频卡顿或者拍照延迟的时候,先想想是不是多路显示把带宽吃满了。
同样道理,如果你在副屏上跑LVGL这类界面轮询较频繁的应用,也会放大显示带宽问题。做整体性能评估时,别只看单屏数据。
5. 从三路点亮到产品落地的几点体会
文章写到这,其实技术主线已经讲得差不多了。最后说几点我在这类项目中总结的个人体会,也是踩过多次坑之后才明白的道理。
多路显示移植,屏能点亮只是第一步。真正交付到量产阶段,更多时间花在稳定性、兼容性和使用体验上。比如HDMI输入源千万种,有些投影仪、有些老式显示器,时序兼容性极差,分辨率协商经常出问题;DSI屏作为模组,不同批次甚至不同温湿度下的初始化时序都会有细微差异。所以我会强烈建议手头多备几种不同品牌、不同接口的显示器,专门做兼容性测试。
另一个体会是,RK3568在这个价位能做到三路显示,确实是行业产品的实用之选。但能不能把三路都稳定跑起来,完全取决于你对设备树的熟悉程度和对显示链路整体架构的理解。设备树里的一个status不改,屏就是不亮;route节点里一个connect指错了VP,画面上就是花屏;CMA内存给少了,运行一会儿某一路就没输出。整个过程没有捷径,只能一层层查。
最后分享一个小技巧,也是我正在用的工作方法:维护一份专门的多屏验证用例表,把每路屏幕的分辨率、刷新率、模式(同显/异显/独立)、GPU负载、内存占用这些指标登记成表格,每次改动驱动之后,按这套用例回归一遍,看看哪一路显示回退或者劣化。这个习惯帮我免去了很多次“调好第三屏结果第二屏出问题”的尴尬。
多路显示只是OpenHarmony系统定制的冰山一角,后面更复杂的还有多屏窗口策略、显示带宽调度、GPU合成优化这些更深入的议题。但这些话题,等到你把屏真正点亮了,再慢慢研究也不迟。