ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

HDMI 2.0 SCDC配置实战:寄存器、I2C读写与TMDS切换避坑指南

HDMI 2.0 SCDC配置实战:寄存器、I2C读写与TMDS切换避坑指南 1. 先搞清楚SCDC是干什么的别再对着黑屏瞎猜如果你做过HDMI 2.0的4K60输出多半遇到过这么个场景板卡能正常读回EDID显示器也明确写了支持3840x216060Hz可信号就是出不来输出端明明已经切到了594MHz像素时钟屏幕上却是满屏雪花或者干脆黑屏。拿逻辑分析仪抓DDC总线看到的全是EDID重复读取压根找不到任何关于TMDS配置的寄存器访问记录。很多人会怀疑PCB布线、怀疑FPGA时序、怀疑线缆但真正的问题往往出在一个容易被忽略的地方——SCDC配置寄存器。SCDC全称是Status and Control Data Channel它是HDMI 2.0里专门用来在源端和接收端之间传递TMDS高速传输配置信息的通道底层实现就是一条I2C总线挂在HDMI DDC上。这篇文章不聊HDMI 2.0的宏观协议背景直接聚焦SCDC从原理到实操它的寄存器怎么编排、在Linux下怎么用i2c-tools读写、在MCU或者FPGA上怎么写I2C操作序列以及TMDS配置切换时我实际踩过的那些坑。正在做HDMI 2.0源端/接收端或者调驱动时显示一直不稳定的朋友可以收藏慢慢看。1.1 4K60为什么要把TMDS时钟压到297MHz先讲个很多人没仔细想的问题为什么HDMI 2.0非要引入一个“0.5x时钟模式”HDMI 2.0的带宽标称是18Gbps对应的就是4K60Hz、24bit色深、RGB/YCbCr 4:4:4这种常见格式。很多工程师一看到18Gbps这个数字第一反应是“传4K60绰绰有余”然后照着参考设计把TMDS时钟直接怼到594MHz。问题是594MHz只是像素时钟TMDS编码后实际跑在链路上的信号频率还要更复杂线缆、连接器、PCB过孔、接收端均衡器在这种频率下都有明显的损耗和反射。HDMI论坛在制定规范时做了一个很实际的妥协像素时钟超过340MHz时让TMDS时钟跑像素时钟的一半也就是0.5x模式。4K60像素时钟594MHzTMDS Bit Clock Ratio设为2TMDS时钟就是297MHz信号完整性压力瞬间小了很多。这个妥协的代价是接收端不能简单地用TMDS时钟直接恢复像素时钟了它必须知道“源端现在用的是1x还是0.5x”否则恢复出来的像素时钟就是错的画面必然乱套。这个“告知”的动作就是通过SCDC寄存器来完成的。你可以把SCDC想成一个对讲机源端在切换TMDS模式之前要先通过对讲机喊一声“我准备切到0.5x模式了你那边做好准备”接收端回应“收到我按0.5x来接”。如果没有这一步两边各跑各的黑屏花屏就是必然结果。1.2 SCDC挂在DDC总线上地址却是0xA8/0xA9搞清楚SCDC的物理位置也很关键。HDMI的DDC通道本质就是I2C总线传统用途是读EDIDEDID的7位地址是0x50用8位写法就是0xA0写/0xA1读。SCDC不一样它在同一根DDC总线上的7位地址是0x54换算成8位访问地址就是0xA8写、0xA9读。这个地址很容易让人绕晕。很多第一次调SCDC的人会在代码里用0xA8/0xA9结果发现i2cget这类工具怎么都读不到数据然后开始怀疑硬件。实际上i2c-tools默认用的是7位地址你要写i2cget -y 3 0x54 0x00而不是0xA8。反过来如果你在FPGA的Verilog代码里写I2C字节因为I2C的SLAVE_ADDRESS字节本身是无符号8位从机地址左移一位得到的所以第一字节应该发0xA8写方向或者0xA9读方向。7位0x54和8位0xA8/0xA9是同一个从机只是表达方式不同这个换算关系在调试时最好一次性刻在脑子里。另外要明确一点SCDC不是一个独立的物理芯片一般和EDID EEPROM一起放在显示器或转换芯片内部挂在同一条DDC I2C总线上。所以读SCDC之前正常的顺序是先探一下0x50能不能读到EDID确认DDC链路通了再去访问0x54。2. SCDC寄存器地图核心地址从头到尾过一遍SCDC寄存器的地址空间并不大0x00到0x11这一段是HDMI 2.0真正会用到的主干0x20之后还有状态标志和一些扩展字段0x30之后到0xFF基本上是HDMI 2.1才扩展的FRL相关能力。做HDMI 2.0的话抓住下面几个核心寄存器就够了。偏移地址寄存器名称读写属性作用0x00SCDC Present只读接收端是否支持SCDCbit0为1表示支持0x01Source Version读/写源端SCDC版本必须写10x02Sink Version只读接收端SCDC版本通常为10x03Update Flags读/写清接收端向源端发请求写1清对应位0x10TMDS Configuration读/写TMDS时钟比率与加扰使能0x20-0x23Status Flags只读接收端状态标志比如是否锁定信号0x30Sink Features只读接收端能力描述2.1时才有实际意义2.1 0x00到0x03SCDC对话的握手环节0x00是SCDC Present最高位或者bit0是使能标志。很多资料里写的是“bit01表示Sink支持SCDC”实际做兼容性测试时建议你把整个寄存器读出来看因为不同芯片厂商可能会把保留位也置1。一个经验是如果读0x00返回0x00就别再做任何SCDC写操作了这个接收端八成是HDMI 1.4的你写版本号、写TMDS配置它都不会理老老实实把输出降到1080p或者4K30才是正道。0x01和0x02分别是Source Version和Sink Version。Source Version是源端主动写的必须写0x01表示“我支持SCDC版本1”。这一步看着简单却是整个SCDC机制能跑起来的前提。Sink Version是接收端上报的只读一般也是0x01。如果读回来是0x00说明这个显示器的SCDC实现不完整后续对TMDS配置的处理可能也有问题。0x03 Update Flags是接收端向源端发信号的窗口。接收端不会主动在总线上发中断它只能把这个寄存器的某位置1源端需要周期性去轮询。比如接收端希望源端重新配置TMDS模式它会把bit2TMDS配置请求置1。源端处理完请求后要向对应位写1来清除标志这是“写1清”的典型机制。如果源端不清标志接收端可能认为请求没有被处理一直保持请求状态也可能导致一些诡异的现象。2.2 0x10 TMDS Configuration时钟比率和加扰都在这里0x10是SCDC里最核心的寄存器bit0负责TMDS加扰Scrambling使能bit1负责TMDS Bit Clock Ratio写1表示使用0.5x模式像素时钟高于340MHz时写0表示1x模式。可以这样理解bit0是“要不要加扰”bit1是“要不要半速率”。为什么4K60需要同时设置这两个位一方面TMDS时钟降到297MHz之后接收端恢复像素时钟时需要知道实际比率另一方面高频TMDS信号在线上传输时如果数据码型重复度高会产生明显的EMI和串扰HDMI 2.0引入了一个基于线性反馈移位寄存器的加扰器让高频数据在传输前被打散接收端再根据同步种子解扰。0.5x模式和加扰是配套出现的像素时钟超过340MHz时几乎强制要求同时启用0.5x和加扰否则信号质量和兼容性都难保证。实际操作中有些驱动会先写bit1等接收端适应后再写bit0也有驱动直接写0x03。从实测来看分步写更稳妥尤其在兼容性测试时不同显示器的SCDC状态机实现差异很大。如果你在FPGA里自己写控制逻辑建议模仿Linux内核drm_scdc_helper的做法分两次写中间留几十微秒间隔给接收端一点反应时间。2.3 一条容易忽视的规则先写Source Version再谈其他这是SCDC配置里最容易踩的坑没有之一。我之前在调试一块国产HDMI 2.0发送芯片时反复确认0x00读回来是0x010x02 Sink Version也是0x01但无论怎么往0x10写TMDS配置接收端都不切换状态。最后翻接收端的驱动源码才发现它要求源端必须先完成0x01寄存器的版本宣告否则直接忽略0x10的写入。说白了Source Version是你的“自我介绍”你不先报名字对方不会跟你谈正事。3. 实测Linux命令行、MCU和FPGA三种玩法3.1 Linux下用i2c-tools直接摸DDC总线在嵌入式Linux调试阶段i2c-tools是最高效的工具。先确认HDMI对应的I2C总线号一般可以通过/sys/class/drm/card0-HDMI-A-1/下的i2c接口找到或者直接看/sys/bus/i2c/devices/。ls /dev/i2c-* i2cdetect -y -l i2cdetect -y -r 3假设HDMI DDC挂在i2c-3上i2cdetect -y -r 3扫描后你应该能在0x50附近看到EDID设备如果显示器支持SCDC0x54附近也会出现一个设备。如果只有0x50没有0x54要么显示器是HDMI 1.4要么SCDC设备没有被正确唤醒。接下来就是读取SCDC寄存器# 读取SCDC Present i2cget -y 3 0x54 0x00 b # 写Source Version宣告源端支持SCDC i2cset -y 3 0x54 0x01 0x01 b # 读取Sink Version i2cget -y 3 0x54 0x02 b # 读取Update Flags检查是否有TMDS配置请求 i2cget -y 3 0x54 0x03 b # 读取TMDS Configuration当前值 i2cget -y 3 0x54 0x10 b如果一切正常你会在0x00读到0x01在0x02读到0x01。如果0x03返回的bit2是1说明显示器在用SCDC请求你重新配置TMDS这时候再写0x10才有意义。注意i2c-tools里i2cset默认使用7位地址所以这里是0x54不是0xA8。一种更符合产品开发习惯的做法是在调试早期先不要同时打开系统自带的显示驱动否则内核DRM驱动和你的i2c-tools命令会同时访问DDC总线可能出现总线竞争导致某一边读到异常数据。如果必须用现成内核驱动建议通过debugfs看SCDC状态或者用drm_scdc_helper导出的接口去读而不是直接用i2c-tools硬刚。3.2 MCU裸机或FPGA里的I2C读写序列如果你的HDMI源是由FPGA或者MCU直接控制的那么你就得自己实现I2C主控制器。很多人在这里有个误区以为I2C地址写0xA8就万事大吉却忘了I2C的完整访问流程里读操作是需要“重复起始条件”的。写一个寄存器值的序列如下// 写SCDC寄存器 i2c_start(); i2c_write(0xA8); // 7位地址0x54左移1位写位 i2c_wait_ack(); i2c_write(reg_addr); // 例如0x01 i2c_wait_ack(); i2c_write(reg_data); // 例如0x01 i2c_wait_ack(); i2c_stop();读一个寄存器值的序列要复杂一点// 先写寄存器地址再用重复起始条件切到读方向 i2c_start(); i2c_write(0xA8); i2c_wait_ack(); i2c_write(reg_addr); i2c_wait_ack(); i2c_repeat_start(); // 重复起始条件这是很多新手漏掉的关键 i2c_write(0xA9); // 读方向 i2c_wait_ack(); data i2c_read(); i2c_send_nack(); i2c_stop();重复起始条件Repeated START把I2C的写地址和读地址连在一起让从机知道自己当前要操作哪个寄存器。漏掉这一步读回来的永远是0xFF或者上一次的数据。另外一个细节是从机地址字节0xA8发送后如果从机NACK不要继续发寄存器地址直接STOP重试避免总线卡死。关于I2C时钟频率HDMI DDC链路在规范里的标准模式是100kHz虽然很多显示器的SCDC能跑到400kHz但嵌入式平台上我仍然建议保守一点。我曾经为了提升读EDID的速度把SCL配置到400kHz结果手头三台显示器里有两台偶发性NACK换回100kHz后问题再没出现过。SCDC和EDID共用一条总线稳定性优先级远高于速度。3.3 逻辑分析仪怎么看SCDC时序逻辑分析仪测SCDC时建议采样率至少设到1MHz以上因为SCL就100kHz采样率太低会把边沿细节吃没。抓波形时要同时抓SCL、SDA、HPD热插拔检测三路信号重点观察几个位置启动后第一段I2C访问是否以0xA8地址开始SCDC从机是否返回了ACK写Source Version时数据字节是否为0x01以及重复起始条件是否出现在读操作里。如果从机一直NACK大概率是地址写错或者上拉有问题如果ACK正常但读回来的值和你预期不一致检查寄存器偏移地址是不是写错了不少工程师把0x10错写成0x01。另外要注意逻辑分析仪探头的电容有些劣质探头电容很大会拉低I2C总线信号质量尤其当上拉电阻比较大的时候边沿会被拖得很钝。最好用低电容探头或者直接从排针上用短线引出。4. TMDS配置避坑指南实测中的六个雷4.1 雷1Source Version漏写Sink不置位Update Flags前面提过但这里值得单独拉出来说因为它是所有SCDC问题的头号原因。现象是0x00和0x02读着都对但显示器就是不出图Update Flags永远是0。原因就是源端没有先写0x01。解决办法是在检测到显示器HPD有效后先i2cset 0x01为0x01再去做后续的TMDS配置。验证方法也简单写完0x01后再读0x03看TMDS请求位是否被置位。4.2 雷20.5x切换后InfoFrame和数据岛时序乱了TMDS配置切到0.5x模式之后一个很容易忽略的问题是TMDS时钟变了一半但像素时钟没变这意味着每个像素在TMDS链路上占用的时钟周期数发生了变化。很多自制HDMI发送器在切换后只是改了PLL没有同步调整数据岛Data Island的时序生成逻辑结果AVI InfoFrame里的视频时序参数错位显示器无法正确识别分辨率表现为画面撕裂、间歇性黑屏或者音频断断续续。解决思路是在切换TMDS时钟比率之前把发送器内部的数据包生成状态机一并复位。尤其是AVI InfoFrame、Audio InfoFrame、VSIF这些辅助数据的发送位置不能简单沿用1x模式下的周期计数。更保险的做法是只在消隐区做模式切换切换完成后第一帧不发送任何有效音视频数据只发空Data Island等接收端状态锁定了再恢复。我在FPGA里就是这么做的加了一个“切换完成前保持几个HBlank空包”的逻辑屏幕表现立刻稳定了。4.3 雷3DDC上拉电阻太小或太大直接导致通信失败SCDC本质是I2CI2C必须开漏输出加上拉电阻。嵌入式板卡上常见的上拉值是2.2kΩ到4.7kΩ但HDMI DDC这条总线比较特殊它要同时兼容显示器内部的EDID EEPROM、SCDC模块以及可能的HDCP EEPROM总线电容和其他I2C总线不完全一样。如果你发现i2cget能读到EDID但读不到SCDC或者访问偶尔超时先查上拉电阻。上拉太小比如只有470Ω总线低电平时灌入电流过大可能导致Sink端逻辑电平识别异常通信失败上拉太大比如10kΩ总线上升沿过缓超过I2C时序要求同样会失败。另外注意源端的其他下拉器件比如ESD保护管的结电容也会影响总线时序。建议用示波器看SCL/SDA的上升沿如果上升时间超过1μs就把上拉电阻调小一档。4.4 雷4热插拔之后SCDC寄存器复位只配一次远远不够很多人写的HDMI初始化代码只在系统启动时配置一次SCDC但实际使用中用户会在系统运行时插拔HDMI。每次热插拔接收端SCDC寄存器都可能回到默认状态Source Version清零TMDS配置清零。如果系统没有在HPD中断处理里重新初始化SCDC就会出现“第一次开机正常拔了再插就无信号”的经典问题。正确的做法是把SCDC初始化流程写进HPD中断处理函数只要检测到HPD的有效沿就重新走一遍读EDID、读0x00、写0x01、检查Update Flags、写TMDS配置。有些显示器的SCDC设备在热插拔之后需要几百毫秒才能稳定访问HPD触发后最好延时100ms到200ms再开始访问DDC避免读到中间态。4.5 雷5SCL跑到400kHz某些Sink直接NACK精工和日系的一些HDMI接收芯片SCDC模块在高速SCL下会有意想不到的NACK。我在一套基于某国产SoC方案的板卡上测试时系统默认把DDC总线设成400kHz结果同一台显示器在另一块标准100kHz的板子上正常在这块板子上读SCDC时偶发失败。深入研究后发现SPD里的EEPROM能承受400kHz但SCDC从机的状态机比较“脆”当时序余量不够时直接不响应。结论是SCDC和EDID共享一条总线时SCL速率以最慢设备为准。别为了读EDID快那几百毫秒去牺牲SCDC的稳定性100kHz足够用了。如果产品必须用400kHz至少要在驱动里做NACK重试重试次数太少用户实际体验就是偶尔黑屏。4.6 雷6把SCDC Present当成判断HDMI 2.0的唯一依据SCDC Present为1只代表这个接收端对外宣称支持SCDC不直接代表它能跑HDMI 2.0的高带宽模式。有些显示器厂商的HDMI 2.0端口虽然置位了SCDC Present但EDID里的视频时序列表只声明到4K30或者没有声明任何超过340MHz像素时钟的模式。如果你只凭SCDC Present为1就强制输出4K60照样可能黑屏。因此完整的能力判断应该是EDID里的最大TMDS时钟字段位于Extended EDID的0x2D字节 SCDC Present Sink Version这几个信息综合起来看。EDID的0x2D字节如果小于340MHz说明显示器没有声明能接收4K60就算SCDC Present为1也建议先降级输出。5. 推荐的上电配置流程和验证方法把这个流程固化成代码基本能解决大部分SCDC相关的疑难杂症HPD有效后延时100ms用i2cdetect或者直接读EDID确认DDC链路正常。读0x00判断SCDC Present是否为1。写0x01 0x01完成Source Version宣告。读0x02确认Sink Version不为0。读0x03看bit2TMDS配置请求是否置位如果置位说明接收端在等你配置TMDS。读0x10保留当前值然后先设bit10.5x模式延时50μs再设bit0加扰使能延时50μs。清除0x03对应的Update Flags位写1清0。切换发送端PLL和Data Island时序启用加扰在几个消隐区内完成模式切换。启动第一帧正式视频传输前读0x20-0x23状态标志确认接收端已经锁定信号。验证SCDC是否真正生效最直接的办法是抓DDC总线的I2C波形在配置完成后你应当能看到一帧完整的0xA8写操作序列其中包含0x01寄存器写入0x01的动作。很多HDMI协议分析仪也提供SCDC解码窗口直接显示寄存器读写事务比手工扒波形省事很多。如果没有协议分析仪也可以用i2cget在配置完成后再读一遍0x10确认读回来的值和你写入的一致。如果读回的是0x00说明接收端没有接受配置这时候回查前面提到的雷1和雷3。6. 关于SCDC调试的一点个人补充最后再分享一个我实际项目里的小技巧不要把SCDC配置逻辑写死在初始化流程里最好留一个运行时可触发的接口比如一个调试串口命令或者sysfs属性用它在系统已经跑起来之后重新执行一遍SCDC配置流程。理由很简单SCDC相关的问题往往在特定显示器、特定线缆、特定时序下才会出现等到整机联调再发现问题时如果每次都要重启机器才能复现排障效率会非常低。我在驱动的debugfs节点里加了一个“scdc_dump”命令可以随时读回所有SCDC寄存器并打印配置历史碰到疑难问题能少走很多弯路。另外一个趋势也可以提前了解HDMI 2.1里SCDC这块寄存器的内容做了不少扩展引入了FRL Link Training相关的寄存器底层还是I2C访问。把HDMI 2.0的SCDC机制吃透之后再去理解FRL的Training流程会顺畅得多。至于2.1里那些新寄存器等真正遇到再说也来得及但I2C读写、寄存器握手、时序切换这些基础功现在就可以打好。
返回列表