基于FPGA的HDMI接口设计

描述

一、HDMI概述

HDMI(High_Definition Multiface Interface)是一种音视频传输协议,主要用于解决VGA接口传输速度过慢以及体积过大的问题。HDMI传输主要是采用的TMDS(Transition Minimized Differential Signaling)协议。此外,使用TMDS也是DVI标准的主要特点 。

二、HDMI物理结构

FPGA

上图就是HDMI block结构图,可以看出HDMI在传输 数据时,是经过三对差分信号传输数据和一对差分信号传输clock。其他协议暂时没有使用也就不展开说明。 引脚定义在其他博客中也有很多说明,包括在各个厂家购买的开发板上也有详细说明,如果需要的话可以自己在网站中寻找一下。

三、TMDS编码过程 

(1)TMDS编码示意图

TMDS编码一般是将8位数据经过编码和直流平衡达到10位的最小数据。这仿佛是增加了冗余位,对传输要求更高了。但是实际上这种算法使得到的数据在同轴电缆中可靠性更强了,下图就是编码过程,8位并行的RED数据编码后进行并转串。(这些数据在各家提供的开发手册中都有,这些只是为了让自己更明白才写的)

FPGA

(1)将 8 位并行 RED 数据发送到 TMDS 収送端。

(2)并/串转换。

(3)进行最小化传输处理,加上第 9 位,即编码过程。第 9 位数据称为编码位。

直流平衡:

直流平衡(DC-balanced)就是指在编码过程中保证信道中直流偏移为零。方法是在原来的 9 位数据癿后面加上第 10 位数据,返样,传输的数据趋于直流平衡,使信号对传输线的电磁干扰减少,提高信号传输的可靠性。

(2)TMDS发送和接收的链接示意图

FPGA

上图是 TMDS 发送端和接收端的连接示意图。DVI 或 HDMI 视频传输所使用的 TMDS 连接通过四个串行通道实现。对于DVI来说,其中三个通道分别用于传输视频中每个像素点的红、绿、蓝三个颜色分量(RGB44格式)。HDMI 默认也是使用三个 RGB 通道,但是它同样可以选择传输像素点的亮度和色度信息(YCrCb44或YCrCb 42格式)。第四个通道是时钟通道,用于传输像素时钟。独立的 TMDS 时钟通道为接收端提供接收的参考频率,保证 数据在接收端能够正确恢复。 电子零配件

在传输视频图像的过程中,数据通道上传输的是编码后的有效像素字符。而在每一帧图像的行与行之间,以及视频中不同帧之间的时间间隔(消隐期)内,数据通道上传输的则是控制字符。每个通道上有两位控制信号的输入接口,共对应四种不同的控制字符。这些控制字符提供了视频的行同步(HZYNC)以及帧同步(VSYNC)信息,也可以用来指定所传输数据的边界(用于同步)。对于 DVI 传输,整个视频的消隐期都用来传输控制字符。而 HDMI 传输的消隐期除了控制字符之外,还可以用于传输音频或者其他附加数据(例如字母信息),4-bit 音频和附加数据将通过 TERC4 编码机制转换成 10-bit TERC4 字符,然后再绿色和红色通道上传输。从上图也可以看出这一差别,即“Auxiliary  Data”接口标有“HDMI Olny”,即它是 HDMI 所独有的接口。如果我们不需要附加数据,只传输视频数据的话,完全可以把 HDMI 接口当做 DVI 接口进行驱动。下图是DVI编码器示意图:

FPGA

每个通道输入的视频像素数据都要使用 DVI 规范中的 TMDS 编码算法进行编码。每个 8-bit 的数据都将被转换成 460 个特定 10-bit 字符中的一个。这个编码机制大致上实现了传输过程中的直流平衡,即一段时间内传输的高电平(数字“1”)的个数大致等于低电平(数字“0”)的个数。同时,每个编码后的 10-bit 字符中状态跳转(“由 1 到 0”或者“由 0 到 1”)的次数将被限制在五次以内。除了视频数据之外,每个通道 2-bit 控制信号的状态也要进行编码,编码后分别对应四个不同的 10-bit 控制字符,分别是 10'b1101010100,10'b0010101011,10'b0101010100,和 10'b1010101011。可以看出,每个控制字符都有七次以上的状态跳转。视频字符和控制字符状态跳转次数的不同将会被用于发送和接收设备的同步。  数据管理

再重复一遍,HDMI 在输入附加 数据的同时,还需要输入 ADE(Aux/Audio Data  Enable)信号,其作用和 VDE 是类似的:当 ADE 为高电平时,表明输入端的附加数据或者音频数据有效,DIV 是不能传音频的。想了解更多有关 HDMI的细节,可以参考HDMI 接口规范——《High-Definition Multimedia Interface Specification Version 1.3a》,英语不好的也可以查看文档《HDMI1.4规范中文版》。

