BSP调试#11这次换了平台,全志T527,目标是把一块国产720P MIPI DSI接口的LCD模组点亮。板子是客户新打的工业控制板,屏是淘宝都能买到的普通模组,驱动基本没有现成的,只能从DSI初始化代码和时序参数一点一点调出来。这块屏看着简单,就是接上去亮起来,但中途遇到的坑远比预想的多:U-Boot阶段黑屏、Linux阶段闪屏、背光时序不对、分辨率参数差一个像素图像整体偏移……整个过程基本覆盖了MIPI DSI调试的典型场景。
这篇记录不适合纯做应用开发的,更适合BSP工程师、嵌入式驱动开发、或者正在搞全志平台显示适配的人。我会把调试思路、设备树关键参数、时钟计算方法、实测现象和排查步骤全部写出来,方便下次遇到类似问题直接有个参考。
1. 项目整体设计与调试思路
1.1 为什么是MIPI DSI和全志T527
先说下背景。全志T527是一颗面向工业控制、边缘计算场景的SoC,四核Cortex-A55,内置了完整的显示系统,支持RGB、LVDS、MIPI DSI等多种显示接口。这次项目用的是MIPI DSI,因为客户选的液晶模组只有MIPI接口,而且DSI在相同分辨率下需要的引脚数量比RGB少很多,特别适合工控板上走线空间紧张的场景——4对差分线加电源就能驱动720P的屏幕,放在以前用RGB接口得拉二十多根线。
所谓BSP调试,核心工作就是让全志的板级支持包适配到我们的具体硬件上。显示调试在BSP里面又属于比较靠前的步骤,如果U-Boot阶段屏幕就不能亮,后面跑Linux应用根本无从谈起。所以这次调试贯穿了U-Boot和Kernel两个阶段,主要解决三件事:一是把DSI控制器的时钟和时序配置正确,二是把LCD面板初始化序列完整下发,三是确保背光、复位、电源上下电的先后顺序符合屏幕硬件规格。
1.2 调试总体思路:从链路分层切入
MIPI DSI调试最忌讳一上来就抓着一堆寄存器乱试。显示链路是分层的,每一层出问题表现不一样,排查手段也不一样。我在这次调试里把整条链路拆成了三层:
- 第一层是时钟和电压,涉及PLL配置、D-PHY供电、IO电平;
- 第二层是协议层,涉及DSI包格式、LP/HS模式切换、虚通道;
- 第三层是面板层,涉及LCD初始化代码、时序参数、背光控制。
我调试时严格按照这个顺序推进:先确认时钟树、再确认PHY有没有产生正确的D-PHY信号、接着验证DSI包有没有正常发送,最后才去看面板初始化和背光。每一层都验证通过后再进下一层,如果直接跳层排查,很容易出现花了半天时间调时钟,结果发现是LCD初始化序列漏了0x11命令的问题。
另外提醒一句,如果用U-Boot来调试,效率会高很多。全志BSP在U-Boot里已经有显示驱动,U-Boot起来时如果能点亮屏幕,说明硬件链路和大部分配置是对的,后面Linux内核的问题就缩小到驱动对接层面。这次调试我一开始就把U-Boot当成了第一验证环境。
2. 核心原理与设备树参数拆解
2.1 MIPI DSI链路的关键组成
MIPI DSI是高速串行接口,物理层是D-PHY,数据线上工作在两种模式:高速模式(HS)用来传视频流数据,低功耗模式(LP)用来传命令和状态信息。一条DSI链路最少需要一对时钟差分线和1~4对数据差分线,这次用的720P屏幕数据线用了4对,也就是常说4-lane。
对于全志平台,显示数据链路大致是:
DE(显示引擎) → TCON(时序控制器) → DSI控制器 → D-PHY → LCD面板TCON负责生成像素时钟和扫描时序,DSI控制器把像素数据编码成MIPI协议包,然后通过PHY以差分信号发出去。这时候出现一个关键概念:像素时钟和DSI数据时钟是两个不同的量。像素时钟决定了屏的实际扫描频率,而DSI数据时钟决定了每条lane上传输数据的比特速率,两者之间有个换算关系:DSI线速率 ≈ 像素时钟 × 每像素比特数 / lane数。
如果把像素时钟比作“出货速度”,那DSI数据时钟就是“打包速度”。打包太快,PHY跟不上会出错;打包太慢,屏幕刷新率又达不到。所以设备树里必须把这两个频率都配对的。
2.2 设备树关键字段详解
全志T527的BSP里,LCD面板配置一般放在设备树的lcd0节点中。下面是我们调试用到的核心配置,我剔除了没用到的部分:
&lcd0 { lcd_used = <1>; lcd_if = <3>; /* 3表示MIPI DSI接口 */ lcd_x = <720>; lcd_y = <1280>; lcd_dclk_freq = <74>; /* 像素时钟,单位MHz,实际74.25MHz */ lcd_pixel_fmt = <10>; /* 10对应RGB888 */ /* 水平、垂直扫描参数 */ lcd_hbp = <88>; lcd_ht = <800>; lcd_vbp = <12>; lcd_vt = <1324>; lcd_hfp = <?>; /* DSI 专用参数 */ lcd_dsi_lane = <4>; /* 4线DSI */ lcd_dsi_format = <0>; /* 0: video mode 视频模式 */ lcd_dsi_hbp = <20>; lcd_dsi_hfp = <20>; lcd_dsi_hsa = <10>; lcd_dsi_vbp = <4>; lcd_dsi_vfp = <4>; lcd_dsi_vsa = <2>; /* 背光PWM配置 */ lcd_pwm_used = <1>; lcd_pwm_ch = <0>; lcd_pwm_freq = <50000>; lcd_pwm_pol = <0>; };稍微解释下这里面几个容易混淆的参数。全志的显示参数分两套命名:一套是lcd_hbp/lcd_ht/lcd_vbp/lcd_vt,这是TCON的扫描时序;另一套是lcd_dsi_hbp/lcd_dsi_hfp/lcd_dsi_hsa这些,是DSI包内部的数据包时序。
有个调试心得:这两组参数必须严格对齐,TCON生成多少行、多少像素的显示区域,DSI侧必须以完全相同的方式分包,否则会出现图像只显示一半、上下撕裂、整体偏移一类的现象。这次我就踩了TCON水平和DSI水平参数没对齐的坑,后面排查实录会详细说。
2.3 时钟计算和PLL配置
像素时钟的计算公式比较朴素:
像素时钟 = 水平总像素 × 垂直总行数 × 刷新率以720×1280、60Hz刷新率为例,水平总像素是lcd_ht=800,垂直总行数是lcd_vt=1324,那么:
像素时钟 = 800 × 1324 × 60 = 63.6 MHz但是实际屏幕标准值大概是74.25MHz,这里还有一个细微的差别——不同厂商给的lcd_ht、lcd_vt不是实际屏幕的物理分辨率,而是包含了消隐区域的总计数值。我用74MHz来配置,误差在允许范围内,实际验证能正常锁屏。
DSI的线速率根据像素时钟换算:
DSI线速率 = 74MHz × 24(bpp) / 4(lane) = 444Mbps也就是说每条lane的发送速率是444Mbps,对应D-PHY时钟是222MHz(因为DDR模式双沿采样)。这个速率对720P来说比较富余,PHY工作压力不大,调试时可以先按这个值跑,后续想做高刷新率再往上升。
在设备树里,PLL参数通常不用手工指定精确数值,BSP会根据lcd_dclk_freq自动选PLL分频,但有一点必须保证:选择的PLL父时钟要能整除得到目标频率,否则出来的像素时钟是带小数误差的,长期运行会出现偶尔闪屏。这个可以在调试时通过读/sys/kernel/debug/clk/clk_summary确认实际时钟树的输出值。
3. 实操过程与关键环节实现
3.1 前期准备:从原理图和规格书确认物理链路
开始调代码之前,我先做了三件准备工作,这些都是后续排查的基础:
第一,打开原理图,确认DSI lane的走线对应关系,尤其要确认4条数据lane的顺序映射。MIPI DSI规定lane0是必选的,lane1~3可扩展,但是有些模组厂商会自定义lane映射顺序。全志的DSI控制器支持lane swap配置,如果硬件工程师为了布线方便做了lane翻转,就需要在配置里对应交换,否则显示完全是花的或者根本无信号。
第二,确认面板的供电路径和GPIO控制。LCD模组通常有VCI、VCCIO、VSP/VSN等多路电源,由不同的LDO或DCDC供电,并且要满足一定的上电时序。如果电源没按顺序给,面板可能直接处于异常状态,DSI信号进来也不响应。我们用了两个GPIO分别控制复位引脚和背光使能,还有一个PWM通道控制背光亮度。
第三,整理屏规格书里的初始化命令集。不同厂商的屏初始化代码差异很大,有些需要先发0x11退出睡眠模式,延时120ms以后再发其他设置寄存器,最后发0x29打开显示。这个过程只能按规格书来,不能省也不能乱序。很多屏不亮其实不是时钟问题,而是厂商要求必须先写0x11、延时、再写0x29,顺序错一个就黑屏。
3.2 U-Boot阶段快速验证
全志T527的UBoot支持通过环境变量控制显示输出。我们在U-Boot里跑了一次看效果,名字有bootlogo、showlogo这类命令,用来在启动过程刷logo。如果U-Boot阶段就能看到logo,说明显示链路底层基本打通,后面Linux阶段多半是配置同步的问题。
实际过程里我发现U-Boot阶段屏幕没有反应,换了几种可能后首先用示波器量了CLK差分线。注意这里有个经验:DSI的CLK lane哪怕没有数据发送,只要PHY使能了,就应该有持续的时钟信号。当时示波器上CLK没有波形,所以问题在PHY使能或时钟配置,不在面板。
反复检查后发现问题出在PLL的输出频率档位选择和UBoot设备树配置没有同步。T527的UBoot有自己的设备树文件,和内核设备树是两套。我改内核的lcd0配置时必须同步改UBoot的board dts。这是BSP调试特别容易忽略的一点:全志平台显示初始化通常是在UBoot阶段完成的,内核起来后只是复用或重新walk一遍配置,两边不一致就是黑屏。
3.3 内核阶段逐步验证
UBoot能显示之后,进入Linux阶段。这里要看正常一条完整的调试验证链是怎么做下来的:
先确认显示驱动有没有加载成功,抓内核日志:
dmesg | grep -i "disp\|dsi\|lcd"正常情况能看到显示设备注册和lcd驱动的初始化信息。如果没有任何DSI相关日志,可能是设备树节点没有正确匹配,或者驱动没有编译进内核。
确认之后,检查显示状态节点:
cat /sys/class/disp/disp0/status cat /sys/class/disp/disp0/disp0/attached_graph全志BSP提供了一堆sysfs节点,在调试时异常好用。可以查当前分辨率、lane数量、像素格式,这些信息能快速定位配置是不是还在生效。我经常用这几个节点来确认“软件认为自己在输出什么”,然后再对比屏幕实际显示什么,很容易缩小问题范围。
如果状态都对但是屏幕没画面,可以尝试用以下方式直接操作显示层验证:
echo "disp_layer 1 enable" > /sys/class/disp/disp0/disp0/attr/xxx不过不同BSP版本节点名字有些差异,具体以板子上的节点为准。
对于颜色校准,可以利用BSP提供的测试图案接口。我在调试时生成纯红色画面,再切纯绿色、纯蓝色,本质上是在验证每一条颜色通道的数据通路是否正常。如果某个颜色偏色或缺失,问题大概率在像素格式配置,不一定是线缆或PHY。
3.4 面板初始化序列的实测记录
这里记录一下这次面板初始化命令的下发方式。全志BSP一般有两种做法:
第一种是在lcd驱动初始化函数里,通过LCD_OPEN_FUNC回调逐条发送初始化命令,命令以寄存器地址加数据的方式写入。举例来说,面板厂商给了这样的初始化序列:
// 伪码示意 lcd_dsi_panel_init_cmd = { {0x00, 0x00}, {0xFF, 0x20}, {0x04, 0x03}, ... };发送命令时要用MIPI DSI的Short Write或Long Write包,全志驱动里对应DCS命令发送接口。写命令的时候要紧盯延时:命令间隔不够,面板内部状态机跟不上,初始化会失败。
第二种是直接把初始化序列放在设备树里,以lcd_initial_code参数的形式提供。这种方式不用改驱动代码,换屏时更灵活。我们最后采用的是第一种方式,因为国产屏规格书给的命令比较零散,代码方式更容易加延时和调试。
这次调试中最关键的三个初始化命令是:
0x11 -> 退出睡眠模式,延时120ms 0x36 -> 设置扫描方向和RGB顺序(设置后确认显示方向是否正确) 0x29 -> 打开显示,延时20ms如果0x36的参数设置不对,会出现图像左右镜像、上下颠倒、颜色怪异。当时遇到颜色完全错乱的问题,改的正是0x36里的地址控制位。
4. 常见问题与排查技巧实录
4.1 症状对照速查表
在调试过程中我整理了一个快速定位表,基本覆盖了MIPI DSI点屏常见的问题。可以直接对照排查:
| 现象 | 大概率原因 | 排查手段 |
|---|---|---|
| 完全黑屏,无背光 | 背光使能/电源未开启 | 量背光供电、查GPIO状态 |
| 黑屏但有背光 | LCD初始化未完成或DSI时钟异常 | 示波器看CLK差分线是否有波形 |
| 白屏 | 面板处于Reset状态,初始化代码未执行 | 查复位时序、初始化代码是否完整下发 |
| 花屏/雪花 | D-PHY信号质量差、lane映射错误 | 示波器看HS信号,检查lane swap配置 |
| 图像偏移 | TCON时序与DSI包时序不一致 | 核对lcd_ht/lcd_hbp与lcd_dsi_*参数 |
| 亮度不均匀 | 背光PWM频率过低或背光驱动不足 | 调PWM频率,加PWM极性反转 |
| 刷新率不足/卡顿 | 像素时钟过低 | 计算实际dclk,确认PLL配置 |
| 颜色错乱 | pixel_fmt配错或0x36方向命令参数错误 | 检查像素格式,确认0x36寄存器 |
这个表不是万能的,但能覆盖90%的调试场景。后面几个比较典型的坑我再展开讲。
4.2 坑一:U-Boot黑屏,Linux也黑屏
先把最顽固的问题记录下来。当时一上电就黑屏,背光倒是亮的,说明电源已经给了,但面板没收到有效数据流。在U-Boot阶段示波器抓CLK lane,发现根本没有振荡。按照链路分层思路,问题锁定在时钟/PHY。
排查过程:
- 检查PLL配置,确认
lcd_dclk_freq的目标频率在范围内; - 检查
lcd_if = <3>是否生效,打印设备树实际加载值,发现UBoot用的板级dts里这个值被覆盖成了别的接口类型; - 修复UBoot dts后重新编译,CLK有输出了,但画面还是花的;
- 再检查lane映射,发现面板的lane2和lane3在原理图上被交换了,在DSI控制器配置里开了lane swap后正常。
这个案例是典型的前期硬件/软件协同问题,也说明调试时必须先确认每一层的输出特征。
4.3 坑二:图像整体向右偏,左侧有空隙
这个问题发生在U-Boot已经正常之后,Linux内核起来屏幕显示的界面整体偏移,左侧出现一条黑色的竖带,这是因为TCON扫描时序和DSI包时序没对齐。
全志显示系统里,TCON控制整个扫描周期,包括有效显示区域和消隐区;DSI控制器必须同步地把有效像素数据打包发送。如果TCON认为有效数据从某个位置开始,而DSI包的起始位置偏了,图像就会发生整体平移。
排查时我把lcd_hbp和lcd_dsi_hbp设置成了相同值,但忽略了lcd_hsa和lcd_dsi_hsa之间的对齐。把lcd_hsa、lcd_hbp和DSI侧对应参数比对后,问题解决。
这里有一个值得说的计算公式:
lcd_hsa + lcd_hbp = lcd_dsi_hsa + lcd_dsi_hbp + 额外消隐全志BSP文档里对这个关系的描述比较晦涩,实战来说就是把两者列出来做差,差多少、图像偏移多少,这个就是需要补偿的差值。
4.4 坑三:颜色整体偏绿,白屏变成青绿色
这个排查花了点时间,因为一开始怀疑是PHY信号问题或者是PCB焊接虚焊,后来发现是数据格式出了问题。
故障表现为画面明显偏绿,而且连纯红色都显示成了接近黄色的色调。查了像素格式配置,lcd_pixel_fmt设的是RGB888,按理说24比特每像素没错。后来细看规格书发现这个屏实际上内部只支持RGB666,物理层只有18条颜色线,MIPI发送端虽然按24位发,面板内部会丢弃低位。
解决方案有两种:一是把像素格式改成RGB666,让数据真正按18位输出;二是保留RGB888但在初始化时配置面板接收模式,让低两位数据按特定方式丢弃。最后我选择了修改像素格式为RGB666,画面颜色立刻正常。
4.5 坑四:开机一段时间后屏幕闪烁
这个问题最隐蔽,因为它不是设置错了,而是电源纹波问题。开机时间长了以后偶尔会出现背光亮度波动和图像细小的闪烁,抓日志并没有报错、寄存器也没有异常值,看起来像是电池电压下降导致的。
用示波器查看面板VCI和D-PHY电压轨,发现启动过程有轻微的回落纹波。因为给LCD供电的LDO地平面在PCB上没有单独隔离,背光PWM翻转时的噪声串到了数据电源上。解决方法是在LDO输出加了一颗22uF的陶瓷电容,同时调整了背光PWM死区时间,问题基本消失。
这是在真实硬件调试中才会遇到的情况,BSP调试不只是写配置,硬件协同排查往往才是最后的攻坚环节。
4.6 几个调试期的小技巧
最后分享几个纯经验型的小技巧,换个场景也用得上。
技巧一:多用示波器看CLK lane。MIPI DSI的CLK差分线是最容易诊断的信号线。只要PHY使能了,即使没有任何数据,CLK lane也必须有连续的差分时钟。如果CLK完全没波形,说明时钟/PHY链路有问题。如果CLK有波形但数据lane没波形,重点查lane映射和DSI控制器是否在发送数据。
技巧二:用纯色画面测颜色通路。系统启动后先刷纯红、纯绿、纯蓝,本质上是在飞快地验证24位颜色通路是否正常。这一步如果颜色有问题,不要去调背光或伽马,先检查像素格式和0x36寄存器。
技巧三:把U-Boot当成调试中枢工具。全志平台的LCD配置在UBoot和Kernel各有一份,UBoot的验证速度远快于内核。建议把设备树调通、屏幕点亮作为第一目标,再进内核调。很多BSP显示疑难杂症其实在UBoot阶段就能定位出是配置问题还是硬件问题。
技巧四:记录每一条修改前后的diff。这种调试最怕改了一堆参数后哪个生效了都不知道。我建议大家改设备树时用一个git分支,每一次变更单独提交。后面排查闪屏时,能回滚到任意一版配置来验证。这个习惯帮我在本次调试中省了很多重复验证的时间。
5. 后续扩展与验收要点
这块内容本来打算简单带过,但回想整个调试过程,有些话还是值得写出来。
MIPI DSI调试一般不会只调一块屏。同一个项目里很可能出现第二款面板、不同分辨率、不同lane数、甚至不同接口(LVDS转MIPI桥接)的情况。我这次调好LCD之后,又重新梳理了一遍设备树,把分辨率参数、lane数量、像素格式单独抽出来作为宏定义,方便后续换屏时直接改几个地方,而不是翻全篇配置。这个工作在后来的第二块屏适配中至少节省了一半时间。
同时建议做一次完整的显示压力测试:整机跑视频播放、休眠唤醒、背光亮度调节、动态分辨率切换,看看长时间运行有没有潜在大时间延迟的问题。我遇到的那个LDO纹波问题就是在压力测试阶段暴露出来的。
如果只是想要屏幕点亮,调试到这里就可以收工;如果想把显示部分做成产品级稳定,强烈建议把上述几个验证项全部跑一遍。BSP调试和面包板点灯最大的区别就在这里,前者要求的是全时段的稳定,后者只要求瞬时的成功。