1、HDMI 介绍
1.1、HDMI 概述
HDMI,即高清晰度多媒体接口(High - Definition Multimedia Interface),是一种全数字化视频和声音发送接口,由日立、松下、飞利浦、索尼等电器巨头联合研发。它能够同时传送音频和视频信号,无需在信号传送前进行数/模或者模/数转换,这使得它在传输高清音视频方面具有显著优势。HDMI 的出现,极大地简化了家庭影院系统、电脑与显示设备之间的连线,提高了信号传输质量,成为了当今高清设备的标准配置接口。
1.2、HDMI 优点
a、高质量音视频传输
HDMI 支持高达 4K 甚至 8K 的超高清分辨率视频传输,能呈现出极其清晰、细腻的图像效果。同时,它还能支持多声道音频传输,比如常见的 5.1 声道、7.1 声道,甚至是更先进的杜比全景声等,为用户带来身临其境的听觉体验。以观看电影为例,通过 HDMI 连接电视和蓝光播放器,观众可以享受到画面细节丰富、色彩鲜艳,声音环绕立体的震撼效果。
b、兼容性强
HDMI 接口在各种设备上广泛应用,无论是电视机、电脑显示器、投影仪等显示设备,还是 DVD 播放器、游戏机、机顶盒等播放设备,都普遍配备了 HDMI 接口。这使得不同品牌和类型的设备之间能够方便地进行连接和使用,用户无需担心兼容性问题。例如,将 PlayStation 游戏机通过 HDMI 线连接到不同品牌的电视上,都能正常显示游戏画面和输出声音。
c、方便易用
HDMI 采用单一线缆传输音视频信号,相比于传统的需要分别连接音频线和视频线的方式,大大简化了设备之间的连接过程。用户只需要将 HDMI 线的两端分别插入对应的设备接口,就可以完成连接,操作简单便捷。
1.3、HDMI 引脚说明
HDMI 连接器示意图,如下图所示:
HDMI 连接器针脚分配,如下图所示:
逻辑派使用的是板载高清晰度多媒体接口 micro HDMI(High Definition Multimedia Interface)接口,其线序如下图所示:
2、HDMI 时钟与带宽
我们以 逻辑派 所支持的 1280x720p@60Hz 来算一下对应的像素时钟:
1280x720P@60Hz:1280x720x60 ≈ 55.296Mhz 算出来就是1S就是一秒钟传输的像。
再来查一下HDMI的时序标准,如下图所示:
查完后发现所算出来的时钟与HDMI的时序标准时钟不要这是怎么回事呢?
我们拿水平像素来说 1280 是我们在屏幕上看到的可见区域的像素数。这代表了图像内容的显示区域,而在实际的水平时序周期 是一个包含了可见像素和同步/空闲时间的完整周期,时钟频率需要覆盖整个周期,而不仅仅是显示的像素部分。水平像素点来说它包含:水平同步时间(HSync)+水平后沿(HBack Porch)+水平前沿(HFront Porch)。
PCLK = 1650x750x60 = 74.25Mhz
这样算出来就与HDMI的时序标准时钟一致 。
3、TMDS
3.1、TMDS 编码框图
通道 0 对应蓝色分量,且包含行同步信号 HSYNC,场同步信号 VSYNC,通道 1 对应绿色分量,通道 2 对应红色分量。控制信号 CTL0,CTL1,CTL2 和 CTL3 必须设为 0。详细可以参考高云UG938手册
3.2、TMDS 流程图
编码算法定义
经过编码后,8 bits 的视频数据转换成 10 bits 数据,然后利用串行器 OSER10 将并行数据转成串行数据传输,最低位 bit 0 优先传输。
4、Gowin DVI TX IP 的端口说明
5、IP 配置
5.1、DVI TX
在 Options 选项界面中, Clock Setting 我们这里使用外部时钟,则需要勾选 Using External Clock ,IO Setting 选中 TLVDS (I/O模式请查看番外篇) ,我们这里还需要将 DVI 所使用到的 I/O 口给失能。
5.2、CLKDIV
CLKDIV 为时钟分频器,实现时钟频率调整。
由于 GOWIN 软件 IP 核的限制,需要对 rPLL IP 核输出的时钟进行 5 分频(因为每个像素点的颜色深度为24为,转为 RGB 所占比例是8:8:8 ,则在单通道上的颜色数据需要用一个8B/10B的编码器(Encoder)来获得一个10位的像素字符,这个10位的数据需要通过并串转换器(Serializer)转换为串行数据,那为什么这里是选择五呢,因为五倍的时钟上实现了双倍的数据速率,故此才选择五分频的)。
OPtions 选项说明:
- Division Factor:除法因子,选择分屏系数,支持 2、 3.5、 4、 5。
- Calibration:校准时钟使能/失能选项,选择是否启用校准时钟输入。
注:由于逻辑派的HDMI是直接输出,没有涉及到I2c通讯(需要进行EDID数据等)和音频,故此不做任何讲解。
6、程序编写
6.1、video_top
module video_top(
input sys_clk , // 系统时钟,频率为 50MHz
input sys_rst_n , // 系统复位信号,低电平有效
output O_tmds_clk_p , // HDMI 时钟信号的正端
output O_tmds_clk_n , // HDMI 时钟信号的负端
output [2:0] O_tmds_data_p ,// HDMI 数据信号的正端,按 {r,g,b} 顺序排列
output [2:0] O_tmds_data_n // HDMI 数据信号的负端,按 {r,g,b} 顺序排列
);
wire tp0_vs_in ; // 垂直同步信号
wire tp0_hs_in ; // 水平同步信号
wire tp0_de_in ; // 数据使能信号
wire [23:0] RGB_data; //RGB 数据
//HDMI TX 相关信号定义
wire serial_clk; // 串行时钟信号
wire pll_lock; // 锁定信号
wire hdmi_rst_n; // HDMI 模块的复位信号
wire pix_clk; // 像素时钟信号
// 生成 HDMI 模块的复位信号,只有当系统复位信号有效且 PLL 锁定时
assign hdmi_rst_n = sys_rst_n & pll_lock;
// 用于生成测试图案
dispaly u_dispaly(
.I_pxl_clk (pix_clk ),// 像素时钟输入,用于同步测试图案的生成
.I_rst_n (hdmi_rst_n ),// 低电平有效的复位信号
//800x600 //1024x768 //1280x720 //1920x1080
.I_h_total (16'd1650 ),//水平总时间 // 16'd1056 // 16'd1344 // 16'd1650 //16'd2200
.I_h_sync (16'd40 ),//水平同步时间 // 16'd128 // 16'd136 // 16'd40 //16'd44
.I_h_bporch (16'd220 ),//水平后沿 // 16'd88 // 16'd160 // 16'd220 //16'd148
.I_h_res (16'd1280 ),//水平分辨率 // 16'd800 // 16'd1024 // 16'd1280 //16'd1920
.I_v_total (16'd750 ),//垂直总时间 // 16'd628 // 16'd806 // 16'd750 //16'd1125
.I_v_sync (16'd5 ),//垂直同步时间 // 16'd4 // 16'd6 // 16'd5 //16'd5
.I_v_bporch (16'd20 ),//垂直后沿 // 16'd23 // 16'd29 // 16'd20 //16'd36
.I_v_res (16'd720 ),//垂直分辨率 // 16'd600 // 16'd768 // 16'd720 //16'd1080
.I_hs_pol (1'b1 ),// 水平同步极性,1 表示正极性
.I_vs_pol (1'b1 ),// 垂直同步极性,1 表示正极性
.O_de (tp0_de_in ), // 数据使能信号
.O_hs (tp0_hs_in ), // 水平同步信号
.O_vs (tp0_vs_in ), // 垂直同步信号
.RGB_data (RGB_data ) //
);
//HDMI 锁相环
TMDS_rPLL u_tmds_rpll(
.clkin (sys_clk ), // 输入时钟,使用系统时钟
.clkout (serial_clk ), // 输出时钟,生成的串行时钟信号
.lock (pll_lock ) // 输出锁定信号
);
// 用于对串行时钟进行分频得到像素时钟
Gowin_CLKDIV u_Gowin_CLKDIV(
.clkout(pix_clk ), // 输出分频后的像素时钟
.hclkin(serial_clk ), // 输入高频时钟,即串行时钟
.resetn(hdmi_rst_n ) // 输入复位信号,使用 HDMI 模块的复位信号
);
// 实例化 DVI_TX_Top 模块,用于将 RGB 数据转换为 TMDS 信号并通过 HDMI 接口输出
DVI_TX_Top DVI_TX_Top_inst(
.I_rst_n (hdmi_rst_n ), // 异步复位信号,低电平有效
.I_serial_clk (serial_clk ), // 串行时钟
.I_rgb_clk (pix_clk ), // 像素时钟
.I_rgb_vs (tp0_vs_in ), // 垂直同步信号
.I_rgb_hs (tp0_hs_in ), // 水平同步信号
.I_rgb_de (tp0_de_in ), // 数据使能信号
.I_rgb_r (RGB_data[ 7: 0] ), // 红色
.I_rgb_g (RGB_data[15: 8] ), // 绿色
.I_rgb_b (RGB_data[23:16] ), // 蓝色
.O_tmds_clk_p (O_tmds_clk_p ),
.O_tmds_clk_n (O_tmds_clk_n ),
.O_tmds_data_p (O_tmds_data_p ), // {r,g,b} 数据正端
.O_tmds_data_n (O_tmds_data_n ) // {r,g,b} 数据负端
);
endmodule
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
6.2、图像模块
module dispaly(
input I_pxl_clk , // 像素时钟
input I_rst_n , // 低电平有效的复位信号
input [15:0] I_h_total , // 水平总时间
input [15:0] I_h_sync , // 水平同步时间
input [15:0] I_h_bporch , // 水平后沿
input [15:0] I_h_res , // 水平分辨率
input [15:0] I_v_total , // 垂直总时间
input [15:0] I_v_sync , // 垂直同步时间
input [15:0] I_v_bporch , // 垂直后沿
input [15:0] I_v_res , // 垂直分辨率
input I_hs_pol , // 水平同步极性,0 表示负极性,1 表示正极性
input I_vs_pol , // 垂直同步极性,0 表示负极性,1 表示正极性
output O_de , // 数据使能信号,指示有效像素数据的输出时段
output reg O_hs , // 水平同步信号
output reg O_vs , // 垂直同步信号
output reg [23:0] RGB_data
);
// 用于后续对信号进行 N 个时钟周期的延迟
localparam N = 5;
// 参数定义,以 24 位颜色的 RGB 值
parameter WHITE = 24'hFFFFFF; // 白色
parameter BLACK = 24'h000000; // 黑色
parameter RED = 24'hFF0000; // 红色
parameter GREEN = 24'h00FF00; // 绿色
parameter BLUE = 24'h0000FF; // 蓝色
parameter YELLOW = 24'h00FFFF; // 黄色
parameter PURPLE = 24'hFF00FF; // 紫色
parameter CYAN = 24'hFFFF00; // 青色
// 垂直计数器,用于记录当前处理的行数
reg [15:0] V_cnt ;
// 水平计数器,用于记录当前处理的像素列数
reg [15:0] H_cnt ;
// 中间信号,临时存储数据使能、水平同步和垂直同步信号
wire Pout_de_w ;
wire Pout_hs_w ;
wire Pout_vs_w ;
// 延迟寄存器,对数据使能、水平同步和垂直同步信号进行 N 个时钟周期的延迟
reg [N-1:0] Pout_de_dn ;
reg [N-1:0] Pout_hs_dn ;
reg [N-1:0] Pout_vs_dn ;
wire De_pos;// 数据使能信号的上升沿检测信号
reg [15:0] De_hcnt ;// 水平计数器
// 彩色条相关信号定义
reg [15:0] Color_trig_num; // 彩色条触发计数,用于控制彩色条的切换时机
reg Color_trig ;// 彩色条触发信号,触发彩色条颜色的切换
reg [3:0] Color_cnt ;// 彩色条计数,用于选择不同的颜色
reg [23:0] Color_bar ;// 彩色条颜色数据,存储当前彩色条的 RGB 值
// 临时数据存储寄存器
reg [23:0] Data_tmp;
// 输出数据使能信号,取延迟后的第 5 位
assign O_de = Pout_de_dn[4];
// 检测数据使能信号的上升沿
assign De_pos = !Pout_de_dn[1] & Pout_de_dn[0];
// 生成数据使能中间信号,当处于有效像素区域时使能信号有效
assign Pout_de_w = ((H_cnt>=(I_h_sync+I_h_bporch))&(H_cnt<=(I_h_sync+I_h_bporch+I_h_res-1'b1)))&
((V_cnt>=(I_v_sync+I_v_bporch))&(V_cnt<=(I_v_sync+I_v_bporch+I_v_res-1'b1))) ;
// 生成水平同步中间信号,在水平同步时间内信号取反
assign Pout_hs_w = ~((H_cnt>=16'd0) & (H_cnt<=(I_h_sync-1'b1))) ;
// 生成垂直同步中间信号,在垂直同步时间内信号取反
assign Pout_vs_w = ~((V_cnt>=16'd0) & (V_cnt<=(I_v_sync-1'b1))) ;
// 生成垂直计数器信号
always@(posedge I_pxl_clk or negedge I_rst_n)begin
if(!I_rst_n)
V_cnt <= 16'd0;// 复位时,将垂直计数器清零
else begin// 当垂直计数器达到垂直总时间,且水平计数器达到水平总时间时,垂直计数器归零
if((V_cnt >= (I_v_total-1'b1)) && (H_cnt >= (I_h_total-1'b1)))
V_cnt <= 16'd0;
else if(H_cnt >= (I_h_total-1'b1)) // 当水平计数器达到水平总时间时,垂直计数器加 1
V_cnt <= V_cnt + 1'b1;
else // 其他情况,垂直计数器保持不变
V_cnt <= V_cnt;
end
end
// 生成水平计数器信号
always @(posedge I_pxl_clk or negedge I_rst_n) begin
if(!I_rst_n) // 复位时,将水平计数器清零
H_cnt <= 16'd0;
else if(H_cnt >= (I_h_total-1'b1)) // 当水平计数器达到水平总时间时,水平计数器归零
H_cnt <= 16'd0 ;
else // 其他情况,水平计数器加 1
H_cnt <= H_cnt + 1'b1 ;
end
// 对数据使能、水平同步和垂直同步信号进行延迟处理
always@(posedge I_pxl_clk or negedge I_rst_n) begin
if(!I_rst_n)begin // 复位时,延迟寄存器初始化为特定值
Pout_de_dn <= {N{1'b0}};
Pout_hs_dn <= {N{1'b1}};
Pout_vs_dn <= {N{1'b1}};
end
else begin // 通过移位操作实现 N 个时钟周期的延迟
Pout_de_dn <= {Pout_de_dn[N-2:0],Pout_de_w};
Pout_hs_dn <= {Pout_hs_dn[N-2:0],Pout_hs_w};
Pout_vs_dn <= {Pout_vs_dn[N-2:0],Pout_vs_w};
end
end
// 输出水平同步和垂直同步信号,根据输入的极性进行调整
always@(posedge I_pxl_clk or negedge I_rst_n) begin
if(!I_rst_n)begin // 复位时,水平同步和垂直同步信号置为高电平
O_hs <= 1'b1;
O_vs <= 1'b1;
end
else begin
O_hs <= I_hs_pol ? ~Pout_hs_dn[3] : Pout_hs_dn[3] ;// 根据水平同步极性调整水平同步信号的输出
O_vs <= I_vs_pol ? ~Pout_vs_dn[3] : Pout_vs_dn[3] ;// 根据垂直同步极性调整垂直同步信号的输出
end
end
// 数据使能期间的水平计数器
always @(posedge I_pxl_clk or negedge I_rst_n) begin
if(!I_rst_n) // 复位时,数据使能水平计数器清零
De_hcnt <= 16'd0;
else if (De_pos == 1'b1) // 数据使能信号上升沿时,数据使能水平计数器归零
De_hcnt <= 16'd0;
else if (Pout_de_dn[1] == 1'b1) // 数据使能信号有效期间,数据使能水平计数器加 1
De_hcnt <= De_hcnt + 1'b1;
else // 其他情况,数据使能水平计数器保持不变
De_hcnt <= De_hcnt;
end
// 彩色条触发计数
always @(posedge I_pxl_clk or negedge I_rst_n) begin
if(!I_rst_n) // 复位时,彩色条触发计数清零
Color_trig_num <= 16'd0;
else if (Pout_de_dn[1] == 1'b0) // 数据使能无效时,彩色条触发计数设置为水平分辨率的 1/8
Color_trig_num <= I_h_res[15:3];
else if ((Color_trig == 1'b1) && (Pout_de_dn[1] == 1'b1))// 彩色条触发且数据使能有效时,彩色条触发计数增加水平分辨率的 1/8
Color_trig_num <= Color_trig_num + I_h_res[15:3];
else // 其他情况,彩色条触发计数保持不变
Color_trig_num <= Color_trig_num;
end
// 彩色条触发信号
always @(posedge I_pxl_clk or negedge I_rst_n)begin
if(!I_rst_n) // 复位时,彩色条触发信号置为低电平
Color_trig <= 1'b0;
else if (De_hcnt == (Color_trig_num-1'b1)) // 当数据使能水平计数器达到彩色条触发计数时,触发彩色条切换
Color_trig <= 1'b1;
else // 其他情况,彩色条触发信号置为低电平
Color_trig <= 1'b0;
end
// 彩色条计数逻辑
// 此模块用于控制彩色条颜色切换的计数,在时钟上升沿或复位信号有效时触发操作
always @(posedge I_pxl_clk or negedge I_rst_n) begin
if(!I_rst_n) // 当复位信号 I_rst_n 为低电平时,将彩色条计数 Color_cnt 清零
Color_cnt <= 3'd0; //这确保在系统复位时,彩色条计数回到初始状态
else if (Pout_de_dn[1] == 1'b0) // 当数据使能延迟信号 Pout_de_dn[1] 为低电平时,说明此时不在有效数据区域
Color_cnt <= 3'd0; // 将彩色条计数 Color_cnt 清零,重新开始计数
else if ((Color_trig == 1'b1) && (Pout_de_dn[1] == 1'b1)) // 当彩色条触发信号 Color_trig 为高电平,且数据使能延迟信号 Pout_de_dn[1] 为高电平时
Color_cnt <= Color_cnt + 1'b1; // 表示满足彩色条颜色切换的条件,将彩色条计数 Color_cnt 加 1
else // 在其他情况下,保持彩色条计数 Color_cnt 不变
Color_cnt <= Color_cnt;
end
// 根据彩色条计数选择对应的颜色数据
// 此模块在时钟上升沿或复位信号有效时,根据彩色条计数 Color_cnt 选择相应的颜色值
always @(posedge I_pxl_clk or negedge I_rst_n) begin
if(!I_rst_n)// 确保系统复位时,输出的颜色为黑色
RGB_data <= 24'd0;
else if(Pout_de_dn[2] == 1'b1)// 当数据使能延迟信号 Pout_de_dn[2] 为高电平时,说明处于有效数据区域
case(Color_cnt)
3'd0 : RGB_data <= WHITE ; // 白色
3'd1 : RGB_data <= YELLOW ; // 黄色
3'd2 : RGB_data <= CYAN ; // 青色
3'd3 : RGB_data <= GREEN ; // 绿色
3'd4 : RGB_data <= PURPLE ; // 紫色
3'd5 : RGB_data <= RED ; // 红色
3'd6 : RGB_data <= BLUE ; // 蓝色
3'd7 : RGB_data <= BLACK ; // 黑色
default : RGB_data <= BLACK ; // 默认选择黑色
endcase
else
RGB_data <= BLACK ;
end
endmodule
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
6.3、DVI 配置
6.4、CLKDIV
6.5、锁相环
7、仿真编写
`timescale 1ns/1ns
module hdmi_mod();
reg sys_clk ;
reg sys_rst_n ;
wire O_tmds_clk_p ; // TMDS 时钟通道
wire O_tmds_clk_n ;
wire [2:0] O_tmds_data_p ; // TMDS 数据通道
wire [2:0] O_tmds_data_n ;
//在对 FIFO 或者 SP IP 核等(或其它在底层转换 GSR 时调用了全局复位的 IP/原语)进行仿真时,在仿真代码中需要添加如下这一段代码,否则联合仿真时就会报错。
GSR GSR(.GSRI(1'b1));
//信号初始化
initial begin
sys_clk = 1'b1;
sys_rst_n <= 1'b0;
#201
sys_rst_n <= 1'b1;
end
always #10 sys_clk <= ~sys_clk;
video_top u_video_top(
.sys_clk (sys_clk ),
.sys_rst_n (sys_rst_n ),
.O_tmds_clk_p (O_tmds_clk_p ), // TMDS 时钟通道
.O_tmds_clk_n (O_tmds_clk_n ),
.O_tmds_data_p (O_tmds_data_p ), // TMDS 数据通道
.O_tmds_data_n (O_tmds_data_n )
);
endmodule
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
接下来打开 Modelsim 软件对代码进行仿真,需要添加文件如下图所示:
我们看一下,HDMI的时钟与数据的差分信号。
HDMI 顶层差分时钟以及差分数据信号。在 DE 拉高的范围内,将 RGB888 颜色以十六进制进行转换输出,如下图所示:
8、下载验证
在仿真验证完成后,接下来使用 Gowin 对时钟约束和引脚进行分配并上板验证。本实验中,系统时钟、复位按键以及HDMI 时钟与数据输出端口的管脚分配如下表所示:
信号 | 方向 | 引脚 | 端口作用 | 电平标准 |
---|---|---|---|---|
sys_clk | input | T7 | 时钟 | LVCMOS33 |
sys_rst_n | input | F10 | 复位 | LVCMOS33 |
O_tmds_clk_p | output | M10,N11 | HDMI时钟 | LVDS25 |
O_tmds_data_p[0] | output | R13,T14 | HDMI数据0 | LVDS25 |
O_tmds_data_p[1] | output | R11,T12 | HDMI数据1 | LVDS25 |
O_tmds_data_p[2] | output | R12,P13 | HDMI数据2 | LVDS25 |
注:TMDS 数据和时钟信号需要在约束文件中指定电平标准为 LVDS25。另外,对于差分信号我们只需要指定正极的引脚位置,工具会自动对负极进行管脚分配。
接下来下载完程序,插上转接头,然后接上显示屏即可。