四、HDMI电路原理

这边直接引用的是黑金AX7050底板的HDMI原理图接口:

FPGA

AZ1045_04F是TVS二极管,防止外面静电对FPGA产生损坏。本次实验只使用到TMDS数据,时钟,相关的管脚约束如下所示:

############## NET - IOSTANDARD ##################

set_property CFGBVS VCCO [current_design]

set_property CONFIG_VOLTAGE 3.3 [current_design]

#############SPI Configurate Setting##################

set_property BITSTREAM.CONFIG.SPI_BUSWIDTH 4 [current_design]

set_property CONFIG_MODE SPIx4 [current_design]

set_property BITSTREAM.CONFIG.CONFIGRATE 50 [current_design]

create_clock -period 20.000 [get_ports sys_clk]

set_property IOSTANDARD LVCMOS33 [get_ports sys_clk]

set_property PACKAGE_PIN P15 [get_ports sys_clk]

set_property IOSTANDARD LVCMOS33 [get_ports rst_n]

set_property PACKAGE_PIN M15 [get_ports rst_n]

#############HDMI_O##################

set_property IOSTANDARD LVTTL [get_ports tmds_clk_n]

set_property PACKAGE_PIN A20 [get_ports tmds_clk_n]

set_property PACKAGE_PIN B20 [get_ports tmds_clk_p]

set_property IOSTANDARD LVTTL [get_ports tmds_clk_p]

set_property IOSTANDARD LVTTL [get_ports {tmds_data_n[0]}]

set_property PACKAGE_PIN A17 [get_ports {tmds_data_n[0]}]

set_property PACKAGE_PIN A16 [get_ports {tmds_data_p[0]}]

set_property IOSTANDARD LVTTL [get_ports {tmds_data_p[0]}]

set_property IOSTANDARD LVTTL [get_ports {tmds_data_n[1]}]

set_property PACKAGE_PIN A13 [get_ports {tmds_data_n[1]}]

set_property PACKAGE_PIN B13 [get_ports {tmds_data_p[1]}]

set_property IOSTANDARD LVTTL [get_ports {tmds_data_p[1]}]

set_property IOSTANDARD LVTTL [get_ports {tmds_data_n[2]}]

set_property PACKAGE_PIN A12 [get_ports {tmds_data_n[2]}]

set_property PACKAGE_PIN A11 [get_ports {tmds_data_p[2]}]

set_property IOSTANDARD LVTTL [get_ports {tmds_data_p[2]}]

需要注意的是,TMDS  数据和时钟信号需要在约束文件中指定电平标准为 LVTTL,此时钟约束是黑金AX7050提供的时钟约束。虽然差分信号vivado会在我们引导正极引脚位置后自动对负极进行分配,但是本人在测试后发现没有实现效果所以采用黑金提供的时序约束进行代码编写。 电子零配件

五、HDMI程序 设计

TMDS在逻辑功能上有两个阶段:编码和并转串。编码阶段就是将8bit的像素数据,行场数据分别编码成10bit的数据,在进行并转串设计,分别将RGB以及时钟信号发送出去。整个过程示意图如下:

FPGA

其中详解可以得到如下框图:

FPGA

Encoder模块负责对数据进行编码,即8bit编码成为10bit数据。 电子元件

设计流程     

(1)上述框图中,图片数据来源模块与VGA模块的设计,可以在CSDN上寻找现成的代码。

(2)Encoder模块设计

这部分编码可以在Xilinx官网下载例程(关于Xilinx官网下载代码,这个操作比较麻烦,以后找到方法会在列出)在此直接贴出代码,此代码的开发笔记可以在Xilinx提供的DocNav软件中找到(如何操作有时间会贴出)。

Encoder模块把 8 比特的数据进行从新映射为 10bit 数据,防止连续的 0 和 1 出现导致直流不平衡造成误码率升高。

