1. 为什么说 HDMI 环路输出是 FPGA 入门的最佳起点
1.1 视频接口选型:HDMI 比你想的更友好
做 FPGA 图像处理这条路,几乎每个人都会在视频接口上卡一段时间。VGA 虽然时序简单,但模拟信号在现在的显示器上越来越难适配,很多新显示器甚至直接砍掉了 VGA 口。DVI 信号逻辑简单,可接口体积大,而且只支持视频不支持音频,实际项目中用得越来越少。MIPI 在手机屏和摄像头模组上用得多,但时序和协议都复杂,调试要示波器看差分信号,新手很容易被劝退。相比之下,HDMI 是目前最合适的入门选择。
HDMI 在物理层上其实脱胎于 DVI,TMDS 差分信号传输机制是相通的,但 HDMI 接口普及度极高,家里随便就能找到几根线、几个显示器。更重要的是,FPGA 厂商(尤其是 Xilinx 和 Intel)对 HDMI 的支持非常成熟,Xilinx 有现成的 Video Timing Controller、DVI/HDMI Transmitter/Receiver IP 核,配合硬核或软核的 I2C 控制器,基本可以绕开最底层的 TMDS 编码细节做通链路。
加上现在开发板价格也下来了,黑金、正点原子、米联客这些厂商的 FPGA 开发板,不管是 Artix-7 还是 Cyclone V 系列,板载 HDMI 输入输出接口都是标配。入门阶段用一块几百块的板子就能跑通 HDMI 视频输入与环路输出实验,性价比和成就感都非常高。
1.2 环路输出:解决"没有显示就没有调试"的尴尬
很多初学者在接触 HDMI 视频输入实验时会遇到一个痛点:板上只有一个 HDMI 输出接口,输入信号进来之后如果不做任何处理直接接显示器,画面根本出不来,因为输入和输出之间没有任何逻辑支撑。于是你什么也看不见,只能对着时序图和代码猜,这对新手极不友好。
环路输出(Loop-through)实验解决的就是这个尴尬。它的思路很简单:HDMI 输入进来之后,通过 FPGA 内部直接连到 HDMI 输出,把画面原封不动地送出去。这样的话,只要链路通了,显示器上立刻就能看到输入源的画面,这个反馈是即时的、直观的。整个实验的目的不是在传输过程中做什么花哨的图像处理,而是让你先确认"输入链路没问题、输出链路没问题、时钟恢复没问题",在这个基础上再做后续的画面缩放、叠加、识别等处理,才有底。
这个环路结构也是很多商用设备的底层方案。比如视频会议终端、画面分割器、KVM 切换器上,经常会有一个 HDMI 环路输出口,就是把输入信号直接转发给下一级设备,避免因为单一设备的故障或者关闭导致整个信号链路中断。所以别看这个实验听着基础,它的应用场景是真实存在的,理解了它,后面再去做帧缓存、多路输入切换,思路会清晰很多。
另外,环路输出实验还有一个隐性的价值:调试任何视频接口相关的 IP 核时,你都需要一个"最小可用系统"来验证输入侧捕获的数据是否正确。环路输出就是这个最小系统的天然载体,输入数据恢复出来是什么样,经过 FPGA 内部直接送出去就是什么样,任何中间环节出了问题,用显示器一眼就能看出来。
2. 硬件基础与信号链路:搞懂 TMDS 和 DDC 再动手
2.1 做这个实验需要准备哪些硬件
在动笔写代码之前,先把实验台搭好。我做这个实验时的核心硬件清单如下:
- FPGA 开发板:我用的是一块基于 Artix-7 XC7A35T 的黑金 AX7A035 开发板,板载一个 HDMI 输入接口和一个 HDMI 输出接口,输入侧带了一块 ADV7611 芯片做 HDMI 转并行 RGB 信号。如果你用的是别的板子,只要确认板载有 HDMI 输入输出接口就能做类似实验,逻辑上大同小异。有一点要注意:有些板子的 HDMI 输入是靠 FPGA 引脚直接接 TMDS 差分对的(比如很多国产小板子),这种板子在约束和代码上会麻烦一点,但原理一样。
- 视频源:任意的 HDMI 输出设备都可以,机顶盒、笔记本、树莓派、甚至另一块输出测试画面的 FPGA 板都行。我建议用笔记本或者树莓派,因为可以灵活设置输出分辨率和刷新率,方便验证不同参数。
- 显示器或电视:带 HDMI 输入的都可以,推荐至少支持 1080p 的,方便直接对比输入输出。
- HDMI 线缆:至少两根,一根接输入、一根接输出。建议尽量用短线,尤其是输入侧,因为开发板上的 HDMI 座子通常不是标准阻抗匹配最好的,劣质长线很容易引入信号完整性问题,到时候画面闪屏、雪花点,排查起来非常费劲。
- 电源:开发板标配电源,注意电流余量要够,这个我后面专门说。
2.2 TMDS 通道与 DDC 通道的分工
HDMI 接口在物理上包含四对差分信号和若干控制引脚,其中四对差分信号是核心:三对数据通道(D0、D1、D2)加一对时钟通道(CLK)。像素数据经过 8b/10b 编码后,被分配到三对数据通道上串行传输,时钟通道则传输像素时钟,接收端用这个时钟来采样数据通道上的比特流。
8b/10b 编码的作用有两个:一是保证传输过程中直流平衡,避免信号在长距离传输后产生直流偏移;二是提供了足够的跳变沿,方便接收端从数据流中恢复时钟。在 HDMI 1.4 以内的规范里,像素时钟最高到 340MHz 左右,对应 4K@30Hz 或者 1080p@120Hz 这个量级,数据通道的串行速率是像素时钟的 10 倍。比如 1080p@60Hz 的像素时钟是 148.5MHz,那 TMDS 数据通道上的串行速率就是 1.485Gbps。
DDC(Display Data Channel)通道是基于 I2C 协议的,像素时钟和串行链路是隔离的,专门用来传输 EDID 数据。EDID 简单说就是显示器给视频源的一份"自我介绍"文件,里面写了这台显示器支持哪些分辨率、哪些刷新率、对应的像素时钟是多少。视频源设备(比如笔记本)上电后会通过 DDC 读取显示器的 EDID,然后在这个范围内选择一个合适的分辨率输出信号。环路输出实验里,输入侧的视频源也是靠 DDC 去读你接的那台显示器(或者任意 HDMI 信号接收设备)的 EDID。
这里面有个在开发板设计时需要留意的点:如果你的开发板在 HDMI 输入接口和输出接口之间做了 DDC 通道的串联(很多带环路输出设计的板子会这样做),那显示器插在输出口上时,源设备就能直接读到它的 EDID;但如果开发板没有做这个串联,你就需要外接一个 EDID 模拟器或者用 I2C 逻辑在 FPGA 里模拟一个 EDID,否则很多视频源检测不到显示器就不输出信号。黑金的 AX7350 这块板子我没记错的话是做了输入输出 DDC 串联处理的,但不同板卡差异很大,动手前先看一下原理图,确认你这块板的 DDC 是怎么走的。
2.3 HDMI 供电电流与接线细节
HDMI 接口里有一个 5V 的电源引脚,规范要求源端设备要能提供最大 50mA 的电流,这个电流主要是给接收端的 HDMI 接收芯片或者 TMDS 接收缓冲器供电的。如果开发板设计得中规中矩,这个 5V 会经过一个 LDO 或者 DCDC 变换成接收芯片需要的电压,所以整块 FPGA 开发板的功耗来源之一就是这个 HDMI 输入的 5V。实测下来,大部分开发板整体的 HDMI 相关电路(输入接收芯片加输出驱动芯片或者 FPGA 内部终端电阻)对 5V 电源的需求在 100~200mA 左右,不算大,但如果你用的是那种设计比较粗糙的 USB 供电小板,接上 HDMI 线和显示器之后可能会出现电压跌落导致重启的情况,这时就要果断换成外部电源。
另外有个接线细节:HDMI 座子是带屏蔽壳的,很多开发板上这个屏蔽壳和地平面是直接相连的,正常情况没问题,但要确保开发板的电源适配器是接地的那种,不然通过 HDMI 线缆形成了地环路,在某些环境下会出现低频纹波干扰画面,表现为缓慢的水平滚动条纹。我在实验室就遇到过这个问题,换一个三插脚的电源适配器就解决了,排查起来比较折腾,提前知道能省很多时间。
3. 工程搭建与代码实现:从零写一个最小环路
3.1 方案选型:用 IP 还是自己写 TMDS 收发器
在 Vivado 工程里实现 HDMI 环路,有两种大方向。第一种是全部用 Xilinx 提供的 IP 核:Video Timing Controller(VTC)用来生成和解析视频时序,DVI/HDMI Transmitter/Receiver IP(其实 7 系列里主要是 DVI IP,HDMI 的扩展功能需要额外逻辑)用来做 TMDS 编解码。第二种是完全自己写 RTL,用 ISERDESE2 和 OSERDESE2 原语自己去怼 TMDS 串行收发。我这个实验用的是第一种,原因很直接:本实验的核心目标是打通输入到输出的通路,不是研究 TMDS 编码算法,用成熟的 IP 核可以把时间花在理解整体数据流和时序上,而不是纠结于 8b/10b 编码的细节实现。
用 IP 核有个隐性好处是 IP 核自带的时序约束文件比较完整,对入门来说少了很多约束文件的坑。自己写 TMDS 收发器虽然对理解底层有巨大帮助,但对新手来说,时序约束不过、上板黑屏的概率非常高,容易打击信心。我的建议是:第一遍用 IP 核做完环路实验,确保整条链路通了之后,再回头去看数据手册和 RTL 源码,把 TMDS 编码器一章一章啃下来,这样做既有全局观又有细节感,效率反而更高。
3.2 详细实现流程:从创建工程到输出画面
下面是这个实验的完整流程,每个环节我都标了容易踩的坑。
第一步,创建 Vivado 工程,选用你开发板对应的 FPGA 型号。以我用的 XC7A35T 为例,新建工程后先到 IP Catalog 里例化以下四个 IP:DVI Transmitter、DVI Receiver、Video Timing Controller、AXI I2C Controller(如果输入侧用了 ADV7611 这类芯片就需要,用来配置接收芯片的寄存器;如果你的板子输入侧是直连 FPGA,就不需要这个)。
第二步,把 DVI Receiver 的输入引脚连接到板卡原理图上对应的 HDMI 输入引脚,DVI Transmitter 的输出引脚连接到 HDMI 输出引脚。这里要重点确认 TMDS 差分对的正负极性。有些人上板黑屏,或者画面颜色怪异,查了半天发现是 RX 的 P/N 反了,但 DVI Receiver IP 又不允许直接在引脚端翻转极性(因为模块内部没有反相逻辑),只能在约束文件里把差分对的正负端对调来解决。如果是直连 FPGA 的板子,这个操作是合法的;如果输入侧有 ADV7611 这类芯片,那引脚是芯片输出的并行信号,就不存在这个问题了。
第三步,VTC 的配置。Video Timing Controller IP 可以配置成"自动检测输入时序"或"固定生成输出时序"两种模式。在这个实验里,输出侧用 VTC 生成你想要的固定时序(比如 1080p@60Hz),输入侧也需要 VTC 来解析输入的视频时序。需要特别注意:DVI Receiver IP 内部会自动检测输入的行场同步和有效数据信号,VTC 只是辅助做时序解析和生成,两者配合时要保证时钟域处理正确。很多人在这一步会把时序搞混,我的建议是输入侧先用 ChipScope/ILA 抓一下 DVI Receiver 输出的 vsync、hsync、de 信号,确认输入时序参数后再去调输出 VTC,不要凭感觉输入。
第四步,编写顶层代码把 IP 串起来。逻辑非常简单:输入侧 DVI Receiver 输出的 RGB 数据和 de/vsync/hsync 信号,直接连接到输出侧 DVI Transmitter 的输入端口。像素时钟也不需要做跨时钟域处理,因为接收端恢复出的像素时钟直接作为输出端的像素时钟。关键代码如下所示:
// 环路输出核心逻辑 // rx_pclk:DVI Receiver 恢复出的输入像素时钟 // rgb_data_in:DVI Receiver 输出的 24bit RGB 数据 // de_in / vsync_in / hsync_in:输入视频时序控制信号 dvi_transmitter u_tx ( .tmds_clk_p (tmds_clk_p), .tmds_clk_n (tmds_clk_n), .tmds_data_p (tmds_data_p), .tmds_data_n (tmds_data_n), .rgb_pclk (rx_pclk), // 直接把输入像素时钟给输出 .rgb_vsync (vsync_in), .rgb_hsync (hsync_in), .rgb_de (de_in), .rgb_data (rgb_data_in) // 直接把输入数据透传给输出 );这段代码就是一个"无脑"透传,没有任何缓存、没有帧缓冲,因此延迟极低,基本就是一两个时钟周期的事。这也是环路输出的经典实现方式。
第五步,编写约束文件(XDC)。除了引脚约束,还要生成一个非常重要的时序约束:输入时钟约束。如果你的开发板输入侧接了 ADV7611,那么 ADV7611 输出的像素时钟是它自己产生的时钟,FPGA 的一个 MMCM/PLL 要用这个时钟作为输入,然后生成接收逻辑和输出逻辑所需的时钟,这一步通常需要在 XDC 里用 create_clock 声明这个异步时钟输入。如果直连 TMDS 差分对,那么时钟是由 DVI Receiver IP 内部恢复的,约束文件会由 IP 自动生成,你只需要把源时钟约束好就行。
第六步,综合、布线、生成 bitstream,下载到开发板。打开显示器,给视频源上电,正常情况下你会在显示器上看到视频源的画面。如果没画面,进入下一节的排查流程。
3.3 视频时序参数:1080p 不是随便配出来的
为了让你不只是"抄代码",这段说一下 VTC 参数从哪来。以 1920x1080@60Hz 为例,像素时钟是 148.5MHz。这里的 148.5MHz 不是拍脑门定的,而是从 VESA 时序标准查来的。VESA 标准对 1080p@60Hz 给出了详细的行场时序参数:水平方向,一行总共 2200 个像素时钟周期,其中有效像素 1920,前肩(H Front Porch)88,同步脉冲(H Sync)44,后肩(H Back Porch)148;垂直方向,一帧总共 1125 行,其中有效行 1080,前肩(V Front Porch)4,同步脉冲(V Sync)5,后肩(V Back Porch)36。这些数字加起来:2200 × 1125 × 60Hz = 148.5MHz,刚好是像素时钟。
VTC IP 里配置这些参数时,要区分清楚 VESA 标准和 HDMI 标准在某些刷新率下参数不同(比如 1440p@60Hz 用 CVT 和 CEA 标准的参数就不一样),但 1080p@60Hz 的这个 CEA 参数是最通用的,几乎所有显示器都支持,做实验首选它。如果你用的视频源输出分辨率不是 1080p,你需要先查对应标准的时序表,把 VTC 的配置改掉,然后再去调像素时钟频率,两者必须严格匹配。
4. 调试方法与故障排查:实战中我踩过的那些坑
4.1 无视频输入:从源端到终端逐级定位
最常遇到的问题就是显示器黑屏,"无视频输入"五个字直接打在屏幕上。这个问题可以从信号链路的最源端开始排查。
先确认视频源本身有没有信号输出。把视频源直接用 HDMI 线接到显示器上,如果能正常显示,说明源没问题,问题出在开发板的链路上。如果源端都不输出,检查源端的显示设置,确认它是否识别到了显示器。如果用了 DDC 通道串联的设计,显示器插在开发板输出口上,但源端没识别到,十有八九是 DDC 通道断了或者开发板上的 I2C 缓冲芯片没供电。
确认源端没问题之后,用 ILA(集成逻辑分析仪)抓 FPGA 内部信号。把 ILA 的探针接到 DVI Receiver 输出的 vsync、hsync、de 和像素时钟上,如果这些信号全是 0,说明 DVI Receiver 没有正确恢复输入信号。接着往前面查,看接收侧的 PLL 是否锁定,注意 7 系列里 DVI Receiver IP 通常会输出一个 pll_locked 信号,如果一直拉低,大概率是 TMDS 输入有问题或者参考时钟配置不对。如果 PLL 锁定了但 de 信号一直不跳变,那就是 TMDS 通道上的数据没被正确解析,检查差分引脚约束是否正确,或者干脆用示波器看 HDMI 输入座子上的差分信号有没有波形。
还有一个很隐蔽的坑:开发板上 HDMI 输入接口的 5V 检测信号(HPD,Hot Plug Detect)。很多视频源是靠 HPD 引脚是否被拉高来判断显示器是否接入的。如果开发板原理图上 HPD 没有正确处理(比如没接上拉电阻),源端就不知道有设备接入,自然也不会输出信号。排查时可以直接测一下 HDMI 座上 5V 和 HPD 之间的电压,正常情况下 HPD 应该是高电平。如果是低电平,那问题就出在这里。
4.2 图像偏色、闪烁、雪花点:信号完整性问题
画面上有雪花点或者斜纹闪烁,属于典型的高速信号完整性问题。我遇到过几种情况,按概率排序如下:
第一种是线缆问题。HDMI 线太长或者质量太差,损耗大导致眼图闭合。这种问题的特征是:短细线可能没问题,一换长线就花屏。解决办法最简单也最有效:换一根短线,最好 1 米以内,出现在实验阶段不要用杂牌线。
第二种是地环路问题。这个在上一节提到过,表现为水平滚动条纹,频率在几十 Hz 以内。优先检查开发板的供电电源是否三脚接地,再把 HDMI 线的屏蔽层接触是否良好检查一遍。
第三种是 TMDS 终端电阻问题。如果你的开发板输入侧是直连 FPGA 引脚,那么 FPGA 的差分输入引脚需要配置 100 欧终端电阻(DIFF_TERM),这个必须在 XDC 约束里显式设置:
set_property DIFF_TERM TRUE [get_ports hdmi_rx_clk_p] set_property DIFF_TERM TRUE [get_ports hdmi_rx_data_p[0]] set_property DIFF_TERM TRUE [get_ports hdmi_rx_data_p[1]] set_property DIFF_TERM TRUE [get_ports hdmi_rx_data_p[2]]如果漏了这一步,信号反射会非常严重,画面上必然出现雪花点或者重影。这个坑我当年是花了两个晚上才定位出来。
第四种是供电不足。前面提到 HDMI 5V 电流需求,如果电源适配器虚标,开发板的 HDMI 接收芯片工作不稳定,会出现间歇性黑屏或者颜色失真。这时可以直接用万用表量一下 HDMI 座子上的 5V,如果带载后发现电压掉到 4.5V 以下,果断换电源。
4.3 常见问题速查表
| 现象 | 可能原因 | 排查/处理办法 |
|---|---|---|
| 显示器显示无视频输入 | 视频源没识别到接收端 | 检查 HPD 引脚电平和 DDC 通路 |
| 黑屏但 PLL 锁定 | 输出时序参数配置错误 | 用 ILA 确认输入 de/vsync/hsync 后再比对 VTC 配置 |
| 画面雪花点严重 | TMDS 终端电阻未启用 | 在 XDC 里加 DIFF_TERM 约束 |
| 颜色偏色(如偏紫、偏绿) | RX 数据通道错位或者极性翻转 | 检查差分对 P/N 是否反接,数据通道是否映射错 |
| 水平滚动条纹 | 地环路干扰 | 换三插脚电源适配器,检查 HDMI 屏蔽层 |
| 间歇性黑屏 | 供电不足 | 量 HDMI 5V 电压,换电源适配器 |
| 图像有锯齿或边缘拖影 | 像素时钟不稳 | 检查 MMCM/PLL 配置和时钟来源,确认 DVI Receiver 恢复时钟质量 |
4.4 演示一个实际调试过程:1080p 输入 1080p 输出
最后分享一次完整的调试过程,帮大家把整个思路串起来。
那次是在实验室里做这块 AX7350 的环路输出验证,视频源是笔记本的 HDMI 口。上电后显示器显示"无信号"。我先把笔记本直接用 HDMI 线连到显示器,显示器正常亮,排除了源端和显示器的硬件问题。然后把线接回开发板,用 ILA 抓 DVI Receiver 的输出信号。发现 vsync 和 hsync 是有的,但 de 信号只在非常小的范围内跳变,看起来像是同步到了但有效区域不对。
因为 vsync/hsync 有了,说明 TMDS 接收链路基本是通的,问题大概率出在 VTC 对输入时序的解析上。我进到 VTC 的配置里,把水平前端、水平同步、水平后肩这些参数,对照我查到的输入信号时序重新核对了一遍,发现我不小心把源端输出的分辨率设成了 1366x768,而 VTC 里还是 1080p 的参数。这种分辨率匹配问题,靠眼睛看信号波形其实不容易发现,但 VTC 会判断出 de 的时间占比和标准 1080p 差别很大。
改回正确的分辨率配置后,重新生成 bitstream,下板,画面立刻就出来了。整个调试过程大概十分钟,核心就是一步步确认链路,锁定问题层级。
5. 实验之后还能怎么玩
跑通了环路输出,说明你已经掌握了 HDMI 视频输入输出的基本链路。下一步有几个方向我觉得很有意思:
方向一,在链路上加一个简单图像处理,比如灰度化、反色、亮度调节。这个时候你不需要改任何输入输出逻辑,只需要在 RGB 数据透传的位置插入一个组合逻辑或者流水线处理,画面效果立竿见影,这几乎是理解 FPGA 图像处理流水线最好的入门方式。
方向二,加入 DDR3 帧缓存。有了 DDR 帧缓存之后,就能实现输入输出的帧率转换、画面冻结、画中画、像素格式转换等功能。环路输出是从"低延迟直通"走向"帧缓存处理"的必经跳板。
方向三,做输入分辨率自适应检测。前面提到 VTC 可以自动检测输入时序,如果把输入分辨率检测代码写出来,配合动态时钟切换,就能做成一个"无论你插什么分辨率进去,输出设备都能正常显示"的通用视频接口模块,这也是很多商用量产设备的基本功能。
我个人在实际做这个实验的时候,最大的体会是:FPGA 视频处理的复杂度不在于代码量,而在于整个链路的时序和信号完整性概念。环路输出实验用最少的逻辑把这条链路的所有环节都过了一遍,相当于给之后的图像处理学习打了一个非常扎实的地基。如果你也准备做 FPGA 图像处理方向,多花点时间把这个实验做透,后面一路会顺很多。