1. 程式人生 > >VGA系列之一:VGA顯示網絡圖片

VGA系列之一:VGA顯示網絡圖片

參數 sed 學習 nal 計數 2個 導入 查看 這一

一休哥是在讀研究生的時候開始正式接觸FPGA的,之所以這麽說呢,是因為之前本科參加電賽的時候也學過一點FPGA的知識,可惜學習周期太短導致那次電賽慘敗。可能世上就是有這麽巧的事,剛上研究生的第一天,老板就給了我一塊FPGA板,讓我自己玩去,從此就踏上了這條不歸路。

好了,閑話不多說,接下來我們來講講如何用FPGA實現VGA顯示網絡圖片。這裏我們先提出幾個問題,通過解決這幾個問題,從而實現工程效果。

1、 如何用FPGA實現VGA顯示

2、 網絡圖片和VGA顯示有何區別

3、 VGA如何顯示圖片

1 如何用FPGA實現VGA顯示

首先,我們需要清楚一個概念,VGA是一個外設模塊,VGA模塊有兩個接口,一個接口用於連接FPGA,另一個用於連接VGA線,VGA線的另一頭我們常常會與顯示器相連。連接VGA線的接口一般都是一個標準VGA接口的母口。而與FPGA相連的接口有很多種,在這裏,我介紹其中的一種。

技術分享技術分享

上圖就是VGA模塊的硬件電路,可以看到,在圖片的左邊就是與FPGA相連的接口信號,一共有29個信號,我們可以大致分成三類,

  • 恒定不變的信號(VGA_BLANK,VGA_SYN)
  • 時序控制信號(VGA_CLK,VGA_HS,VGA_VS)
  • 數據信號(VGA_R0-7,VGA_G0-7,VGA_B0-7)

在使用VGA顯示時我們需要將VGA_BLANK默認置1,VGA_SYNC默認置0。VGA_CLK是VGA顯示的主時鐘,它的頻率決定了VGA顯示的分辨率。VGA_HS是VGA的水平同步信號,它決定了VGA顯示的寬度。VGA_VS是VGA的垂直同步信號,它決定了VGA顯示的高度。數據信號中R、G、B三種顏色的小大都是8位,所以這個VGA模塊顯示的顏色深度為24

位,也就是說VGA顯示的是24位真彩色,顯示的質量非常高。

剛才提到過VGA顯示的分辨率、寬度和高度。我們需要知道,VGA也是一個視頻傳輸標準,所以VGA的分辨率也就是視頻的分辨率。我們通過查看視頻格式手冊可以知道VGA的分辨率有哪些。在這裏,我選擇了一個最常見的分辨率[email protected],這裏的640、480表示水平和垂直方向的像素點個數,也就表示VGA輸出了一個60Hz的640*480的視頻信號。

選擇了VGA的分辨率之後,我們就可以開始著手編寫程序了嗎?其實不然,我們還不知道VGA顯示的時序,這點我們也可以查看視頻格式手冊

技術分享

在上圖中,我們可以看到VGA_HS(HSYNC)

信號是一個周期信號,在一個周期內,VGA_HS的低電平時間為96個VGA_CLK信號周期,高電平時間為704個VGA_CLK信號周期。VGA的數據信號在VGA_HS高電平的第49個VGA_CLK信號周期開始有效,一直持續到VGA_HS高電平的第688個VGA_CLK信號周期。

VGA_VS(VSYNC)信號也是一個周期信號,在一個周期內,VGA_VS的低電平時間為2個VGA_HS信號周期,高電平時間為523個VGA_HS信號周期。VGA的數據信號在VGA_VS高電平的第34個VGA_HS信號周期開始有效,一直持續到高電平的第513個VGA_HS信號周期。為了更直觀的表達這一時序,我們用下面這個圖表示。

技術分享

