本篇主要讨论基于FPGA的VGA驱动设计--第一版(全屏一个颜色、黑白棋盘)。
VGA(Video Graphics Array)视频图形阵列是IBM公司于20世纪80年代提出的一个使用模拟信号的显示标准。
VGA 接口有15 针孔,分成三排,每排五个针孔。其中比较重要的是3 根RGB(红绿蓝)彩色分量信号和2 根扫描同步信号HSYNC(水平同步信号)和VSYNC(垂直同步信号) 针。其母头插座引脚编号图如下图所示:
三基色是指RGB三色,大多数的颜色可以通过RGB三色按照不同的比例合成产生。同样绝大多数单色光也可以分解成RGB三种色光。这是色度学的最基本原理,即三基色原理。RGB三基色按照不同的比例相加合成混色称为相加混色,除了相加混色法之外还有相减混色法。可根据需要相加相减调配颜色。
VGA 接口中,表示颜色分量的只有红绿蓝三种基色。由于VGA 接口的三基色为模拟信号值,FPGA(FPGA为数字电路) 无法输出,所以在FPGA 的IO 到VGA 接口中间要有对应的数字量转模拟量的电路。
有些板卡在设计时,利用电阻网络来完成数字量到模拟量的转换。
有些板卡在设计时,利用AD芯片来完成这个数字量到模拟量的转换。
图片在数字设备中,都是由像素点构成。
像素是指由图像的小方格组成的,这些小方块都有一个明确的位置和被分配的色彩数值,小方格颜色和位置就决定该图像所呈现出来的样子。可以将像素视为整个图像中不可分割的单位或者是元素。不可分割的意思是它不能够再切割成更小单位,它是以一个单一颜色的小格存在。每一个点阵图像包含了一定量的像素,这些像素决定图像在屏幕上所呈现的大小。
VGA 显示器上每一个像素点可以很多种颜色,由R、G、B 三种颜色构成。如果每个像素点采用3 位二进制数表示,即R 用1bit 表示,G 用1bit 表示,B 用1bit 表示,则此像素点一共可以显示8 中颜色;如果每个像素点采用16位二进制数表示,即R 用5bit 表示,G 用6bit 表示,B 用5bit 表示,则此像素点一共可以显示65536种颜色。
笔者的本次设计采用RGB565的板卡、数字量转模拟量是采用电路网络。
在VGA 显示器中,像素点RGB 的二进制数越多,能够表示的颜色就越多,此时,显示的图像就会越清晰。
在VGA 显示器中,像素点的个数也是一个非常重要的一个指标。
我们可以打开自己电脑的显示分辨率。
分频率有各种模式,但是基本都是固定好的。分辨率都是长乘宽,前面的数为长,后面的数为宽。长表示屏幕横向可以有多少个像素点;宽表示屏幕纵向可以用多少个像素点。一般来说屏幕都是扁平的,所以长一般都会比宽大。
像素的多少不改变实际物理的尺寸大小,只是呈现的清晰度不同。可以对比500 万像素的相机拍的图片和2000 万像素的相机拍的图片,大小相同的情况下,清晰度是不同的。
只要我们按照显示器能够支持的分辨率的长和宽,将对应的像素点传输给VGA 接口就可以了。但是VGA 协议中,要求进行传输像素点的同时,还需要去传输一部分的同步信号。
显示器扫描方式分为逐行扫描和隔行扫描:逐行扫描是扫描从屏幕左上角一点开始,从左像右逐点扫描,每扫描完一行,电子束回到屏幕的左边下一行的起始位置,在这期间,每行结束时,用行同步信号进行同步;当扫描完所有的行,形成一帧,用场同步信号进行场同步,并使扫描回到屏幕左上方,开始下一帧。隔行扫描是指电子束扫描时每隔一行扫一线,完成一屏后在返回来扫描剩下的线,隔行扫描的显示器闪烁的厉害,会让使用者的眼睛疲劳。在此我们选择逐行扫描的方式。
VGA 的时序主要包括行时序与场时序两个部分。其中行时序主要包括:行同步(Hor Sync) 、行消隐(Hor Back Porch) 、行视频有效(Hor Active Video)和行前肩(Hor Front Porch)这四个参数,行时序的时序图如下图所示:
场时序主要包括:场同步(Ver Sync) 、场消隐(Ver BackPorch) 、场视频有效(Ver Active Video)和场前肩(Ver Front Porch)这四个参数,场时序的时序图如下图所示:
需要注意的有三点:
1、行时序是以”像素”为单位的, 场时序是以”行”为单位的。
2、VGA 工业标准显示模式要求:行同步,场同步都为负极性,即同步脉冲要求是负脉冲。
3、VGA 行时序对行同步时间、 消隐时间、 行视频有效时间和行前肩时间有特定的规范, 场时序也是如此。 常用VGA 分辨率时序参数如下表所示:
本实验中选择640x480@60Hz。时钟的速率为25.175MHz,我们在设计时,时钟速率选择为25MHz 即可。
功能说明:
-
利用条件编译的方式,实现显示三基色(红、绿、蓝)以及黑白棋 盘。
-
选择分辨率为640x480@60Hz。
-
RGB为5:6:5,共计16bit。
-
黑白棋盘的小方块为边长为40像素。
使用平台:本次设计应用Altera的平台设计(芯片:EP4CE10F17C8N)。
仿真平台:Modelsim。
作者QQ:746833924
说明:本篇设计中涉及到ip电路(PLL),rtl代码在其他平台依然可以适用(需要调用其他平台的时钟管理单元);当其他板卡电路不同时,会导致不同的现象出现,如有需要修改代码请联系作者;如需作者使用的板卡,请联系作者;
设计思想如下:
pll_vga模块是一个ip电路(PLL),负责利用外部的50MHz的时钟产生25MHz的时钟。
vga_ctrl模块产生vga同步时序,并且控制像素点的输出。
下图为在quaruts中,pll的配置界面。
vga_ctrl模块设计思想如下:
条件编译说明如下:
需要显示那种模式,就把那个解注释,把其他三个注释掉。例:下图为显示全屏绿色。
//`define RED
`define GREEN
//`define BLUE
//`define CHECKERBOARD
当我们选择640x480@60Hz 的标准后,根据对应格式可以发现,此标准的一行为800 个像素值,共有525 行。也就是说并不是所有的像素值都可以显示出来,显示出来的只有中间的640 列和480 行,其他的像素值不显示(要求其他的像素值为黑色,即RGB 全部给0)。
扫描方式为逐行扫描,从左上角开始。定义一个列坐标计数器(cnt_hs),每个驱动时钟周期加1,当一行结束后,计数器也同时清零。一行为800 个像素值,所以计数器将会在0 到799 无限循环。HSYNC 信号在此计数器的前96 的计数值拉低,其他时间拉高即可。
always @ (posedge clk, negedge rst_n) begin
if (rst_n == 1'b0)
cnt_hs <= 10'd0;
else
if (cnt_hs < HS_E - 1'b1)
cnt_hs <= cnt_hs + 1'b1;
else
cnt_hs <= 10'd0;
end
always @ (posedge clk, negedge rst_n) begin
if (rst_n == 1'b0)
vga_hs <= 1'b1;
else
if (cnt_hs < HS_A)
vga_hs <= 1'b0;
else
vga_hs <= 1'b1;
end
定义一个行坐标计数器(cnt_vs),扫描完一行后,进行加一,当一帧图片结束后,计数器清零。一行为800 个像素值,所以等cnt_hs 为799 时,cnt_vs 进行加一或者清零,由于一帧图片共有525 行,所以计数器在0 到524 之间无限循环。VSYNC 信号在此计数器的前两个计数器拉低,其他时间拉高即可。
always @ (posedge clk, negedge rst_n) begin
if (rst_n == 1'b0)
cnt_vs <= 10'd0;
else
if (cnt_hs == HS_E - 1'b1)
if (cnt_vs < VS_E - 1'b1)
cnt_vs <= cnt_vs + 1'b1;
else
cnt_vs <= 10'd0;
else
cnt_vs <= cnt_vs;
end
always @ (posedge clk, negedge rst_n) begin
if (rst_n == 1'b0)
vga_vs <= 1'b1;
else
if (cnt_vs < VS_A)
vga_vs <= 1'b0;
else
vga_vs <= 1'b1;
end
根据cnt_hs 和cnt_vs,按照对应的标准,就可以得出显示的640 列和480 行的具体位置。
列显示的范围为:HS_A + HS_B + HS_C>cnt_hs>HS_A + HS_B - 1'b1.
行显示的范围为:VS_A + VS_B + VS_C>cnt_vs>VS_A + VS_B - 1'b1.
同时在两个有效显示区范围内,就可以显示出来。
assign hs_en = (cnt_hs > HS_A + HS_B - 1'b1) && (cnt_hs < HS_A + HS_B + HS_C);
assign vs_en = (cnt_vs > VS_A + VS_B - 1'b1) && (cnt_vs < VS_A + VS_B + VS_C);
`ifdef RED always @ (posedge clk, negedge rst_n) begin
if (rst_n == 1'b0)
vga_rgb <= 16'd0;
else
if (hs_en == 1'b1 && vs_en == 1'b1)
vga_rgb <= 16'b11111_000000_00000;
else
vga_rgb <= 16'd0;
end
`endif
`ifdef GREEN
always @ (posedge clk, negedge rst_n) begin
if (rst_n == 1'b0)
vga_rgb <= 16'd0;
else
if (hs_en == 1'b1 && vs_en == 1'b1)
vga_rgb <= 16'b00000_111111_00000;
else
vga_rgb <= 16'd0;
end
`endif
`ifdef BLUE
always @ (posedge clk, negedge rst_n) begin
if (rst_n == 1'b0)
vga_rgb <= 16'd0;
else
if (hs_en == 1'b1 && vs_en == 1'b1)
vga_rgb <= 16'b00000_000000_11111;
else
vga_rgb <= 16'd0;
end
`endif
红色显示如下:
绿色显示如下:
有兴趣的小伙伴,可以自己调整rgb的输出值,例:红色和绿色分量都写1(16'b11111_111111_00000),看看是什么颜色。
棋盘显示的设计原理:
我们设计的棋盘为边长为40像素的方块,正好横着可以放下16个(640),竖着可以放下12行(480)。首先计算每一个方格的横纵坐标,然后将横纵坐标加起来,奇数显示白色,偶数显示黑色。这样正好是黑白棋盘。