VGA系列之一:VGA顯示網絡圖片
一休哥是在讀研究生的時候開始正式接觸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顯示的分辨率、寬度和高度。我們需要知道,VGA也是一個視頻傳輸標準,所以VGA的分辨率也就是視頻的分辨率。我們通過查看視頻格式手冊可以知道VGA的分辨率有哪些。在這裏,我選擇了一個最常見的分辨率[email protected],這裏的640、480表示水平和垂直方向的像素點個數,也就表示VGA輸出了一個60Hz的640*480的視頻信號。
選擇了VGA的分辨率之後,我們就可以開始著手編寫程序了嗎?其實不然,我們還不知道VGA顯示的時序,這點我們也可以查看視頻格式手冊。
在上圖中,我們可以看到VGA_HS(HSYNC)
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 <= 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位真彩色。但是,網絡圖片一般不會完全符合這兩個參數,因此,我們需要借助一個軟件工具轉換一下。
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 - 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
VGA系列之一:VGA顯示網絡圖片