FPGA實現VGA顯示圖像(VHDL版)

一、VGA工作流程

      常見的彩色顯示器,一般由CRT(陰極射線管)構成,彩色是由R、G、B(紅、綠、藍)三基色組成,CRT用逐行掃描或者隔行掃描的方式實現圖像顯示,由VGA控制模塊產生的水平同步信號和垂直同步信號控制陰極射線槍產生的電子束,打在塗有熒光粉的熒光屏上,產生R、G、B三基色,合成一個彩色像素。掃描從屏幕的左上方開始,由左至右,由上至下,逐行進行掃描,每掃完一行,電子束回到屏幕下一行的起始位置,在回掃期間,CRT對電子束進行消隱,每行結束時用行同步信號HS進行行同步;掃描完所有行,再由場同步信號VS進行場同步,並使掃描回到屏幕的左上方,同時進行場消隱,預備下一場的掃描。

消隱分爲兩種:行消隱和場消隱,從上圖可以清楚看出什麼時候應該執行消隱

       行消隱:當一行掃描完畢後然後電子槍又轉到下一行的這段時間執行。

       場消隱:當掃描完所有的行後電子槍回到第一行起始位的這段時間執行。

在消隱期間,數據是無效的,消隱這個動作是顯示屏(CRT)執行的,我們在編程時只要注意有這個東西就行。

 

同步信號分爲兩種:行同步信號(HS)和場同步信號(VS),它們就相當於一個數據起始信號,表明數據馬上就要開始了。

行同步信號產生:

constant HSYNC_A	:std_logic_vector(15 downto 0):=x"0080";--128 行同步 
--給VGA_HS賦值
process(clk,RST_N)
begin
	if RST_N='0' then
		VGA_HS<='0';
	elsif clk'event and clk='1' then
		VGA_HS<=VGA_HS_N;
	end if;
end process;

--行同步信號
process(hsync_cnt)
begin
	if hsync_cnt<HSYNC_A then--行同步計數器<行同步
		VGA_HS_N<='0';
	else
		VGA_HS_N<='1';
	end if;
end process;

--給vsync_cnt賦值
process(clk,RST_N)
begin
	if RST_N='0' then
		vsync_cnt<=x"0000";
	elsif clk'event and clk='1' then
		vsync_cnt<=vsync_cnt_n;
	end if;
end process;

場同步信號產生:

--採用800*600*60Hz,時鐘頻率=HSYNC_D*VSYNC_R*60Hz=40MHz
--水平參數
constant HSYNC_A	:std_logic_vector(15 downto 0):=x"0080";--128 行同步 
constant HSYNC_B	:std_logic_vector(15 downto 0):=x"00D8";--128+88 行同步+行後沿
constant HSYNC_C	:std_logic_vector(15 downto 0):=x"03F8";--128+88+800 行同步+行後沿+行有效時間
constant HSYNC_D	:std_logic_vector(15 downto 0):=x"0420";--128+88+800+40 行同步+行後沿+行有效時間+行前沿=行週期時間
--垂直參數
constant VSYNC_O	:std_logic_vector(15 downto 0):=x"0004";--4	場同步
constant VSYNC_P	:std_logic_vector(15 downto 0):=x"001B";--4+23 場同步+場後沿
constant VSYNC_Q	:std_logic_vector(15 downto 0):=x"0273";--4+23+600 場同步+場後沿+場有效時間
constant VSYNC_R	:std_logic_vector(15 downto 0):=x"0274";--4+23+600+1 場同步+場後沿+場有效時間+場前沿=場週期時間
--給vsync_cnt賦值
process(clk,RST_N)
begin
	if RST_N='0' then
		vsync_cnt<=x"0000";
	elsif clk'event and clk='1' then
		vsync_cnt<=vsync_cnt_n;
	end if;
end process;

--場同步計數器
process(vsync_cnt,hsync_cnt)
begin
	if ((vsync_cnt=VSYNC_R) and (hsync_cnt=HSYNC_D)) then
		vsync_cnt_n<=x"0000";
	elsif (hsync_cnt=HSYNC_D) then
		vsync_cnt_n<=vsync_cnt+'1';
	else
		vsync_cnt_n<=vsync_cnt;
	end if;
end process;

--VGA_VS賦值
process(clk,RST_N)
begin
	if RST_N='0' then
		VGA_VS<='0';
	elsif clk'event and clk='1' then
		VGA_VS<=VGA_VS_N;
	end if;
