基于FPGA的VGA显示控制的设计
发布时间:2017-03-24 17:50:54
发布时间:2017-03-24 17:50:54
基于FPGA的VGA图像显示设计及应用
摘 要:VGA(视频图形阵列)作为一种标准的显示接口得到广泛的应用。本文依据VGA显示原理,介绍了利用 FPGA 实现对VGA图形控制器VHDL设计方法。详细描述了各硬件模块的工作原理及实现途径,并给出了软件设计思路及部分代码。
关键词:VGA;FPGA;VHDL;仿真
一、引言
随着可编程逻辑器件的不断发展及其价格的不断下降,EDA 开发软件的不断完善,可编程逻辑设计的应用优势逐渐显示出来, 特别是大规模可编程器件。 而 FPGA 具有功能强大,开发过程投资小、周期短等特点,成为当今硬件设计的首选方式之一。VGA(视频图形阵列)作为一种标准的显示接口得到广泛地应用。利用 FPGA 芯片和 EDA设计方法,可以灵活地根据用户需求,设计出针对性强的 VGA 显示控制器,不仅降低了生产成本也可以快速地对产品进行升级换代。
本文设计采用 Quartus II 9.0软件工具,并以 Altera公司的 Cyclone 系列 FPGA 的器件EP1C12F324C8为主实现硬件平台的设计。
二、方案设计及工作原理
2.1方案设计
采用模块化设计方法,我们对 VGA 图形控制器按功能进行层次划分。本设计的VGA控制器主要由以下模块组成:VGA 时序控制模块、ROM 内存地址控制模块、RO内存模块、按键扫描控制模块。如图 1 所示。
图1.方案模块框图
2.2工作原理
根据VGA的显示协议,可以选择不同的显示分辨率。本设计选择的是800*600的分辨率。根据相应协议,在“VGA时序控制模块”对行扫描信号HS和场扫描信号VS进行相应的时序控制(具体控制详细信息见“VGA驱动原理”资料文档)。
“ROM内存地址控制模块”根据“按键扫描控制模块”获得的图片显示首坐标xx、yy信息和“VGA时序控制模块”传送过来的hcnt(列计数器值)、vcnt(行计数器值)及相关控制信号计算出正确的内存地址,从rom中取出所需要的像素点的三基色数据。
本设计的功能实现包括:图像旋转、放大、单步步进移动和屏保移动功能。其中图像的旋转运用了矩阵的转置原理,通过对图像的内存存储地址矩阵进行转置运算获得图像90°旋转的效果。图像的放大是通过将包括原像素点在内,相邻的4个点填写相同的颜色来实现的。
三、各功能模块设计和仿真
3.1 VGA时序控制模块
VGA 时序控制模块是整个显示控制器的关键部分,其实质就是完成 VGA 显示卡的功能。主要作用就是在一定的工作频率下,产生准确的时序关系(VS-垂直同步信号,HS-水平同步信号,消隐信号之间的关系)。及其在准确的时序下对ROM存储器数据进行读取。
其中产生准确的时序关系为此模块重点,在 VGA显示过程中,完成一行扫描所需要的时间称为水平扫描时间,完成一帧(一屏)扫描所需要的时间称为垂直扫描时间。每扫描完一行用行同步信号进行同步;扫描完所有行后用场同步信号进行同步。本文设计采用的是800×600×75Hz 模式。依据时序标准,每显示行包含 1056 点,其中 800 点为有效显示区,256 点为消隐区,每行的行同步脉冲低电平宽度为 80个像素点;同理每场有 625 行,有效行为 600 行,其中场同步脉冲低电平宽度为 3 行。其行、场时序如表 1 ,时序图见图2。
表1 行扫描、场扫描时序
依照这个标准,正好与开发板EP1C12外部晶振频率50MHz相近,可以直接引用外部晶振时钟作为其驱动时钟(只要所用时钟和协议要求的时钟相差不大,不会影响显示效果,最多就是显示的刷新频率不是真正等于75Hz而已)。
图2 行HS、场VS时序图
3.2 ROM内存地址控制模块
整个显示思路是在800*600分辨率的显示器上开辟一256*64的显示区域来显示图片,因为显示的图片的大小为256*64。在此区域以外显示指定颜色,例如黑色、蓝色等,作为一个背景色显示。在本模块中,通过在对ROM内存地址的控制,实现了图片的旋转和一倍放大功能。以下通过一段关键代码详细讲解其实现原理。
(1) 首先分析无旋转状态下的romaddr_control的计算原理:
romaddr_control <= (vcnt(5 downto 0)-count_tempv(5 downto 0))
&(hcnt(7 downto 0)-count_temph(7 downto 0));
注:romaddr_control为从rom中取数据时所需要的地址,它对应着图片的没一个像素点的三基色数据;vcnt(5 downto 0)和hcnt(7 downto 0)分别表示取vcnt(9 downto 0)、hcnt(10 downto 0)的后6、后8 个二进制位来做运算。count_tempv(5 downto 0)和count_temph(7 downto 0)同理可以理解其含义。Vcnt、hcnt实际上可以分别理解为屏幕显示的行和列计数器,count_tempv和count_temph可以分别理解为图片在屏幕上显示的起始行和列坐标。
抛开以上等式,按照正常思路,我们可以得到图片控制地址:addr_control=( vcnt - count_tempv ) * 256 + ( hcnt – count_temph )
通过分析整个mid.vhdl文件,会发现其实上面代码是该等式的高效等效实现。
(2) 从矩阵的角度分析90°旋转的实现
旋转的控制,将图片的显示分为4种状态,“00”表示0°旋转,“01”表示90°旋转,“10”表示180°旋转,“11”表示270°旋转。
首先对比0°旋转和90°旋转图片在屏幕上显示所对应的内存地址矩阵图:
0度显示对应内存地址矩阵:
90度显示对应内存地址矩阵:
观察前后变化规律,可以看成是进行了矩阵的转置运算。根据矩阵的转制原理,我们可以根据0°状态下romaddr_control的算法获得90°状态下romaddr_control的算法。
romaddr_control <= (64-(hcnt(5 downto 0)-count_temph(5 downto 0)))
&(vcnt(7 downto 0)-count_tempv(7 downto 0));
同理可以计算出“10”、“11”状态下的地址控制表达式,即旋转到180°和270°状态的地址控制表达式。
(3) 实现一倍放大
实现一倍放大的基本思路为将原来的像素点相邻的另外三个点填上同样的颜色,也就是一个地址对应屏幕上的四个像素位,从而实现放大的效果。这个比较容易实现,也比较容易想到,将原来的算法改为:
romaddr_control <= (hcnt(6 downto 1)-count_temph(6 downto 1))
&(vcnt(8 downto 1)-count_tempv(8 downto 1));
3.3 按键扫描控制模块
按键扫描控制采用的电平触发机制,5Hz左右的扫描频率,适合于人们的使用习惯。其中按键的功能里包括了:a.单步上下、左右的移动;b.放大和不放大两种模式的选择;c.顺时针90°旋转按钮;d.屏保模式自由移动和停止选择按钮。
3.4 ROM内存模块
ROM内存中存储的是图片的依次行扫描三基色数据,作为显示时送给VGA显示器的RGB数据。本设计存储的是一幅256*64的图片,所以定义了一256*64=16384 byte的rom内存。
四、结果分析和调试
在调试过程中,我们遇到图片在靠近行和列的零边界线时,会出现整个图片突然消失的问题,而不是想象中的逐渐步入,逐渐消失。经过查阅资料,发现VHDL中没有能表示负数的数据类型,而在程序的运算过程中,有会出现负数的可能性,即在对图片初始坐标的减运算过程中,可能会将图片的起始坐标减成负数,使图片初始坐标变量进入未知状态,致使图片在屏幕上立即消失的。经过对程序的修改,排除了大部分的漏洞,让图片显示基本能按设想显示,不排除还存在部分Bug的可能性。
5、体会和感受
利用可编程逻辑器件(FPGA)可以很方便地实现数字系统设计,而在Altera 的QuartusII 软件平台下,FPGA 设计的各个阶段都得到了很好的支持,两者有效结合使得数字系统的设计更加方便快捷。通过硬件平台的验证,基于FPGA 的VGA 图形显示器已达到设计要求,可以稳定地实现界面的显示,并可以在界面上根据需要进行数字的动态改变显示,对于一些工业监控场合的应用,尤其数据量不是很大的场合尤为适合,不仅可以及时显示当前状态,还可以保存关机时状态。
经过本次课程设计,学到了很多VHDL的知识,比纯粹的理论教学课堂上学到的知识更多,更加深刻。实践教学方式对于我们工程运用专业是一个非常适合的教学方式,不仅锻炼了个人的动手能力,而且调动了学习的积极性,改变了我们的学习状态,是一种非常值得重视和推广的教学方式。
六、参考文献
【1】 王诚,吴继华,范丽珍.[Altera FPGA/CPLD 设计(基础篇)][M].北京人民邮电出版社(2005)
【2】 王诚,吴继华,范丽珍.[Altera FPGA/CPLD 设计(高级篇)][M].北京:人民邮电出版社(2005)
【3】 夏宇闻. [Verilog 数字系统设计教程][M]. 北京:北京航空航天大学出版社(2003)
【4】 潘松、王国栋.[VHDL实用教程] [M].电子科技大学出版社 (2000)
【5】 李广军孟宪元.[可编程ASIC设计及应用] [M].电子科技大学出版社(2000)
【6】 曹允.[基于FPGA的时序彩条信号实现方法及应用] [M] .电子技术应用,第七期 (2006)
【7】 朱耀东,经亚枝,张焕春.[基于FPGA的LCD&VGA控制器设计] [M].电子技术应用,第十一期(2006)
【8】 陈志生,陈景贤.[基于FPGA的多分辨率VGA图像控制器设计] [J].现代电子技术 (2008)
【9】 王恒心.熊庆国.基于FPGA/CPLD的嵌入式VGA显示系统 [J].微计算机信息 (2008)
【10】 潘松,黄继业.[EDA技术与VHDL] [M].2版.北京:清华大学出版社(2007)
1. 附录(程序和元件清单)
(1)800*600VGA时序控制模块代码:----------------------------------
library IEEE;
use IEEE.std_logic_1164.all;
use IEEE.STD_LOGIC_UNSIGNED.ALL;
entity vga800600 is
port (
clk : in STD_LOGIC;
hs : out STD_LOGIc;
vs : out STD_LOGIc;
r : out STD_LOGIC_VECTOR(2 downto 0);
g : out STD_LOGIC_VECTOR(2 downto 0);
b : out STD_LOGIC_VECTOR(1 downto 0);
rgbin : in std_logic_vector(7 downto 0);
hcntout : out std_logic_vector(10 downto 0);
vcntout : out std_logic_vector(9 downto 0));
end vga800600;
architecture ONE of vga800600 is
signal hcnt : std_logic_vector(10 downto 0);
signal vcnt : std_logic_vector(9 downto 0);
begin
hcntout <= hcnt;
vcntout <= vcnt;
process(clk) begin
if (rising_edge(clk)) then
if(hcnt < 1056) then
hcnt <= hcnt + 1;
else
hcnt <= (others => '0');
end if;
end if;
end process;
--this is Vertical counter
process(clk) begin
if (rising_edge(clk)) then
if (hcnt = 800+8 ) then
if(vcnt < 625) then
vcnt <= vcnt + 1;
else
vcnt <= (others => '0');
end if;
end if;
end if;
end process;
--this is hs pulse
process(clk) begin
if (rising_edge(clk)) then
if((hcnt>=800+8+8)and (hcnt<800+8+8+80 )) then
hs <= '0';
else
hs <= '1';
end if;
end if;
end process;
--this is vs pulse
process(vcnt) begin
if ((vcnt >= 600+0+1) and (vcnt < 600+0+1+3)) then
vs <= '0';
else
vs <= '1';
end if;
end process;
process(clk) begin
if (rising_edge(clk)) then
if (hcnt<800 and vcnt<600) then
r(2 downto 0)<=rgbin(7 downto 5);
g(2 downto 0)<=rgbin(4 downto 2);
b(1 downto 0)<=rgbin(1 downto 0);
else
r<="000";
g<="000";
b<="00";
end if;
end if;
end process;
end ONE;
(2)rom内存地址控制模块代码:--------------------------------------
library ieee;
use ieee.std_logic_1164.all;
use ieee.std_logic_unsigned.all;
entity mid is
port (
clk : in std_logic;
fangda_temp : in std_logic;
mode : in std_logic_vector(1 downto 0);--key按键输入旋转90度信号,下降沿有效
qin : in std_logic_vector(7 downto 0);
xx: in std_logic_vector(9 downto 0);
yy: in std_logic_vector(9 downto 0);
hcntin : in std_logic_vector(10 downto 0);
vcntin : in std_logic_vector(9 downto 0);
qout : out std_logic_vector(7 downto 0);
romaddr_control : out std_logic_vector(13 downto 0)
);
end mid;
architecture one of mid is
signal xuanzhuanjiaodu: std_logic_vector(1 downto 0);
signal hcnt : std_logic_vector(10 downto 0);
signal vcnt : std_logic_vector(9 downto 0);
signal qout_temp : std_logic_vector(7 downto 0);
signal count_temph : std_logic_vector(9 downto 0);
signal count_tempv : std_logic_vector(9 downto 0);
signal wide : integer range 0 to 1024;
signal long : integer range 0 to 1024;
begin
-- Assign pin
hcnt <= hcntin;
vcnt <= vcntin;
qout <= qout_temp;
xuanzhuanjiaodu <=mode;
process( fangda_temp )
begin
if(fangda_temp='0') then
wide <=256;
long <=64;
else
wide <=512;
long <=128;
end if;
end process;
process( xuanzhuanjiaodu )
BEGIN
case xuanzhuanjiaodu IS
WHEN "00" =>
if(fangda_temp = '0') then
romaddr_control <= (vcnt(5 downto 0)-count_tempv(5 downto 0))--0 du
&(hcnt(7 downto 0)-count_temph(7 downto 0));
else
romaddr_control <= (vcnt(6 downto 1)-count_tempv(6 downto 1))--0 du
&(hcnt(8 downto 1)-count_temph(8 downto 1));
end if;
WHEN "01" =>
if(fangda_temp = '0') then
romaddr_control <= (64-(hcnt(5 downto 0)-count_temph(5 downto 0)))--90 du
&(vcnt(7 downto 0)-count_tempv(7 downto 0));
else
romaddr_control <= (64-(hcnt(6 downto 1)-count_temph(6 downto 1)))--90 du
&(vcnt(8 downto 1)-count_tempv(8 downto 1));
end if;
WHEN "10" =>
if(fangda_temp = '0') then
romaddr_control <= (64-(vcnt(5 downto 0)-count_tempv(5 downto 0)))--180 du
&(256-(hcnt(7 downto 0)-count_temph(7 downto 0)));
else
romaddr_control <= (64-(vcnt(6 downto 1)-count_tempv(6 downto 1)))--180 du
&(256-(hcnt(8 downto 1)-count_temph(8 downto 1)));
end if;
WHEN OTHERS =>
if(fangda_temp = '0') then
romaddr_control <= ((hcnt(5 downto 0)-count_temph(5 downto 0))) --270 du
&(256-(vcnt(7 downto 0)-count_tempv(7 downto 0)));
else
romaddr_control <= ((hcnt(6 downto 1)-count_temph(6 downto 1))) --270 du
&(256-(vcnt(8 downto 1)-count_tempv(8 downto 1)));
end if;
end case;
end process;
process(xx,yy) begin
if((vcnt = yy) and( hcnt=xx) )then
count_temph<=xx;
count_tempv<=yy;
end if;
if((xuanzhuanjiaodu =1 ) or (xuanzhuanjiaodu =3 )) then
if((vcnt < yy) or (vcnt > yy+wide)) then qout_temp<="00000111";--cnt(31 downto 24);
elsif((hcnt>xx)and(hcnt
qout_temp<=qin;---------input logo.hex
else
qout_temp<="00000111";--cnt(31 downto 24);
end if;
else
if((vcnt < yy) or (vcnt > yy+long)) then qout_temp<="00000111";--cnt(31 downto 24);
elsif((hcnt>=xx)and(hcnt<=xx + wide)) then
qout_temp<=qin;---------input logo.hex
else
qout_temp<="00000111";--cnt(31 downto 24);
end if;
end if;
end process;
end one;
(4) 按键控制模块(图象显示顶层程序)
LIBRARY ieee; --图象显示顶层程序
USE ieee.std_logic_1164.all;
use ieee.std_logic_unsigned.all;
ENTITY img IS
port
( clk50MHz : IN STD_LOGIC;
key1,key2,key3,key4,key,s2,s3 : in std_logic;
hs, vs : OUT STD_LOGIC;
r,g : OUT STD_LOGIC_VECTOR(2 downto 0);
b : out STD_LOGIC_VECTOR(1 downto 0)
);
END img;
ARCHITECTURE modelstru OF img IS
component vga800600 --VGA显示控制模块
PORT(clk : IN STD_LOGIC;
rgbin : IN STD_LOGIC_VECTOR(7 downto 0);
hs, vs : OUT STD_LOGIC;
r, g: OUT STD_LOGIC_VECTOR(2 downto 0);
b : OUT STD_LOGIC_VECTOR(1 downto 0);
hcntout :OUT STD_LOGIC_VECTOR(10 downto 0);
vcntout : OUT STD_LOGIC_VECTOR(9 downto 0)
);
end component;
component imgrom --图象数据ROM,数据线8位;地址线12位
PORT(clock : IN STD_LOGIC;
address : IN STD_LOGIC_VECTOR(13 downto 0);
q : OUT STD_LOGIC_VECTOR(7 downto 0)
);
end component;
component mid
port (
clk : in std_logic;
fangda_temp : in std_logic;
mode : in std_logic_vector(1 downto 0);
qin : in std_logic_vector(7 downto 0);
xx: in std_logic_vector(9 downto 0);
yy: in std_logic_vector(9 downto 0);
hcntin : in std_logic_vector(10 downto 0);
vcntin : in std_logic_vector(9 downto 0);
qout : out std_logic_vector(7 downto 0);
romaddr_control : out std_logic_vector(13 downto 0)
);
end component;
signal rgb : STD_LOGIC_VECTOR(7 downto 0);
signal rgb1 : STD_LOGIC_VECTOR(7 downto 0);
signal clk25MHz : std_logic;
signal clk1Hz : std_logic;
signal romaddr : STD_LOGIC_VECTOR(13 downto 0);
signal hpos: std_logic_vector(10 downto 0);
signal vpos : std_logic_vector(9 downto 0);
signal txx,tyy : std_logic_vector(9 downto 0);
signal clk_count: std_logic_vector(24 downto 0);
signal clkm: std_logic;
signal key_mode : std_logic_vector(1 downto 0);
signal fangda ,yidong : std_logic ;
signal yidong_mode : std_logic_vector(1 downto 0);
signal yidong_y,yidong_x :std_logic;
BEGIN------------------ARCHITECTURE begin
div: process(clk50MHz)
begin
if (clk50MHz'event and clk50MHz='1') then
if(clk_count < "0011110111111110011000000")then
clk_count <= clk_count + 1;
clkm <= '0';
else
clk_count <= "0000000000000000000000001";
clkm <= '1';
end if;
end if;
end process;
process(clkm)--clkm的频率为2Hz,做按键扫描用
variable ttxx : std_logic_vector(9 downto 0) :="0011000000"; --192
variable ttyy : std_logic_vector(9 downto 0) :="0011010000"; --208
begin
if(clkm'event and clkm='1')then
if( key ='0')then ttyy := ttyy+10; --key1
elsif ( key2 ='0')then if(( ttyy-10)>9)then ttyy := ttyy-10;end if;--key2
elsif ( key3 ='0')then ttxx := ttxx+10;--key3
elsif ( key4 ='0')then if(( ttxx-10)>9)then ttxx := ttxx-10;end if;
elsif ( key1 ='0')then --key
if( key_mode =3)then key_mode <= "00";
else key_mode<=key_mode+1;
end if;
if(fangda = '0') then
if(key_mode=1) then ttyy:=ttyy+96;if((ttxx-96)>=0)then ttxx:=ttxx-96;else yidong_x<='0'; end if;
elsif(key_mode=2) then ttxx:=ttxx+96;if((ttyy-96)>=0)then ttyy:=ttyy-96;else yidong_y<='0';end if;
elsif(key_mode=3) then ttyy:=ttyy+96;if((ttxx-96)>=0)then ttxx:=ttxx-96;else yidong_x<='0'; end if;
else ttxx:=ttxx+96;if((ttyy-96)>=0)then ttyy:=ttyy-96;else yidong_y<='0';end if;--ttyy:="0000000000";
end if;
else
if(key_mode=1) then ttyy:=ttyy+192;if((ttxx-192)>=0)then ttxx:=ttxx-192;else yidong_x<='0';end if;
elsif(key_mode=2) then ttxx:=ttxx+192;if((ttyy-192)>=0)then ttyy:=ttyy-192;else yidong_y<='0';end if;
elsif(key_mode=3) then ttyy:=ttyy+192;if((ttxx-192)>=0)then ttxx:=ttxx-192;else yidong_x<='0'; end if;
else ttxx:=ttxx+192;if((ttyy-192)>=0)then ttyy:=ttyy-192;else yidong_y<='0';end if;
end if;
end if;
elsif (s2 = '0') then fangda <= not fangda; --s2
if(fangda='1') then ttxx :=ttxx+128; ttyy :=ttyy+32;
else ttxx :=ttxx-128; ttyy :=ttyy-32;
end if;
elsif( s3 = '0' ) then yidong<= not yidong;
end if;
if ( yidong='1') then
if(yidong_x='0') then ttxx:=ttxx+12;------yidong_X等于'0',图片的首坐标做自增运算,否则做自减运算
else ttxx:=ttxx-12;
end if;
if(yidong_y='0') then ttyy:=ttyy+8;-------yidong_y等于'0',图片的首坐标做自增运算,否则做自减运算
else ttyy:=ttyy-8;
end if;
if(ttxx<=11) then yidong_x<='0';
end if;
if(ttyy<=7) then yidong_y<='0';
end if;
if(fangda='0') then
case key_mode is
when "00" =>
if(ttxx>=544) then yidong_x<='1';
end if;
if(ttyy>=536) then yidong_y<='1';
end if;
when "10" =>
if(ttxx>=544) then yidong_x<='1';
end if;
if(ttyy>=536) then yidong_y<='1';
end if;
when "01" =>
if(ttxx>=736) then yidong_x<='1';
end if;
if(ttyy>=334) then yidong_y<='1';
end if;
when "11" =>
if(ttxx>=736) then yidong_x<='1';
end if;
if(ttyy>=334) then yidong_y<='1';
end if;
end case;
else
case key_mode is
when "00" =>
if(ttxx>=288) then yidong_x<='1';
end if;
if(ttyy>=472) then yidong_y<='1';
end if;
when "10" =>
if(ttxx>=288) then yidong_x<='1';
end if;
if(ttyy>=472) then yidong_y<='1';
end if;
when "01" =>
if(ttxx>=672) then yidong_x<='1';
end if;
if(ttyy>=88) then yidong_y<='1';
end if;
when "11" =>
if(ttxx>=672) then yidong_x<='1';
end if;
if(ttyy>=88) then yidong_y<='1';
end if;
end case;
end if;
end if;
end if;
-------------------------------------------一下为防出边界检验及相应矫正操作,因为当ttxx和ttyy因为一些随
-------------------------------------------机的按键操作让其做自减操作时可能会出现为负数的情况。
if(ttxx<=0) then yidong_x<='0';--ttxx:="0000000000";
end if;
if(ttyy<=0) then yidong_y<='0'; --ttyy:="0000000000";
end if;
txx<=ttxx;
tyy<=ttyy;
end process;
process(clk50MHz) begin
if clk50MHz'event and clk50MHz = '1' then clk25MHz <= not clk25MHz ; end if;
end process;
i_vga800600 : vga800600 PORT MAP(clk => clk50MHz, rgbin => rgb1, hs => hs,
vs => vs, r => r, g => g, b => b, hcntout => hpos, vcntout => vpos);
i_rom : imgrom PORT MAP(clock => clk50MHz, address => romaddr, q => rgb);
i_mid : mid PORT MAP(clk => clk50MHz,fangda_temp=>fangda,mode=>key_mode, xx => txx, yy=> tyy ,qin => rgb, hcntin => hpos, vcntin => vpos, qout =>rgb1,romaddr_control => romaddr);
end;