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位怎么来的?分两步:

  1. 第一步:减少跳变
    把8位数据转换成9位,原则是让相邻位之间的跳变尽可能少。如果原始数据中1的个数太多,就取反,并加一个标志位告诉接收端。
  2. 第二步:DC平衡
    再根据之前传输的累计偏差,决定是否把9位数据整体取反。最终输出10位,保证长期来看,0和1的数量基本相等。

我刚开始看这个算法时,觉得挺绕的。后来自己用Verilog写了一遍,发现其实就几个判断和异或操作。嗯,这里要注意:编码器的状态机必须清零复位,否则累计偏差会越跑越偏。

核心要点: TMDS编码不是加密,而是为了信号完整性。每8位数据变成10位,效率是80%。

16.1.2 控制字符

除了数据,TMDS还支持4个控制字符:

控制字符10位编码用途
CTL01101010100视频控制
CTL10010101011视频控制
CTL20101010100视频控制
CTL31010101011视频控制

这些控制字符在消隐期传输,用来区分数据通道和控制通道。我在调试时遇到过一个问题:消隐期的控制字符没发对,导致显示器黑屏。查了半天才发现是编码状态机在消隐期没有切换到控制模式。

16.2 DVI/HDMI协议区别

很多人以为DVI和HDMI是两回事,其实它们的物理层几乎一样。DVI是HDMI的前身,HDMI在DVI基础上加了音频和更小的连接器。

16.2.1 主要区别

特性DVIHDMI
音频传输不支持支持(最多32通道)
最大分辨率1920x1200@60Hz4K@60Hz(HDMI 2.0)
连接器DVI-D/DVI-IType 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输出模块分为这几部分:

  1. 视频时序生成器:产生行场同步信号和有效数据使能
  2. TMDS编码器:把RGB数据和控制字符编码成10位
  3. 并串转换器:用OSERDES把10位数据转成串行
  4. 差分输出:通过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输出的完整流程:

  1. PLL生成像素时钟和串行时钟
  2. 视频时序生成器产生同步信号和DE
  3. TMDS编码器把RGB数据编码成10位
  4. OSERDES把10位数据转成串行
  5. OBUFDS输出差分信号到HDMI连接器

嗯,这里要提醒一下:HDMI连接器的ESD保护很重要。我有个项目,因为没加ESD保护芯片,结果插拔几次后FPGA的IO口就烧了。后来加了TI的TPD12S521,再也没出过问题。

好了,这一章的内容就到这里。HDMI接口看起来复杂,但拆开来看,其实就是编码+串行化+差分输出。你只要掌握了TMDS编码和OSERDES的使用,剩下的就是时序和协议细节了。