1. 双屏旋转调试到底在调什么
第一次拿到"双屏显示旋转方向调试"这个任务的时候,我脑子里第一反应是:不就是把屏幕转个方向吗,设置里点一下不就完了。真上手才发现,嵌入式平台上的双屏旋转远没有桌面系统那么"理所当然"。桌面系统里旋转是显示服务层帮你兜底,驱动层基本不用操心;而在嵌入式板卡上,两个显示控制器、两条独立的显示通路、各自的图层合成单元,旋转这件事必须从设备树配置、显示控制器能力、图层格式、内存布局一路捋到应用层的坐标变换,任何一环没对齐,出来的结果就是花屏、撕裂、触摸错位或者干脆黑屏。
这篇内容围绕的就是这样一个场景:在一块搭载双显示控制器的嵌入式板卡上,让两路显示输出各自按需要的方向旋转,并且保证旋转之后画面正确、触摸坐标正确、性能可接受。核心关键词是双屏显示、旋转方向、显示控制器、设备树配置、图层合成、触摸坐标映射。适合正在做嵌入式显示调试的驱动工程师、系统集成人员,以及需要把产品界面适配到竖屏、倒装屏、异形安装位的硬件产品开发者参考。哪怕你之前只调过单屏,这里面的思路拆解和踩坑记录也能直接拿去用。
先把结论性的认知摆出来:嵌入式双屏旋转不是"一个开关",而是硬件能力、驱动配置、应用适配三层协同的结果。硬件层决定"能不能转、转到什么粒度",驱动层决定"怎么配、配在哪",应用层决定"转完之后坐标怎么对齐"。三层里任何一层想当然,调试周期就会从半天变成一周。下面我按实际调试的顺序,把这套东西拆开讲。
2. 整体方案设计与选型考量
2.1 为什么旋转要分硬件旋转和软件旋转两条路
嵌入式显示控制器通常提供两种旋转实现路径,理解这两条路的差别是方案选型的第一步。
硬件旋转指的是显示控制器内部的图层合成单元(或者叫扫描输出单元)自带旋转能力,在把图层数据扫描输出到屏幕的过程中直接完成 90 度、180 度、270 度的坐标变换。它的好处非常直接:不占用 CPU、不额外消耗内存带宽做整帧拷贝、延迟低、功耗友好。代价是旋转角度往往受限于硬件支持的枚举值,而且不同图层、不同像素格式对旋转的支持程度可能不一样,比如某些控制器只支持 RGB 格式图层旋转,YUV 图层旋转要走另一条通路。
软件旋转则是在应用层或者合成层把画面先渲染到一块离屏缓冲区,再用 CPU 或者 GPU 做一次坐标变换写回,最后交给显示控制器正常扫描。它的灵活性高,任意角度、任意裁剪都能做,但代价是每帧多一次全屏读写,1080P 分辨率下一帧 RGB888 就是约 6MB 的数据量,60 帧就是 360MB/s 的额外带宽,对内存带宽紧张的嵌入式平台来说不是小数目。
我这次调试的平台,两路显示控制器都具备硬件旋转能力,所以主方案毫不犹豫选硬件旋转。判断依据很简单:先查芯片手册里显示控制器章节的图层能力表,确认目标图层格式在目标旋转角度下是否在支持列表内。如果支持,硬件旋转优先;只有当硬件不支持某个特定角度,或者需要做非 90 度整数倍的旋转时,才退到软件方案。这个判断顺序很重要,反过来先上软件方案,后面性能压不住再回头改,返工成本很高。
2.2 双屏场景下两路显示通路为什么要分开配
单屏调试时,旋转配置往往集中在一个显示控制器节点里,改完就完事。双屏就不一样了,两路显示输出通常是两个独立的显示控制器实例,各自有独立的设备树节点、独立的时钟、独立的图层分配。这意味着旋转方向必须逐路单独配置,不能指望一个全局开关搞定两路。
这里有个容易踩的坑:有些平台的设备树里,两路显示控制器共享一部分公共配置节点,比如显示时钟源或者电源域。如果只改了其中一路的旋转属性,另一路没动,表面上看起来"只转了一个屏",实际上可能因为公共节点的默认值影响,导致未配置的那一路出现异常。我的做法是:两路显示控制器的旋转属性都显式写出来,哪怕其中一路就是 0 度不旋转,也明确写上,避免依赖默认值带来的不确定性。
另外要考虑两路屏幕的物理安装关系。实际产品里双屏可能是左右并排、上下堆叠、或者一块正装一块倒装。旋转方向不是随便定的,要跟结构件的实际朝向对齐。我一般会先跟结构确认每块屏的"自然方向"(也就是排线朝下、视角正常的方向),再决定每路需要旋转多少度。这个信息如果搞错,后面所有调试都是白费。
2.3 旋转粒度与像素格式的匹配关系
硬件旋转支持的粒度通常是 90 度的整数倍,也就是 0、90、180、270 四个值。但"支持 90 度旋转"这句话背后还有细节:旋转之后图层的宽高会互换,原本 1920x1080 的图层旋转 90 度后占用的显示区域变成 1080x1920。如果显示控制器的图层尺寸寄存器是分开配置宽高的,那没问题;如果某些平台对旋转后的最大尺寸有限制,比如旋转后宽度不能超过某个值,那就需要提前算清楚。
像素格式这块也要留意。RGB565、RGB888、ARGB8888 这些格式在旋转支持上通常没问题,但 YUV 格式的图层旋转在很多平台上要走视频通路的专用旋转单元,配置方式跟 RGB 图层完全不同。我这次两路屏都是 UI 界面为主,用的是 ARGB8888 图层,硬件旋转支持良好,所以方案上比较省心。如果你的场景涉及视频层旋转,建议单独拉出来验证,不要跟 UI 层混在一起调。
3. 设备树配置与驱动层关键细节
3.1 显示控制器节点的旋转属性怎么写
设备树是这一切的起点。以常见的嵌入式显示子系统为例,每个显示控制器节点下会有图层相关的子节点,旋转属性一般挂在图层节点或者显示输出节点上。具体属性名各平台不同,常见的有rotation、rotate、screen-rotation这类命名,取值通常是角度数值或者枚举宏。
配置时的核心原则是:属性写在真正生效的那一层。有些平台把旋转属性放在显示控制器节点,表示整个输出旋转;有些平台放在图层节点,表示单个图层旋转。这两者语义不同,放错位置要么不生效,要么只转了一部分图层导致画面错乱。我的做法是先查平台显示驱动源码里解析这个属性的位置,确认它最终作用到哪个寄存器,再决定写在哪。这一步花十分钟看代码,能省掉后面几小时的试错。
配置示例(属性名以实际平台为准,这里用通用写法示意):
&display_controller_0 { status = "okay"; display-timings { /* 时序配置 */ }; layer@0 { format = "argb8888"; rotation = <90>; /* 该路屏幕旋转 90 度 */ }; }; &display_controller_1 { status = "okay"; display-timings { /* 时序配置 */ }; layer@0 { format = "argb8888"; rotation = <270>; /* 另一路屏幕旋转 270 度 */ }; };改完设备树重新编译烧录,第一件事不是看画面,而是看内核日志里显示驱动初始化时打印的旋转配置回读值。很多平台的驱动会在 probe 阶段把解析到的旋转角度打印出来,确认这个值和预期一致,再往下走。如果日志里没有,可以临时加一行打印,验证解析链路通了没有。
3.2 旋转后显示区域的尺寸计算
这是双屏旋转调试里最容易算错的地方。假设屏幕物理分辨率是 1920x1080(横向),旋转 90 度后,显示控制器输出的有效区域变成 1080x1920。但"输出区域"和"图层尺寸"是两个概念,需要分开算。
图层尺寸指的是应用提交的缓冲区大小。如果应用按 1920x1080 渲染,旋转 90 度后,显示控制器会把这个缓冲区当作 1080x1920 的区域来扫描。这里的关键是:应用提交的缓冲区宽高,要和旋转后的显示区域匹配,否则会出现拉伸或者黑边。具体来说,如果屏幕旋转 90 度,应用应该按 1080x1920 的逻辑分辨率来渲染,或者由合成层做一次尺寸适配。
我踩过的一个坑是:应用层还按旋转前的分辨率渲染,驱动层做了旋转,结果画面被压扁。排查时先确认驱动日志里图层尺寸和旋转角度,再确认应用提交的缓冲区尺寸,两边一对就发现问题了。所以调试顺序建议是:先固定应用输出分辨率,再调驱动旋转,最后核对两者尺寸关系,不要同时改两边。
3.3 时钟与带宽的隐性约束
旋转本身不改变像素总量,但会改变扫描顺序,某些平台上旋转后的扫描输出对时钟频率的要求会略有变化。更实际的影响在内存带宽:硬件旋转虽然不额外拷贝整帧,但旋转后的扫描访问模式可能导致内存访问效率下降,尤其是行跨度和缓存行不对齐的时候。
我的经验是,双屏同时开启旋转后,重点观察两个指标:一是显示是否出现偶发撕裂或者闪烁,二是系统在高负载下是否出现掉帧。如果出现,先查显示控制器的时钟配置是否留了足够余量,再查内存带宽占用。有些平台可以通过调整显示时钟的 parent 或者分频来缓解,但要注意不要超过屏幕时序允许的范围。这部分没有通用公式,得结合具体平台的时钟树和实测数据来调。
4. 实操过程与核心环节实现
4.1 从零开始的第一轮验证流程
我把第一轮验证拆成四步,每步都有明确的通过标准,避免问题叠加后难以定位。
第一步,单路先通。先只开一路显示控制器,配置旋转 90 度,屏幕上电,确认画面方向正确、无花屏、无偏移。通过标准是:画面内容完整、方向符合预期、边缘无异常色块。这一步不通过,绝不往下走。
第二步,单路换角度。把同一路改成 180 度和 270 度分别验证,确认四个角度都能正常出图。这一步是为了排除"某个角度硬件不支持但配置没报错"的情况。有些平台配置了不支持的角度,驱动不报错但实际输出异常,只有逐个角度试才能发现。
第三步,双路同时开。两路都配置好各自的旋转角度,同时上电。通过标准是:两路画面各自正确、互不干扰、系统日志无显示相关报错。这一步常见的问题是两路争抢公共资源导致其中一路初始化失败,日志里会有时钟或者电源相关的错误。
第四步,动态切换验证。如果产品需要运行时切换旋转方向,还要验证动态修改配置后画面能否正确刷新。这一步涉及驱动层的重新配置流程,有些平台需要先关闭输出再改配置再开启,直接改寄存器可能不生效。
4.2 触摸坐标映射的联动调试
屏幕转了,触摸不跟着转,用户点左边响应右边,这是双屏旋转调试里最高频的问题。触摸坐标映射的本质是:触摸控制器上报的是触摸屏自身的物理坐标,而系统需要的是旋转后显示画面的逻辑坐标,两者之间要做一次坐标变换。
变换关系取决于旋转角度和触摸屏与显示屏的贴合方向。以常见的 90 度旋转为例,如果显示画面顺时针旋转 90 度,触摸坐标通常需要做对应的逆变换。具体公式各平台触摸驱动里一般有现成的变换矩阵或者变换函数,关键是确认变换参数和显示旋转角度一致。
我的调试方法是:在屏幕上画四个角的标记,依次点击四个角,看系统上报的坐标是否落在对应标记附近。如果整体偏移,是变换公式的问题;如果某个方向反了,是旋转方向符号的问题;如果只有边缘偏差,可能是触摸屏校准参数的问题。这三类现象对应三种不同的修法,分开定位效率最高。
注意:双屏场景下,两路触摸要分别配置变换参数,不要复用同一套。曾经见过把一路的变换参数套到另一路,结果一路正常一路错位的情况。
4.3 性能实测与参数记录
旋转配置跑通之后,我习惯做一轮简单的性能记录,作为后续优化的基线。记录项包括:两路同时输出时的帧率、CPU 占用、内存带宽占用(如果平台有统计接口)、以及高负载场景下的掉帧情况。
实测下来,硬件旋转方案在两路 1080P 输出下,CPU 占用基本没有明显变化,说明旋转确实没走 CPU 路径。内存带宽方面,旋转后的访问模式导致带宽占用比不旋转时略高,但在可接受范围内。这些数据我整理成表格存档,后面如果产品要加图层或者提高分辨率,可以直接对比判断余量够不够。
| 测试项 | 单路不旋转 | 双路均旋转 | 备注 |
|---|---|---|---|
| 输出帧率 | 60fps | 60fps | 稳定无掉帧 |
| CPU 占用 | 基准值 | 基本持平 | 硬件旋转不占 CPU |
| 内存带宽 | 基准值 | 略高 | 访问模式变化导致 |
| 画面异常 | 无 | 无 | 长时间运行观察 |
这张表的价值在于,它把"感觉没问题"变成了"数据上没问题",后面出问题时也有对照基准。
5. 常见问题与排查技巧实录
5.1 画面方向对了但内容被裁切
这个现象通常是旋转后的显示区域和图层尺寸不匹配导致的。排查顺序:先看驱动日志里旋转角度和图层尺寸的回读值,再算一下旋转后应有的显示区域尺寸,两者对比。如果图层尺寸还是旋转前的值,说明应用层没跟着适配;如果显示区域配置错了,是设备树或者驱动配置的问题。修法是让应用按旋转后的逻辑分辨率渲染,或者由合成层做尺寸适配,二选一,不要两边都改。
5.2 双路同时开启后其中一路黑屏
优先查公共资源冲突。两路显示控制器如果共享时钟源或者电源域,初始化顺序和资源申请可能互相影响。排查方法是看内核日志里两路显示控制器的 probe 顺序和返回值,找到失败的那一路报了什么错。常见原因是时钟没使能、电源域没打开、或者引脚复用冲突。修法上,确认设备树里两路的资源申请没有重叠,必要时调整 probe 顺序或者加依赖关系。
5.3 旋转后画面出现周期性闪烁
这类问题多半跟时钟或者带宽有关。先确认显示时钟配置是否在屏幕时序允许范围内,再看旋转后的内存访问是否导致带宽瞬时打满。实测中遇到过旋转后扫描行跨度和缓存行不对齐,导致偶发的访问延迟抖动,表现为轻微闪烁。调整图层缓冲区的对齐方式后缓解。这类问题没有万能解,得结合平台的内存控制器特性来调。
5.4 触摸坐标在旋转后整体偏移
先确认触摸变换参数是否和显示旋转角度严格对应。常见错误是显示转了 90 度,触摸变换用了 270 度的参数,或者旋转方向符号搞反。用四角点击法定位:如果四个角都偏同一个方向,是平移参数问题;如果对角互换,是旋转方向问题;如果只有一边偏,是缩放或者校准问题。分清楚现象再动手,比盲目改参数快得多。
5.5 动态切换旋转方向后画面不刷新
有些平台的显示控制器在运行中修改旋转配置不会立即生效,需要触发一次显示通路的重新配置。排查时先确认配置写入后寄存器回读值是否变化,如果变了但画面没变,说明需要额外的刷新触发;如果寄存器都没变,说明配置写入路径有问题。修法上,参考平台驱动里初始化时的配置序列,在动态切换时复现同样的序列,通常能解决。
| 问题现象 | 可能原因 | 排查入口 | 处理方向 |
|---|---|---|---|
| 画面被裁切 | 图层尺寸与旋转后区域不匹配 | 驱动日志尺寸回读 | 应用或合成层适配尺寸 |
| 一路黑屏 | 公共资源冲突 | probe 日志与返回值 | 检查时钟电源引脚复用 |
| 周期性闪烁 | 时钟或带宽余量不足 | 时钟配置与带宽统计 | 调整时钟或缓冲区对齐 |
| 触摸整体偏移 | 变换参数与角度不符 | 四角点击定位 | 修正变换矩阵参数 |
| 动态切换不生效 | 缺少刷新触发 | 寄存器回读对比 | 复现初始化配置序列 |
6. 我在实际调试中攒下的几条经验
设备树改完一定要回读验证,不要假设"写了就生效"。显示子系统的属性解析链路有时候比想象的长,中间任何一层没接上,配置就是静默失效,画面看起来"没变化"而不是"报错",这种静默失败最耗时间。
双屏调试永远先单路后双路,先静态后动态。问题叠加是调试效率的最大杀手,每步只引入一个变量,通过标准明确,看起来慢,实际最快。
触摸和显示要当成一对来调,不要分开验收。显示方向对了不代表触摸对了,两者都通过才算这一路真正通了。我习惯在每路屏幕上做一次完整的四角点击验证,作为该路验收的固定动作。
性能数据要留基线。旋转配置跑通只是开始,后面加功能、提分辨率、换屏幕,都需要一个对照基准来判断余量。花十分钟记录一张表,后面能省很多反复验证的时间。
最后说一个后续可以扩展的方向:如果产品需要支持屏幕热插拔或者运行时切换安装方向,动态旋转配置的可靠性验证要单独做一轮,重点测反复切换后的稳定性,这部分跟静态配置的验证思路不完全一样,值得单独拉出来做。