end process;

--生成場同步信號
process(vsync_cnt)
begin
	if vsync_cnt<VSYNC_O then 
		VGA_VS_N<='0';
	else
		VGA_VS_N<='1';
	end if;
end process;

這HS和VS兩個信號不就出來了嘛,有的人會問,“啊,你怎麼知道這兩個信號在什麼時候拉低呢?”,欸,你彆着急,現在就告訴你怎麼判斷的,請看不知傳了多少手的表格:

我選擇的是倒數第二個 800/600@60Hz的,Ta=同步脈衝,Tb+Tc=消隱後沿時間,Td=數據有效時間,Te+Tf=消隱前沿時間,Tg=週期,它們的數字對應的都是VGA的時鐘週期個數,所以,你觀察上面的時序圖,你會發現他們的本質就是由一個低電平(也就是同步信號)加一個高電平(前沿+後沿+有效時間)構成,這樣按照這個時序,寫相同的vga時鐘週期個數來構成一個佔空比周期就可以了,其中刷新速率可以在你電腦上設置,桌面->右鍵->顯示設置->高級顯示設置->顯示適配器屬性->監視器->刷新頻率。

的人也許早就發現了VGA時鐘的計算方法,vga時鐘=水平幀長*垂直幀長*頻率

對於普通的VGA顯示器,需要引出5個信號:R,G,B,HS,VS。你要是條件允許的話你可以把RGB三個信號分爲24位,這樣圖像也不會失真,像我這樣窮的人,只選擇了三位。下面講講爲什麼RGB位數越多圖像越清晰。(我怎麼感覺我前面說的全是廢話呢,剛剛進入正題?不要在意這些細節,畢竟第一次寫得獎感言)。

      首先,要顯示一幅圖像,必須有一個ROM,ROM裏面裝着.MIF,MIF裏裝着圖像轉換的1和0。用Bmp2RGB.exe把圖像轉換爲mif,最後把數據轉換成這樣的就可以了,就在mif結尾要有“end;”。

這一步搞定了,那我們準備把大象裝冰箱裏吧,呸!把mif裝到ROM裏,首先建一個ROM的ip

這個選項要根據你當初轉換mif的格式來選擇,mif格式爲8bit,此處就選8bit,mif深度爲1200,那麼rom的深度就要大於1200,點擊下一步,把mif導入到FILE name中,下一步,直到完成。

把ROM添加到工程中

--圖片參數
constant BMP1_W	:std_logic_vector(15 downto 0):=x"001E";	--圖片寬度30
constant BMP1_H	:std_logic_vector(15 downto 0):=x"0028";	--圖片高度40
constant BMP1_X	:std_logic_vector(15 downto 0):=x"00e9";	--圖片左上角x座標(233,151)		
constant BMP1_Y	:std_logic_vector(15 downto 0):=x"0097";	--圖片左上角y座標

--存儲圖片.mif的ROM
component BMPRom is
	port(
		address	: IN STD_LOGIC_VECTOR (11 DOWNTO 0);
		clock		: IN STD_LOGIC  := '1';
		q		: OUT STD_LOGIC_VECTOR (7 DOWNTO 0)
		);
end component;

uu1:BMPRom port map
		(
			clock=>clk,
			address=>bmp_rom_add,
			q=>bmp_rom_data
		);

--產生bmp_add信號
tmp1<='1' when (vga_x>=(BMP1_X-X"03")) else '0';
tmp2<='1' when (vga_x<(BMP1_X+BMP1_W-X"03")) else '0';
tmp3<='1' when (vga_y>=BMP1_Y) else '0';
tmp4<='1' when (vga_y<=(BMP1_Y+BMP1_H)) else '0';

tmp9<='1' when (vga_x=(BMP1_X-X"03")) else '0';
tmp10<='1' when (vga_y=BMP1_Y) else '0';

--用於生成圖片位置信號
bmp_add<=tmp1 and tmp2 and tmp3 and tmp4;
process(bmp_add,tmp9,tmp10,RST_N,clk)
begin
	if RST_N='0' then 
		bmp_rom_add<=x"000";
	elsif clk'event and clk='1' then
		if (tmp9='1' and tmp10='1') then
			bmp_rom_add<=x"000";
		elsif bmp_add='1' then
			bmp_rom_add<=bmp_rom_add+'1';
		else
			bmp_rom_add<=bmp_rom_add;
		end if;
	end if;
