1、介绍
HDMI,英文全称“High Definition Multimedia Interface”,高清多媒体接口。
HDMI接口是目前最主流的接口了。它在2002年提出,现在已经发展到HDMI 2.1标准,而且随着行业发展,HDMI 2.1标准已经能够支持4K 120Hz及8K 60Hz,支持高动态范围成像(HDR),可以针对场景或帧数进行优化,向后兼容HDMI 2.0、HDMI 1.4。最主要的是,它是视音频同时传输的。
HDMI规格 | 诞生日期 | 支持的分辨率和对应的刷新率 |
---|---|---|
HDMI2.0 | 2013年 | 1080p 144Hz 2K 144Hz 4K 60Hz |
HDMI2.1 | 2017年 | 1080p 240Hz 2K 240Hz 4K 120Hz 8K 30Hz |
VGA接口成本低,结构简单,应用灵活,但是传输的是模拟信号,就容易受到外界干扰源的影响,受到影响会导致信号畸变,传输的图像就会出现问题,VGA接口体积大,不利于便携设备的使用。后来出现了DVI接口,DVIA(只传输模拟信号)、DVID(只传输数字信号)、DVII(兼容数字信号和模拟信号),数字信号比较不容易受到干扰,体积比VGA还大,只能传输图像,不能传输音频信号。后来出现了HDMI接口,体积小,抗干扰能力更强,可以实现音频和数字信号的同步传输,兼容性好。不同接口的区别如下:
接口类型 | 设计年代 | 最大支持分辨率 | 刷新率 | 特点 |
VGA | 1987年 | 1080p | 60Hz | 1.传输的是模拟信号 2.画面传输质量不高 3.不能传输声音信号 |
DVI | 1999年 | 2K | 120Hz(1080p下)或60Hz(2K下) | 1.传输的是数字信号 2.画面传输质量不高 3.不能传输声音信号 |
HDMI | 2002年 | 8K | 240Hz(1080P下) | 1.传输的是数字信号 2.画面传输质量高 3.可以传输声音信号 |
DP | 2006年 | 16K | 240Hz(4K下) | 1.传输的是数字信号 2.画面传输质量高 3.可以传输声音信号 |
HDMI引脚:
引脚 | 定义 | 引脚 | 定义 |
---|---|---|---|
1 | 数据2+ | 11 | 时钟屏蔽 |
2 | 数据2屏蔽 | 12 | 时钟- |
3 | 数据2- | 13 | CEC |
4 | 数据1+ | 14 | 保留 |
5 | 数据1屏蔽 | 15 | DDC时钟线(SCL) |
6 | 数据1- | 16 | DDC数据线(SDA) |
7 | 数据0+ | 17 | DDC / CEC地 |
8 | 数据0屏蔽 | 18 | +5V电源 |
9 | 数据0- | 19 | 热插拔检测 |
10 | 时钟+ |
引脚分类:
(1)TMDS,是一种传输技术,引脚1~引脚12,用于发送音频、视频和各种辅助数据,遵循DVI编码方式,引脚1和引脚3、引脚4和引脚6、引脚7和引脚9、引脚10和引脚12是四对差分信号
(2)DDC通道,显示数据通道,引脚15~引脚17,只需要单向获取接收端的信息,也就是显示器,其实就是I2C接口
(3)CEC通道,引脚13和引脚17
(4)其他,保留、电源、热插拔检测
HDMI显示原理:
发送端将图像数据、音频数据和控制信号转换成4对差分信号,传给接收方,供接收方解码使用。
TMDS传输原理 :
发送端收到表示RGB信号的24bit并行数据,这些数据在行场同步信号和使能信号下,与时钟信号一起进行编码和并串转换,之后将3个表示RGB的数据和1个时钟信号分别分配到4个独立的通道发送出去。
接收端接收到发送端发过来的串行信号,进行解码和串并转换,之后发送到显示器的控制端,在时钟信号的同步下进行显示。
信号传输步骤:
(1)8bit视频、音频数据转换为10bit的最小传输、直流均衡数据,编码
(2)并行10bit编码后的数据转换为串行差分信号,并串转换
2、HDMI显示器驱动设计与验证
① 模块设计
可以使用前面的vga_coloarbar工程,得到rgb、hsync、vsync信号,clk由PPL锁相环得到,DE使能信号可以用从vga_ctrl模块引出的rgb_valid信号代替;经过了vga_pic和vga_ctrl,就可以得到基于TMDS传输原理的hdmi_ctrl模块输入端口信号,R、G、B信号,hsync、vsync行场同步和DE使能信号。
hdmi_ctrl模块要完成TMDS传输方式,需要有两组逻辑实现,encode对接收过来的数据进行编码、par_to_ser对编码后的10bit数据进行串行发送,可以给里面安排独立的两个模块逻辑。
HDMI的15、16引脚分别是DDC时钟线(SCL)、DDC数据线(SDA),这两路信号连接的是显示器的ROM,采用的是I2C协议,用于读取显示器ROM中的数据,比如生产厂商、型号、支持的分辨率等。这两个接口暂时不使用,强制置位高电平。
encode模块代码:
module encode (
input wire vga_clk ,
input wire sys_rst_n ,
input wire hsync ,
input wire vsync ,
input wire rgb_valid ,
input wire [ 7: 0] data_in ,
output reg [ 9: 0] data_out
);
wire ctrl_1 ; // 组合逻辑计算D数据1的个数
wire ctrl_2 ;
wire ctrl_3 ;
wire [ 8: 0] q_m ; // 8bit到9bit扩展
reg [ 3: 0] data_in_n1 ;
reg [ 7: 0] data_in_reg ; // 输入数据打拍
reg [ 4: 0] cnt ; // 后面要计算(cnt(t-1) + (N0{q_m[0:7]} - N1{q_m[0:7]})),8+8就需要4bit,再加上一个符号位
reg [ 3: 0] q_m_n1 ;
reg [ 3: 0] q_m_n0 ;
reg [ 8: 0] q_m_reg ;
reg de_reg1 ;
reg de_reg2 ;
reg c0_reg1 ;
reg c0_reg2 ;
reg c1_reg1 ;
reg c1_reg2 ;
always @ (posedge vga_clk or negedge sys_rst_n)
if (sys_rst_n == 1'b0)
data_in_n1 <= 4'd0;
else
data_in_n1 <= data_in[0] + data_in[1] + data_in[2] + data_in[3] +
data_in[4] + data_in[5] + data_in[6] + data_in[7];
always @ (posedge vga_clk or negedge sys_rst_n)
if (sys_rst_n == 1'b0)
data_in_reg <= 8'b0;
else
data_in_reg <= data_in;
assign ctrl_1 = ((data_in_n1 > 4'd4) || ((data_in_n1 == 4'd4) && (data_in_reg[0] == 1'b0)))
? 1'b1 : 1'b0 ;
assign q_m[0] = data_in_reg[0];
assign q_m[1] = (ctrl_1 == 1'b1) ? (q_m[0] ^~ data_in_reg[1]) : (q_m[0] ^ data_in_reg[1]) ;
assign q_m[2] = (ctrl_1 == 1'b1) ? (q_m[1] ^~ data_in_reg[2]) : (q_m[1] ^ data_in_reg[2]) ;
assign q_m[3] = (ctrl_1 == 1'b1) ? (q_m[2] ^~ data_in_reg[3]) : (q_m[2] ^ data_in_reg[3]) ;
assign q_m[4] = (ctrl_1 == 1'b1) ? (q_m[3] ^~ data_in_reg[4]) : (q_m[3] ^ data_in_reg[4]) ;
assign q_m[5] = (ctrl_1 == 1'b1) ? (q_m[4] ^~ data_in_reg[5]) : (q_m[4] ^ data_in_reg[5]) ;
assign q_m[6] = (ctrl_1 == 1'b1) ? (q_m[5] ^~ data_in_reg[6]) : (q_m[5] ^ data_in_reg[6]) ;
assign q_m[7] = (ctrl_1 == 1'b1) ? (q_m[6] ^~ data_in_reg[7]) : (q_m[6] ^ data_in_reg[7]) ;
assign q_m[8] = (ctrl_1 == 1'b1) ? 1'b0 : 1'b1;
always @ (posedge vga_clk or negedge sys_rst_n)
if (sys_rst_n == 1'b0)
begin
q_m_n1 <= 4'd0;
q_m_n0 <= 4'd0;
end
else
begin
q_m_n1 <= q_m[0] + q_m[1] + q_m[2] + q_m[3] +
q_m[4] + q_m[5] + q_m[6] + q_m[7];
q_m_n0 <= 4'd8 - (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
// assign ctrl_2 = ((cnt == 5'd0) || (q_m_n1 == q_m_n0)) ? 1'b1 : 1'b0;
// assign ctrl_3 = (((cnt > 5'd0) && (q_m_n1 > q_m_n0))
// || ((cnt < 5'd0) && (q_m_n0 > q_m_n1)))
// ? 1'b1 : 1'b0;
/* cnt是由符号位的,所以不能简单的进行大于小于判断,而是应该判断符号位*/
assign ctrl_2 = ((cnt == 5'd0) || (q_m_n1 == q_m_n0)) ? 1'b1 : 1'b0;
assign ctrl_3 = (((cnt[4] == 1'b0) && (q_m_n1 > q_m_n0))
|| ((cnt[4] == 1'b1) && (q_m_n0 > q_m_n1)))
? 1'b1 : 1'b0;
// 打拍信号统一赋值
always @ (posedge vga_clk or negedge sys_rst_n)
if (sys_rst_n == 1'b0)
begin
q_m_reg <= 9'b0;
de_reg1 <= 1'b0;
de_reg2 <= 1'b0;
c0_reg1 <= 1'b0;
c0_reg2 <= 1'b0;
c1_reg1 <= 1'b0;
c1_reg2 <= 1'b0;
end
else
begin
q_m_reg <= q_m;
de_reg1 <= rgb_valid;
de_reg2 <= de_reg1;
c0_reg1 <= hsync;
c0_reg2 <= c0_reg1;
c1_reg1 <= vsync;
c1_reg2 <= c1_reg1;
end
always @ (posedge vga_clk or negedge sys_rst_n)
if (sys_rst_n == 1'b0)
begin
data_out <= 10'b0;
cnt <= 5'd0;
end
else
begin
if (de_reg2 == 1'b1)
begin
if (ctrl_2 == 1'b1)
begin
data_out[9] <= ~q_m_reg[8];
data_out[8] <= q_m_reg[8];
data_out[ 7: 0] <= (q_m_reg[8] ? q_m_reg[ 7: 0] : ~q_m_reg[ 7: 0]);
cnt <= (q_m_reg[8] == 1'b0) ? (cnt + (q_m_n0 - q_m_n1)) : (cnt + (q_m_n1 - q_m_n0));
end
else
begin
if (ctrl_3 == 1'b1)
begin
data_out[9] <= 1'b1;
data_out[8] <= q_m_reg[8];
data_out[ 7: 0] <= ~q_m_reg[ 7: 0];
cnt <= cnt + {q_m_reg[8], 1'b0} + (q_m_n0 - q_m_n1);
end
else
begin
data_out[9] <= 1'b0;
data_out[8] <= q_m_reg[8];
data_out[ 7: 0] <= q_m_reg[ 7: 0];
cnt <= cnt - {~q_m_reg[8], 1'b0} + (q_m_n1 - q_m_n0);
end
end
end
else
begin
case ({c1_reg2, c0_reg2})
2'b00: data_out <= 10'b00_1010_1011;
2'b01: data_out <= 10'b11_0101_0100;
2'b10: data_out <= 10'b00_1010_1010;
2'b11: data_out <= 10'b11_0101_0101;
default: ;
endcase
cnt <= 5'd0;
end
end
endmodule
par_to_ser模块代码:
- 并行转串行
- 单端信号到差分信号的转换
- 单沿到双沿的转换
该模块分为两部分:
第一部分,将并行的10bit数据在25MHz下经过重组、移位,变成串行的10bit数据,再将这10bit数据分成两组5bit信号data_rise、data_fall,方便区分双沿发送,在125MHz下,以移位寄存的方式从低位逐比特发送
第二部分,调用ddio模块,在125MHz下,上升沿发送送入的data_rise[0]、下降沿发送送入的data_fall[0]
module par_to_ser (
input wire clk_5x ,
input wire [ 9: 0] data_in ,
output wire ser_p ,
output wire ser_n
);
wire [ 4: 0] data_rise ; // 偶数位上升沿发送
wire [ 4: 0] data_fall ; // 奇数位下降沿发送
reg [ 4: 0] data_rise_s = 0;
reg [ 4: 0] data_fall_s = 0;
reg [ 2: 0] cnt = 0;
assign data_rise = {data_in[8], data_in[6], data_in[4], data_in[2], data_in[0]};
assign data_fall = {data_in[9], data_in[7], data_in[5], data_in[3], data_in[1]};
always @ (posedge clk_5x)
begin
cnt <= (cnt[2] == 1'b1) ? 3'd0 : (cnt + 1'b1);
data_rise_s <= (cnt[2] == 1'b1) ? data_rise : {1'b0, data_rise_s[4:1]};
data_fall_s <= (cnt[2] == 1'b1) ? data_fall : {1'b0, data_fall_s[4:1]};
end
// 时钟取反的好处:源clk_5x上升沿数据传送过来,取反后ddio相当于在源clk_5x的下降沿位置进行数据采样,更稳定
ddio_out ddio_out_inst (
.datain_h ( data_rise_s[0] ),
.datain_l ( data_fall_s[0] ),
.outclock ( ~clk_5x ),
.dataout ( ser_p )
);
// 对data_in进行取反,两根线传输
ddio_out ddio_out_inst1 (
.datain_h ( ~data_rise_s[0] ),
.datain_l ( ~data_fall_s[0] ),
.outclock ( ~clk_5x ),
.dataout ( ser_n )
);
endmodule
hdmi_ctrl模块:
hdmi_ctrl是采用TDMS原理实现的,需要将输入数据进行扩展编码、双沿发送差分信号,差分信号也就是一组数据和该组数据的反,比如有一组数据3’b010,那么它本身和3’b101就是一组差分信号,分成两个字模块实现,hdmi_ctrl内部就没有逻辑代码,全都是连线。
module hdmi_ctrl(
input wire vga_clk,
input wire clk_5x,
input wire sys_rst_n,
input wire hsync,
input wire vsync,
input wire rgb_valid,
input wire [7:0]rgb_blue,
input wire [7:0]rgb_green,
input wire [7:0]rgb_red,
output wire hdmi_r_p,
output wire hdmi_r_n,
output wire hdmi_g_p,
output wire hdmi_g_n,
output wire hdmi_b_p,
output wire hdmi_b_n,
output wire hdmi_clk_p,
output wire hdmi_clk_n
);
wire [9:0] red;
wire [9:0] green;
wire [9:0] blue;
encode encode_inst_r(
.vga_clk(vga_clk) ,
.sys_rst_n(sys_rst_n) ,
.hsync(hsync) ,
.vsync(vsync) ,
.rgb_valid(rgb_valid) ,
.data_in(rgb_red) ,
.data_out(red)
);
encode encode_inst_g(
.vga_clk(vga_clk) ,
.sys_rst_n(sys_rst_n) ,
.hsync(hsync) ,
.vsync(vsync) ,
.rgb_valid(rgb_valid) ,
.data_in(rgb_green) ,
.data_out(green)
);
encode encode_inst_b(
.vga_clk(vga_clk) ,
.sys_rst_n(sys_rst_n) ,
.hsync(hsync) ,
.vsync(vsync) ,
.rgb_valid(rgb_valid) ,
.data_in(rgb_blue) ,
.data_out(blue)
);
par_to_ser par_to_ser_inst_r(
.clk_5x(clk_5x) ,
.data_in(red) ,
.ser_p(hdmi_r_p) ,
.ser_n(hdmi_r_n)
);
par_to_ser par_to_ser_inst_g(
.clk_5x(clk_5x) ,
.data_in(green) ,
.ser_p(hdmi_g_p) ,
.ser_n(hdmi_g_n)
);
par_to_ser par_to_ser_inst_b(
.clk_5x(clk_5x) ,
.data_in(blue) ,
.ser_p(hdmi_b_p) ,
.ser_n(hdmi_b_n)
);
par_to_ser par_to_ser_inst_clk(
.clk_5x(clk_5x) ,
.data_in(10'b11111_00000) ,
.ser_p(hdmi_clk_p) ,
.ser_n(hdmi_clk_n)
);
endmodule
顶层文件:
module hdmi_colorbar(
input wire sys_clk,
input wire sys_rst_n,
output wire ddc_scl,
output wire ddc_sda,
output wire hdmi_r_p,
output wire hdmi_r_n,
output wire hdmi_g_p,
output wire hdmi_g_n,
output wire hdmi_b_p,
output wire hdmi_b_n,
output wire hdmi_clk_p,
output wire hdmi_clk_n
);
wire vga_clk;
wire clk_5x;
wire locked;
wire rst_n;
wire [15:0]pix_data;
wire [9:0]pix_x;
wire [9:0]pix_y;
wire hsync;
wire vsync;
wire [15:0]vga_rgb;
wire rgb_valid;
assign rst_n = (sys_rst_n & locked);
assign ddc_scl = 1'b1;
assign ddc_sda = 1'b1;
clk_gen clk_gen_inst (
.areset ( ~sys_rst_n ),
.inclk0 ( sys_clk ),
.c0 ( vga_clk ),
.c1 ( clk_5x ),
.locked ( locked )
);
vga_pic vga_pic_inst
(
.vga_clk(vga_clk),
.sys_rst_n(rst_n),
.pix_x(pix_x),
.pix_y(pix_y),
.pix_data(pix_data)
);
vga_ctrl vga_ctrl_inst
(
.vga_clk(vga_clk),
.sys_rst_n(rst_n),
.pix_data(pix_data),
.pix_x(pix_x),
.pix_y(pix_y),
.hsync(hsync),
.vsync(vsync),
.vga_rgb(vga_rgb),
.rgb_valid(rgb_valid)
);
hdmi_ctrl hdmi_ctrl_inst(
.vga_clk(vga_clk),
.clk_5x(clk_5x),
.sys_rst_n(rst_n),
.hsync(hsync),
.vsync(vsync),
.rgb_valid(rgb_valid),
.rgb_blue({vga_rgb[4:0],3'b0}),
.rgb_green({vga_rgb[10:5],2'b0}),
.rgb_red({vga_rgb[15:11],3'b0}),
.hdmi_r_p(hdmi_r_p),
.hdmi_r_n(hdmi_r_n),
.hdmi_g_p(hdmi_g_p),
.hdmi_g_n(hdmi_g_n),
.hdmi_b_p(hdmi_b_p),
.hdmi_b_n(hdmi_b_n),
.hdmi_clk_p(hdmi_clk_p),
.hdmi_clk_n(hdmi_clk_n)
);
endmodule
testbench文件:
`timescale 1ns/1ns
module tb_hdmi_colorbar();
//********************************************************************//
//****************** Parameter and Internal Signal *******************//
//********************************************************************//
//wire define
wire ddc_scl ;
wire ddc_sda ;
wire hdmi_r_p ;
wire hdmi_r_n ;
wire hdmi_g_p ;
wire hdmi_g_n ;
wire hdmi_b_p ;
wire hdmi_b_n ;
wire hdmi_clk_p ;
wire hdmi_clk_n ;
//reg define
reg sys_clk ;
reg sys_rst_n ;
//********************************************************************//
//**************************** Clk And Rst ***************************//
//********************************************************************//
//sys_clk,sys_rst_n初始赋值
initial
begin
sys_clk = 1'b1;
sys_rst_n <= 1'b0;
#30
sys_rst_n <= 1'b1;
end
//sys_clk:产生时钟
always #10 sys_clk = ~sys_clk ;
//********************************************************************//
//*************************** Instantiation **************************//
//********************************************************************//
//------------- hdmi_colorbar_inst -------------
hdmi_colorbar hdmi_colorbar_inst(
.sys_clk(sys_clk),
.sys_rst_n(sys_rst_n),
.ddc_scl(ddc_scl),
.ddc_sda(ddc_sda),
.hdmi_r_p(hdmi_r_p),
.hdmi_r_n(hdmi_r_n),
.hdmi_g_p(hdmi_g_p),
.hdmi_g_n(hdmi_g_n),
.hdmi_b_p(hdmi_b_p),
.hdmi_b_n(hdmi_b_n),
.hdmi_clk_p(hdmi_clk_p),
.hdmi_clk_n(hdmi_clk_n)
);
endmodule