總的來說,其實VGA的顯示時序就是一個逐行掃描的過程,每完成一個行掃描(即VGA_HS信號經過一個周期),則開始掃描下一行。只有當VGA_HS和VGA_VS同時有效,VGA數據信號才有效。

講完VGA的顯示時序後,我們還需要計算出VGA_CLK信號的頻率。我們需要按照這個公式計算:HS_total×VS_total×60Hz

其中,HS_total為VGA_HS信號的一個周期內包含的VGA_CLK信號周期個數,VS_total為VGA_VS信號的一個周期內包含的VGA_HS信號周期個數,分辨率[email protected]時,HS_total為800,VS_total為525。

因此VGA_CLK信號的頻率為800×525×60Hz=25.2MHz

接下來,我們開始編寫VGA的顯示時序代碼,我們用兩個計數器hsync_cnt和vsync_cnt來實現。部分代碼如下:

 1 /* 時序邏輯,用來給hsync_cnt寄存器賦值 */
 2 always @ (posedge CLK_VGA or negedge RST_N)
 3 begin
 4     if(!RST_N)                                  
 5         hsync_cnt <= 16b0;             
 6     else
 7         hsync_cnt <= hsync_cnt_n;           
 8 end
 9 
10 /* 組合邏輯,水平掃描計數器,在啟動標誌拉高後再計數,每個時鐘循環遞增 */
11 always @ (*)
12 begin
13     if(hsync_cnt == `HSYNC_D - 16h1)
14         hsync_cnt_n = 16b0;                    
15     else
16         hsync_cnt_n = hsync_cnt + 1b1; 
17 end
18 
19 /* 時序邏輯,用來給vsync_cnt寄存器賦值 */
20 always @ (posedge CLK_VGA or negedge RST_N)
21 begin
22     if(!RST_N)                                  
23         vsync_cnt <= 16b0;                 
24     else
25         vsync_cnt <= vsync_cnt_n;           
26 end
27 
28 /* 組合邏輯,垂直掃描計數器,每次水平掃描計數器計滿時循環遞增 */
29 always @ (*)
30 begin
31     if((vsync_cnt == `VSYNC_R - 16h1) && (hsync_cnt == `HSYNC_D - 16h1))
32         vsync_cnt_n = 16b0;                    
33     else if(hsync_cnt == `HSYNC_D - 16h1)      
34         vsync_cnt_n = vsync_cnt + 1b1; 
35     else
36         vsync_cnt_n = vsync_cnt;            
37 end
38 
39 /* 時序邏輯,用來給VGA_HSYNC寄存器賦值 */
40 always @ (posedge CLK_VGA or negedge RST_N)
41 begin
42     if(!RST_N)                                  
43         VGA_HSYNC <= 1b0;                          
44     else
45         VGA_HSYNC <= VGA_HSYNC_N;                       
46 end
47 
48 /* 組合邏輯,在B C D區間拉高水平掃描信號 */
49 always @ (*)
50 begin   
51     if(hsync_cnt == `HSYNC_A - 16h1)               
52         VGA_HSYNC_N = 1b1;                     
53     else if(hsync_cnt == `HSYNC_D - 16h1)
54         VGA_HSYNC_N = 1b0;
55     else
56         VGA_HSYNC_N = VGA_HSYNC;
57 end
58 
59 /* 時序邏輯,用來給VGA_VSYNC寄存器賦值 */
60 always @ (posedge CLK_VGA or negedge RST_N)
61 begin
62     if(!RST_N)                                  
63         VGA_VSYNC <= 1b0;                          
64     else
65         VGA_VSYNC <= VGA_VSYNC_N;                       
66 end
67 
68 /* 組合邏輯,在P Q R區間拉高垂直掃描信號 */
69 always @ (*)
70 begin   
71     if(vsync_cnt == `VSYNC_O - 16h1 && hsync_cnt == `HSYNC_D - 16h1)              
72         VGA_VSYNC_N = 1b1;                     
73     else if((vsync_cnt == `VSYNC_R - 16h1) && (hsync_cnt == `HSYNC_D - 16h1))
74         VGA_VSYNC_N = 1b0;
75     else
76         VGA_VSYNC_N = VGA_VSYNC;
77 end

