1. 一块板子引发的血案:为什么HDMI搭错线能让人调三天
搞嵌入式的人大概都有过这种经历:板子打回来,电源正常,系统能跑,串口有打印,一切看起来都对,但屏幕就是不亮。你开始怀疑屏幕坏了、线材有问题、驱动没编进去,折腾一圈下来发现——原理图上两根线画反了。
这次踩的坑就属于这一类。项目主控用的是RK3576,需要从HDMI接口输出画面到一个外接显示设备。硬件方案上,HDMI连接器到主控之间走了I2C做EDID读取,同时还有HPD(热插拔检测)信号和几路电源控制。问题出在I2C的SDA和SCL上——PCB上这两根线被交换了。
你可能觉得这算什么大事,I2C就两根线,换回来不就行了?但实际调试的时候,你面对的是一个“没有任何反应”的系统。HDMI没有输出,串口log里I2C通信全是超时,你甚至不确定是硬件问题还是软件问题。更麻烦的是,这个板子上的I2C总线还挂了EEPROM和其他外设,你不能简单地把所有设备都判死刑。
这篇文章会把整个排查过程拆开讲清楚:从HDMI链路的基本工作原理,到I2C在其中的角色,再到怎么一步步定位到“线搭错了”这个结论,最后给出硬件改板和软件规避的具体方案。如果你正在用RK3576做HDMI相关设计,或者任何涉及I2C通信的嵌入式项目,这些经验应该能帮你少走几天弯路。
2. HDMI链路里到底藏了多少根线
2.1 不只是TMDS:HDMI的完整信号构成
很多人一提HDMI就觉得是“四对差分线传视频”,这个理解不算错,但远远不够。一个能正常工作的HDMI链路,至少包含以下几类信号:
- TMDS差分对:四对,其中三对传视频和音频数据,一对传时钟。这是高速信号,速率从几百Mbps到几Gbps不等。
- DDC通道:本质上就是一路I2C,包含SDA和SCL两根线。它的作用是让源端(我们的板子)读取接收端(显示器)的EDID信息,也就是显示器支持哪些分辨率、刷新率、音频格式等。
- HPD:热插拔检测,接收端通过拉高这根线告诉源端“我插上了,可以开始通信”。
- CEC:消费电子控制,用于设备间联动控制,很多项目里直接不用。
- +5V电源:源端通过HDMI线给接收端的EDID芯片供电,让显示器在未开机时也能被读取到信息。
你看,光是和“通信”相关的就有DDC和HPD两套机制。DDC走的是I2C协议,HPD是GPIO电平。这两个任何一个出问题,HDMI都不会正常输出。
2.2 为什么I2C在HDMI里这么关键
EDID读取是整个HDMI握手的第一步。源端上电后,会先检测HPD是否有效,然后通过DDC通道(I2C)去读接收端EDID ROM里的数据。只有读到了合法的EDID,系统才知道该输出什么分辨率、什么时序。
如果I2C通信失败,会发生什么?分几种情况:
- 如果驱动写得比较“硬”,读不到EDID就直接不输出,你会看到HDMI接口完全没有信号。
- 如果驱动有fallback逻辑,可能会用一个默认分辨率强行输出,但兼容性很差,有些显示器能亮,有些不能。
- 如果I2C时好时坏,可能会出现“偶尔能亮、偶尔不亮”的玄学现象。
这次的问题属于第一种:I2C完全不通,HDMI直接躺平。
2.3 RK3576的HDMI控制器架构
RK3576内部集成了HDMI TX控制器,它负责TMDS信号的编码和发送。但EDID读取这部分,通常不是HDMI控制器自己完成的,而是通过SoC内部的I2C控制器去访问外部连接器上的DDC线路。
这里就涉及到一个硬件设计上的关键点:HDMI连接器的DDC引脚(Pin 15和Pin 16)需要连接到SoC的某一路I2C总线。这一路I2C总线上通常还会挂一个EEPROM,用于存储板级信息或者作为备用EDID。
在我们的设计里,HDMI的DDC连到了I2C总线的一路,同时这路I2C还挂了一个EEPROM。问题就出在这里:PCB上SDA和SCL走线交换了。
3. 排查过程:从“屏幕不亮”到“线搭错了”
3.1 第一轮怀疑:软件配置和驱动
板子回来第一次上电,串口有正常启动log,系统起来了,但接上显示器没有任何反应。第一反应是软件问题,于是开始查:
- 设备树里HDMI节点有没有使能?确认了,status = "okay"。
- I2C控制器有没有使能?确认了,对应的i2c节点也是okay。
- HDMI驱动有没有加载?用
lsmod看了,相关模块都在。 - 内核log里有没有HDMI相关的报错?有,出现了
i2c timeout和edid read failed。
到这里,基本可以确定是EDID读取失败导致HDMI没有输出。但为什么读取失败?可能是I2C通信本身有问题,也可能是EDID芯片没供电,还可能是HPD信号不对。
3.2 第二轮怀疑:HPD和电源
先查HPD。用万用表量了HDMI连接器的HPD引脚,插上显示器后电平有变化,说明显示器那边正常拉高了。再查+5V,也正常。EDID芯片的供电来自HDMI线的+5V,量了一下EEPROM的供电引脚,电压正常。
那问题就缩小到I2C通信本身了。
3.3 第三轮怀疑:I2C总线上其他设备
这路I2C上还挂了一个EEPROM。用i2cdetect扫了一下总线,发现所有地址都扫不到。这就有意思了——如果只是HDMI的EDID读不到,那可能是显示器那边的问题,但现在连板载EEPROM都扫不到,说明整条I2C总线都不工作。
这时候开始怀疑几种可能:
- I2C控制器配置有问题(时钟频率、引脚复用等)。
- 上拉电阻没焊或者阻值不对。
- PCB走线有问题,SDA和SCL交换了。
3.4 用示波器抓波形:真相浮出水面
拿示波器同时抓SDA和SCL两根线。发送读取命令时,可以看到SCL上有正常的时钟脉冲,但SDA上的波形很奇怪——它看起来像是被SCL干扰了,而不是正常的数据跳变。
再仔细看,发现一个关键现象:当主机发送起始条件时,SDA应该在SCL高电平期间拉低。但实际抓到的波形里,是SCL在SDA高电平期间拉低了。这说明什么?说明我们以为的SDA和SCL,在物理上被交换了。
为了确认这个判断,我把探头对调了一下再抓,波形立刻变得正常了。到这一步,问题定位完成:PCB上HDMI DDC的SDA和SCL走线交换了。
4. I2C通信的核心机制与常见硬件陷阱
4.1 I2C的起始、停止和数据有效性
I2C协议看起来简单,但它的时序规则非常严格。核心规则就几条:
- 起始条件:SCL为高时,SDA从高变低。
- 停止条件:SCL为高时,SDA从低变高。
- 数据有效性:SCL为高期间,SDA必须保持稳定;SDA只能在SCL为低时改变。
如果SDA和SCL交换了,会发生什么?主机发出的起始条件会变成“SCL在SDA高时拉低”,从设备根本识别不到起始条件,通信自然无法建立。
4.2 开漏输出与上拉电阻
I2C的SDA和SCL都是开漏(open-drain)结构,这意味着:
- 设备只能把线拉低,不能主动拉高。
- 线的高电平靠上拉电阻提供。
- 总线上任何一个设备拉低,整条线就是低。
这就解释了为什么SDA和SCL交换后,波形会那么奇怪——两个开漏引脚互相干扰,谁也没法正常通信。
上拉电阻的选取也有讲究。阻值太大,上升沿太慢,高速通信时波形会变形;阻值太小,功耗增加,低电平时的灌电流可能超过器件规格。一般3.3V系统用4.7kΩ比较常见,高速模式(400kHz以上)可能会用2.2kΩ甚至更小。
4.3 电平转换的必要性
如果HDMI接收端的DDC电压和SoC的I2C电压不一致,就需要电平转换。比如SoC侧是1.8V,HDMI侧是5V,直接连会烧芯片。常见的方案是用专用的I2C电平转换芯片,或者用MOS管搭一个简单的双向电平转换电路。
这次的设计里,SoC侧和HDMI侧都是3.3V,所以不需要电平转换。但如果你的设计里电压不一致,这一步绝对不能省。
5. 硬件改板与软件规避方案
5.1 硬件改板:飞线还是改PCB
确认SDA和SCL交换后,最直接的解决办法是改板。但改板有几种选择:
- 飞线:在PCB上把两根线割断,用细导线交叉连接。适合调试阶段,但不适合量产。
- 改PCB:下一版修正走线。这是最终方案,但需要时间。
- 软件规避:如果I2C控制器支持引脚交换,可以在软件层面把SDA和SCL的功能对调。
我们当时选择了飞线先验证,确认HDMI能正常输出后,下一版PCB修正了走线。
5.2 软件规避的可行性分析
有些SoC的I2C控制器支持通过寄存器配置来交换SDA和SCL的引脚功能。RK3576的I2C控制器是否支持这个功能,需要查TRM(技术参考手册)。如果支持,可以在设备树或者驱动里配置,不用改硬件。
但即使支持,也不建议在量产项目里这么做。原因很简单:软件规避会增加维护成本,而且一旦有人改了配置或者换了芯片型号,问题会再次出现。硬件问题最好在硬件层面解决。
5.3 验证方法:怎么确认HDMI链路完全正常
改完之后,怎么确认HDMI链路完全正常?我一般会做这几步:
- 用
i2cdetect扫描总线,确认EEPROM和HDMI DDC地址都能扫到。 - 用
i2cget读取EDID的前几个字节,确认数据合法(EDID头部固定是00 FF FF FF FF FF FF 00)。 - 接上显示器,确认系统能识别到显示器的分辨率和刷新率。
- 实际输出画面,确认图像稳定、无闪烁、无偏色。
这四步都过了,才能说HDMI链路没问题。
6. 常见问题速查与避坑经验
6.1 I2C通信失败排查清单
| 现象 | 可能原因 | 排查方法 |
|---|---|---|
| 所有地址都扫不到 | SDA/SCL交换、上拉缺失、控制器未使能 | 示波器抓波形、量上拉电压 |
| 部分地址能扫到 | 地址冲突、某个设备拉死总线 | 逐个断开设备排查 |
| 通信时好时坏 | 上拉阻值不合适、走线过长、干扰 | 调整上拉、缩短走线、加屏蔽 |
| 读到的数据全FF | 设备未供电、设备未就绪 | 量供电、加延时重试 |
6.2 实操心得:几个容易忽略的细节
- 上拉电阻的位置:尽量靠近主控端,不要放在总线末端。
- 走线长度:I2C虽然速率不高,但走线太长也会导致上升沿变缓,400kHz以上尤其明显。
- 地线回路:HDMI连接器的地要和板子的地良好连接,否则DDC通信会受干扰。
- HPD时序:有些显示器HPD拉高比较慢,驱动里要加足够的延时再去读EDID。
6.3 关于RK3576的HDMI设计建议
如果你正在用RK3576做HDMI相关设计,这几点值得注意:
- 仔细核对原理图里HDMI连接器的DDC引脚和SoC I2C引脚的对应关系,SDA对SDA,SCL对SCL。
- PCB布局时,DDC走线尽量短,远离高速差分线。
- 如果这路I2C还挂了其他设备,确保地址不冲突。
- 调试阶段先用
i2cdetect确认总线能扫到设备,再调HDMI输出。
7. 从这次踩坑里提炼出的通用方法论
嵌入式调试最怕的就是“不知道问题在哪一层”。软件工程师习惯先查驱动和配置,硬件工程师习惯先量电平和波形。但实际项目里,问题往往跨越软硬件边界。
这次的经验告诉我,遇到“完全没反应”的问题,应该按这个顺序排查:
- 电源和时钟:最基本的,先确认供电正常、时钟正常。
- 物理层:量关键信号的电平,确认没有短路、断路、交换。
- 协议层:用示波器或逻辑分析仪抓波形,确认时序符合协议规范。
- 驱动层:确认驱动加载、设备树配置、寄存器配置正确。
- 应用层:确认上层软件逻辑没有问题。
这个顺序的核心逻辑是:从底层往上层查,因为底层问题会表现为上层各种奇怪的现象。如果你反过来查,很容易被表象迷惑,浪费大量时间。
另外,I2C虽然简单,但它的开漏结构和严格时序规则决定了它对硬件设计很敏感。SDA和SCL交换、上拉缺失、电平不匹配,这些问题在原理图阶段很容易被忽略,但到了调试阶段就是灾难。所以画原理图和PCB的时候,对I2C总线多看一眼,可能就能省下几天的调试时间。
最后说一个实际体会:调试的时候,示波器比万用表有用得多。万用表只能告诉你“现在是高还是低”,示波器能告诉你“什么时候高、什么时候低、上升沿多快、有没有干扰”。对于I2C这种时序敏感的协议,示波器基本是必备工具。如果手头没有示波器,一个便宜的逻辑分析仪也能凑合用,至少能看到时序关系对不对。