VGA(Video Graphics Array)是IBM在1987年隨PS/2機一起推出的一種視頻傳輸標准,具有分辨率高、顯示速率快、顏色豐富等優點,在彩色顯示器領域得到了廣泛的應用。不支持熱插拔,不支持音頻傳輸。對於一些嵌入式VGA顯示系統,可以在不使用VGA顯示卡和計算機的情況下,實現VGA圖像的顯示和控制。VGA顯示器具有成本低、結構簡單、應用靈活的優點。對於一名FPGA工程師,尤其是視頻圖像的方向的學習者,VGA協議是必須要掌握的。
一、外部接口
由電路圖可以看到,VGA並沒有特殊的外部芯片,我們需要關注的其實只有5個信號:HS行同步信號,VS場同步信號,R紅基色,G綠基色,B藍基色。下面慢慢解釋這些信號。
二、色彩原理
經過九年義務教育的我們都應該聽過三基色,還給老師了的那就在再復習一下。三基色是指通過其他顏色的混合無法得到的“基本色”由於人的肉眼有感知紅、綠、藍三種不同顏色的錐體細胞,因此色彩空間通常可以由三種基本色來表達。這是色度學的最基本原理,即三基色原理。三種基色是相互獨立的,任何一種基色都不能有其它兩種顏色合成。紅綠藍是三基色,這三種顏色合成的顏色范圍最為廣泛。我們的RGB信號真是三基色的運用,對這三個信號賦予不同的數值,混合起來便是不同的色彩。
設計RGB信號時,既可以R信號、G信號和B信號獨立的賦值,最后連到端口上,也可以直接用RGB當做一個整體信號,RGB信號在使用時的位寬有三種常見格式,以你的VGA解碼芯片的配置有關。
1. RGB_8,R:G:B = 3:3:2,即RGB332
2. RGB_16,R:G:B = 5:6:5,即RGB565
3. RGB_24,R:G:B = 8:8:8,即RGB888
三、掃描方式
VGA顯示器掃描方式分為逐行掃描和隔行掃描:逐行掃描是掃描從屏幕左上角一點開始,從左像右逐點掃描,每掃描完一行,電子束回到屏幕的左邊下一行的起始位置,在這期間,CRT對電子束進行消隱,每行結束時,用行同步信號進行同步;當掃描完所有的行,形成一幀,用場同步信號進行場同步,並使掃描回到屏幕左上方,同時進行場消隱,開始下一幀。隔行掃描是指電子束掃描時每隔一行掃一線,完成一屏后在返回來掃描剩下的線,隔行掃描的顯示器閃爍的厲害,會讓使用者的眼睛疲勞。因此我們一般都采用逐行掃描的方式。
掃描原理如下所示:
四、行場信號
行場信號共有 4 種模式,即 hsync 和 vsync 的高低狀態不同,如下所示:
一開始看這些時序圖可能看不懂,它是把行場信號繪制在同一張圖里,說明行場信號的控制是相似的,只是時間參數不一樣而已。如果展開的話,其實時序是這樣的:
這樣就清楚了,大致是若干個HS信號才組合而成一個VS,如果在一副圖片中,那正確的時序表示方式應該如下圖這樣。
現在稍稍解釋一下這些參數。SYNC是“信號同步”,Back proch和Left border常常加在一起稱為“顯示后沿”,Addressable video為“顯示區域”,Right porder和Front porch常常加在一起稱為“顯示前沿”,一個時序其實就是先拉高一段較短的“信號同步”時間,然后拉低一段很長的時間,這就是一個回合。同時需要注意,其實也可以完全相反。即先拉低一段時間“信號同步”時間,然后拉高一段很長的時間。
具體這些時間參數是怎么來的呢?且看下文。
五、規格參數
直接拿數據手冊說話!
以上是 640x480 @60Hz 的參數表,對着這個表即可確定時間。如果為了嫌麻煩,也可以先計算好寫在代碼里。
//************************************************************************** // *** 名稱 : VGA_driver.v // *** 作者 : xianyu_FPGA // *** 博客 : https://www.cnblogs.com/xianyufpga/ // *** 日期 : 2019-06-26 // *** 描述 : VGA驅動模塊,VGA_req和VGA_x、VGA_y信號一般不同時使用 //************************************************************************** module VGA_driver //========================< 端口 >========================================== ( //system ---------------------------------------- input wire clk , //時鍾,25Mhz input wire rst_n , //復位,低電平有效 //VGA_display ----------------------------------- input wire [15:0] VGA_din , //得到圖像數據 output wire VGA_req , //請求圖像數據 output wire [10:0] VGA_x , //請求顯示區域橫坐標 output wire [10:0] VGA_y , //請求顯示區域縱坐標 //VGA output ------------------------------------ output wire VGA_clk , //VGA接口時鍾信號 output wire VGA_blank , //VGA接口空白信號,低有效 output wire VGA_de , //VGA接口使能信號,高有效 output wire VGA_hsync , //VGA接口行信號 output wire VGA_vsync , //VGA接口場信號 output wire [15:0] VGA_data //VGA接口數據信號 ); //========================< 參數 >========================================== /* 640x480 @60Hz 25Mhz //--------------------------------------------------------------- parameter H_TOTAL = 800 ; //行掃描周期 parameter H_ADDR = 640 ; //行有效數據 parameter H_RIGHT_BORDER = 8 ; parameter H_FRONT_PORCH = 8 ; parameter H_FRONT = H_RIGHT_BORDER + H_FRONT_PORCH ; //行顯示前沿 parameter H_SYNC = 96 ; //行同步 parameter H_BACK_PORCH = 40 ; parameter H_LEFT_BORDER = 8 ; parameter H_BACK = H_BACK_PORCH + H_LEFT_BORDER ; //行顯示后沿 //----------------------- parameter V_TOTAL = 525 ; //場掃描周期 parameter V_ADDR = 480 ; //場有效數據 parameter V_BOTTOM_BORDER = 8 ; parameter V_FRONT_PORCH = 2 ; parameter V_FRONT = V_BOTTOM_BORDER + V_FRONT_PORCH ; //場顯示前沿 parameter V_SYNC = 2 ; //場同步 parameter V_BACK_PORCH = 25 ; parameter V_TOP_BORDER = 8 ; parameter V_BACK = V_BACK_PORCH + V_TOP_BORDER ; //場顯示后沿 //--------------------------------------------------------------- */ // 1024x768 @60Hz 65Mhz //--------------------------------------------------------------- parameter H_TOTAL = 1344 ; //行掃描周期 parameter H_ADDR = 1024 ; //行有效數據 parameter H_RIGHT_BORDER = 0 ; parameter H_FRONT_PORCH = 24 ; parameter H_FRONT = H_RIGHT_BORDER + H_FRONT_PORCH ; //行顯示前沿 parameter H_SYNC = 136 ; //行同步 parameter H_BACK_PORCH = 160 ; parameter H_LEFT_BORDER = 0 ; parameter H_BACK = H_BACK_PORCH + H_LEFT_BORDER ; //行顯示后沿 //----------------------- parameter V_TOTAL = 806 ; //場掃描周期 parameter V_ADDR = 768 ; //場有效數據 parameter V_BOTTOM_BORDER = 0 ; parameter V_FRONT_PORCH = 3 ; parameter V_FRONT = V_BOTTOM_BORDER + V_FRONT_PORCH ; //場顯示前沿 parameter V_SYNC = 6 ; //場同步 parameter V_BACK_PORCH = 29 ; parameter V_TOP_BORDER = 0 ; parameter V_BACK = V_BACK_PORCH + V_TOP_BORDER ; //場顯示后沿 //--------------------------------------------------------------- /* 1280x720 @60Hz 74.25Mhz //--------------------------------------------------------------- parameter H_TOTAL = 1650 ; //行掃描周期 parameter H_ADDR = 1280 ; //行有效數據 parameter H_RIGHT_BORDER = 0 ; parameter H_FRONT_PORCH = 110 ; parameter H_FRONT = H_RIGHT_BORDER + H_FRONT_PORCH ; //行顯示前沿 parameter H_SYNC = 40 ; //行同步 parameter H_BACK_PORCH = 220 ; parameter H_LEFT_BORDER = 0 ; parameter H_BACK = H_BACK_PORCH + H_LEFT_BORDER ; //行顯示后沿 //----------------------- parameter V_TOTAL = 750 ; //場掃描周期 parameter V_ADDR = 720 ; //場有效數據 parameter V_BOTTOM_BORDER = 0 ; parameter V_FRONT_PORCH = 5 ; parameter V_FRONT = V_BOTTOM_BORDER + V_FRONT_PORCH ; //場顯示前沿 parameter V_SYNC = 5 ; //場同步 parameter V_BACK_PORCH = 20 ; parameter V_TOP_BORDER = 0 ; parameter V_BACK = V_BACK_PORCH + V_TOP_BORDER ; //場顯示后沿 //--------------------------------------------------------------- */ //========================< 信號 >========================================== reg [10:0] cnt_h ; wire add_cnt_h ; wire end_cnt_h ; reg [10:0] cnt_v ; wire add_cnt_v ; wire end_cnt_v ; //========================================================================== //== 行、場計數 //========================================================================== always @(posedge clk or negedge rst_n) begin if(!rst_n) cnt_h <= 0; else if(add_cnt_h) begin if(end_cnt_h) cnt_h <= 0; else cnt_h <= cnt_h + 1; end end assign add_cnt_h = 1; assign end_cnt_h = add_cnt_h && cnt_h==H_TOTAL-1; always @(posedge clk or negedge rst_n) begin if(!rst_n) cnt_v <= 0; else if(add_cnt_v) begin if(end_cnt_v) cnt_v <= 0; else cnt_v <= cnt_v + 1; end end assign add_cnt_v = end_cnt_h; assign end_cnt_v = add_cnt_v && cnt_v==V_TOTAL-1; //========================================================================== //== 數據請求和數據坐標 //========================================================================== //VGA請求 assign VGA_req = (cnt_h >= H_SYNC + H_BACK - 1) && (cnt_h < H_SYNC + H_BACK + H_ADDR - 1) && (cnt_v >= V_SYNC + V_BACK ) && (cnt_v < V_SYNC + V_BACK + V_ADDR ) ? 1 : 0; //VGA坐標 assign VGA_x = VGA_req ? (cnt_h - (H_SYNC + H_BACK - 1'b1)) : 10'd0; assign VGA_y = VGA_req ? (cnt_v - (V_SYNC + V_BACK - 1'b1)) : 10'd0; //========================================================================== //== VGA output //========================================================================== //時鍾 assign VGA_clk = ~clk; //行場 assign VGA_hsync = (cnt_h < H_SYNC) ? 0 : 1; assign VGA_vsync = (cnt_v < V_SYNC) ? 0 : 1; //空白,低有效,可以看成是de assign VGA_blank = VGA_hsync & VGA_vsync; //或=VGA_de //使能,高有效,非VGA必須,但大多數LCD屏都需要 assign VGA_de = (cnt_h >= H_SYNC + H_BACK) && (cnt_h < H_SYNC + H_BACK + H_ADDR) && (cnt_v >= V_SYNC + V_BACK) && (cnt_v < V_SYNC + V_BACK + V_ADDR) ? 1 : 0; //數據 assign VGA_data = VGA_de ? VGA_din : 16'b0; endmodule
注意一下,VGA 的驅動電路常用的有 2 種:
(1) R-2R 電阻模擬電路設計方案
該方案更便宜,在 1024x768@60hz 及以下的分辨率條件下穩定運行,多見於 16 位的 VGA 接口中,通常不需要 VGA_clk 和 VGA_blank 信號。
(2)專用視頻轉換 DAC 芯片實現 VGA電路方案
各方面都更牛逼,常見於 24 位的 VGA 接口中,通常需要 VGA_clk 和 VGA_blank 信號,有些還有 VGA_sync 信號,但該信號一般在電路上就接地了。
本代碼中對這些信號都進行了設計,要用就用,不用就不連到 FPGA 引腳就行。如果需要 24 位,只需要修改輸入輸出的位寬即可。
六、實例講解
最近使用的開發板帶了一個TFT屏,分辨率為480x272,其顯示原理和VGA接口完全相同,因此拿這個屏幕編寫一段程序看看。
1 //========================================================================== 2 // --- 名稱 : TFT_driver.v 3 // --- 作者 : xianyu_FPGA 4 // --- 日期 : 2019-01-03 5 // --- 描述 : TFT顯示屏控制器,分辨率480x272,顯示三個豎着的彩條 6 //========================================================================== 7 8 module TFT_driver 9 //=====================<端口聲明>=========================================== 10 ( 11 //input ------------------------------------- 12 input wire clk , //時鍾,9Mhz 13 input wire rst_n , //復位,低電平有效 14 //user interfaces --------------------------- 15 output wire TFT_req , //輸出請求信號 16 input wire [15:0] data , //得到圖像數據 17 //output ------------------------------------ 18 output wire TFT_clk , //TFT像素時鍾 19 output wire TFT_de , //TFT使能 20 output wire TFT_pwm , //TFT背光控制 21 output wire TFT_hsync , //TFT行同步信號 22 output wire TFT_vsync , //TFT場同步信號 23 output reg [15:0] TFT_rgb //TFT像素輸出 24 ); 25 //=====================<參數定義>=========================================== 26 //480x272 @60 9Mhz -------------------------- 27 parameter H_TOTAL = 525 ; //行掃描周期 28 parameter H_ADDR = 480 ; //行有效數據 29 parameter H_FRONT = 2 ; //行顯示前沿 30 parameter H_SYNC = 41 ; //行同步 31 parameter H_BACK = 2 ; //行顯示后沿 32 parameter V_TOTAL = 286 ; //場掃描周期 33 parameter V_ADDR = 272 ; //場有效數據 34 parameter V_FRONT = 2 ; //場顯示前沿 35 parameter V_SYNC = 10 ; //場同步 36 parameter V_BACK = 2 ; //場顯示后沿 37 38 //=====================<信號定義>=========================================== 39 //行場信號 40 reg [9:0] cnt_h ; 41 wire add_cnt_h ; 42 wire end_cnt_h ; 43 reg [9:0] cnt_v ; 44 wire add_cnt_v ; 45 wire end_cnt_v ; 46 reg TFT_en ; 47 wire red_area ; 48 wire green_area ; 49 wire blue_area ; 50 51 //-------------------------------------------------------------------------- 52 //-- 行、場計數 53 //-------------------------------------------------------------------------- 54 always @(posedge clk or negedge rst_n) begin 55 if(!rst_n) 56 cnt_h <= 0; 57 else if(add_cnt_h) begin 58 if(end_cnt_h) 59 cnt_h <= 0; 60 else 61 cnt_h <= cnt_h + 1; 62 end 63 end 64 65 assign add_cnt_h = 1; 66 assign end_cnt_h = add_cnt_h && cnt_h==H_TOTAL-1; 67 68 always @(posedge clk or negedge rst_n) begin 69 if(!rst_n) 70 cnt_v <= 0; 71 else if(add_cnt_v) begin 72 if(end_cnt_v) 73 cnt_v <= 0; 74 else 75 cnt_v <= cnt_v + 1; 76 end 77 end 78 79 assign add_cnt_v = end_cnt_h; 80 assign end_cnt_v = add_cnt_v && cnt_v==V_TOTAL-1; 81 82 //-------------------------------------------------------------------------- 83 //-- TFT請求信號和使能信號,注意時序的對齊 84 //-------------------------------------------------------------------------- 85 assign TFT_req = (cnt_h >= H_SYNC + H_BACK - 1) && (cnt_h < H_SYNC + H_BACK + H_ADDR - 1) && 86 (cnt_v >= V_SYNC + V_BACK ) && (cnt_v < V_SYNC + V_BACK + V_ADDR ) 87 ? 1 : 0; 88 89 always @(posedge clk) begin 90 TFT_en <= TFT_req; 91 end 92 93 //-------------------------------------------------------------------------- 94 //-- 行場信號 95 //-------------------------------------------------------------------------- 96 assign TFT_hsync = (cnt_h < H_SYNC) ? 0 : 1; 97 assign TFT_vsync = (cnt_v < V_SYNC) ? 0 : 1; 98 99 //-------------------------------------------------------------------------- 100 //-- 其他信號 101 //-------------------------------------------------------------------------- 102 assign TFT_clk = clk; 103 assign TFT_de = TFT_en; 104 assign TFT_pwm = rst_n; 105 106 //-------------------------------------------------------------------------- 107 //-- rgb信號 108 //-------------------------------------------------------------------------- 109 //assign TFT_rgb = TFT_en ? data : 0; 110 111 always @(*) begin 112 if(TFT_en) begin 113 114 if(red_area) begin //紅色區域 115 TFT_rgb <= 16'b11111_000000_00000; 116 end 117 else if(green_area) begin //綠色區域 118 TFT_rgb <= 16'b00000_111111_00000; 119 end 120 else if(blue_area) begin //藍色區域 121 TFT_rgb <= 16'b00000_000000_11111; 122 end 123 124 end 125 else begin //非顯示區域 126 TFT_rgb <= 0; 127 end 128 end 129 130 131 assign red_area = cnt_h >= (H_SYNC + H_BACK) && cnt_h < (H_SYNC + H_BACK + H_ADDR*1/3) && 132 cnt_v >= (V_SYNC + V_BACK) && cnt_v < (V_SYNC + V_BACK + V_ADDR); 133 assign green_area = cnt_h >= (H_SYNC + H_BACK) && cnt_h < (H_SYNC + H_BACK + H_ADDR*2/3) && 134 cnt_v >= (V_SYNC + V_BACK) && cnt_v < (V_SYNC + V_BACK + V_ADDR); 135 assign blue_area = cnt_h >= (H_SYNC + H_BACK) && cnt_h < (H_SYNC + H_BACK + H_ADDR*3/3) && 136 cnt_v >= (V_SYNC + V_BACK) && cnt_v < (V_SYNC + V_BACK + V_ADDR); 137 138 139 140 141 endmodule
這個工程還包括頂層top模塊,pll分頻模塊,這些就不展示了。還一點是接口處的user interfaces的信號沒有使用到,而是自己通過代碼賦的值。工程最終正常運行,顯示出從左到右的三個豎彩條,其效果如下所示:
七、后記
這樣只是簡單的使用了VGA,最終還是要以顯示視頻或圖像為目標,這就涉及到模塊之間的交互問題,下次再總結吧!
參考資料:
[1]開源騷客.VGA系列之一:VGA顯示驅動篇
[2]NingHeChuan.基於FPGA的VGA顯示靜態圖片
[3]威三學院FPGA教程
[4]袁玉卓, 曾凱鋒, 梅雪松. FPGA自學筆記:設計與驗證[M]. 北京航空航天出版社, 2017.