2 網絡圖片和VGA顯示有何區別

在第一個問題中,我們知道了VGA顯示分辨率為640*480,顏色深度為24位真彩色。但是,網絡圖片一般不會完全符合這兩個參數,因此,我們需要借助一個軟件工具轉換一下。

1、 首先,我們在網上隨意找到一副圖片。

技術分享

2、 可以看到這個圖片的參數為分辨率為700*718,顏色深度為8位。我們用軟件Image2Lcd打開該圖片並設置相應參數,可以發現我們得到一張分辨率為174*179,顏色深度為8位的bmp圖片。由於我使用的FPGA芯片的片內存儲器資源較少,而為了將生成的mif文件順利導入Rom IP核中,我們需要壓縮圖片。這裏為什麽要轉換成bmp圖片呢,因為bmp格式是非壓縮的,數據格式比較簡單容易處理,方便我們將這個圖片存取FPGA中。

技術分享

3、我們知道FPGA不能直接讀取圖片,我們要將圖片轉換成mif文件,存入FPGA的rom中。因此,這裏我們將制作一個包含圖片全部有效數據的mif文件。這裏,我們要使用另一個常用的工具MATLAB,用m語言來實現。代碼如下:

 1 clear;  
 2 clc;  
 3 n=31146;%174*179  
 4 mat = imread(tu1.bmp);%讀取.bmp文件
 5 mat = double(mat);
 6 fid=fopen(bmp_data.mif,w);%打開待寫入的.mif文件  
 7 fprintf(fid,WIDTH=8;\n);%寫入存儲位寬8位  
 8 fprintf(fid,DEPTH=31146;\n);%寫入存儲深度31146
 9 fprintf(fid,ADDRESS_RADIX=UNS;\n);%寫入地址類型為無符號整型  
10 fprintf(fid,DATA_RADIX=HEX;);%寫入數據類型為無符號整型   
11 fprintf(fid,CONTENT BEGIN\n);%起始內容  
12 for i=0:n-1  
13     x = mod(i,174)+1;  %174為bmp圖片的水平分辨率
14     y = fix(i/174)+1;  
15     k = mat(y,x);
16 fprintf(fid,\t%d:%x;\n,i,k);  
17 end  
18 fprintf(fid,END;\n);  
19 fclose(fid);%關閉文件 

3 VGA如何顯示圖片

解決了上述兩個問題之後,終於可以顯示圖片了。

首先,我們調用一個Rom IP核,由於我們顯示的bmp圖片分辨率為174*179,顏色深度為8位,所以我們設置Rom IP核如下圖所示。

技術分享

技術分享

技術分享

如圖中所示,我們設置了一個32768*8bit大小的Rom,並且使輸出q受時鐘控制,加入mif文件。

VGA顯示圖片的代碼也十分簡單,我們以VGA顯示有效區設置了vga_x和vga_y坐標變量,定義了兩個信號用來控制產生Rom表的地址信號,與VGA顯示的數據信號。由於bmp圖的深度為8bit,所以我們按照332來分配紅綠藍三色數據,並將末尾置1。具體代碼如下:

 1 assign vga_x = hsync_cnt - `HSYNC_B;
 2 assign vga_y = vsync_cnt - `VSYNC_P;
 3 
 4 Rom                     Rom_init
 5 (
 6     .clock          (CLK_25M            ),
 7     .address            (bmp_rom_add    ),
 8     .q                  (bmp_rom_data   )
 9 );
