1. 项目背景与整体设计思路
U5 平台上调 LT9211 桥接芯片那阵子,我们被一个叫 BHK 的怪问题折磨了整整一周。屏幕在量产测试时偶尔出现瞬间的横纹闪烁,几十秒一次,毫无规律。逻辑分析仪抓 LVDS 输出端,能看到行消隐期莫名其妙多出一串短脉冲,我们内部把这种现象记作 BHK,全称是 Blanking Hazard Key,用来描述"水平消隐期间出现的危险信号"。问题最诡异的点是:没有人能说清楚它到底是谁生成的。STM32U5 侧的输出看起来一切正常,LT9211 的配置读回来也符合预期,LVDS 线对重新焊接过,电源纹波也压下去了,可 BHK 就执着地随机出现。
这篇文章我会把整个排查过程完整记录下来。从现象确认、物理层排查、寄存器手册阅读,到最终定位根因,包括踩过的坑和工具使用技巧,都会写清楚。如果你正在做 MIPI DSI 转 LVDS 的显示方案,或者手上也有类似的"异常信号不知道谁产生"的难题,这篇文章应该能给你一些可复用的思路。
1.1 硬件链路:U5 平台和 LT9211 的分工
先交代一下系统架构。这个项目的显示链路是这样走的:
- STM32U5 系列主控(具体型号是 STM32U599,带硬件图形加速和 LTDC 控制器)负责 UI 渲染。
- 主控通过 MIPI DSI 接口输出图像数据。
- LT9211 是龙讯半导体的 MIPI DSI/CSI 转双通道 LVDS 桥接芯片,负责把 DSI 信号转成 LVDS 差分信号。
- LVDS 输出直接驱动一块 1920x1080 分辨率的工业液晶屏。
选 STM32U5 是因为项目需要低功耗+较高图形性能,U5 的 Cortex-M33 内核加上 2D 图形加速器够用,而且集成了 LTDC,配合 DSI 输出比较顺手。选 LT9211 则是从成本、供货、成熟度三个维度综合考虑的:MIPI 转 LVDS 的桥接方案里它出货量很大,参考资料多,可配置的寄存器也比较细,适合做多型号屏幕适配。
这个链路里有个容易出现"责任真空"的地方:DSI 两端分别属于两个芯片,物理上中间只有几对差分线。一旦出现 BHK 这种信号异常,主控觉得是桥接芯片的问题,桥接芯片觉得是主控时序的问题,双方都能给出"我没问题"的证据,最后就变成悬案。
1.2 BHK 现象描述:为什么它能成为悬案
先精确描述一下 BHK 的表现,方便后面和你们遇到的现象对照:
- 屏幕偶发横纹闪烁,频率大概是每分钟一次到几分钟一次不等。
- 闪烁不是整屏花屏,而是屏幕上部或中部出现一条大约 10 到 20 行高的细纹。
- 闪烁持续时间极短,肉眼能看见,但用手机拍慢动作才能捕捉清楚。
- 正常屏幕自检和 LVDS 信号完整性测试都通过,老化测试也稳定,就是量产测试的间歇性场景会触发。
我们用逻辑分析仪在 LT9211 的 LVDS 输出端抓取完整帧,发现问题总是发生在水平消隐区(HBlank)。正常的 LVDS 信号在 HBlank 期间应该是稳定的空电平或特定的消隐编码,但 BHK 出现时,HBlank 区间插入了一个不符合协议规范的短脉冲组合。这个脉冲不是主控通过 DSI 明确送过来的,因为我们在 DSI 输入端同时抓包,没有看到主控发送对应的数据包。
这就带来三个嫌疑端:STM32U5 主控在背地里通过某些未被察觉的机制生成了这个信号?LT9211 在内部悄悄插入了它?还是屏幕面板端的寄生耦合产生的假象?
2. 先做减法:排除物理层和主控侧嫌疑
遇到这种"谁生成"的问题,我的习惯是先不碰协议层,把所有物理层嫌疑排除干净,再往上走。BHK 这种短促脉冲,第一反应确实很像噪声,但噪声通常不会稳定地出现在 HBlank 的特定位置,也不会每次都避开有效数据区。所以物理干扰只是需要在清单上划掉,不是主要嫌疑人。
2.1 先把 STM32U5 的输出钉死
第一步是确认 STM32U5 送出去的 DSI 信号到底干不干净。我们用示波器在 DSI 端口的终端电阻附近测量 HS 和 LP 状态转换。要特别注意 MIPI DSI 的 HS(高速)模式传输的是图像数据,LP(低功耗)模式传输的是命令或者进入/退出逃逸模式。在行消隐期间,DSI 链路应该进入 BLLP(Blanking and Low-Power Period)状态,也就是先发少量空包,然后链路进入 LP 状态,等待下一个 HS 突发。
用示波器抓 DSI 数据线的电平切换,对比参考波形:
- HS 突发周期是否和 LTDC 配置的行长度一致。
- HS 到 LP 的切换毛刺是否在规范允许范围内。
- LP 状态下的共模电压是否稳定,有没有跌落。
我们反复抓了上百次,BHK 发生时 DSI 输入端完全没有对应异常。也就是说,至少在 BMC(Bit-level Clock? 这里实际应为 Burst Mode?) 层面,主控没有主动发送任何"导致 BHK"的数据。当然,有一种可能性是主控的 LTDC 配置里某个 FIFO 溢出导致 DSI 控制器丢数据、自动插入填充包,但即便发生这种情况,LT9211 也应该能正常处理,不应当输出一个非法脉冲。所以主控侧的嫌疑大幅下降。
2.2 物理链路和电源排查的常见误区
第二刀砍向物理层。LVDS 是差分信号,对共模噪声不敏感,但前提是差分对的两根线保持等长、等距、阻抗匹配。我们做了这几件事:
- 用 LCR 表量 LVDS 线对,确认没有短路或开路。
- 用示波器在屏幕连接器处量差分电压幅值,确认在 350mV 左右,没有明显衰减。
- 检查 LT9211 供电:3.3V 和 1.2V(实际按手册可能是 1.8V)纹波,BHK 出现时没有明显跌落。
- 重新焊接 FPC 连接器附近的焊点,排除接触不良。
这里有个容易踩的坑:总觉得"BHK 是干扰",但如果不先确认信号是 " 从哪一段物理链路上被观测到" 的,就可能被假象带偏。我们的逻辑分析仪探头挂在 LT9211 输出端和屏幕连接器两端,对比两端信号:如果只在屏幕侧能看到、输出端没有,那就是链路耦合的问题;如果输出端就有,那说明是 LT9211 之后、线缆之前的源头发出的。实测下来,LT9211 的输出端就能抓到 BHK,这说明源头在 LT9211 内部或更上游,线缆耦合论基本排除。
2.3 确认外部控制和 I2C 配置链路
第三步检查外部控制链路。LT9211 的初始化是通过 I2C 完成的,如果初始化过程有误,芯片可能处于半配置状态。我们做了两个验证:
- 把 I2C 读写时序全部加上超时和重试机制,防止偶发 NACK。
- 在正常运行中周期性读回 LT9211 的芯片 ID 和关键配置寄存器,确认没有发生寄存器值漂移。
在这里我也犯了先入为主的错误:总觉得初始化已经跑通,屏幕能出画面,说明配置应该没问题。但"能出画面"不等于"配置完全正确"。后来我才意识到,可能有一个寄存器被写入了非预期值,它不影响主功能,但会在特定条件下触发异常行为。这就是 BHK 的温床。
3. 深挖寄存器手册:U5 版手册里藏着答案
物理层和主控侧排查完,BHK 依旧我行我素。这时候只能回到文档,把 LT9211 寄存器手册从头到尾翻一遍。我要特别提一句:我手里这份寄存器手册版本标记是 U5,网上如果搜 "lt9211 u5 寄存器手册",基本就是同一份文档的最新修订版。这份手册对我这次排查帮助非常大,因为它比早期版本补充了一些保留寄存器在特定测试模式下的描述。
3.1 LT9211 寄存器体系速览
LT9211 的配置寄存器大体分这几类:
- 芯片控制类:软件复位、I2C 地址、中断状态。
- 输入配置类:MIPI DSI 通道数、lane 速率、时序参数。
- 输出配置类:LVDS 通道映射、数据位宽、时钟极性。
- PLL 相关:输入输出像素时钟的分频倍频关系。
- 保留寄存器:手册里标为 Reserved,理论上驱动不应该去碰。
初次看手册,大家都容易把注意力放在输入和输出配置上,逻辑也很简单:我要 1920x1080 分辨率、60Hz 刷新率、LVDS 双通道 8bit 模式,按参考设计把寄存器和 PLL 算好就行。但这次 BHK 恰恰是保留寄存器区域出了问题。
3.2 对比官方参考配置,找到那处"多出来的字节"
我们的初始化代码是从厂商 SDK 移植过来,代码里有一张很长的配置表,类似这样:
static const uint8_t lt9211_init_table[][2] = { {0x00, 0x00}, {0x01, 0x01}, {0x02, 0x03}, // ... 中间省略 {0xE3, 0x10}, {0xE4, 0x20}, // 后面其实应该结束了 };当时出问题的并不是表里的某一个寄存器,而是整个配置表的长度在移植时被一个宏定义搞错了。C 语言里数组长度和初始化条目数不一致,导致循环写寄存器时多写了一两个字节。这些多余的字节不是我们想配置的寄存器地址,而是被当成了地址和值继续写入。
这种错位问题在嵌入式里很常见,尤其是用手工维护的长配置表时。它导致的后果是:某个本来应该是保留的寄存器,被写入了一个随机值。这个值触发了 LT9211 内置的某种测试逻辑,让芯片在水平消隐期间额外产生一个内部触发脉冲。这个脉冲从 DSI 输入侧看不到,因为是芯片内部生成的,通过 LVDS 输出端就能观测到。
你可以理解成:LT9211 被误喊了一声"开始自测",它就在每个 HBlank 的某个边界低调地执行了一小段内部信号翻转,而这段翻转正好被屏幕理解为非法信号。
3.3 消隐期机制:为什么 BHK 偏偏出现在 HBlank
要真正理解根因,还得补一下消隐期机制。视频信号由有效图像区和消隐区组成,消隐区又分为水平消隐(HBlank)和垂直消隐(VBlank)。在 MIPI DSI 链路里,HBlank 期间链路会进入低功耗状态,节省功耗。LT9211 负责把 DSI 的 HS/LP 切换转换成 LVDS 的 DE(Data Enable)和行同步信号。正常情况下,DE 拉低时 LVDS 输出应该保持空数据或特定编码,屏幕不会采样。
问题寄存器被误配置后,LT9211 内部的状态机认为消隐期应该插入某种"测试标记",于是 DE 拉低期间出现了不该有的电平翻转。屏幕控制板在接收 LVDS 信号时,对 HBlank 期间的信号变化本来就敏感,一旦出现非法翻转,内部的显示控制器就会把一部分行数据误当作有效数据,导致屏幕上出现瞬态横纹。
这个机制也解释了为什么 BHK 只是偶发、看起来无规律:触发条件和具体插入位置与内部 PLL 锁定状态、温度、电压都有微弱的关联,只要处于临界状态就可能触发,不像写错一个静态寄存器那样每次上电都固定复现。
4. 定位与修复:BHK 到底是谁生成的
前面说的都是推理和排除法,真正定位还得靠实锤。下面是我觉得对同类问题最有价值的几个实测步骤。
4.1 用逻辑分析仪卡时间点,读回寄存器现场
BHK 是偶发的,不能指望一抓就能抓到。我当时的做法是:
- 用逻辑分析仪长时间录制 LVDS 输出,采样率设到 1GS/s,保证能看清差分信号的细节。
- 在逻辑分析仪的触发条件里设置"当 HBlank 期间出现异常脉冲时触发"。
- 录制到 BHK 事件后,立即通过 I2C 读回 LT9211 的一批关键寄存器值。
- 和正常状态下的寄存器快照做 diff。
这个方法听起来简单,但执行起来有三个细节要注意:
- 逻辑分析仪的地线必须接在 LT9211 输出端的参考地附近,不要隔着屏幕的线缆去接远端地,否则抓到的波形可能包含地弹噪声。
- I2C 读回动作要写成一个随时可触发的脚本,不能等事件发生后再手动敲命令,因为 BHK 是偶发的,手动操作根本来不及。
- 寄存器快照对比不能只看配置寄存器,还要看状态类和中断类寄存器。很多状态下,芯片会置位某个内部标志,但它并不会主动上报,只有主动读回才能发现。
我第一次读回寄存器,惊讶地发现读出来配置表和预期值完全一致,说明静态配置本身没有漂移。但当我读到一块状态寄存器区域时,里面多了一个非零的 bit。这个 bit 在 U5 版手册里对应的是"测试模式激活指示"。虽然手册把它归为只读状态,但正常初始化流程下它永远应该是 0。它变成 1,说明芯片确实进入了异常测试模式。
4.2 根因确认:一个错位字节引发的内部测试信号
接下来就是追溯这个状态位为什么被置位。思路回到初始化流程:既然状态寄存器显示"测试模式激活",那一定是在初始化阶段某个地址被写入了非预期值。我把厂商 SDK 的原始配置文件调出来,和当前工程里的配置数组逐字节对比,最终发现工程里的配置表比 SDK 版本多了两个元素。
这两个多余元素导致 I2C 写入的时候,把本来是"配置结束"之后的数据当成下一步的地址继续写。具体来说,多出来的地址落入了保留寄存器区域。保留寄存器本来应该保持复位默认值,却被写入了 0x02。而 U5 版手册里有一段补充说明:该保留寄存器 bit1 写 1 会启动一种内部测试信号,测试信号从 LVDS 输出端可见,且在水平消隐期间最容易被观测到。
到这里,"BHK 是谁生成的"这个问题有了明确答案:既不是 STM32U5 主动发的,也不是屏幕面板反向耦合的,而是 LT9211 因为保留寄存器被误写、进入内部测试模式后自己生成的。
4.3 修复方案与验证
修复就很简单了:把配置数组恢复成 SDK 原始长度,或者干脆在初始化末尾加一个寄存器回读校验,确保保留寄存器区域都是默认值。改完之后重新烧录,连续跑了 72 小时老化测试,BHK 再没有出现过。
为了防止以后再犯同类错误,我还在初始化函数里加了这样一段防御性代码:
static int verify_lt9211_reserved_regs(void) { uint8_t val; // 以 0x80 到 0x9F 之间的保留地址为例,实际按手册确认 for (uint8_t addr = 0x80; addr <= 0x9F; addr++) { read_lt9211_reg(addr, &val); if (val != 0x00) { return -1; } } return 0; }每次初始化完成后调用一次,一旦发现保留寄存器被意外改写,立刻报警并停止输出,绝不允许带着隐患进入量产。
5. 问题排查实录与避坑技巧
最后这部分算是我这次调 BHK 的经验输出,不一定只适用于 BHK,所有"异常信号不知道谁生成"的问题都可以参考。
5.1 类 BHK 问题速查表
我整理了一个速查表,下次遇到类似现象可以直接对照排查:
| 现象 | 可能原因 | 首查手段 |
|---|---|---|
| 屏幕偶发横纹/闪线 | 配置表错位写入保留寄存器 | 读保留寄存器状态位 |
| HBlank 期间出现莫名脉冲 | LT9211 内部测试模式被意外触发 | 状态寄存器读回 |
| 屏幕顶部固定行出现细纹 | DSI 输入时序 HFP/HBP 设置过小 | 对比主控 LTDC/DSI 配置 |
| 全屏规则性闪烁 | PLL 不稳定或时钟恢复异常 | 测量像素时钟和 LVDS 时钟抖动 |
| 偶发横纹 + I2C 偶发 NACK | 初始化过程存在未处理的重试竞态 | 加超时和重读校验 |
| LVDS 线缆过长导致的拖影 | 线缆衰减或阻抗不连续 | 测眼图或换短线验证 |
这个表不一定覆盖所有场景,但可以帮你快速划分责任方向,少走弯路。
5.2 示波器和逻辑分析仪的接线避坑
抓 HBlank 期间的短脉冲,对探头的接法要求很高。我这次踩了两个坑:
第一个坑是探头地线夹得太长。普通示波器探头的地线夹如果悬空太长,会形成一个天线环,把环境噪声耦合进来,导致看到的波形里都是假毛刺。正确做法是使用接地弹簧针,把地线缩短到探头针尖附近,再测量 LVDS 的单端参考信号。
第二个坑是逻辑分析仪的采样率不够。LVDS 信号在 1080p60 下,像素时钟可能跑到 148.5MHz,差分数据速率数百 Mbps 量级。逻辑分析仪采样率如果只有 100MS/s,根本看不清细节。至少要用 500MS/s 以上,带宽跟不上,很容易把真实脉冲误判为噪声,或者反过来把噪声当成真实信号。
5.3 用 I2C 读寄存器做现场快照
排查这类问题时,手头最好有一个能快速读寄存器的工具。我是直接用调试器和一块小的 USB-I2C 转接板做的临时方案。核心代码不复杂,但建议你提前封装好,不要到现场再临时写。一个参考的 C 语言风格伪代码如下:
uint8_t reg_val; for (uint16_t reg = 0x00; reg <= 0xFF; reg++) { if (i2c_read(0x98, reg, ®_val) == 0) { printf("reg[0x%02X] = 0x%02X\n", reg, reg_val); } else { printf("reg[0x%02X] read failed\n", reg); } }同时记得在事件发生前后各抓一次快照,然后做 diff。不需要每次全量导出来,除非你想做全寄存器对比。我当时是只对比差异区域,重点看状态位,这样效率高很多。
5.4 预防性检查:别迷信"能出画面就没事"
吃过这次亏之后,我给自己定了一条规矩:桥接芯片初始化完成后,不能只看屏幕有没有画面,还要主动做一次关键寄存器回读校验。具体来说:
- 配置写入完成,等待稳定时间。
- 读回所有配置过的寄存器,和预期值逐一比对。
- 读回保留寄存器区域,确认没有被意外改写。
- 如果发现任何异常,直接打印崩溃原因,不做后续流程。
另外,长配置表建议在编译期做长度校验。可以用 C 语言的静态断言或者脚本生成检查代码,确保数组长度和循环边界严格一致。这是最能防微杜渐的一环。
这次 BHK 排查给我最大的教训是:很多看似神秘的信号异常,根因往往藏在最不起眼的初始化和细节里。不要因为"配置能出画面"就忽略寄存器回读,不要因为"嫌疑集中在某颗芯片"就直接下结论,也不要把偶发问题轻易归给干扰。先确认物理层,再深挖文档,最后用工具做实锤,这条路径虽然耗时,但一定能找到答案。