基於FPGA的720P HDMI顯示
1.4.1 HDMI 硬件電路分析
本次設計採用了 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 視頻輸出時序
1.4.2 工程文件設計
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
下載過程下載完成後按中間的按鍵顯示器會切換一次顯示的圖像,這些都是我們在程序部分定義的圖像。
圖1 40 實驗結果