end process;

最後輸出RGB數據

tmp16<='1' when hsync_cnt>HSYNC_B else '0';
tmp11<='1' when hsync_cnt<=(HSYNC_B+X"A0") else '0';--彩條寬度=800/5=160

tmp12<='1' when hsync_cnt>(HSYNC_B+x"A0") else '0';
tmp13<='1' when hsync_cnt<=(HSYNC_B+X"140") else '0';

tmp14<='1' when hsync_cnt>(HSYNC_B+X"140") else '0';
tmp15<='1' when hsync_cnt<=(HSYNC_B+X"1E0") else '0';

tmp17<='1' when hsync_cnt>(HSYNC_B+X"1E0") else '0';
tmp18<='1' when hsync_cnt<=(HSYNC_B+X"280") else '0';

tmp19<='1' when hsync_cnt>(HSYNC_B+X"280") else '0';
tmp20<='1' when hsync_cnt<=(HSYNC_B+X"320") else '0';

tmp_P<='1' when vsync_cnt>VSYNC_P else '0';
tmp_Q<='1' when vsync_cnt<VSYNC_Q else '0';
process(RST_N,clk,bmp_en,tmp11,tmp12,tmp13,tmp14,tmp15,tmp16,tmp17,tmp18,tmp19,tmp20,tmp_P,tmp_Q)
begin
	if RST_N='0' then
		VGA_DATA_N<="110";--測試
	elsif clk'event and clk='1' then
		if (bmp_en='1') then 
			VGA_DATA_N<=bmp_rom_data(7)&bmp_rom_data(4)&bmp_rom_data(0);	--由於FPGA只有三個引腳關於VGA的,所以取中,VGA引腳越多,圖像越精確。
		elsif ((tmp16='1')and(tmp11='1')and(tmp_P='1')and(tmp_Q='1'))then	--圖片以外,用彩條填充
			VGA_DATA_N<="100";
		elsif((tmp12='1')and(tmp13='1')and(tmp_P='1')and(tmp_Q='1'))then
			VGA_DATA_N<="110";
		elsif((tmp14='1')and(tmp15='1')and(tmp_P='1')and(tmp_Q='1'))then
			VGA_DATA_N<="010";
		elsif((tmp17='1')and(tmp18='1')and(tmp_P='1')and(tmp_Q='1'))then
			VGA_DATA_N<="011";
		elsif((tmp19='1')and(tmp20='1')and(tmp_P='1')and(tmp_Q='1'))then
			VGA_DATA_N<="001";	
		else
			VGA_DATA_N<="000";
		end if;
	end if;

基本就是這些內容,然後仿真

library IEEE;
use IEEE.STD_LOGIC_1164.ALL;

entity vga_tb is
--	port();
end vga_tb;

architecture behave of vga_tb is
component vga is
	port(
			CLK_50M	:in std_logic;
			RST_N	:in std_logic;
			VGA_HS,VGA_VS	:out std_logic;--vga時鐘、空白信號、同步信號、水平信號、垂直信號
			VGA_DATA	:out std_logic_vector(2 downto 0)	--vga數據端口 rgb三色
		);
end component;

signal CLK_50M,RST_N,VGA_HS,VGA_VS:std_logic;
signal VGA_DATA:std_logic_vector(2 downto 0);

constant clk_period:time:=20 ns;

begin
uuu1:vga port map
	(
		CLK_50M=>CLK_50M,
		RST_N=>RST_N,
		VGA_HS=>VGA_HS,
		VGA_VS=>VGA_VS,
		VGA_DATA=>VGA_DATA
	);
	
clock:process
begin
	CLK_50M<='1';
	wait for clk_period/2;
	CLK_50M<='0';
	wait for clk_period/2;
end process;

reset:process
begin
	RST_N<='0';
	wait for clk_period*2;
	RST_N<='1';
	wait;
end process;

end behave;
	

從前有個人叫仿真,他得了一種不長毛的病,大聲說出來,仿真得了什麼病。

效果圖:(這就是RGB只用了三個輸出的結果,哪怕有一點條件我都會用24bit)

圖像,你細品,是不是這回事。

謝謝大家,我的演講到此結束,不喜勿噴,有錯誤請指出。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章