FPGA实现VGA显示图像(VHDL版)

一、VGA工作流程

      常见的彩色显示器,一般由CRT(阴极射线管)构成,彩色是由R、G、B(红、绿、蓝)三基色组成,CRT用逐行扫描或者隔行扫描的方式实现图像显示,由VGA控制模块产生的水平同步信号和垂直同步信号控制阴极射线枪产生的电子束,打在涂有荧光粉的荧光屏上,产生R、G、B三基色,合成一个彩色像素。扫描从屏幕的左上方开始,由左至右,由上至下,逐行进行扫描,每扫完一行,电子束回到屏幕下一行的起始位置,在回扫期间,CRT对电子束进行消隐,每行结束时用行同步信号HS进行行同步;扫描完所有行,再由场同步信号VS进行场同步,并使扫描回到屏幕的左上方,同时进行场消隐,预备下一场的扫描。

FPGA实现VGA显示图像(VHDL版)_第1张图片

消隐分为两种:行消隐和场消隐,从上图可以清楚看出什么时候应该执行消隐

       行消隐:当一行扫描完毕后然后电子枪又转到下一行的这段时间执行。

       场消隐:当扫描完所有的行后电子枪回到第一行起始位的这段时间执行。

在消隐期间,数据是无效的,消隐这个动作是显示屏(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

场同步信号产生:

--采用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

这HS和VS两个信号不就出来了嘛,有的人会问,“啊,你怎么知道这两个信号在什么时候拉低呢?”,欸,你别着急,现在就告诉你怎么判断的,请看不知传了多少手的表格:

FPGA实现VGA显示图像(VHDL版)_第2张图片

我选择的是倒数第二个 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;”。

FPGA实现VGA显示图像(VHDL版)_第3张图片FPGA实现VGA显示图像(VHDL版)_第4张图片

这一步搞定了,那我们准备把大象装冰箱里吧,呸!把mif装到ROM里,首先建一个ROM的ip

FPGA实现VGA显示图像(VHDL版)_第5张图片FPGA实现VGA显示图像(VHDL版)_第6张图片

FPGA实现VGA显示图像(VHDL版)_第7张图片

这个选项要根据你当初转换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

基本就是这些内容,然后仿真

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;
	

FPGA实现VGA显示图像(VHDL版)_第8张图片

从前有个人叫仿真,他得了一种不长毛的病,大声说出来,仿真得了什么病。

效果图:(这就是RGB只用了三个输出的结果,哪怕有一点条件我都会用24bit)

FPGA实现VGA显示图像(VHDL版)_第9张图片

图像,你细品,是不是这回事。

谢谢大家,我的演讲到此结束,不喜勿喷,有错误请指出。

 

你可能感兴趣的:(vhdl,fpga)