一、 軟件平臺(tái)與硬件平臺(tái)
軟件平臺(tái):
1、操作系統(tǒng):Windows-8.1
2、開發(fā)套件:ISE14.7
硬件平臺(tái):
1、 FPGA型號(hào):Xilinx公司的XC6SLX45-2CSG324
2、 VGA接口
3、 液晶顯示器
二、 原理介紹
VGA(Video Graphics Array)即視頻圖形陣列,是IBM在1987年推出的使用模擬信號(hào)的一種視頻傳輸標(biāo)準(zhǔn),在當(dāng)時(shí)具有分辨率高、顯示速率快、顏色豐富等優(yōu)點(diǎn),在彩色顯示器領(lǐng)域得到了廣泛的應(yīng)用。這個(gè)標(biāo)準(zhǔn)對(duì)于現(xiàn)今的個(gè)人電腦市場(chǎng)已經(jīng)十分過時(shí)。即使如此,VGA仍然是最多制造商所共同支持的一個(gè)標(biāo)準(zhǔn),個(gè)人電腦在加載自己的特殊驅(qū)動(dòng)程序之前,都必須支持VGA的標(biāo)準(zhǔn)。
VGA接口實(shí)物所示
?
左邊帶針的叫VGA公頭,右邊帶槽的叫VGA母頭。
VGA接口的特點(diǎn):
1、 VGA接口不支持熱插拔
VGA接口跟HDMI接口一樣是不支持熱插拔的。熱插拔是指一般帶電狀態(tài)下對(duì)于接插件的插入或是拔除,并不只是針對(duì)有電源接口或者帶供電的接口的接插件,而是所有。在運(yùn)行狀態(tài)時(shí),插拔會(huì)產(chǎn)生耦合電流,電流不穩(wěn)造成硬件燒壞,導(dǎo)致筆記本的接口端的保護(hù)受到?jīng)_擊。就像U盤不能再一個(gè)時(shí)間段多次在一個(gè)端口插拔使用一樣。各種電器的外露端子都會(huì)有金屬的部分,它們都是要求接地的,但是不同的電器之間的地并不一定相同,比如一臺(tái)DVD的地和一臺(tái)電視機(jī)的接地都是相對(duì)于本身系統(tǒng)而言。
當(dāng)端子插入時(shí),首先要建立共同的地來對(duì)傳輸?shù)男盘?hào)作參考,這就要依靠端子和傳輸線上的金屬部分了,金屬部分接地同時(shí)也是對(duì)信號(hào)的屏蔽和保護(hù)。兩個(gè)地相接觸一瞬間,會(huì)有很高的尖峰脈沖產(chǎn)生,這種脈沖如果不加以濾除可能會(huì)直達(dá)芯片并將其損壞。另外還有一種是ESD,即靜電損壞,這種更難以避免,因?yàn)樵?a target="_blank">電子產(chǎn)品上,只能去防護(hù),ESD的持續(xù)時(shí)間會(huì)更短US級(jí)別。所以正規(guī)的電子產(chǎn)品對(duì)于金屬端子的接地有比較高的要求,同時(shí)在信號(hào)線上增加ESD防護(hù)器件來避免熱插拔的損壞。但實(shí)際上很多廠家為了節(jié)省成本而偷工減料,或者是對(duì)熱插拔的防護(hù)意識(shí)不夠?qū)е略O(shè)計(jì)不合理,使得用戶會(huì)出現(xiàn)熱插拔損壞電器的現(xiàn)象產(chǎn)生。
2、 ?VGA不能傳輸音頻
因?yàn)橐曨l是VGA信號(hào),而音頻信號(hào)不是,所以VGA不能傳輸音頻,只能傳輸視頻。相信這就是為什么這幾年極度的需求創(chuàng)新轉(zhuǎn)換器的原因。VGA不支持音頻傳輸也是給很多消費(fèi)者帶來煩惱,這最好的辦法其實(shí)就是購(gòu)買一款轉(zhuǎn)換器,VGA轉(zhuǎn)HDMI或者HDMI轉(zhuǎn)VGA,達(dá)到視頻傳輸?shù)耐瑫r(shí)還支持音頻信號(hào)的輸出,一舉兩得。但是不要只想著轉(zhuǎn)換器的輸入與輸出成問題,同時(shí)想想音頻輸出口,3.5mm是音頻輸出信號(hào)的重要連接線。購(gòu)買時(shí)可以考慮想轉(zhuǎn)換器有沒有帶3.5mm的音頻輸出口,然后另外購(gòu)買一條音頻線。
3、 VGA接口是一種D型接口,上面共有15針孔,分成三排,每排五個(gè)。其中比較重要的是3根RGB彩色分量信號(hào)和2根掃描同步信號(hào)HSYNC和VSYNC針。其引腳編號(hào)圖如下圖所示:
其中每個(gè)管腳的詳細(xì)定義如下表所示
管腳 | 名稱 | 定義 |
1 | RED | 紅基色(75Ω,0.7Vp-p) |
2 | GREEN | 綠基色(75Ω,0.7Vp-p) |
3 | BLUE | 藍(lán)基色(75Ω,0.7Vp-p) |
4 | ID2 | 地址碼(顯示器標(biāo)識(shí)位2) |
5 | GND | 地 |
6 | RGND | 紅色地 |
7 | GGND | 綠色地 |
8 | BGND | 藍(lán)色地 |
9 | KEY | 保留 |
10 | SGND | 同步信號(hào)地 |
11 | ID0 | 地址碼(顯示器標(biāo)識(shí)位0) |
12 | ID1 | 地址碼(顯示器標(biāo)識(shí)位1) |
13 | HSYNC | 行同步信號(hào) |
14 | VSYNC | 場(chǎng)同步信號(hào) |
15 | ID3 | 地址碼(顯示器標(biāo)識(shí)位3) |
?
VGA接口時(shí)序詳解
VGA 顯示器掃描方式從屏幕左上角一點(diǎn)開始,從左向右逐點(diǎn)掃描,每掃描完一行,電子束回到屏幕的左邊下一行的起始位置,在這期間,CRT 對(duì)電子束進(jìn)行消隱,每行結(jié)束時(shí),用行同步信號(hào)進(jìn)行同步;當(dāng)掃描完所有的行,形成一幀,用場(chǎng)同步信號(hào)進(jìn)行場(chǎng)同步,并使掃描回到屏幕左上方,同時(shí)進(jìn)行場(chǎng)消隱,開始下一幀。完成一行掃描的時(shí)間稱為水平掃描時(shí)間,其倒數(shù)稱為行頻率;完成一幀(整屏)掃描的時(shí)間稱為垂直掃描時(shí)間,其倒數(shù)稱為場(chǎng)頻率,即屏幕的刷新頻率,常見的有 60Hz,75Hz 等等,但標(biāo)準(zhǔn)的 VGA 顯示的場(chǎng)頻 60Hz。其掃描示意圖如下圖所示
在對(duì)VGA掃描方式有一個(gè)直觀的感受以后接下來在看一看VGA接口的詳細(xì)時(shí)序與各個(gè)參數(shù)的定義。VGA的詳細(xì)時(shí)序如下圖所示:
總的來說,VGA的時(shí)序主要包括行時(shí)序與場(chǎng)時(shí)序兩個(gè)部分。
其中行時(shí)序主要包括:行同步(Hor Sync) 、行消隱(Hor Back Porch) 、行視頻有效(Hor Active Video)和行前肩(Hor Front Porch)這四個(gè)參數(shù),行時(shí)序的時(shí)序圖如下圖所示
而場(chǎng)時(shí)序主要包括:場(chǎng)同步(Ver Sync) 、場(chǎng)消隱(Ver Back Porch) 、場(chǎng)視頻有效(Ver Active Video)和場(chǎng)前肩(Ver Front Porch)這四個(gè)參數(shù),場(chǎng)時(shí)序的時(shí)序圖如下圖所示
需要注意的有三點(diǎn):
1、行時(shí)序是以”像素”為單位的, 場(chǎng)時(shí)序是以”行”為單位的。
2、VGA 工業(yè)標(biāo)準(zhǔn)顯示模式要求:行同步,場(chǎng)同步都為負(fù)極性,即同步脈沖要求是負(fù)脈沖。
3、VGA 行時(shí)序?qū)π型綍r(shí)間、 消隱時(shí)間、 行視頻有效時(shí)間和行前肩時(shí)間有特定的規(guī)范, 場(chǎng)時(shí)序也是如此。常用VGA 分辨率時(shí)序參數(shù)如下表所示
其中:
Pixel Clock = (Screen Refresh Frequency)*(Hor Active Video + Hor Front Porch + Hor Synv Pulse + Hor Back Porch)* (Ver Active Video + Ver Front Porch + Ver Synv Pulse + Ver Back Porch)
以640x480,60Hz這種分辨率格式來說,25.175MHz = 25175000Hz = 60*(640 + 16 + 96 + 48)*(480 + 11 + 2 + 31) = 60 * 800 * 525
三、 目標(biāo)任務(wù)
1、編寫VGA驅(qū)動(dòng)代碼,并用ModelSim對(duì)時(shí)序進(jìn)行仿真,然后下載到開發(fā)板中使屏幕產(chǎn)生彩色條紋
2、在上個(gè)任務(wù)的基礎(chǔ)上,把一張存在ROM里面的圖片數(shù)據(jù)顯示到顯示器上
四、 設(shè)計(jì)思路與Verilog代碼編寫
4.1、 VGA驅(qū)動(dòng)模塊的接口定義與整體設(shè)計(jì)
Verilog編寫的VGA模塊除了Red,Green,Blue三基色、行同步HS以及場(chǎng)同步VS以外還要包括時(shí)鐘、復(fù)位信號(hào)。其框圖如下所示
其中:
I_clk是系統(tǒng)時(shí)鐘;
I_rst_n是系統(tǒng)復(fù)位;
O_hs是行同步信號(hào);
O_vs是場(chǎng)同步信號(hào);
O_red是紅色分量;
O_green是綠色分量;
O_blue是藍(lán)色分量;
上面的模塊框圖中沒有看到測(cè)試數(shù)據(jù)(彩條或者圖片)的輸入端口,原因是由于VGA的邏輯比較簡(jiǎn)單,所以我準(zhǔn)備把發(fā)送測(cè)試圖案(彩條或者圖片)的邏輯也直接集成到vga_driver模塊中,這樣可能更加方便理解。但是對(duì)于實(shí)際一個(gè)比較復(fù)雜的項(xiàng)目來說,最好還是把各個(gè)模塊獨(dú)立開來,這樣更加方便二次移植。在寫代碼之前,先了解一個(gè)關(guān)于圖片的分辨率與位深度的知識(shí)點(diǎn)。
4.2、 圖片的分辨率、圖片的尺寸與位深度
圖片的分辨率指圖像中存儲(chǔ)的信息量,是每英寸圖像內(nèi)有多少個(gè)像素點(diǎn),它決定了位圖圖像細(xì)節(jié)的精細(xì)程度。描述分辨率的單位有:dpi(dots per inch)點(diǎn)每英寸、lpi(line per inch)線每英寸和ppi(pixel per inch)像素每英寸。
圖片的尺寸是指一幅圖片長(zhǎng)度和寬度各占多少像素,我們平常說的一張640×480的圖片指的就是這張圖片的長(zhǎng)度有640個(gè)像素點(diǎn),寬度有480個(gè)像素點(diǎn)
位深度是指圖片的每個(gè)像素是用多少位(bit)來表示的。比如黑白二色的圖像是數(shù)字圖像中最簡(jiǎn)單的一種,它只有黑、白兩種顏色,也就是說它的每個(gè)像素只有1位顏色,位深度是1,用2的零次冪來表示;考慮到位深度平均分給R, G, B和Alpha,而只有RGB可以相互組合成顏色。所以4位顏色的圖,它的位深度是4,只有2的4次冪種顏色,即16種顏色或16種灰度等級(jí) )。8位顏色的圖,位深度就是8,用2的8次冪表示,它含有256種顏色 ( 或256種灰度等級(jí) )。24位顏色可稱之為真彩色,位深度是24,它能組合成2的24次冪種顏色,即:16777216種顏色 ( 或稱千萬(wàn)種顏色 ),超過了人眼能夠分辨的顏色數(shù)量。當(dāng)我們用24位來記錄顏色時(shí),實(shí)際上是以2^(8×3),即紅、綠、藍(lán) ( RGB )?三基色各以2的8次冪,256種顏色而存在的,三色組合就形成一千六百萬(wàn)種顏色。除了上面這幾種情況以外,有的圖片的位深度是16位,其中紅基色占5位,綠基色占6位,藍(lán)基色占5位,他們一共可以組成2^16中顏色。
在電腦上用選中圖片以后,然后鼠標(biāo)右鍵在菜單中點(diǎn)擊屬性,然后在詳細(xì)信息選項(xiàng)卡中就能查看圖片的各個(gè)詳細(xì)信息了,上面這張圖片的信息如下圖所示
由上面的信息可知這張圖片的大小為128*128。水平分辨率與垂直分辨率為96dpi(dots per inch),位深度為24-bit。
4.3、 原理圖分析
在寫代碼之前,先來分析一下我的開發(fā)板的VGA接口原理圖。由于FPGA輸出的RGB數(shù)據(jù)為數(shù)字信號(hào),而VGA接口的RGB數(shù)據(jù)為模擬信號(hào),所以需要一個(gè)數(shù)模轉(zhuǎn)換器把FPGA輸出的數(shù)字信號(hào)轉(zhuǎn)化為VGA接口的模擬RGB數(shù)據(jù)輸出。一般情況下,為了保證輸出數(shù)據(jù)的保真度,都會(huì)使用一個(gè)專用的數(shù)模轉(zhuǎn)換芯片(比如ADV7123)來實(shí)現(xiàn)這個(gè)數(shù)模轉(zhuǎn)換的功能,但是在我的開發(fā)板上為了簡(jiǎn)單起見,設(shè)計(jì)了一個(gè)電阻匹配網(wǎng)絡(luò)來實(shí)現(xiàn)這個(gè)數(shù)模轉(zhuǎn)換的功能,F(xiàn)PGA輸出的RGB三基色數(shù)字信號(hào)一共占16-bit,其中Red分量占5-bit,Green分量占6-bit,Blue分量占5-bit。下面是VGA接口部分的原理圖
4.4、 vga_driver模塊顯示彩條Verilog代碼編寫
有了上面的基礎(chǔ)之后就可以開始著手編寫代碼,現(xiàn)在在回過頭去看行時(shí)序與場(chǎng)時(shí)序,其實(shí)可以發(fā)現(xiàn)VGA的時(shí)序真的是非常簡(jiǎn)單。
對(duì)行時(shí)序來說,只需要定義一個(gè)計(jì)數(shù)器,當(dāng)計(jì)數(shù)器在像素時(shí)鐘的作用下計(jì)滿一行的總點(diǎn)數(shù)后清零,然后利用assign語(yǔ)句在計(jì)數(shù)值為Hor Sync期間把行時(shí)序信號(hào)拉低產(chǎn)生一個(gè)低脈沖就可以了。場(chǎng)時(shí)序與行時(shí)序非常類似,當(dāng)行計(jì)數(shù)器計(jì)滿一行了場(chǎng)計(jì)數(shù)器才加1,當(dāng)計(jì)滿一場(chǎng)的時(shí)間后,計(jì)數(shù)值清零,然后利用assign語(yǔ)句在Ver Sync期間把場(chǎng)時(shí)序信號(hào)拉低產(chǎn)生一個(gè)低脈沖就OK了。
有了行時(shí)序與場(chǎng)時(shí)序以后,接下來就是在Hor Active Video和Ver Active Video均有效的期間往Red,Green,Blue三個(gè)分量送數(shù)據(jù),數(shù)據(jù)就會(huì)在在屏幕上顯示出來了。而Hor Active Video有效的期間正是行計(jì)數(shù)器的計(jì)數(shù)值在大于(Hor Sync + Hor Back Porch),小于(Hor Sync + Hor Back Porch + Hor Active Video)的時(shí)候,而Ver Active Video有效的期間正是場(chǎng)計(jì)數(shù)器的計(jì)數(shù)值在大于(Ver Sync + Ver Back Porch),小于(Ver Sync + Ver Back Porch + Ver Active Video)的時(shí)候,所以在代碼里面可以利用assign語(yǔ)句產(chǎn)生一個(gè)激活標(biāo)志,當(dāng)激活標(biāo)志為高的時(shí)候給Red,Green,Blue三個(gè)分量送數(shù)據(jù),數(shù)據(jù)就會(huì)在屏幕顯示出來了。
下面以分辨率為640x480為例來編寫vga_driver的代碼,由前面的分辨率時(shí)序參數(shù)表可知,640x480分辨率的像素時(shí)鐘為25.175Hz,但實(shí)際并不需要這么精確的時(shí)鐘頻率,我們?nèi)?5MHz就可以了,我的開發(fā)板的時(shí)鐘頻率為50MHz,所以只需要簡(jiǎn)單的寫一個(gè)二分頻邏輯就可以得到這個(gè)像素時(shí)鐘了。如果你想顯示其他分辨率的圖片,比如800x600分辨率的時(shí)鐘頻率是40MHz,這時(shí)候就需要用FPGA內(nèi)部的Clocking Wizard IP核來得到這個(gè)40MHz的時(shí)鐘,Clocking Wizard IP核內(nèi)部回調(diào)用FPGA的PLL資源對(duì)輸入頻率進(jìn)行處理來得到想要的輸出頻率。
下面是VGA接口產(chǎn)生彩條的完整代碼
module vga_driver ( input I_clk , // 系統(tǒng)50MHz時(shí)鐘 input I_rst_n , // 系統(tǒng)復(fù)位 output reg [4:0] O_red , // VGA紅色分量 output reg [5:0] O_green , // VGA綠色分量 output reg [4:0] O_blue , // VGA藍(lán)色分量 output O_hs , // VGA行同步信號(hào) output O_vs // VGA場(chǎng)同步信號(hào) ); // 分辨率為640*480時(shí)行時(shí)序各個(gè)參數(shù)定義 parameter C_H_SYNC_PULSE = 96 , C_H_BACK_PORCH = 48 , C_H_ACTIVE_TIME = 640 , C_H_FRONT_PORCH = 16 , C_H_LINE_PERIOD = 800 ; // 分辨率為640*480時(shí)場(chǎng)時(shí)序各個(gè)參數(shù)定義 parameter C_V_SYNC_PULSE = 2 , C_V_BACK_PORCH = 33 , C_V_ACTIVE_TIME = 480 , C_V_FRONT_PORCH = 10 , C_V_FRAME_PERIOD = 525 ; parameter C_COLOR_BAR_WIDTH = C_H_ACTIVE_TIME / 8 ; reg [11:0] R_h_cnt ; // 行時(shí)序計(jì)數(shù)器 reg [11:0] R_v_cnt ; // 列時(shí)序計(jì)數(shù)器 reg R_clk_25M ; wire W_active_flag ; // 激活標(biāo)志,當(dāng)這個(gè)信號(hào)為1時(shí)RGB的數(shù)據(jù)可以顯示在屏幕上 ////////////////////////////////////////////////////////////////// //功能:產(chǎn)生25MHz的像素時(shí)鐘 ////////////////////////////////////////////////////////////////// always @(posedge I_clk or negedge I_rst_n) begin if(!I_rst_n) R_clk_25M <= 1'b0 ; else R_clk_25M <= ~R_clk_25M ; end ////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////// // 功能:產(chǎn)生行時(shí)序 ////////////////////////////////////////////////////////////////// always @(posedge R_clk_25M or negedge I_rst_n) begin if(!I_rst_n) R_h_cnt <= 12'd0 ; else if(R_h_cnt == C_H_LINE_PERIOD - 1'b1) R_h_cnt <= 12'd0 ; else R_h_cnt <= R_h_cnt + 1'b1 ; end assign O_hs = (R_h_cnt < C_H_SYNC_PULSE) ? 1'b0 : 1'b1 ; ////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////// // 功能:產(chǎn)生場(chǎng)時(shí)序 ////////////////////////////////////////////////////////////////// always @(posedge R_clk_25M or negedge I_rst_n) begin if(!I_rst_n) R_v_cnt <= 12'd0 ; else if(R_v_cnt == C_V_FRAME_PERIOD - 1'b1) R_v_cnt <= 12'd0 ; else if(R_h_cnt == C_H_LINE_PERIOD - 1'b1) R_v_cnt <= R_v_cnt + 1'b1 ; else R_v_cnt <= R_v_cnt ; end assign O_vs = (R_v_cnt < C_V_SYNC_PULSE) ? 1'b0 : 1'b1 ; ////////////////////////////////////////////////////////////////// assign W_active_flag = (R_h_cnt >= (C_H_SYNC_PULSE + C_H_BACK_PORCH )) && (R_h_cnt <= (C_H_SYNC_PULSE + C_H_BACK_PORCH + C_H_ACTIVE_TIME)) && (R_v_cnt >= (C_V_SYNC_PULSE + C_V_BACK_PORCH )) && (R_v_cnt <= (C_V_SYNC_PULSE + C_V_BACK_PORCH + C_V_ACTIVE_TIME)) ; ////////////////////////////////////////////////////////////////// // 功能:把顯示器屏幕分成8個(gè)縱列,每個(gè)縱列的寬度是80 ////////////////////////////////////////////////////////////////// always @(posedge R_clk_25M or negedge I_rst_n) begin if(!I_rst_n) begin O_red <= 5'b00000 ; O_green <= 6'b000000 ; O_blue <= 5'b00000 ; end else if(W_active_flag) begin if(R_h_cnt < (C_H_SYNC_PULSE + C_H_BACK_PORCH + C_COLOR_BAR_WIDTH)) // 紅色彩條 begin O_red <= 5'b11111 ; // 紅色彩條把紅色分量全部給1,綠色和藍(lán)色給0 O_green <= 6'b000000 ; O_blue <= 5'b00000 ; end else if(R_h_cnt < (C_H_SYNC_PULSE + C_H_BACK_PORCH + C_COLOR_BAR_WIDTH*2)) // 綠色彩條 begin O_red <= 5'b00000 ; O_green <= 6'b111111 ; // 綠色彩條把綠色分量全部給1,紅色和藍(lán)色分量給0 O_blue <= 5'b00000 ; end else if(R_h_cnt < (C_H_SYNC_PULSE + C_H_BACK_PORCH + C_COLOR_BAR_WIDTH*3)) // 藍(lán)色彩條 begin O_red <= 5'b00000 ; O_green <= 6'b000000 ; O_blue <= 5'b11111 ; // 藍(lán)色彩條把藍(lán)色分量全部給1,紅色和綠分量給0 end else if(R_h_cnt < (C_H_SYNC_PULSE + C_H_BACK_PORCH + C_COLOR_BAR_WIDTH*4)) // 白色彩條 begin O_red <= 5'b11111 ; // 白色彩條是有紅綠藍(lán)三基色混合而成 O_green <= 6'b111111 ; // 所以白色彩條要把紅綠藍(lán)三個(gè)分量全部給1 O_blue <= 5'b11111 ; end else if(R_h_cnt < (C_H_SYNC_PULSE + C_H_BACK_PORCH + C_COLOR_BAR_WIDTH*5)) // 黑色彩條 begin O_red <= 5'b00000 ; // 黑色彩條就是把紅綠藍(lán)所有分量全部給0 O_green <= 6'b000000 ; O_blue <= 5'b00000 ; end else if(R_h_cnt < (C_H_SYNC_PULSE + C_H_BACK_PORCH + C_COLOR_BAR_WIDTH*6)) // 黃色彩條 begin O_red <= 5'b11111 ; // 黃色彩條是有紅綠兩種顏色混合而成 O_green <= 6'b111111 ; // 所以黃色彩條要把紅綠兩個(gè)分量給1 O_blue <= 5'b00000 ; // 藍(lán)色分量給0 end else if(R_h_cnt < (C_H_SYNC_PULSE + C_H_BACK_PORCH + C_COLOR_BAR_WIDTH*7)) // 紫色彩條 begin O_red <= 5'b11111 ; // 紫色彩條是有紅藍(lán)兩種顏色混合而成 O_green <= 6'b000000 ; // 所以紫色彩條要把紅藍(lán)兩個(gè)分量給1 O_blue <= 5'b11111 ; // 綠色分量給0 end else // 青色彩條 begin O_red <= 5'b00000 ; // 青色彩條是由藍(lán)綠兩種顏色混合而成 O_green <= 6'b111111 ; // 所以青色彩條要把藍(lán)綠兩個(gè)分量給1 O_blue <= 5'b11111 ; // 紅色分量給0 end end else begin O_red <= 5'b00000 ; O_green <= 6'b000000 ; O_blue <= 5'b00000 ; end end /* //////////////////////////////////////////////////////////////// // 功能:產(chǎn)生黑白相間的格子圖案 //////////////////////////////////////////////////////////////// always @(posedge R_clk_25M or negedge I_rst_n) begin if(!I_rst_n) begin O_red <= 5'b00000 ; O_green <= 6'b000000 ; O_blue <= 5'b00000 ; end else if(W_active_flag) begin if((R_h_cnt[4]==1'b1) ^ (R_v_cnt[4]==1'b1)) begin O_red <= 5'b00000 ; O_green <= 6'b000000 ; O_blue <= 5'b00000 ; end else begin O_red <= 5'b11111 ; O_green <= 6'b111111 ; O_blue <= 5'b11111 ; end end else begin O_red <= 5'b00000 ; O_green <= 6'b000000 ; O_blue <= 5'b00000 ; end end */ endmodule
整個(gè)代碼的編寫思路與上面的分析基本一致。除了顯示彩條以外,還可以顯示黑白格子,代碼在上面也已經(jīng)給出。更多更有趣的圖案大家可以自己多試一下。在下載到開發(fā)板之前先用ModelSim對(duì)整個(gè)邏輯進(jìn)行一下仿真,在測(cè)試激勵(lì)文件里面只需要給時(shí)鐘和復(fù)位加上激勵(lì)就可以了,非常簡(jiǎn)單,我就不貼激勵(lì)文件的代碼了,下圖是ModelSim仿真圖
具體的時(shí)序細(xì)節(jié)大家可以自己仿的試一下,整個(gè)時(shí)序都是沒問題的。
下面是代碼下載到我的開發(fā)板中顯示器顯示的彩條圖案
4.5、 如何把一張圖片轉(zhuǎn)化為存放在ROM中的.coe文件
顯示完彩條以后,接下來就是把一張圖片顯示在液晶顯示器上。完成這個(gè)任務(wù)的第一步就是要把一張圖片轉(zhuǎn)化為ROM可以導(dǎo)入的.coe文件,第二部就是把ROM中的圖片數(shù)據(jù)在有效區(qū)域輸出出來,這樣就可以在圖片上顯示一張圖片了。由于我的FPGA型號(hào)為XC6SLX45-2CSG324,它內(nèi)部的BRAM資源十分有限,無(wú)法存儲(chǔ)一張完整640*480分辨率的圖片數(shù)據(jù),所以接下來的實(shí)驗(yàn)我會(huì)把上文那張128x128的圣誕老人的圖片顯示在液晶顯示器上。如果你的FPGA內(nèi)部資源足夠的話,你可以以我這個(gè)例子作為參考把你想顯示的圖片顯示出來。
為了把圖片轉(zhuǎn)化為.coe文件存放在ROM,這時(shí)可以利用Matlab軟件先把圖片轉(zhuǎn)化為一個(gè)三維矩陣,然后把三維矩陣中的Red,Green,Blue分量的顏色數(shù)據(jù)提取出來按照5-bit Red,6-bit Green, 5-bit Blue的格式進(jìn)行拼接,最后寫入到.coe文件中,Matlab的完整代碼如下:
clear clc % 利用imread函數(shù)把圖片轉(zhuǎn)化為一個(gè)三維矩陣 image_array = imread('333.jpg'); % 利用size函數(shù)把圖片矩陣的三個(gè)維度大小計(jì)算出來 % 第一維為圖片的高度,第二維為圖片的寬度,第三維為圖片的RGB分量 [height,width,z]=size(image_array); % 128*128*3 % imshow(image_array); % 顯示圖片 red = image_array(:,:,1); % 提取紅色分量,數(shù)據(jù)類型為uint8 green = image_array(:,:,2); % 提取綠色分量,數(shù)據(jù)類型為uint8 blue = image_array(:,:,3); % 提取藍(lán)色分量,數(shù)據(jù)類型為uint8 % 把上面得到了各個(gè)分量重組成一個(gè)1維矩陣,由于reshape函數(shù)重組矩陣的 % 時(shí)候是按照列進(jìn)行重組的,所以重組前需要先把各個(gè)分量矩陣進(jìn)行轉(zhuǎn)置以 % 后在重組 % 利用reshape重組完畢以后,由于后面需要對(duì)數(shù)據(jù)拼接,所以為了避免溢出 % 這里把uint8類型的數(shù)據(jù)擴(kuò)大為uint32類型 r = uint32(reshape(red' , 1 ,height*width)); g = uint32(reshape(green' , 1 ,height*width)); b = uint32(reshape(blue' , 1 ,height*width)); % 初始化要寫入.coe文件中的RGB顏色矩陣 rgb=zeros(1,height*width); % 因?yàn)閷?dǎo)入的圖片是24-bit真彩色圖片,每個(gè)像素占用24-bit,其中RGB分別占用8-bit % 而我這里需要的是16-bit,其中R為5-bit,G為6-bit,B為5-bit,所以需要在這里對(duì) % 24-bit的數(shù)據(jù)進(jìn)行重組與拼接 % bitshift()函數(shù)的作用是對(duì)數(shù)據(jù)進(jìn)行移位操作,其中第一個(gè)參數(shù)是要進(jìn)行移位的數(shù)據(jù),第二個(gè)參數(shù)為負(fù)數(shù)表示向右移,為 % 正數(shù)表示向左移,更詳細(xì)的用法直接在Matlab命令窗口輸入 doc bitshift 進(jìn)行查看 % 所以這里對(duì)紅色分量先右移3位取出高5位,然后左移11位作為ROM中RGB數(shù)據(jù)的第15-bit到第11-bit % 對(duì)綠色分量先右移2位取出高6位,然后左移5位作為ROM中RGB數(shù)據(jù)的第10-bit到第5-bit % 對(duì)藍(lán)色分量先右移3位取出高5位,然后左移0位作為ROM中RGB數(shù)據(jù)的第4-bit到第0-bit for i = 1:height*width rgb(i) = bitshift(bitshift(r(i),-3),11) + bitshift(bitshift(g(i),-2),5) + bitshift(bitshift(b(i),-3),0); end fid = fopen( 'image.coe', 'w+' ); % .coe文件的最前面一行必須為這個(gè)字符串,其中16表示16進(jìn)制 fprintf( fid, 'memory_initialization_radix=16; '); % .coe文件的第二行必須為這個(gè)字符串 fprintf( fid, 'memory_initialization_vector = '); % 把rgb數(shù)據(jù)的前 height*width-1 個(gè)數(shù)據(jù)寫入.coe文件中,每個(gè)數(shù)據(jù)之間用逗號(hào)隔開 fprintf( fid, '%x, ',rgb(1:end-1)); % 把rgb數(shù)據(jù)的最后一個(gè)數(shù)據(jù)寫入.coe文件中,并用分號(hào)結(jié)尾 fprintf( fid, '%x;',rgb(end)); fclose( fid ); % 關(guān)閉文件指針
基本上每行我都做了詳細(xì)的注釋,最后我在強(qiáng)調(diào)幾點(diǎn):
1、運(yùn)行這段代碼的時(shí)候必須把圖片文件和Matlab的.m文件放在同一目錄。imread()函數(shù)的參數(shù)是一個(gè)字符串,這個(gè)字符串是圖片的名字。如果圖片與Matlab的.m文件不再同一個(gè)目錄,則imread()里面需要填寫圖片的絕對(duì)路徑。
2、在Matlab對(duì)矩陣進(jìn)行轉(zhuǎn)置只需要在矩陣的右邊打一個(gè)單引號(hào)就可以了
3、Matlab執(zhí)行循環(huán)的效率非常低,所以我在數(shù)據(jù)進(jìn)行重組與拼接之前把數(shù)據(jù)用reshape函數(shù)轉(zhuǎn)化為1維矩陣并用uint32函數(shù)擴(kuò)大了數(shù)據(jù)的范圍,這樣的好處是重組拼接的時(shí)候只需要一個(gè)for循環(huán)就ok。如果你覺得用reshape和uint32這兩個(gè)函數(shù)對(duì)數(shù)據(jù)進(jìn)行預(yù)處理太麻煩了,那么你可以使用一個(gè)二維for循環(huán)進(jìn)行處理對(duì)128*128的矩陣的行和列分別處理。因?yàn)檫@里數(shù)據(jù)量不算大,所以用單個(gè)for循環(huán)和二維for循環(huán)區(qū)別不太大。
4、如果你的開發(fā)板硬件支持顯示24-bit真彩色圖片,那么上面的代碼中你就不需要對(duì)RGB分量分別右移進(jìn)行截取操作,直接左移位并相加(拼接)就可以了
4.6、 vga_driver模塊顯示圖片Verilog代碼編寫
有了之前顯示彩條的基礎(chǔ)以后,這個(gè)實(shí)驗(yàn)就非常簡(jiǎn)單了,只要在把顯示彩條的邏輯修改為從ROM讀數(shù)據(jù)就可以了。而ROM的配置過程如下所示:
1、選擇Block Memory Generator
2、選擇類型為Single Port ROM
3、選擇ROM的Read Width為16,Read Depth為16384(128*128=16384)
4、導(dǎo)入用Matlab生成的.coe圖片文件
5、其他的所有參數(shù)我都保持默認(rèn),如果你需要有其他特殊需求的話可以按需修改。
配置好ROM就可以修改發(fā)送RGB數(shù)據(jù)的邏輯了,完整的代碼如下:
module vga_driver ( input I_clk , // 系統(tǒng)50MHz時(shí)鐘 input I_rst_n , // 系統(tǒng)復(fù)位 output reg [4:0] O_red , // VGA紅色分量 output reg [5:0] O_green , // VGA綠色分量 output reg [4:0] O_blue , // VGA藍(lán)色分量 output O_hs , // VGA行同步信號(hào) output O_vs // VGA場(chǎng)同步信號(hào) ); // 分辨率為640*480時(shí)行時(shí)序各個(gè)參數(shù)定義 parameter C_H_SYNC_PULSE = 96 , C_H_BACK_PORCH = 48 , C_H_ACTIVE_TIME = 640 , C_H_FRONT_PORCH = 16 , C_H_LINE_PERIOD = 800 ; // 分辨率為640*480時(shí)場(chǎng)時(shí)序各個(gè)參數(shù)定義 parameter C_V_SYNC_PULSE = 2 , C_V_BACK_PORCH = 33 , C_V_ACTIVE_TIME = 480 , C_V_FRONT_PORCH = 10 , C_V_FRAME_PERIOD = 525 ; parameter C_IMAGE_WIDTH = 128 , C_IMAGE_HEIGHT = 128 , C_IMAGE_PIX_NUM = 16384 ; reg [11:0] R_h_cnt ; // 行時(shí)序計(jì)數(shù)器 reg [11:0] R_v_cnt ; // 列時(shí)序計(jì)數(shù)器 reg R_clk_25M ; // 25MHz的像素時(shí)鐘 reg [13:0] R_rom_addr ; // ROM的地址 wire [15:0] W_rom_data ; // ROM中存儲(chǔ)的數(shù)據(jù) wire W_active_flag ; // 激活標(biāo)志,當(dāng)這個(gè)信號(hào)為1時(shí)RGB的數(shù)據(jù)可以顯示在屏幕上 ////////////////////////////////////////////////////////////////// //功能:產(chǎn)生25MHz的像素時(shí)鐘 ////////////////////////////////////////////////////////////////// always @(posedge I_clk or negedge I_rst_n) begin if(!I_rst_n) R_clk_25M <= 1'b0 ; else R_clk_25M <= ~R_clk_25M ; end ////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////// // 功能:產(chǎn)生行時(shí)序 ////////////////////////////////////////////////////////////////// always @(posedge R_clk_25M or negedge I_rst_n) begin if(!I_rst_n) R_h_cnt <= 12'd0 ; else if(R_h_cnt == C_H_LINE_PERIOD - 1'b1) R_h_cnt <= 12'd0 ; else R_h_cnt <= R_h_cnt + 1'b1 ; end assign O_hs = (R_h_cnt < C_H_SYNC_PULSE) ? 1'b0 : 1'b1 ; ////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////// // 功能:產(chǎn)生場(chǎng)時(shí)序 ////////////////////////////////////////////////////////////////// always @(posedge R_clk_25M or negedge I_rst_n) begin if(!I_rst_n) R_v_cnt <= 12'd0 ; else if(R_v_cnt == C_V_FRAME_PERIOD - 1'b1) R_v_cnt <= 12'd0 ; else if(R_h_cnt == C_H_LINE_PERIOD - 1'b1) R_v_cnt <= R_v_cnt + 1'b1 ; else R_v_cnt <= R_v_cnt ; end assign O_vs = (R_v_cnt < C_V_SYNC_PULSE) ? 1'b0 : 1'b1 ; ////////////////////////////////////////////////////////////////// // 產(chǎn)生有效區(qū)域標(biāo)志,當(dāng)這個(gè)信號(hào)為高時(shí)往RGB送的數(shù)據(jù)才會(huì)顯示到屏幕上 assign W_active_flag = (R_h_cnt >= (C_H_SYNC_PULSE + C_H_BACK_PORCH )) && (R_h_cnt <= (C_H_SYNC_PULSE + C_H_BACK_PORCH + C_H_ACTIVE_TIME )) && (R_v_cnt >= (C_V_SYNC_PULSE + C_V_BACK_PORCH )) && (R_v_cnt <= (C_V_SYNC_PULSE + C_V_BACK_PORCH + C_V_ACTIVE_TIME )) ; ////////////////////////////////////////////////////////////////// // 功能:把ROM里面的圖片數(shù)據(jù)輸出 ////////////////////////////////////////////////////////////////// always @(posedge R_clk_25M or negedge I_rst_n) begin if(!I_rst_n) R_rom_addr <= 14'd0 ; else if(W_active_flag) begin if(R_h_cnt >= (C_H_SYNC_PULSE + C_H_BACK_PORCH ) && R_h_cnt <= (C_H_SYNC_PULSE + C_H_BACK_PORCH + C_IMAGE_WIDTH - 1'b1) && R_v_cnt >= (C_V_SYNC_PULSE + C_V_BACK_PORCH ) && R_v_cnt <= (C_V_SYNC_PULSE + C_V_BACK_PORCH + C_IMAGE_HEIGHT - 1'b1) ) begin O_red <= W_rom_data[15:11] ; // 紅色分量 O_green <= W_rom_data[10:5] ; // 綠色分量 O_blue <= W_rom_data[4:0] ; // 藍(lán)色分量 if(R_rom_addr == C_IMAGE_PIX_NUM - 1'b1) R_rom_addr <= 14'd0 ; else R_rom_addr <= R_rom_addr + 1'b1 ; end else begin O_red <= 5'd0 ; O_green <= 6'd0 ; O_blue <= 5'd0 ; R_rom_addr <= R_rom_addr ; end end else begin O_red <= 5'd0 ; O_green <= 6'd0 ; O_blue <= 5'd0 ; R_rom_addr <= R_rom_addr ; end end rom_image U_rom_image ( .clka(R_clk_25M), // input clka .addra(R_rom_addr), // input [13 : 0] addra .douta(W_rom_data) // output [15 : 0] douta ); endmodule
綁定管腳生成bit文件以后下載到開發(fā)板中液晶顯示器上就出現(xiàn)了圣誕老人的圖片
至此,整個(gè)VGA的實(shí)驗(yàn)全部完成。
五、 進(jìn)一步思考
5.1、 如何修改屏幕背景為其他顏色(比如藍(lán)色)
上面已經(jīng)可以讓一張圖片正常在屏幕上顯示出來了,而圖片以外的區(qū)域是黑色的,如果我們想把圖片以外的區(qū)域改成藍(lán)色的其實(shí)很簡(jiǎn)單,只需要圖片以外區(qū)域的RGB分量的Red,Green分量給0,Blue分量給1就可以了
5.2、如何讓128*128圣誕老人的圖片在屏幕中間
上圖的圣誕老人圖片顯示在屏幕的左上角,如果我們想把他顯示在圖片中間位置只需要在
?
?
if(R_h_cnt >= (C_H_SYNC_PULSE + C_H_BACK_PORCH + C_H_OFFSET ) && R_h_cnt <= (C_H_SYNC_PULSE + C_H_BACK_PORCH + C_H_OFFSET + C_IMAGE_WIDTH - 1'b1 ) && R_v_cnt >= (C_V_SYNC_PULSE + C_V_BACK_PORCH + C_V_OFFSET ) && R_v_cnt <= (C_V_SYNC_PULSE + C_V_BACK_PORCH + C_V_OFFSET + C_IMAGE_HEIGHT - 1'b1 ) )
?
?
這個(gè)判斷語(yǔ)句中加上一個(gè)固定的C_H_OFFSET與C_V_OFFSET偏移分量就可以了,當(dāng)然這個(gè)偏移分量不能太大,否則會(huì)使圖片跑到屏幕外面
5.3、 如何讓128*128圣誕老人的圖片在屏幕上動(dòng)起來,并且碰到顯示器邊框以后就反彈
上面已經(jīng)知道修改圖片的位置的方法以后,我們可以把圖片的行和列的偏移量設(shè)置成一個(gè)reg變量,然后在每一幀結(jié)束以后修改這個(gè)偏移量就可以了,而場(chǎng)脈沖的下降沿可以看做每一幀結(jié)束的標(biāo)志位,所以為了實(shí)現(xiàn)這個(gè)功能需要寫一個(gè)檢測(cè)場(chǎng)脈沖下降沿的邏輯。
至于碰到屏幕以后產(chǎn)生“反彈”的效果實(shí)際上就是一個(gè)狀態(tài)機(jī),這個(gè)狀態(tài)機(jī)一共有四個(gè)狀態(tài):
狀態(tài)2’b00:圖片向右下方移動(dòng)
狀態(tài)2’b01:圖片向右上方移動(dòng)
狀態(tài)2’b10:圖片向左下方移動(dòng)
狀態(tài)2’b11:圖片向左上方移動(dòng)
這個(gè)狀態(tài)機(jī)很容易就抽象出來了,但關(guān)鍵是狀態(tài)的跳變一定要理清楚了,下面這個(gè)狀態(tài)機(jī)的狀態(tài)跳變圖
上面這個(gè)狀態(tài)轉(zhuǎn)換圖清晰的描述了圖片在屏幕上運(yùn)動(dòng)的狀態(tài)切換圖,其中每次狀態(tài)切換都是在場(chǎng)脈沖的下降沿觸發(fā),這樣才能保證圖片在運(yùn)動(dòng)過程中不會(huì)閃爍。
完整的代碼如下:
module vga_driver ( input I_clk , // 系統(tǒng)50MHz時(shí)鐘 input I_rst_n , // 系統(tǒng)復(fù)位 output reg [4:0] O_red , // VGA紅色分量 output reg [5:0] O_green , // VGA綠色分量 output reg [4:0] O_blue , // VGA藍(lán)色分量 output O_hs , // VGA行同步信號(hào) output O_vs // VGA場(chǎng)同步信號(hào) ); // 分辨率為640*480時(shí)行時(shí)序各個(gè)參數(shù)定義 parameter C_H_SYNC_PULSE = 96 , C_H_BACK_PORCH = 48 , C_H_ACTIVE_TIME = 640 , C_H_FRONT_PORCH = 16 , C_H_LINE_PERIOD = 800 ; // 分辨率為640*480時(shí)場(chǎng)時(shí)序各個(gè)參數(shù)定義 parameter C_V_SYNC_PULSE = 2 , C_V_BACK_PORCH = 33 , C_V_ACTIVE_TIME = 480 , C_V_FRONT_PORCH = 10 , C_V_FRAME_PERIOD = 525 ; parameter C_IMAGE_WIDTH = 128 , C_IMAGE_HEIGHT = 128 , C_IMAGE_PIX_NUM = 16384 ; reg [11:0] R_h_cnt ; // 行時(shí)序計(jì)數(shù)器 reg [11:0] R_v_cnt ; // 列時(shí)序計(jì)數(shù)器 reg R_clk_25M ; reg [13:0] R_rom_addr ; // ROM的地址 wire [15:0] W_rom_data ; // ROM中存儲(chǔ)的數(shù)據(jù) reg [11:0] R_h_pos ; // 圖片在屏幕上顯示的水平位置,當(dāng)它為0時(shí),圖片貼緊屏幕的左邊沿 reg [11:0] R_v_pos ; // 圖片在屏幕上顯示的垂直位置,當(dāng)它為0時(shí),圖片貼緊屏幕的上邊沿 reg R_vs_reg1 ; reg R_vs_reg2 ; wire W_vs_neg ; // 場(chǎng)脈沖下降沿標(biāo)志 reg [1:0] R_state ; wire W_active_flag ; // 激活標(biāo)志,當(dāng)這個(gè)信號(hào)為1時(shí)RGB的數(shù)據(jù)可以顯示在屏幕上 ////////////////////////////////////////////////////////////////// //功能:產(chǎn)生25MHz的像素時(shí)鐘 ////////////////////////////////////////////////////////////////// always @(posedge I_clk or negedge I_rst_n) begin if(!I_rst_n) R_clk_25M <= 1'b0 ; else R_clk_25M <= ~R_clk_25M ; end ////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////// // 功能:產(chǎn)生行時(shí)序 ////////////////////////////////////////////////////////////////// always @(posedge R_clk_25M or negedge I_rst_n) begin if(!I_rst_n) R_h_cnt <= 12'd0 ; else if(R_h_cnt == C_H_LINE_PERIOD - 1'b1) R_h_cnt <= 12'd0 ; else R_h_cnt <= R_h_cnt + 1'b1 ; end assign O_hs = (R_h_cnt < C_H_SYNC_PULSE) ? 1'b0 : 1'b1 ; ////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////// // 功能:產(chǎn)生場(chǎng)時(shí)序 ////////////////////////////////////////////////////////////////// always @(posedge R_clk_25M or negedge I_rst_n) begin if(!I_rst_n) R_v_cnt <= 12'd0 ; else if(R_v_cnt == C_V_FRAME_PERIOD - 1'b1) R_v_cnt <= 12'd0 ; else if(R_h_cnt == C_H_LINE_PERIOD - 1'b1) R_v_cnt <= R_v_cnt + 1'b1 ; else R_v_cnt <= R_v_cnt ; end assign O_vs = (R_v_cnt < C_V_SYNC_PULSE) ? 1'b0 : 1'b1 ; ////////////////////////////////////////////////////////////////// assign W_active_flag = (R_h_cnt >= (C_H_SYNC_PULSE + C_H_BACK_PORCH )) && (R_h_cnt <= (C_H_SYNC_PULSE + C_H_BACK_PORCH + C_H_ACTIVE_TIME )) && (R_v_cnt >= (C_V_SYNC_PULSE + C_V_BACK_PORCH )) && (R_v_cnt <= (C_V_SYNC_PULSE + C_V_BACK_PORCH + C_V_ACTIVE_TIME )) ; ////////////////////////////////////////////////////////////////// // 功能:把ROM中的圖片數(shù)據(jù)顯示到屏幕上 ////////////////////////////////////////////////////////////////// always @(posedge R_clk_25M or negedge I_rst_n) begin if(!I_rst_n) R_rom_addr <= 14'd0 ; else if(W_active_flag) begin if(R_h_cnt >= (C_H_SYNC_PULSE + C_H_BACK_PORCH + R_h_pos ) && R_h_cnt <= (C_H_SYNC_PULSE + C_H_BACK_PORCH + R_h_pos + C_IMAGE_WIDTH - 1'b1) && R_v_cnt >= (C_V_SYNC_PULSE + C_V_BACK_PORCH + R_v_pos ) && R_v_cnt <= (C_V_SYNC_PULSE + C_V_BACK_PORCH + R_v_pos + C_IMAGE_HEIGHT - 1'b1) ) begin O_red <= W_rom_data[15:11] ; O_green <= W_rom_data[10:5] ; O_blue <= W_rom_data[4:0] ; if(R_rom_addr == C_IMAGE_PIX_NUM - 1'b1) R_rom_addr <= 14'd0 ; else R_rom_addr <= R_rom_addr + 1'b1 ; end else begin O_red <= 5'd0 ; O_green <= 6'd0 ; O_blue <= 5'd0 ; R_rom_addr <= R_rom_addr ; end end else begin O_red <= 5'd0 ; O_green <= 6'd0 ; O_blue <= 5'd0 ; R_rom_addr <= R_rom_addr ; end end ////////////////////////////////////////////////////////////////// // 功能:產(chǎn)生場(chǎng)脈沖的下降沿標(biāo)志,在這個(gè)標(biāo)志用來修改R_h_pos和R_v_pos // 兩個(gè)參數(shù),從而改變圖片的位置 ////////////////////////////////////////////////////////////////// always @(posedge R_clk_25M or negedge I_rst_n) begin if(!I_rst_n) begin R_vs_reg1 <= 1'b0 ; R_vs_reg2 <= 1'b0 ; end else begin R_vs_reg1 <= O_vs ; R_vs_reg2 <= R_vs_reg1 ; end end assign W_vs_neg = ~R_vs_reg1 & R_vs_reg2 ; ////////////////////////////////////////////////////////////////// // 功能:使圖片移動(dòng)的狀態(tài)機(jī) ////////////////////////////////////////////////////////////////// always@(posedge R_clk_25M or negedge I_rst_n) begin if(!I_rst_n) begin R_h_pos <= 12'd0 ; R_v_pos <= 12'd0 ; R_state <= 2'b00 ; end else if(W_vs_neg) begin case(R_state) 2'b00: // 圖片往右下方移動(dòng) begin R_h_pos <= R_h_pos + 1 ; R_v_pos <= R_v_pos + 1 ; if(R_h_pos + C_IMAGE_WIDTH == C_H_ACTIVE_TIME) // 如果碰到右邊框 R_state <= 2'b10 ; else if((R_v_pos + C_IMAGE_HEIGHT) == C_V_ACTIVE_TIME) // 如果碰到下邊框 R_state <= 2'b01 ; end 2'b01: // 圖片往右上方移動(dòng) begin R_h_pos <= R_h_pos + 1 ; R_v_pos <= R_v_pos - 1 ; if(R_h_pos + C_IMAGE_WIDTH == C_H_ACTIVE_TIME) // 如果碰到右邊框 R_state <= 2'b11 ; else if(R_v_pos == 1) // 如果碰到上邊框 R_state <= 2'b00 ; end 2'b10: // 圖片往左下方移動(dòng) begin R_h_pos <= R_h_pos - 1 ; R_v_pos <= R_v_pos + 1 ; if(R_h_pos == 1) // 如果碰到左邊框 R_state <= 2'b00 ; else if(R_v_pos + C_IMAGE_HEIGHT == C_V_ACTIVE_TIME) // 如果碰到下邊框 R_state <= 2'b11 ; end 2'b11: // 圖片往左上方移動(dòng) begin R_h_pos <= R_h_pos - 1 ; R_v_pos <= R_v_pos - 1 ; if(R_h_pos == 1) // 如果碰到上邊框 R_state <= 2'b01 ; else if(R_v_pos == 1) // 如果碰到左邊框 R_state <= 2'b10 ; end default:R_state <= 2'b00 ; endcase end end rom_image U_rom_image ( .clka(R_clk_25M), // input clka .addra(R_rom_addr), // input [13 : 0] addra .douta(W_rom_data) // output [15 : 0] douta ); endmodule
代碼里面碰到左邊框和碰到上邊框的判決方式為?if(R_h_pos == 1)?和if(R_h_pos == 1),我之所以設(shè)置為1而不設(shè)置為0是因?yàn)槲野l(fā)現(xiàn)設(shè)置為0的時(shí)候當(dāng)圖片碰到左邊框和上邊框會(huì)閃爍一下,具體原因大家自己分析一下。
六、 總結(jié)
VGA的時(shí)序是非常適合初學(xué)者入門的,相對(duì)于其他接口時(shí)序來說,VGA時(shí)序確實(shí)是最簡(jiǎn)單的,所以初學(xué)者最好能自己把代碼從頭到尾敲一遍,然后用ModelSim仿一下,看一看中間變量的波形以加深對(duì)VGA時(shí)序的理解。
編輯:黃飛
?
評(píng)論
查看更多