16、HDMI显示接口:TMDS编码原理、DVI/HDMI协议区别、HDMI输出实现(基于OSERDES)
说到HDMI,做FPGA的工程师几乎都会碰到。我最早接触这个接口是在一个视频拼接项目上,当时被TMDS编码折腾了好几天。说白了,HDMI就是把并行视频数据转成高速串行信号,再通过差分线传出去。今天咱们就把这块彻底讲透。
16.1 TMDS编码原理
TMDS,全称是Transition Minimized Differential Signaling,翻译过来就是「最小化传输差分信号」。名字挺长,但核心思想很简单:减少信号跳变,同时保证DC平衡。
为什么会这样?因为高速信号在传输时,如果跳变太多,电磁辐射会变大,接收端也容易误判。TMDS编码就是为了解决这个问题。
16.1.1 编码流程
TMDS编码器处理的是8位数据,输出10位。这10位怎么来的?分两步:
- 第一步:减少跳变
把8位数据转换成9位,原则是让相邻位之间的跳变尽可能少。如果原始数据中1的个数太多,就取反,并加一个标志位告诉接收端。 - 第二步:DC平衡
再根据之前传输的累计偏差,决定是否把9位数据整体取反。最终输出10位,保证长期来看,0和1的数量基本相等。
我刚开始看这个算法时,觉得挺绕的。后来自己用Verilog写了一遍,发现其实就几个判断和异或操作。嗯,这里要注意:编码器的状态机必须清零复位,否则累计偏差会越跑越偏。
核心要点: TMDS编码不是加密,而是为了信号完整性。每8位数据变成10位,效率是80%。
16.1.2 控制字符
除了数据,TMDS还支持4个控制字符:
| 控制字符 | 10位编码 | 用途 |
|---|---|---|
| CTL0 | 1101010100 | 视频控制 |
| CTL1 | 0010101011 | 视频控制 |
| CTL2 | 0101010100 | 视频控制 |
| CTL3 | 1010101011 | 视频控制 |
这些控制字符在消隐期传输,用来区分数据通道和控制通道。我在调试时遇到过一个问题:消隐期的控制字符没发对,导致显示器黑屏。查了半天才发现是编码状态机在消隐期没有切换到控制模式。
16.2 DVI/HDMI协议区别
很多人以为DVI和HDMI是两回事,其实它们的物理层几乎一样。DVI是HDMI的前身,HDMI在DVI基础上加了音频和更小的连接器。
16.2.1 主要区别
| 特性 | DVI | HDMI |
|---|---|---|
| 音频传输 | 不支持 | 支持(最多32通道) |
| 最大分辨率 | 1920x1200@60Hz | 4K@60Hz(HDMI 2.0) |
| 连接器 | DVI-D/DVI-I | Type A/C/D |
| TMDS通道数 | 3个数据通道+1个时钟 | 3个数据通道+1个时钟 |
| 热插拔检测 | 有 | 有(更完善) |
| 消费电子控制 | 无 | CEC协议 |
你想想看,DVI和HDMI的TMDS部分完全兼容。所以很多DVI转HDMI的转接头,其实就是物理接口转换,协议层面不需要改动。我在项目中就用过这种方案,把DVI输出的FPGA板子接到HDMI显示器上,完全没问题。
个人经验: 如果只是做视频显示,用DVI协议就够了。但如果你需要同时传音频,那就必须上HDMI。另外,HDMI的CEC功能可以用来控制设备联动,挺实用的。
16.2.2 数据岛周期
HDMI比DVI多了一个概念叫「数据岛周期」。在消隐期,HDMI会插入音频和辅助数据包。这些数据包通过TMDS通道传输,格式有严格规定。
我记得第一次做HDMI音频时,被数据岛周期搞得很头疼。音频包必须放在特定的行消隐区间,而且每个包有固定的包头和校验。后来我干脆写了一个状态机,专门处理数据岛的插入。
16.3 HDMI输出实现(基于OSERDES)
好了,理论说完了,咱们来点实际的。在紫光同创FPGA上实现HDMI输出,核心就是利用OSERDES把并行数据转成高速串行。
16.3.1 系统架构
整个HDMI输出模块分为这几部分:
- 视频时序生成器:产生行场同步信号和有效数据使能
- TMDS编码器:把RGB数据和控制字符编码成10位
- 并串转换器:用OSERDES把10位数据转成串行
- 差分输出:通过OBUFDS输出差分信号
说白了,FPGA内部跑的是像素时钟(比如148.5MHz),但TMDS通道上跑的是10倍像素时钟(1.485GHz)。这个高速时钟必须用PLL生成,而且要注意时钟抖动。
注意: 紫光同创的OSERDES最高支持多少速率?以PGL22G为例,OSERDES最高可以跑到1.6Gbps。对于1080p@60Hz,每个通道需要1.485Gbps,刚好够用。但如果你要做4K,就得换更高端的芯片了。
16.3.2 OSERDES配置
OSERDES在紫光同创的IP核里叫「OSERDESE2」。配置时要注意:
- 数据宽度:设置为10位(TMDS编码后的宽度)
- 时钟模式:使用DDR模式,上升沿和下降沿都输出数据
- 复位方式:建议使用同步复位,避免亚稳态
我曾经在配置OSERDES时,忘记设置DDR模式,结果输出只有一半速率,显示器直接没信号。查了两天才发现是这个问题。
16.3.3 代码示例
下面是一个简化的OSERDES配置代码:
// OSERDESE2 实例化
OSERDESE2 #(
.DATA_RATE_OQ("DDR"), // DDR模式
.DATA_WIDTH(10), // 10位数据
.SERDES_MODE("MASTER") // 主模式
) u_oserdes (
.OQ(tmds_data_p), // 串行输出
.CLK(clk_x10), // 10倍像素时钟
.CLKDIV(clk_pixel), // 像素时钟
.D1(tmds_10b[9]),
.D2(tmds_10b[8]),
.D3(tmds_10b[7]),
.D4(tmds_10b[6]),
.D5(tmds_10b[5]),
.D6(tmds_10b[4]),
.D7(tmds_10b[3]),
.D8(tmds_10b[2]),
.D9(tmds_10b[1]),
.D10(tmds_10b[0]),
.OCE(1'b1), // 输出时钟使能
.RST(~pll_locked) // 复位
);
这段代码里,clk_x10是10倍像素时钟,clk_pixel是像素时钟。注意复位信号用了PLL锁定信号的反相,这样能保证OSERDES在PLL稳定后才开始工作。
16.3.4 时钟生成
时钟生成是HDMI实现的关键。以1080p@60Hz为例:
- 像素时钟:148.5MHz
- TMDS时钟:148.5MHz(差分对)
- 串行时钟:1.485GHz(OSERDES内部使用)
紫光同创的PLL可以生成这些时钟。我建议用两个PLL:一个生成像素时钟,另一个生成串行时钟。因为串行时钟对抖动要求更高,单独用一个PLL可以避免干扰。
调试技巧: 如果显示器没画面,先检查TMDS时钟有没有输出。用示波器量一下差分时钟对,频率对不对。我遇到过PLL配置错误,导致时钟频率偏差0.1%,结果显示器就是不同步。
16.3.5 完整流程
最后,总结一下HDMI输出的完整流程:
- PLL生成像素时钟和串行时钟
- 视频时序生成器产生同步信号和DE
- TMDS编码器把RGB数据编码成10位
- OSERDES把10位数据转成串行
- OBUFDS输出差分信号到HDMI连接器
嗯,这里要提醒一下:HDMI连接器的ESD保护很重要。我有个项目,因为没加ESD保护芯片,结果插拔几次后FPGA的IO口就烧了。后来加了TI的TPD12S521,再也没出过问题。
好了,这一章的内容就到这里。HDMI接口看起来复杂,但拆开来看,其实就是编码+串行化+差分输出。你只要掌握了TMDS编码和OSERDES的使用,剩下的就是时序和协议细节了。