一休哥是在讀研究生的時候開始正式接觸FPGA的,之因此這麼說呢,是由於以前本科參加電賽的時候也學過一點FPGA的知識,惋惜學習週期過短致使那次電賽慘敗。可能世上就是有這麼巧的事,剛上研究生的第一天,老闆就給了我一塊FPGA板,讓我本身玩去,今後就踏上了這條不歸路。網絡
好了,閒話很少說,接下來咱們來說講如何用FPGA實現VGA顯示網絡圖片。這裏咱們先提出幾個問題,經過解決這幾個問題,從而實現工程效果。工具
一、 如何用FPGA實現VGA顯示學習
二、 網絡圖片和VGA顯示有何區別spa
三、 VGA如何顯示圖片3d
首先,咱們須要清楚一個概念,VGA是一個外設模塊,VGA模塊有兩個接口,一個接口用於鏈接FPGA,另外一個用於鏈接VGA線,VGA線的另外一頭咱們經常會與顯示器相連。鏈接VGA線的接口通常都是一個標準VGA接口的母口。而與FPGA相連的接口有不少種,在這裏,我介紹其中的一種。code
上圖就是VGA模塊的硬件電路,能夠看到,在圖片的左邊就是與FPGA相連的接口信號,一共有29個信號,咱們能夠大體分紅三類,視頻
在使用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