`timescale 1 ps / 1ps

module encode (

input            clkin,    // pixel clock input

input            rstin,    // async. reset input (active high)

input      [7:0] din,      // data inputs: expect registered

input            c0,       // c0 input

input            c1,       // c1 input

input            de,       // de input

output reg [9:0] dout      // data outputs

);

////////////////////////////////////////////////////////////

// Counting number of 1s and 0s for each incoming pixel

// component. Pipe line the result.

// Register Data Input so it matches the pipe lined adder

// output

////////////////////////////////////////////////////////////

reg [3:0] n1d; //number of 1s in din

reg [7:0] din_q;

always @ (posedge clkin) begin

n1d <=#1 din[0] + din[1] + din[2] + din[3] + din[4] + din[5] + din[6] + din[7];

din_q <=#1 din;

end

///////////////////////////////////////////////////////

// Stage 1: 8 bit -> 9 bit

// Refer to DVI 1.0 Specification, page 29, Figure 3-5

///////////////////////////////////////////////////////

wire decision1;

assign decision1 = (n1d > 4'h4) | ((n1d == 4'h4) & (din_q[0] == 1'b0));

wire [8:0] q_m;

assign q_m[0] = din_q[0];

assign q_m[1] = (decision1) ? (q_m[0] ^~ din_q[1]) : (q_m[0] ^ din_q[1]);

assign q_m[2] = (decision1) ? (q_m[1] ^~ din_q[2]) : (q_m[1] ^ din_q[2]);

assign q_m[3] = (decision1) ? (q_m[2] ^~ din_q[3]) : (q_m[2] ^ din_q[3]);

assign q_m[4] = (decision1) ? (q_m[3] ^~ din_q[4]) : (q_m[3] ^ din_q[4]);

assign q_m[5] = (decision1) ? (q_m[4] ^~ din_q[5]) : (q_m[4] ^ din_q[5]);

assign q_m[6] = (decision1) ? (q_m[5] ^~ din_q[6]) : (q_m[5] ^ din_q[6]);

assign q_m[7] = (decision1) ? (q_m[6] ^~ din_q[7]) : (q_m[6] ^ din_q[7]);

assign q_m[8] = (decision1) ? 1'b0 : 1'b1;

/////////////////////////////////////////////////////////

// Stage 2: 9 bit -> 10 bit

// Refer to DVI 1.0 Specification, page 29, Figure 3-5

/////////////////////////////////////////////////////////

reg [3:0] n1q_m, n0q_m; // number of 1s and 0s for q_m

always @ (posedge clkin) begin

n1q_m  <=#1 q_m[0] + q_m[1] + q_m[2] + q_m[3] + q_m[4] + q_m[5] + q_m[6] + q_m[7];

n0q_m  <=#1 4'h8 - (q_m[0] + q_m[1] + q_m[2] + q_m[3] + q_m[4] + q_m[5] + q_m[6] + q_m[7]);

end

parameter CTRLTOKEN0 = 10'b1101010100;

parameter CTRLTOKEN1 = 10'b0010101011;

parameter CTRLTOKEN2 = 10'b0101010100;

parameter CTRLTOKEN3 = 10'b1010101011;

reg [4:0] cnt; //disparity counter, MSB is the sign bit

wire decision2, decision3;

assign decision2 = (cnt == 5'h0) | (n1q_m == n0q_m);

/////////////////////////////////////////////////////////////////////////

// [(cnt > 0) and (N1q_m > N0q_m)] or [(cnt < 0) and (N0q_m > N1q_m)]

/////////////////////////////////////////////////////////////////////////

assign decision3 = (~cnt[4] & (n1q_m > n0q_m)) | (cnt[4] & (n0q_m > n1q_m));

////////////////////////////////////

// pipe line alignment

////////////////////////////////////

reg       de_q, de_reg;

reg       c0_q, c1_q;

reg       c0_reg, c1_reg;

reg [8:0] q_m_reg;

always @ (posedge clkin) begin

de_q    <=#1 de;

de_reg  <=#1 de_q;

c0_q    <=#1 c0;

c0_reg  <=#1 c0_q;

c1_q    <=#1 c1;

c1_reg  <=#1 c1_q;

q_m_reg <=#1 q_m;

end

///////////////////////////////

// 10-bit out

// disparity counter

///////////////////////////////

always @ (posedge clkin or posedge rstin) begin

if(rstin) begin

dout <= 10'h0;

cnt <= 5'h0;

end else begin

if (de_reg) begin

if(decision2) begin

dout[9]   <=#1 ~q_m_reg[8]; 

dout[8]   <=#1 q_m_reg[8]; 

dout[7:0] <=#1 (q_m_reg[8]) ? q_m_reg[7:0] : ~q_m_reg[7:0];

cnt <=#1 (~q_m_reg[8]) ? (cnt + n0q_m - n1q_m) : (cnt + n1q_m - n0q_m);

end else begin

if(decision3) begin

dout[9]   <=#1 1'b1;

dout[8]   <=#1 q_m_reg[8];

dout[7:0] <=#1 ~q_m_reg;

cnt <=#1 cnt + {q_m_reg[8], 1'b0} + (n0q_m - n1q_m);

end else begin

dout[9]   <=#1 1'b0;

dout[8]   <=#1 q_m_reg[8];

dout[7:0] <=#1 q_m_reg[7:0];

cnt <=#1 cnt - {~q_m_reg[8], 1'b0} + (n1q_m - n0q_m);

end

end

end else begin

case ({c1_reg, c0_reg})

2'b00:   dout <=#1 CTRLTOKEN0;

2'b01:   dout <=#1 CTRLTOKEN1;

2'b10:   dout <=#1 CTRLTOKEN2;

default: dout <=#1 CTRLTOKEN3;

endcase

cnt <=#1 5'h0;

end

end

end

endmodule

Encoder模块是依据DVI接口规范中TMDS编码算法进行的,不过这部分是在Xilinx官方网站中直接贴出。如果对TMDS编码算法有兴趣的同学可以依据下图算法进行学习:

FPGA

TMDS是DVI接口所定义的,不做深入研究,其中各个参数的定义如下:

FPGA

Encoder模块的调用编码完成后例化时需要注意,在Blue里的c0和c1输入的是hsync 和 vsync,另外的颜色通道是不需要的。

例化过程如下:

encode encb (

.clkin      (pixelclk),

.rstin      (rstin),

.din        (blue_din),

.c0         (hsync),

.c1         (vsync),

.de         (de),

.dout       (blue)) ;

encode encr (

.clkin      (pixelclk),

.rstin      (rstin),

.din        (green_din),

.c0         (1'b0),

.c1         (1'b0),

.de         (de),

.dout       (green)) ;

encode encg (

.clkin      (pixelclk),

.rstin      (rstin),

.din        (red_din),

.c0         (1'b0),

.c1         (1'b0),

.de         (de),

.dout       (red)) ;

(3)并转串模块 设计

并转串行 模块通过调用 OSERDESE2 原语来实现 10:1 的并串转换。原语是 Xilinx 器件底层硬件中的功能模块,它使用专用的资源来实现一系列的功能。相比于 IP 核,原语的调用方法更简单,但是一般只用于实现一些简单的功能。OSERDESE2 详细介绍见 Xilinx 官方提供的UG768(P338)和UG471(P161)手册(该手册寻找方式和上方寻找例程方式一样)。OSERDESE2 原语模型如下所示:

FPGA

在UG471用户手册中指出如果只采用一个OSERDESE2只能实现8:1的转换速率,当采用10:1或者14:1转换的时候,需要采用两个原语级联的方式。我们需要将 10bit  数据进行串行化,因此根据文档说明,我们需要使用两个OSERDESE2原语进行级联,OSERDESE2 原语的 Master 端承接 10bit 数据的低 8 位,OSERDESE2 原语的 Slave 端的 D1、D2 不使用,D3 和 D4 承接10bit数据的高 2bit,(若是14:1则可以继续使用D5,D6,D7,D8,依然不使用D1,D2)。并将 Slave 的输出 SHIFTOUT1, SHIFTOUT2 连接到 master 的 SHIFTIN1, SHIFTIN2,输入的 10bit 数据将会按照由低位到高位的顺序从  DataOut 端输出。因此可以通过对位宽的拓展实现10:1的转换,转换模型图如下所示:

FPGA

OSERDESE2原语获取方法如下所示:

FPGA

原语的使用可以参考Xilinx提供的手册UG768。这一部分原语的使用本人在使用时出现了问题所以这部分代码就不贴出,后续验证正确,在贴出这段代码。 计算机组件

(4)差分模块设计:

OBUFDS原语用于将三路数据和一路时钟信号转换成差分信号输出,OBUFDS 是差分输出缓冲器,用于将来自 FPGA 内部逻辑的信号转换成差分信号输出,支持 TMDS 电平标准。OBUFDS 原语示意图如下所示:

FPGA

OBUFDS获取途径如下所示:

FPGA

为了程序简单化,可以将此模块也放入上面 Serializer10_1 模块的 OSERDESE2 调用后面,如下所示:

OBUFDS

#(

.IOSTANDARD      ("DEFAULT"      ), // Specify the output I/O standard

.SLEW            ("SLOW"         )  // Specify the output slew rate

)

OBUFDS_inst

(

.O               (do_p           ), // Diff_p output (connect directly to top-level port)

.OB              (do_n           ), // Diff_n output (connect directly to top-level port)

.I               (do             )  // Buffer input

);

(5)模块链接 设计: 设计

创建一个HDMI_trans模块将上述编码,由于本人能力有限,采用的是黑金提供的链接代码,并没有采用上述的原语设计,代码就不采用贴出。后续如果自己能完整调试出代码,那么在后续贴出代码,感谢各位理解

(6)顶层模块设计:

FPGA

可以看出 HDMI 模块还是比较复杂的,因此我们添加 VGA 时序模块,和 HDMI_trans 模块一起封装成顶层模块,以后可以直接把这个模块拿来用。HDMI_top 的框图如下所示(此模块也是黑金AX7050所提供的代码生成的模块):

六、上板现象:

建立工程,编译下载后会产生一个彩条。

打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分