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

一休哥是在讀研究生的時候開始正式接觸FPGA的,之因此這麼說呢,是由於以前本科參加電賽的時候也學過一點FPGA的知識,惋惜學習週期過短致使那次電賽慘敗。可能世上就是有這麼巧的事,剛上研究生的第一天,老闆就給了我一塊FPGA板,讓我本身玩去,今後就踏上了這條不歸路。網絡

好了,閒話很少說,接下來咱們來說講如何用FPGA實現VGA顯示網絡圖片。這裏咱們先提出幾個問題,經過解決這幾個問題,從而實現工程效果。工具

一、  如何用FPGA實現VGA顯示學習

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

三、  VGA如何顯示圖片3d

1 如何用FPGA實現VGA顯示

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

上圖就是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位真彩色,顯示的質量很是高。blog

剛纔提到過VGA顯示的分辨率、寬度和高度。咱們須要知道,VGA也是一個視頻傳輸標準,因此VGA的分辨率也就是視頻的分辨率。咱們經過查看視頻格式手冊能夠知道VGA的分辨率有哪些。在這裏,我選擇了一個最多見的分辨率640*480p@60Hz,這裏的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信號週期個數,分辨率640*480p@60Hz時,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 <= 16'b0;             
 6     else
 7         hsync_cnt <= hsync_cnt_n;           
 8 end
 9 
10 /* 組合邏輯,水平掃描計數器,在啓動標誌拉高後再計數,每一個時鐘循環遞增 */
11 always @ (*)
12 begin
13     if(hsync_cnt == `HSYNC_D - 16'h1)
14         hsync_cnt_n = 16'b0;                    
15     else
16         hsync_cnt_n = hsync_cnt + 1'b1; 
17 end
18 
19 /* 時序邏輯,用來給vsync_cnt寄存器賦值 */
20 always @ (posedge CLK_VGA or negedge RST_N)
21 begin
22     if(!RST_N)                                  
23         vsync_cnt <= 16'b0;                 
24     else
25         vsync_cnt <= vsync_cnt_n;           
26 end
27 
28 /* 組合邏輯,垂直掃描計數器,每次水平掃描計數器計滿時循環遞增 */
29 always @ (*)
30 begin
31     if((vsync_cnt == `VSYNC_R - 16'h1) && (hsync_cnt == `HSYNC_D - 16'h1))
32         vsync_cnt_n = 16'b0;                    
33     else if(hsync_cnt == `HSYNC_D - 16'h1)      
34         vsync_cnt_n = vsync_cnt + 1'b1; 
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 <= 1'b0;                          
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 - 16'h1)               
52         VGA_HSYNC_N = 1'b1;                     
53     else if(hsync_cnt == `HSYNC_D - 16'h1)
54         VGA_HSYNC_N = 1'b0;
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 <= 1'b0;                          
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 - 16'h1 && hsync_cnt == `HSYNC_D - 16'h1)              
72         VGA_VSYNC_N = 1'b1;                     
73     else if((vsync_cnt == `VSYNC_R - 16'h1) && (hsync_cnt == `HSYNC_D - 16'h1))
74         VGA_VSYNC_N = 1'b0;
75     else
76         VGA_VSYNC_N = VGA_VSYNC;
77 end

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

         在第一個問題中,咱們知道了VGA顯示分辨率爲640*480,顏色深度爲24位真彩色。可是,網絡圖片通常不會徹底符合這兩個參數,所以,咱們須要藉助一個軟件工具轉換一下。

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

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

    三、咱們知道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 - 8'h3) && (vga_x < `BMP1_X + `BMP1_W - 8'h3) && (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 <= 1'h0;
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 - 8'h3) && (vga_y == `BMP1_Y) && bmp_add)
29         bmp_rom_add_n = 1'h0;
30     else if(bmp_add)
31         bmp_rom_add_n = bmp_rom_add + 1'b1;
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 <= 1'b0;
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],5'b11111,bmp_rom_data[4:2],5'b11111,bmp_rom_data[1:0],6'bb111111}; 
51     else if(hsync_cnt > `HSYNC_B && hsync_cnt <= `HSYNC_B + 16'd128 && vsync_cnt > `VSYNC_P && vsync_cnt < `VSYNC_Q)
52         VGA_DATA_N = 24'hFF0000;
53     else if(hsync_cnt > `HSYNC_B + 16'd128 && hsync_cnt <= `HSYNC_B + 16'd256 && vsync_cnt > `VSYNC_P && vsync_cnt < `VSYNC_Q)
54         VGA_DATA_N = 24'hFFFF00;
55     else if(hsync_cnt > `HSYNC_B + 16'd256 && hsync_cnt <= `HSYNC_B + 16'd384 && vsync_cnt > `VSYNC_P && vsync_cnt < `VSYNC_Q)
56         VGA_DATA_N = 24'h00FF00;
57     else if(hsync_cnt > `HSYNC_B + 16'd384 && hsync_cnt <= `HSYNC_B + 16'd512 && vsync_cnt > `VSYNC_P && vsync_cnt < `VSYNC_Q)
58         VGA_DATA_N = 24'h00FFFF;
59     else if(hsync_cnt > `HSYNC_B + 16'd512 && hsync_cnt <= `HSYNC_B + 16'd640 && vsync_cnt > `VSYNC_P && vsync_cnt < `VSYNC_Q)
60         VGA_DATA_N = 24'h0000FF;
61     else
62         VGA_DATA_N = 24'd0;
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

相關文章
相關標籤/搜索