概述
VGA是一種學習FPGA最常見的基礎實驗。雖然現在的顯示屏大多已經采用DVI和HDMI方案,但其實VGA在另一個地方還有應用,那就是大屏的LCD。目前4.3寸以上的TFT基本都是VGA接口,這樣在完成一個FPGA系統設計時,選擇一個VGA接口的TFT用來顯示便是最簡單方便的方案。
現在2017年全國大學生電子設計大賽還有不到一個月,熟練的使用VGA顯示各種圖形、文字、波形還是很重要的,而不是停留在只能顯示彩條的入門實驗上。這篇博文便致力于解決這個問題。
VGA顯示驅動
目前常見的電路板上的VGA接口是這樣的,單獨使用R、G、B三條線控制顏色:
現在應該很少會看到專門使用VGA驅動芯片的了。使用電阻網絡已經能獲得不錯的顯示效果。FPGA需要處理的信號有行同步信號HSYNC和場同步信號VSYNC,以及R、G、B三組顏色控制信號。在驅動VGA之前,我們首先要確定自己的顯示參數,分辨率及刷新率,比如800*600@60Hz的顯示方式其時序參數如下所示:
不同的分辨率和刷新率有不同的參數,這個數據可以在這個網頁中查到。進下來就進行VGA的時序驅動,我的習慣是先將關鍵性數據用parameter定義出來:
//-------------------------------------------------//
// 掃描參數的設定 640*480 60Hz VGA
//-------------------------------------------------//
parameter H_SYNC_END = 96; //行同步脈沖結束時間
parameter V_SYNC_END = 2; //列同步脈沖結束時間
parameter H_SYNC_TOTAL = 800; //行掃描總像素單位
parameter V_SYNC_TOTAL = 525; //列掃描總像素單位
parameter H_SHOW_START = 144; //顯示區行開始像素點
parameter V_SHOW_START = 35; //顯示區列開始像素點
VGA的時序驅動部分是相當固定的,只要我們使用VGA,肯定會加入下面這段代碼。主要方法是定義兩個計數器,一個管理行掃描,一個管理列掃描;當行掃描計數器掃描完行同步脈沖后置高HSYNC信號;同理,當列掃描計數器掃描完列同步脈沖后置高VSYNC信號。
//水平掃描
always@(posedge clk_25M or negedge RSTn)
if(!RSTn) x_cnt 《= ‘d0;
else if (x_cnt == H_SYNC_TOTAL) x_cnt 《= ’d0;
else x_cnt 《= x_cnt + 1‘b1;
//垂直掃描
always@(posedge clk_25M or negedge RSTn)
if(!RSTn) y_cnt 《= ’d0;
else if (y_cnt == V_SYNC_TOTAL) y_cnt 《= ‘d0;
else if (x_cnt == H_SYNC_TOTAL) y_cnt 《= y_cnt + 1’b1;
else y_cnt 《= y_cnt;
//H_SYNC信號
always@(posedge clk_25M or negedge RSTn)
if(!RSTn) hsync 《= ‘d0;
else if (x_cnt == ’d0) hsync 《= 1‘b0;
else if (x_cnt == H_SYNC_END) hsync 《= 1’b1;
else hsync 《= hsync;
//_SYNC信號
always@(posedge clk_25M or negedge RSTn)
if(!RSTn) vsync 《= ‘d0;
else if (y_cnt == ’d0) vsync 《= 1‘b0;
else if (y_cnt == V_SYNC_END) vsync 《= 1’b1;
else vsync 《= vsync;
由于VGA需要一個時鐘來管理掃描的速度,因此這個時鐘大小就應當為掃面面積*刷新率,如上例中的640*480@60Hz的顯示方式需要的VGA時鐘大小為800*525*60=25.2MHz(掃描時不僅包括顯示區域,還有同步脈沖、顯示前沿和顯示后沿,因此整個區域要大于分辨率)。VGA時鐘可以用PLL或分頻等方法產生,通常要求不會太嚴苛,上例取整為25MHz即可。
為了后面的顯示方便,一種實用的方法是再定義兩個寄存器,專門存儲顯示區域的坐標,即以可以顯示的第一個像素點為坐標(0,0)。
assign x_pos = x_cnt - H_SHOW_START;
assign y_pos = y_cnt - V_SHOW_START;
這樣上例中行掃描計數器和列掃描計數器的范圍分別為800和525,而顯示區域的坐標x_pos和y_pos范圍只有640和480。
VGA顯示圖形、波形、文字
其實在得到了顯示區域的坐標后,我們控制顯示圖像的方法就是當計數器掃描到指定位置后,為R、G、B三組信號賦值得到對應的圖形。以前面寫的“FPGA綜合系統設計(一):貪吃蛇游戲(VGA+鍵盤)”這個工程中的顯示部分代碼為例:
always@(posedge clk_25M)
if (area) //坐標處于顯示分數的區域內,80*80
begin
case(pop)
0: color_out 《= data0 ? 3‘b111 : 3’b000;
1: color_out 《= data1 ? 3‘b111 : 3’b000;
2: color_out 《= data2 ? 3‘b111 : 3’b000;
3: color_out 《= data3 ? 3‘b111 : 3’b000;
4: color_out 《= data4 ? 3‘b111 : 3’b000;
5: color_out 《= data5 ? 3‘b111 : 3’b000;
6: color_out 《= data6 ? 3‘b111 : 3’b000;
7: color_out 《= data7 ? 3‘b111 : 3’b000;
8: color_out 《= data8 ? 3‘b111 : 3’b000;
9: color_out 《= data9 ? 3‘b111 : 3’b000;
10: color_out 《= data10 ? 3‘b111 : 3’b000;
11: color_out 《= data11 ? 3‘b111 : 3’b000;
12: color_out 《= data12 ? 3‘b111 : 3’b000;
default : color_out 《= 3‘b000;
endcase
end
else //坐標處于游戲界面的區域內
begin
lox=x_pos[3:0]; //取偏移坐標
loy=y_pos[3:0];
/* 根據當前掃描到的點是哪一部分輸出相應顏色 */
/*蘋果*/
if(x_pos[9:4]==apple_x&&y_pos[9:4]==apple_y)
case({loy,lox})
8’b0000_0000:color_out=3‘b000;
default:color_out=3’b001;
endcase
/*背景*/
else if(snake==NONE)
color_out=3‘b000;
/*墻壁*/
else if(snake==WALL)
color_out=3’b101;
/*蛇頭與蛇身*/
else if(snake==HEAD|snake==BODY)
case({lox,loy})
8‘b0000_0000:color_out=3’b000;
default:color_out=(snake==HEAD)?HEAD_COLOR:BODY_COLOR;
endcase
end
看這個的設計思路,always里我把整個顯示區域劃分為if(area)和else兩個區域,area是用邏輯判斷定義好的一個80*80大小的區域,用來顯示分數;else則是顯示屏的剩下區域, 用來顯示游戲。
先看if(area)區域,我事先將各個分數以圖片的形式存到了ROM中,所有的ROM接的是一組地址線,每個ROM又有不同的數據線。根據當前的游戲分數,我使用case來決定選擇哪個ROM中的圖形作為當前區域的輸出。這個設計思路用處就大了,比如顯示電壓、電流、頻率等如何讓其動態變化,這就是一種好的方法。
再來看else區域,我使用計數器位數之間的關系,來將整個屏幕劃分為幾個小格子,然后根據格子應當屬于哪種游戲元素來決定顯示什么顏色,這樣整個顯示區域就劃分為蘋果、墻壁、蛇頭、蛇身等各個游戲元素。
其實顯示波形的方法和else區域顯示的方法基本是一樣的。假設我要畫一段512個點長的頻譜,我就可以選擇出行計數器掃描中的512個像素點,每一個像素點作為一個頻譜點;用同樣的方法,我們把列計數器掃描的像素點按一定比例分配給不同的峰值,這樣掃描到指定點時輸出顏色,看起來就是一個完整的頻譜圖了。當然如果覺得圖形不夠連貫,可以用更多像素點來顯示,對中間的像素點插值即可。
-
FPGA
+關注
關注
1626文章
21665瀏覽量
601807 -
VGA
+關注
關注
5文章
532瀏覽量
62825
發布評論請先 登錄
相關推薦
評論