10 
11 //組合電路,用於生成圖片位置信號
12 assign  bmp_add = (vga_x >= `BMP1_X - 8h3) && (vga_x < `BMP1_X + `BMP1_W - 8h3) && (vga_y >= `BMP1_Y) && (vga_y < `BMP1_Y + `BMP1_H);     
13 //組合電路,用於生成圖片使能信號
14 assign  bmp_en = (vga_x >= `BMP1_X) && (vga_x < `BMP1_X + `BMP1_W) && (vga_y >= `BMP1_Y) && (vga_y < `BMP1_Y + `BMP1_H);    
15 
16 //時序電路,用來給bmp_rom_add寄存器賦值
17 always @ (posedge CLK_25M or negedge RST_N)
18 begin
19     if(!RST_N)
20         bmp_rom_add <= 1h0;
21     else
22         bmp_rom_add <= bmp_rom_add_n;
23 end
24 
25 //組合電路,用於生成bmp_rom_add
26 always @ (*)
27 begin
28     if((vga_x == `BMP1_X - 8h3) && (vga_y == `BMP1_Y) && bmp_add)
29         bmp_rom_add_n = 1h0;
30     else if(bmp_add)
31         bmp_rom_add_n = bmp_rom_add + 1b1;
32     else
33         bmp_rom_add_n = bmp_rom_add;
34 end
35 
36 
37 /* 時序電路,用來給VGA_DATA寄存器賦值 */
38 always @ (posedge CLK_25M or negedge RST_N)
39 begin
40     if(!RST_N)
41         VGA_DATA <= 1b0;
42     else
43         VGA_DATA <= VGA_DATA_N;
44 end
45 
46 /* 組合電路,用來生成VGA_DATA */
47 always @ (*)
48 begin
49     if(bmp_en)
50         VGA_DATA_N = {bmp_rom_data[7:5],5b11111,bmp_rom_data[4:2],5b11111,bmp_rom_data[1:0],6bb111111}; 
51     else if(hsync_cnt > `HSYNC_B && hsync_cnt <= `HSYNC_B + 16d128 && vsync_cnt > `VSYNC_P && vsync_cnt < `VSYNC_Q)
52         VGA_DATA_N = 24hFF0000;
53     else if(hsync_cnt > `HSYNC_B + 16d128 && hsync_cnt <= `HSYNC_B + 16d256 && vsync_cnt > `VSYNC_P && vsync_cnt < `VSYNC_Q)
54         VGA_DATA_N = 24hFFFF00;
55     else if(hsync_cnt > `HSYNC_B + 16d256 && hsync_cnt <= `HSYNC_B + 16d384 && vsync_cnt > `VSYNC_P && vsync_cnt < `VSYNC_Q)
56         VGA_DATA_N = 24h00FF00;
57     else if(hsync_cnt > `HSYNC_B + 16d384 && hsync_cnt <= `HSYNC_B + 16d512 && vsync_cnt > `VSYNC_P && vsync_cnt < `VSYNC_Q)
58         VGA_DATA_N = 24h00FFFF;
59     else if(hsync_cnt > `HSYNC_B + 16d512 && hsync_cnt <= `HSYNC_B + 16d640 && vsync_cnt > `VSYNC_P && vsync_cnt < `VSYNC_Q)
60         VGA_DATA_N = 24h0000FF;
61     else
62         VGA_DATA_N = 24d0;
63 end

大家可能會比較疑惑,為什麽用來控制產生Rom表的地址信號bmp_add會比控制VGA顯示圖片的信號bmp_en提前三個時鐘。那是因為我們在讀取Rom表數據時存在延時,經過我們signaltap采集後發現,原本作為地址70的輸出數據FF比地址70慢兩個時鐘,即bmp_rom_data的輸出會比bmp_rom_add延遲兩個時鐘,而VGA_DATA又比bmp_rom_data延遲1個時鐘,因此bmp_add信號需要比bmp_en提前三個時鐘

技術分享

最後,奉上一張效果圖。

技術分享

本文所涉及的相關資料鏈接 :http://pan.baidu.com/s/1dFb4J65 密碼:zp2r

VGA系列之一:VGA顯示網絡圖片