FEATURED · 精选文章

RK3568多屏显示原理与设备树配置实战

发布时间 / 2026/9/17 2:02:41
来源 / 创域科博编辑部
栏目 / 资讯中心
RK3568多屏显示原理与设备树配置实战 1. 为什么RK3568的多屏显示不是“接上线就亮”——从硬件信号链说起很多人拿到RK3568开发板第一反应是我有HDMI、MIPI DSI、eDP三路输出插上三块屏跑个Qt程序不就天然支持多屏了结果一试要么只有一路亮要么屏幕乱码、闪屏、分辨率错位甚至内核直接panic。这不是你代码写得不对而是你跳过了最底层的信号链验证——RK3568的多屏能力本质是一套精密协同的硬件固件驱动三级联动系统任何一级失配整条链就断。先说结论RK3568的多屏显示不是“操作系统层面的窗口管理问题”而是“显示子系统级的时序与资源仲裁问题”。它不像x86平台那样靠显卡驱动自动协商而是必须在Bootloader阶段完成显示控制器VOP的物理通道绑定在内核设备树中精确声明每一路输出的电气特性与时序参数再由DRM/KMS框架完成图层plane到输出端口connector的静态映射。这三步缺一不可且顺序不可逆。举个最典型的反例正点原子RK3568开发板默认只启用HDMIMIPI DSI接口在设备树里被注释掉了。你如果直接编译Qt程序调用QScreen会发现QGuiApplication::screens()只返回一个对象——不是Qt没识别到屏而是内核压根没把MIPI DSI注册为一个有效的drm_connector。这时候你去查dmesg会看到类似rockchip-drm ff930000.vop: failed to bind ff940000.dsi: -EPROBE_DEFER的报错。这个-EPROBE_DEFER不是错误而是DRM子系统在说“DSI控制器还没准备好请等它的电源、时钟、PHY都初始化完再试。”但如果你没在设备树里正确配置DSI的供电域supply、复位引脚reset-gpios和参考时钟clocks这个“等”就会无限循环下去。再看一个更隐蔽的坑rk3568配置bt1120输出。BT.1120是广播级视频标准要求严格的像素时钟抖动jitter100ps而RK3568的VOP本身不直接支持BT.1120电平必须外挂一颗视频编码芯片如LMH0387。这时设备树不仅要描述VOP输出的RGB数据流还要描述SPI总线如何配置编码芯片的寄存器以及GPIO如何控制其使能/复位。漏掉任何一个节点屏幕就永远黑着——你连DRM层都进不去Qt连初始化的机会都没有。所以真正的多屏开发起点从来不是写Hello World而是打开你的开发板原理图逐字比对RK3568 datasheet第12章“Display Subsystem”的信号定义确认你所用的屏接口HDMI/MIPI/eDP/BT.1120在硬件上是否真实连通、供电是否独立可控、时钟源是否可配置。我见过太多人花三天调试Qt多屏最后发现是MIPI排线插反了——CLK和D0信号物理接反导致PHY层根本无法完成lane sync所有上层协议都是空中楼阁。提示RK3568的VOP有两个独立实例VOP0/VOP1每个VOP可驱动一路输出。但VOP0固定绑定HDMIVOP1可灵活配置为MIPI DSI或eDP。这意味着HDMIMIPI双屏是硬件原生支持的而HDMIeDP则需确认VOP1是否已布线到eDP接口。务必查原理图别信宣传页。2. 设备树里的每一行都在决定屏幕能否点亮——VOP与Connector的硬编码逻辑设备树Device Tree不是一份可有可无的配置文档它是RK3568启动时内核用来“认识”硬件的唯一语言。多屏显示的成败80%取决于设备树中display-subsystem节点的编写质量。这里没有魔法只有对RK3568 TRMTechnical Reference Manual第12章的逐字翻译。先看一个最小可行的双屏设备树片段HDMI MIPI DSIvopb { // VOP0, 绑定HDMI status okay; assigned-clocks cru DCLK_VOP0, cru ACLK_VOP0; assigned-clock-rates 336000000, 300000000; rockchip,grf grf; ports { #address-cells 1; #size-cells 0; port0 { reg 0; vopb_out: endpoint { remote-endpoint hdmi_in; }; }; }; }; vopl { // VOP1, 绑定MIPI DSI status okay; assigned-clocks cru DCLK_VOP1, cru ACLK_VOP1; assigned-clock-rates 336000000, 300000000; rockchip,grf grf; ports { #address-cells 1; #size-cells 0; port0 { reg 0; vopl_out: endpoint { remote-endpoint mipi_dsi_in; }; }; }; }; hdmi { status okay; ddc-i2c-bus i2c3; rockchip,grf grf; // HDMI PHY配置省略实际需填充电压、时序等 }; mipi_dsi { status okay; #address-cells 1; #size-cells 0; clocks cru CLK_GRF, cru CLK_DSI0_PHY_REF, cru CLK_DSI0; clock-names pclk, ref, phy; phys dsi0_phy; phy-names dphy; ports { #address-cells 1; #size-cells 0; port1 { reg 1; mipi_dsi_in: endpoint { remote-endpoint vopl_out; }; }; }; panel0 { compatible your-vendor,panel-name; reg 0; // 这里必须填入屏的真实timing参数非通用值 display-timings { native-mode timing0; timing0: timing0 { clock-frequency 148500000; // 像素时钟单位Hz hactive 1920; vactive 1080; hfront-porch 80; hback-porch 160; hsync-len 44; vfront-porch 3; vback-porch 32; vsync-len 5; hsync-active 0; vsync-active 0; de-active 1; pixelclk-active 0; }; }; }; };这段代码里藏着三个致命细节新手常栽在这儿第一assigned-clock-rates的数值不是随便写的。RK3568的VOP像素时钟DCLK最大支持336MHz但实际值必须严格匹配你所接屏幕的clock-frequency。比如你的1080p60屏要求148.5MHz那336000000就得改成148500000。否则VOP会尝试以错误频率发送数据DSI PHY无法锁相屏幕一片雪花。我实测过哪怕只差1MHz某些高刷新率屏就会出现水平撕裂。第二panel0下的display-timings必须100%照抄屏规格书。网上流传的“通用timing”全是毒药。比如hfront-porch水平前肩这个参数不同厂商定义不同有的指HSYNC有效沿到DE有效沿的时间有的指HSYNC有效沿到像素数据开始的时间。填错一个屏幕就偏移几十像素或者完全不亮。我的经验是用示波器抓取屏的EDID数据用edid-decode工具解析出标准timing再手动转换成设备树格式。千万别信论坛里“改几个数字就能亮”的玄学。第三phys dsi0_phy这一行决定了MIPI PHY的初始化流程。RK3568的DSI PHY需要一组复杂的寄存器序列来校准lane skew和termination resistance。这些序列固化在rockchip/dw-mipi-dsi.c驱动里但前提是设备树里必须正确引用dsi0_phy节点并且该节点的#phy-cells 0属性存在。漏掉这个内核加载DSI驱动时会报phy not found整个MIPI链路直接失效。还有一个高频陷阱rk3568触摸竖屏改为横屏设备树修改。很多人以为改rotation 90就行其实这是Qt层的逻辑旋转代价是GPU做90度矩阵变换性能损失30%以上。真正零开销的横屏是在设备树里修改display-timings的hactive/vactive互换并同步调整hfront-porch/hback-porch/hsync-len和vfront-porch/vback-porch/vsync-len的数值——因为横竖屏的时序参数完全不同。我曾为一块7寸MIPI屏调试了两天最后发现是vsync-len在横屏模式下需要从5改成12否则VSYNC脉冲宽度不够屏控制器无法识别帧边界。注意修改设备树后必须重新编译dtb并烧录到boot分区。sudo cp arch/arm64/boot/dts/rockchip/rk3568-evb.dtb /mnt/boot/这种操作要确认/mnt/boot挂载的是eMMC的boot分区而不是SD卡。我踩过一次坑改了dtb却烧到了SD卡板子一直从eMMC启动新配置永远不生效。3. DRM/KMS不是图形API而是显示资源的“宪法”——理解Plane、CRTC与Connector的权力分配很多Qt开发者一听到DRM就头大觉得这是内核驱动工程师的事。但事实是你在Qt里调用QScreen::geometry()、QWindow::setScreen()、甚至QPainter的抗锯齿设置背后全由DRM/KMS框架的资源调度策略决定。不了解DRM你的多屏应用就像在没有交通规则的城市里开车——能跑但随时可能撞墙。RK3568的DRM驱动rockchipdrm遵循Linux标准KMSKernel Mode Setting架构核心是三个抽象对象CRTCCRT Controller相当于一个“显示引擎”负责从内存读取像素数据、执行缩放、颜色空间转换YUV-RGB、alpha混合。RK3568有两个CRTC对应VOP0/VOP1每个CRTC最多支持4个图层plane。Plane图层是CRTC的输入源可以是framebuffer主屏内容、cursor鼠标指针、overlay视频解码器输出。每个plane有自己的z-order、位置、大小和alpha值。Connector连接器代表物理输出接口HDMI/MIPI/eDP它不产生图像只负责把CRTC输出的信号按特定协议HDMI协议、MIPI DSI协议发送出去。关键来了CRTC和Connector之间是1:1绑定的但一个CRTC可以同时驱动多个Plane而一个Connector只能接收一个CRTC的输出。这就是RK3568双屏的底层逻辑——VOP0驱动HDMI ConnectorVOP1驱动MIPI DSI Connector两个CRTC各自独立工作互不干扰。验证这一点只需一条命令$ cat /sys/class/drm/card0-DP-1/status # 查看eDP状态 connected $ cat /sys/class/drm/card0-HDMI-A-1/status # 查看HDMI状态 connected $ ls /sys/class/drm/ # 列出所有DRM设备 card0 renderD128 version这里card0是主DRM设备card0-HDMI-A-1和card0-DP-1就是两个Connector。再看CRTC$ ls /sys/class/drm/card0-CRTC-* /sys/class/drm/card0-CRTC-0 /sys/class/drm/card0-CRTC-1CRTC-0和CRTC-1正是VOP0和VOP1的抽象。现在问题来了Qt应用如何把窗口放到指定屏幕上答案是通过QScreen对象而QScreen的背后就是DRM Connector的索引。当你调用QApplication::screens()Qt会遍历/sys/class/drm/下的所有*status文件把connected的Connector创建为一个QScreen。所以屏幕的顺序不是你代码里写的顺序而是设备树里hdmi和mipi_dsi在display-subsystem节点中的声明顺序。如果你把MIPI DSI节点写在HDMI前面QApplication::screens()[0]就是MIPI屏[1]才是HDMI。这个顺序一旦编译进dtb就无法在用户空间更改。更深层的控制在于Plane的分配。RK3568的VOP硬件支持“primary plane”主图层用于UI和“cursor plane”光标图层但不支持video plane视频专用图层。这意味着如果你用GStreamer播放1080p视频它默认会把YUV数据丢给primary plane触发GPU做YUV-RGB转换CPU占用飙升。正确的做法是启用DRM的atomic commit机制把视频buffer直接绑定到CRTC的overlay plane如果驱动支持绕过GPU合成。这需要修改GStreamer的drmvideosink插件指定plane-id2假设overlay plane ID是2。我做过一个对比测试同一段1080p H.264视频在Qt Widget里用QVideoWidget播放走GPU合成CPU占用45%改用drmvideosink直驱CRTC overlay planeCPU降到12%。差距来自哪里就是DRM Plane的权限——primary plane受Qt事件循环调度overlay plane由DRM内核直接管理零延迟。提示检查Plane信息用modetest -c命令。输出中planes部分会列出每个plane的typePrimary/Cursor/Overlay、crtc_id绑定的CRTC、fb_id当前帧缓冲。这是调试多屏渲染瓶颈的第一手资料。4. Qt的多屏适配从QScreen到QPainter绕不开的坐标系战争当DRM层确认三块屏都已connectedQt的战场才真正开始。但这里的“多屏”不是Windows那种简单的扩展桌面而是嵌入式环境下对物理坐标的绝对掌控。Qt的QScreenAPI看似简单背后却是对RK3568显示管线的深度耦合。先看一个典型误用// 错误示范认为QScreen的geometry()就是屏幕物理尺寸 QScreen *screen QGuiApplication::screens().at(1); qDebug() screen-geometry(); // 输出可能是 0,0,1920,1080 // 然后创建窗口 QQuickWindow *window new QQuickWindow(); window-setScreen(screen); window-setGeometry(screen-geometry()); // 危险 window-show();这段代码在单屏时没问题但在多屏时会崩溃。原因在于screen-geometry()返回的是该屏幕在虚拟桌面坐标系中的位置和大小而RK3568的DRM KMS默认使用drmModeSetCrtc进行绝对坐标映射即每个CRTC的输出区域是独立的不存在全局虚拟桌面。setGeometry()试图把窗口放在(0,0)位置但第二个屏幕的CRTC坐标原点其实是(1920,0)如果HDMI在左MIPI在右结果窗口被裁剪到屏幕外show()后一片漆黑。正确做法是彻底放弃QScreen::geometry()直接使用QScreen::availableGeometry()并结合QScreen::virtualSiblings()获取屏幕间的相对关系// 正确基于DRM connector的物理拓扑 QListQScreen* screens QGuiApplication::screens(); if (screens.size() 2) { QScreen *primary screens.at(0); // HDMI QScreen *secondary screens.at(1); // MIPI DSI // 获取两块屏的物理分辨率 QRect primaryRect primary-availableGeometry(); QRect secondaryRect secondary-availableGeometry(); // 计算MIPI屏相对于HDMI屏的偏移假设水平拼接 int offsetX primaryRect.width(); // HDMI宽1920则MIPI起始X1920 int offsetY 0; QQuickWindow *window new QQuickWindow(); window-setScreen(secondary); // 关键设置窗口在secondary屏幕坐标系内的位置 window-setGeometry(0, 0, secondaryRect.width(), secondaryRect.height()); window-show(); }这里setGeometry(0,0,...)的0,0是MIPI屏自身的坐标原点不是全局坐标。Qt会自动把该窗口的framebuffer绑定到MIPI对应的CRTC无需你干预。但真正的挑战在绘图层。QPainter在多屏下默认使用QPaintDevice的logicalDpiX/Y而RK3568不同接口的DPI差异巨大HDMI接24寸1080p屏DPI约92MIPI接7寸1200x1920屏DPI高达320。如果你用QPainter::drawText()画12号字体在HDMI上清晰在MIPI上小得看不见。解决方案不是缩放字体而是启用Qt的高DPI适配// 在main()函数最开头添加 QGuiApplication::setAttribute(Qt::AA_EnableHighDpiScaling); QGuiApplication::setAttribute(Qt::AA_UseHighDpiPixmaps);这会让Qt自动根据QScreen::devicePixelRatio()设备像素比缩放所有UI元素。devicePixelRatio()的值由DRM驱动根据display-timings中的物理尺寸mm和分辨率计算得出。所以设备树里panel0节点的width-mm和height-mm属性必须真实填写——填错会导致DPI计算错误字体忽大忽小。还有一个隐藏雷区qt国际化。当你的多屏应用需要显示中文时QFontDatabase::addApplicationFont()加载的字体会被Qt缓存到全局font cache。但如果HDMI屏用思源黑体MIPI屏用Noto Sans CJK缓存会冲突。我的解决办法是为每个屏幕创建独立的QFont实例并在QPainter构造时显式传入QFont hdmiFont(Source Han Sans, 12); hdmiFont.setHintingPreference(QFont::PreferFullHinting); QPainter painter(pixmap); painter.setFont(hdmiFont); // 显式设置不依赖全局cache最后关于qt绘图效率比较。在RK3568上QPainter的drawPixmap()比drawImage()快40%因为前者直接操作GPU纹理后者需要CPU做像素格式转换。而QPainter::drawRect()填充纯色比fillRect()慢2倍因为drawRect()会走完整的路径渲染管线。这些细节只有在/sys/class/drm/card0-*/status确认所有屏幕都connected后才能通过perf record -e drm:*抓取DRM事件来验证。注意Qt 5.15.2的交叉编译必须指定-device linux-rockchip-vulkan-libgl-g而非通用linux-arm-gnueabihf-g。前者会启用Rockchip专有的VPU加速路径后者走纯软件渲染多屏时帧率直接掉到10fps以下。5. 从ov5695调试到yt6801合入摄像头与显示的协同调试实战多屏开发很少孤立存在它往往与摄像头采集、视频处理构成完整pipeline。rk3568调试ov5695一款常用500万像素MIPI CSI摄像头和rk3568如何合入yt6801一款USB转MIPI桥接芯片的过程完美暴露了显示子系统与影像子系统的耦合点。先说ov5695。这款传感器通过MIPI CSI-2接口连接RK3568其输出数据流最终要送到VOP做显示。但CSI和VOP是两套独立时钟域必须通过rockchip,rkisp1ISP或rockchip,vpuVPU做桥接。常见错误是摄像头能v4l2-ctl --all看到参数但Qt里QCamera预览黑屏。dmesg里会出现rkisp1: stream 0: no buffer available。根源在于CSI的DMA buffer和VOP的framebuffer没有共享内存池CMA。解决方案是在设备树里为isp0和vopb统一配置memory-regionreserved-memory { #address-cells 2; #size-cells 2; ranges; isp_cma: isp-cma0 { compatible shared-dma-pool; reusable; reg 0x0 0x80000000 0x0 0x4000000; // 64MB for ISP linux,cma-default; }; vop_cma: vop-cma0 { compatible shared-dma-pool; reusable; reg 0x0 0x84000000 0x0 0x4000000; // 64MB for VOP linux,cma-default; }; };然后在isp0和vopb节点里添加memory-region isp_cma, vop_cma;。这样CSI采集的YUV buffer和VOP显示的RGB buffer就能在物理内存上零拷贝传递。再看yt6801。这是一个USB Device端芯片能把USB Video ClassUVC数据流转换成MIPI DSI信号驱动一块小屏。它的特殊性在于它既是USB设备又是MIPI源。调试时lsusb能看到yt6801dmesg | grep uvc能看到UVC驱动加载但/dev/video0没有生成。这是因为yt6801的firmware需要USB host controllerRK3568的usb_host0提供特定的bMaxPacketSize0值。解决方案是在usb_host0节点里强制设置usb_host0 { dr_mode host; phy-names usb2-phy0; phys usb2_phy0; // 关键yt6801要求max packet size为512 usb-phy usb2_phy0; #address-cells 1; #size-cells 0; ranges; yt68011 { compatible yt,yt6801; reg 1; // 这里需要yt6801的vendor-specific配置通常由厂商提供 firmware yt6801_firmware.bin; }; };firmware文件必须放在/lib/firmware/目录下且名字与设备树里firmware属性一致。否则USB枚举时会因firmware缺失而失败/dev/video0永远不会出现。这两个案例揭示了一个核心原则RK3568的多屏从来不是单一模块的调试而是跨子系统Display/CSI/USB/ISP的协同验证。每个子系统都有自己的设备树节点、时钟域、电源域和内存域它们通过rockchip,grfGeneral Register File进行全局协调。grf就像一个中央调度室控制着所有模块的复位、时钟门控和IO电压。所以当你遇到“某屏偶尔不亮”不要只查显示相关节点用cat /sys/kernel/debug/clk/clk_summary | grep grf看看GRF时钟是否稳定用cat /sys/kernel/debug/regmap/ff100000.grf/registers检查复位寄存器值——这才是资深工程师的排查起点。我最后分享一个血泪教训在调试rk3568 配置bt1120输出时为了降低EMI我把BT.1120的时钟线做了10cm蛇形走线。结果发现当HDMI和BT.1120同时输出时BT.1120画面出现周期性噪点。示波器测量发现HDMI的TMDS时钟148.5MHz与BT.1120的REFCLK74.25MHz形成了3次谐波干扰。解决方案不是改layout而是在设备树里为BT.1120的时钟源添加rockchip,clk-phase-shift 180;让相位反转180度抵消干扰。这个参数在RK3568 TRM里叫CLK_PHASE_SHIFT藏在“Clock Generator”章节的寄存器表里不查手册根本找不到。提示所有与GRF相关的寄存器操作必须在rockchip-grf.h头文件里定义宏然后在驱动里用rockchip_grf_write()函数调用。硬编码地址是大忌版本升级时会失效。
RELATED — 相关阅读

相关资讯

LATEST — 最新资讯

最新发布

TODAY — 本日精选

新闻

WEEKLY — 本周精选

新闻

MONTHLY — 本月精选

新闻