本次设计采用了 IO 模拟的方式实现 HDMI 的功能。与采用专用 HDMI 芯片相比,此方案具有成本更低、效果不输于采用专用芯片的效果、经过测试,兼容性方面也要比专用芯片要好、最大输出图像分辨率 1080P、图像传输稳定等显著的优势。
具体硬件电路上面已经提到过了,这里简单分析一下。
图1 32 HDMI 硬件电路
从图中可以看到, HDMI 接口设计全由 IO 模拟方式实现, HDMI 的信号线 D0~D2 其实是一个差分信号,在我们程序当中体现为 TMDS 类型,因此其输入信号为串行的数字信号,内部实际上是将输入的 RGB 信号进行编码,转换为 HDMI 数据(实际我们称其为 DVI)进行输出,因此在设计中通常可以将 RGB 信号单独引出,作为 RGB 输出,用来作为双显输出使用。下表是 HDMI 接口信号的功能定义。
表1 5 HDMI 接口信号的功能定义
图1 33 HDMI 视频输出时序
HDMI 采用和 DVI 相同癿传输原理——TMDS(Transition Minimized Differential signal),最小化传输差分信号。
TMDS 传输系统分为两个部分:发送端和接收端。 TMDS 发送端收到HDMI 接口传来的表示 RGB 信号的24 位并行数据(TMDS 对每个像素的 RGB 三原色分别按 8bit 编码,即 R信号有 8 位,G 信号有 8 位,B 信号有 8 位),然后对这些数据进行编码和并/串转换,再将表示 3 个 RGB 信号的数据分别分配到独立的传输通道发送出去。接收端接收来自发送端的串行信号,对其进行解码和串/并转换,然后发送到显示器的控制端。与此同时也接收时钟信号,以实现同步。
TMDS的原理
每一个 TMDS 链路都包括 3 个传输 RGB 信号的数据通道和 1 个传输时钟信号的通道。每一个数据通道都通过编码算法,将 8 位的视、音频数据转换成最小化传输、直流平衡的 10 位数据。这使得数据的传输和恢复更加可靠。最小化传输差分信号是通过异或及异或非等逡、逻辑算法将原始 8 位信号数据转换成 10 位,前 8 为数据由原始信号经运算后获得,第 9 位指示运算的方式,第 10 位用来对应直流平衡。
一般来说,HDMI 传输癿编码格式中要包含视频数据、控制数据和数据包(数据包中包吨音频数据和附加信息数据,例如纠错码等)。 TMDS 每个通道在传输时要包含一个 2bit 的控制数据、 8bit 的视频数据或者 4bit 的数据包即可。在 HDMI 信息传输过程中,可以分为三个阶段:视频数据传输周期、控制数据传输周期和数据岛传输周期,分别对应上述的三种数据类型。
下面介绍 TMDS 中采用的技术:
1. 传输最小化
8 位数据经过编码和直流平衡得到 10 位最小化数据,这仿佛增加了冗余位,对传输链路的带宽要求更高,但事实上,通过这种算法得到的 10 位数据在更长的同轴电缆中传输的可靠性增强了。下图是一个例子,说明对一个 8 位的并行 RED 数据编码、并/串转换。
图1 34 一个 8 位的并行 RED 数据编码、并/串转换
第一步:将 8 位并行 RED 数据发送到 TMDS 収送端;
第二步:并/串转换;
第三步:进行最小化传输处理,加上第 9 位,即编码过程。第 9 位数据称为编码位。
2. 直流平衡
直流平衡(DC-balanced)就是指在编码过程中保证信道中直流偏移为零。方法是在原来的 9 位数据癿后面加上第 10 位数据,返样,传输的数据趋于直流平衡,使信号对传输线的电磁干扰减少,提高信号传输的可靠性。
3. 差分信号
TMDS差分传动技术是一种利用2个引脚间电压差来传送信号的技术。传输数据的数值(“0”或者“1”)由两脚间电压正负极性和大小决定。即,采用 2 根线来传输信号,一根线上传输原来的信号,另一根线上传输与原来信号相反的信号。这样接收端就可以通过让一根线上的信号减去另一根线上的信号的方式来屏蔽电磁干扰,从而得到正确的信号。
图1 35 差分信号
另外,还有一个显示数据通道(DDC),是用于读取表示接收端显示器的清晰度等显示能力的扩展显示标识数据(EDID)的信号线。搭载 HDCP(High-bandwidth Digital Content Protection,高带宽数字内容保护技术)的发送、接收设备之间也利用 DDC 线进行密码键的认证。
接下来是电路设计部分,HDMI驱动部分追寻原始出处应该是迪芝伦官方,该部分代码用VHDL语言描述,为了方便移植,我将该部分代码封装成自定义IP Core,由上文可知,我们需要产生RGB888三路数据,输入给该模块,然后经过解码、串/并转换,差分输出。还需要两个时钟输入,一个是当前显示分辨率的像素时钟,一个是当前显示分辨率的像素时钟的五倍。还有一个行同步信号和场同步信号,这两个信号的产生方法和VGA是一样的,简单来说就是先产生VGA的信号,行同步信号、场同步信号RGB888的数据输入给HDMI驱动模块就可以显示了,我们要修改显示的数据,还是只需要修改VGA时序即可。
图1 36 HDMI IP核结构框图
从上面的分析可以知道,IO模拟IP,主要有两部分组成,一是VGA时序产生,另一个是HDMI IP,整个系统结构图如下:
图1 37 基于FPGA的HDMI显示结构图
核心操作就是产生VGA时序及产生需要显示的视频数据,本次设计共产生几种图像(格子、纯黑、纯白、纯蓝等),可以通过按键来进行切换显示的模式,用 LED 来指示处于何种模式,方便我们调试时分析与处理问题。
本次设计产生一个720P 的图像,故其像素时钟输入应该为H_TotalV_TotalFPS=165075060HZ=74.25MHZ。这一部分程序的编写参考的是 VGA 时序,VGA 的时序是一种 RGB传输时序,其时序图如下图所示:
图1 38 VGA时序
首先看到有 3 个矩形,第 1 个矩形是 VGA 驱动的最大部分,里面包括所有的信息,在此基础之上有 2 个同步信号,即 HSYNC 和 VSYNC(行同步和场同步), HSYNC 可以确定一行的开始和结束, VSYNC 可以确定一场的开始和结束,但是同步信号也有时间,因此就引出 Hor Sync 和 Ver Sync(行同步时间和场同步时间)。接下来就是H Back Proch 和 V Back Porch(行消隐和场消隐),消隐存在主要是为了兼容电子管屏幕设计的。然后是第 2 个矩形,这是 Hor” Active” Video 和 Ver“Active” Video(行视频有效和场视频有效),在这个区域中是显示视频的地方。最后就是 H Front Porch 和 V Front Porch(行前肩和场前肩)。到此,一场完整视频就显示完毕了。在这里说一下第 3 个矩形,有 4 个参数 H Left Border、 H Right Border、 V Top Border 和 V Bottom Border,在不同分辨率中这4 个参数不同,在 800x600 及其以上的分辨率中这 4 个参数为 0。
在此给出一个简化的时序图,如下图所示。一行数据包括: Hor Sync(行同步)、Hor Back Porch(行消隐)、 Hor Active Video(行视频有效)和 Hor Front Porch(行前肩);一场数据包括: Ver Sync(场同步)、 Ver Back Porch(场消隐)、 Ver Active Video(场视频有效)和 Ver Front Porch(场前肩)。
图1 39 VGA时序
VGA 时序主要分为行时序和场时序,行时序是以像素为单位的,场时序是以行为单位的。 VGA 行时序对行同步时间、消隐时间、行视频有效时间和行前肩时间有特定要求,列时序也是如此,如果其中一部分时序出现问题就会造成显示出现问题。常用 VGA 分辨率时序参数如下表所示。
表1 6 VGA 常用分辨率时序参数
在这里说一下时钟频率计算,就是上文提到的第 1 个矩形和场频率有关,思考一下,很简单的。时钟频率=行最大值 x 列最大值 x 扫描频率。
代码1 1 HDMI数据产生模块
1. //****************************************************************************//
2. //# @Author: 碎碎思
3. //# @Date: 2019-11-25 21:58:59
4. //# @Last Modified by: zlk
5. //# @WeChat Official Account: OpenFPGA
6. //# @Last Modified time: 2017-11-08 16:58:26
7. //# Description:
8. //# @Modification History: 2017-11-08 16:58:26
9. //# Date By Version Change Description:
10. //# ========================================================================= #
11. //# 2017-11-08 16:58:26
12. //# ========================================================================= #
13. //# | | #
14. //# | OpenFPGA | #
15. //****************************************************************************//
16.
17.
18. `timescale 1ns / 1ps
19.
20. module hdmi_data_gen
21. (
22. input pix_clk,
23. input turn_mode,
24. output [7:0] VGA_R,
25. output [7:0] VGA_G,
26. output [7:0] VGA_B,
27. output VGA_HS,
28. output VGA_VS,
29. output VGA_DE,
30. output [3:0] mode
31. );
32. /*
33. parameter H_Total = 1344; //2200
34. parameter H_Sync = 136;
35. parameter H_Back = 160;//2-4
36. parameter H_Active = 1024; //1920
37. parameter H_Front = 24;
38. parameter H_Start = 296; //2-3
39. parameter H_End = 1320;
40. //-------------------------------//
41. // 垂直扫描参数的设定1024*600 60HZ
42. //-------------------------------//
43. parameter V_Total = 628; //1125
44. parameter V_Sync = 4;
45. parameter V_Back = 4;
46. parameter V_Active = 600; //1080
47. parameter V_Front = 0;
48. parameter V_Start = 8;
49. parameter V_End = 628;
50. */
51.
52. //---------------------------------//
53. // 水平扫描参数的设定1280*720 60HZ
54. //--------------------------------//
55. parameter H_Total = 1650; //2200
56. parameter H_Sync = 40; //44
57. parameter H_Back = 220;//148
58. parameter H_Active = 1280; //1920
59. parameter H_Front = 110; //88
60. parameter H_Start = 260; //192
61. parameter H_End = 1540; //2112
62. //-------------------------------//
63. // 垂直扫描参数的设定1280*720 60HZ
64. //-------------------------------//
65. parameter V_Total = 750; //1125
66. parameter V_Sync = 5; //5
67. parameter V_Back = 20; //37
68. parameter V_Active = 720; //1080
69. parameter V_Front = 5; //3
70. parameter V_Start = 25; //42
71. parameter V_End = 745; //1122
72.
73. /*
74. //---------------------------------//
75. // 水平扫描参数的设定1980*1080 60HZ
76. //--------------------------------//
77. parameter H_Total = 2200; //2200
78. parameter H_Sync = 44; //44
79. parameter H_Back = 148;//148
80. parameter H_Active = 1920; //1920
81. parameter H_Front = 88; //88
82. parameter H_Start = 192; //192
83. parameter H_End = 2112; //2112
84. //-------------------------------//
85. // 垂直扫描参数的设定1920*1080 60HZ
86. //-------------------------------//
87. parameter V_Total = 1125; //1125
88. parameter V_Sync = 5; //5
89. parameter V_Back = 37; //37
90. parameter V_Active = 1080; //1080
91. parameter V_Front = 3; //3
92. parameter V_Start = 42; //42
93. parameter V_End = 1122; //1122
94. */
95. reg[11:0] x_cnt;
96. always @(posedge pix_clk) //水平计数
97. begin
98. if(1'b0)
99. x_cnt <= 1;
100. else if(x_cnt==H_Total)
101. x_cnt <= 1;
102. else
103. x_cnt <= x_cnt + 1;
104. end
105.
106. reg hsync_r;
107. reg hs_de;
108. always @(posedge pix_clk)
109. begin
110. if(1'b0)
111. hsync_r <= 1'b1;
112. else if(x_cnt==1)
113. hsync_r <= 1'b0;
114. else if(x_cnt==H_Sync)
115. hsync_r <= 1'b1;
116.
117. if(1'b0)
118. hs_de <= 1'b0;
119. else if(x_cnt==H_Start)
120. hs_de <= 1'b1;
121. else if(x_cnt==H_End)
122. hs_de <= 1'b0;
123. end
124.
125. reg[11:0] y_cnt;
126. always @(posedge pix_clk)
127. begin
128. if(1'b0)
129. y_cnt <= 1;
130. else if(y_cnt==V_Total)
131. y_cnt <= 1;
132. else if(x_cnt==H_Total)
133. y_cnt <= y_cnt + 1;
134. end
135.
136. reg vsync_r;
137. reg vs_de;
138. always @(posedge pix_clk)
139. begin
140. if(1'b0)
141. vsync_r <= 1'b1;
142. else if(y_cnt==1)
143. vsync_r <= 1'b0;
144. else if(y_cnt==V_Sync)
145. vsync_r <= 1'b1;
146.
147. if(1'b0)
148. vs_de <= 1'b0;
149. else if(y_cnt==V_Start)
150. vs_de <= 1'b1;
151. else if(y_cnt==V_End)
152. vs_de <= 1'b0;
153. end
154.
155. reg[7:0] grid_data_1;
156. reg[7:0] grid_data_2;
157. always @(posedge pix_clk) //格子图像
158. begin
159. if((x_cnt[4]==1'b1)^(y_cnt[4]==1'b1))
160. grid_data_1 <= 8'h00;
161. else
162. grid_data_1 <= 8'hff;
163.
164. if((x_cnt[6]==1'b1)^(y_cnt[6]==1'b1))
165. grid_data_2 <= 8'h00;
166. else
167. grid_data_2 <= 8'hff;
168. end
169.
170. reg[23:0] color_bar;
171. always @(posedge pix_clk)
172. begin
173. if(x_cnt==192)
174. color_bar <= 24'hff0000;
175. else if(x_cnt==432)
176. color_bar <= 24'h00ff00;
177. else if(x_cnt==672)
178. color_bar <= 24'h0000ff;
179. else if(x_cnt==912)
180. color_bar <= 24'hff00ff;
181. else if(x_cnt==1152)
182. color_bar <= 24'hffff00;
183. else if(x_cnt==1392)
184. color_bar <= 24'h00ffff;
185. else if(x_cnt==1632)
186. color_bar <= 24'hffffff;
187. else if(x_cnt==1872)
188. color_bar <= 24'h000000;
189. else
190. color_bar <= color_bar;
191. end
192.
193. reg[16:0] key_counter;
194. reg[3:0] dis_mode;
195. assign mode=dis_mode;
196. always @(posedge pix_clk) //按键处理程序
197. begin
198. if(turn_mode==1'b0)
199. key_counter <= 14'b0;
200. else if((turn_mode==1'b1)&(key_counter<=17'h11704))
201. key_counter <= key_counter + 1'b1;
202.
203. if(key_counter==17'h11704)
204. begin
205. if(dis_mode==4'd12)
206. dis_mode <= 4'd0;
207. else
208. dis_mode <= dis_mode + 1'b1;
209. end
210. end
211.
212. reg[7:0] VGA_R_reg;
213. reg[7:0] VGA_G_reg;
214. reg[7:0] VGA_B_reg;
215. always @(posedge pix_clk)
216. begin
217. if(1'b0)
218. begin
219. VGA_R_reg<=0;
220. VGA_G_reg<=0;
221. VGA_B_reg<=0;
222. end
223. else
224. case(dis_mode)
225. 4'd0:begin
226. VGA_R_reg<=0; //LCD显示彩色条
227. VGA_G_reg<=0;
228. VGA_B_reg<=0;
229. end
230. 4'd1:begin
231. VGA_R_reg<=8'b11111111; //LCD显示全白
232. VGA_G_reg<=8'b11111111;
233. VGA_B_reg<=8'b11111111;
234. end
235. 4'd2:begin
236. VGA_R_reg<=8'b11111111; //LCD显示全红
237. VGA_G_reg<=0;
238. VGA_B_reg<=0;
239. end
240. 4'd3:begin
241. VGA_R_reg<=0; //LCD显示全绿
242. VGA_G_reg<=8'b11111111;
243. VGA_B_reg<=0;
244. end
245. 4'd4:begin
246. VGA_R_reg<=0; //LCD显示全蓝
247. VGA_G_reg<=0;
248. VGA_B_reg<=8'b11111111;
249. end
250. 4'd5:begin
251. VGA_R_reg<=grid_data_1; // LCD显示方格1
252. VGA_G_reg<=grid_data_1;
253. VGA_B_reg<=grid_data_1;
254. end
255. 4'd6:begin
256. VGA_R_reg<=grid_data_2; // LCD显示方格2
257. VGA_G_reg<=grid_data_2;
258. VGA_B_reg<=grid_data_2;
259. end
260. 4'd7:begin
261. VGA_R_reg<=x_cnt[7:0]; //LCD显示水平渐变色
262. VGA_G_reg<=x_cnt[7:0];
263. VGA_B_reg<=x_cnt[7:0];
264. end
265. 4'd8:begin
266. VGA_R_reg<=y_cnt[8:1]; //LCD显示垂直渐变色
267. VGA_G_reg<=y_cnt[8:1];
268. VGA_B_reg<=y_cnt[8:1];
269. end
270. 4'd9:begin
271. VGA_R_reg<=x_cnt[7:0]; //LCD显示红水平渐变色
272. VGA_G_reg<=0;
273. VGA_B_reg<=0;
274. end
275. 4'd10:begin
276. VGA_R_reg<=0; //LCD显示绿水平渐变色
277. VGA_G_reg<=x_cnt[7:0];
278. VGA_B_reg<=0;
279. end
280. 4'd11:begin
281. VGA_R_reg<=0; //LCD显示蓝水平渐变色
282. VGA_G_reg<=0;
283. VGA_B_reg<=x_cnt[7:0];
284. end
285. 4'd12:begin
286. VGA_R_reg<=color_bar[23:16]; //LCD显示彩色条
287. VGA_G_reg<=color_bar[15:8];
288. VGA_B_reg<=color_bar[7:0];
289. end
290. default:begin
291. VGA_R_reg<=8'b11111111; //LCD显示全白
292. VGA_G_reg<=8'b11111111;
293. VGA_B_reg<=8'b11111111;
294. end
295. endcase
296. end
297.
298. assign VGA_HS = hsync_r;
299. assign VGA_VS = vsync_r;
300. assign VGA_DE = hs_de & vs_de;
301. assign VGA_R = (hs_de & vs_de)?VGA_R_reg:8'h0;
302. assign VGA_G = (hs_de & vs_de)?VGA_G_reg:8'h0;
303. assign VGA_B = (hs_de & vs_de)?VGA_B_reg:8'h0;
304. endmodule
接下来就是顶层模块设计,设计参考图1 37,代码如下:
1. //****************************************************************************//
2. //# @Author: 碎碎思
3. //# @Date: 2019-11-25 21:58:59
4. //# @Last Modified by: zlk
5. //# @WeChat Official Account: OpenFPGA
6. //# @Last Modified time: 2019-12-11 20:26:10
7. //# Description:
8. //# @Modification History: 2019-10-09 22:17:36
9. //# Date By Version Change Description:
10. //# ========================================================================= #
11. //# 2019-10-09 22:17:36
12. //# ========================================================================= #
13. //# | | #
14. //# | OpenFPGA | #
15. //****************************************************************************//
16.
17. `timescale 1ns / 1ps
18.
19. //////////////////////////////////////////////////////////////////////////////////
20. module HDMI_display_Demon(
21. input clk_100M,
22. input KEY,
23.
24. output HDMI1_CLK_P,
25. output HDMI1_CLK_N,
26. output HDMI1_D2_P,
27. output HDMI1_D2_N,
28. output HDMI1_D1_P,
29. output HDMI1_D1_N,
30. output HDMI1_D0_P,
31. output HDMI1_D0_N,
32.
33. output HDMI2_CLK_P,
34. output HDMI2_CLK_N,
35. output HDMI2_D2_P,
36. output HDMI2_D2_N,
37. output HDMI2_D1_P,
38. output HDMI2_D1_N,
39. output HDMI2_D0_P,
40. output HDMI2_D0_N,
41.
42. output [3:0] LED
43. );
44.
45. wire pixclk;
46. wire[7:0] R,G,B;
47. wire HS,VS,DE;
48. assign VGA_HS = HS;
49. assign VGA_VS = VS;
50. assign VGA_D = {R[7:4],G[7:2],B[7:4]};
51. hdmi_data_gen u0_hdmi_data_gen
52. (
53. .pix_clk (pixclk),
54. .turn_mode (KEY),
55. .VGA_R (R),
56. .VGA_G (G),
57. .VGA_B (B),
58. .VGA_HS (HS),
59. .VGA_VS (VS),
60. .VGA_DE (DE),
61. .mode (LED)
62. );
63.
64. wire pixclk_X5;
65. //wire i2c_clk;
66. wire lock;
67. wire[23:0] RGB;
68. assign RGB={R,G,B};
69. hdmi_display_0 u1_hdmi_display_0
70. (
71. // .i2c_clk (i2c_clk),
72. .PXLCLK_I (pixclk),
73. .PXLCLK_5X_I (pixclk_X5),
74. .LOCKED_I (lock),
75. .RST_N (lock),
76. .VGA_RGB (RGB),
77. .VGA_HS (HS),
78. .VGA_VS (VS),
79. .VGA_DE (DE),
80. .HDMI_CLK_P (HDMI1_CLK_P),
81. .HDMI_CLK_N (HDMI1_CLK_N),
82. .HDMI_D2_P (HDMI1_D2_P),
83. .HDMI_D2_N (HDMI1_D2_N),
84. .HDMI_D1_P (HDMI1_D1_P),
85. .HDMI_D1_N (HDMI1_D1_N),
86. .HDMI_D0_P (HDMI1_D0_P),
87. .HDMI_D0_N (HDMI1_D0_N)
88. );
89.
90. hdmi_display_0 u2_hdmi_display_1
91. (
92. // .i2c_clk (i2c_clk),
93. .PXLCLK_I (pixclk),
94. .PXLCLK_5X_I (pixclk_X5),
95. .LOCKED_I (lock),
96. .RST_N (lock),
97. .VGA_RGB (RGB),
98. .VGA_HS (HS),
99. .VGA_VS (VS),
100. .VGA_DE (DE),
101. .HDMI_CLK_P (HDMI2_CLK_P),
102. .HDMI_CLK_N (HDMI2_CLK_N),
103. .HDMI_D2_P (HDMI2_D2_P),
104. .HDMI_D2_N (HDMI2_D2_N),
105. .HDMI_D1_P (HDMI2_D1_P),
106. .HDMI_D1_N (HDMI2_D1_N),
107. .HDMI_D0_P (HDMI2_D0_P),
108. .HDMI_D0_N (HDMI2_D0_N)
109. );
110.
111. clk_wiz_0 u3_clk
112. (
113. .clk_in1 (clk_100M),
114. .resetn (1'b1),
115. .clk_out1 (pixclk),
116. .clk_out2 (pixclk_X5),
117. // .clk_out3 (i2c_clk),
118. .locked (lock)
119. );
120. endmodule