一、简介
VGA(Video Graphics Array)即视频图形阵列,是IBM在1987年推出的使用模拟信号的一种视频传输标准,在当时具有分辨率高、显示速率快、颜色丰富等优点,在彩色显示器领域得到了广泛的应用。这个标准对于现今的个人电脑市场已经十分过时。即使如此,VGA仍然是最多制造商所共同支持的一个标准,个人电脑在加载自己的特殊驱动程序之前,都必须支持VGA的标准。
1. 不支持热插拔
VGA接口跟HDMI接口一样是不支持热插拔的。热插拔是指一般带电状态下对于接插件的插入或是拔除,并不只是针对有电源接口或者带供电的接口的接插件,而是所有。在运行状态时,插拔会产生耦合电流,电流不稳造成硬件烧坏,导致笔记本的接口端的保护受到冲击。就像U盘不能再一个时间段多次在一个端口插拔使用一样。各种电器的外露端子都会有金属的部分,它们都是要求接地的,但是不同的电器之间的地并不一定相同,比如一台DVD的地和一台电视机的接地都是相对于本身系统而言。
当端子插入时,首先要建立共同的地来对传输的信号作参考,这就要依靠端子和传输线上的金属部分了,金属部分接地同时也是对信号的屏蔽和保护。两个地相接触一瞬间,会有很高的尖峰脉冲产生,这种脉冲如果不加以滤除可能会直达芯片并将其损坏。另外还有一种是ESD,即静电损坏,这种更难以避免,因为在电子产品上,只能去防护,ESD的持续时间会更短US级别。所以正规的电子产品对于金属端子的接地有比较高的要求,同时在信号线上增加ESD防护器件来避免热插拔的损坏。但实际上很多厂家为了节省成本而偷工减料,或者是对热插拔的防护意识不够导致设计不合理,使得用户会出现热插拔损坏电器的现象产生。
2. VGA不能传输音频
因为视频是VGA信号,而音频信号不是,所以VGA不能传输音频,只能传输视频。相信这就是为什么这几年极度的需求创新转换器的原因。VGA不支持音频传输也是给很多消费者带来烦恼,这最好的办法其实就是购买一款转换器,VGA转HDMI或者HDMI转VGA,达到视频传输的同时还支持音频信号的输出,一举两得。但是不要只想着转换器的输入与输出成问题,同时想想音频输出口,3.5mm是音频输出信号的重要连接线。购买时可以考虑想转换器有没有带3.5mm的音频输出口,然后另外购买一条音频线。
二、接口
VGA接口是一种D型接口,上面共有15针孔,分成三排,每排五个。 其中比较重要的是3根RGB彩色分量信号和2根扫描同步信号HSYNC和VSYNC针。其引脚编号图如下图所示:
其中每个管脚的详细定义如下表所示
管脚 | 名称 | 定义 |
1 | RED | 红基色(75Ω,0.7Vp-p) |
2 | GREEN | 绿基色(75Ω,0.7Vp-p) |
3 | BLUE | 蓝基色(75Ω,0.7Vp-p) |
4 | ID2 | 地址码(显示器标识位2) |
5 | GND | 地 |
6 | RGND | 红色地 |
7 | GGND | 绿色地 |
8 | BGND | 蓝色地 |
9 | KEY | 保留 |
10 | SGND | 同步信号地 |
11 | ID0 | 地址码(显示器标识位0) |
12 | ID1 | 地址码(显示器标识位1) |
13 | HSYNC | 行同步信号 |
14 | VSYNC | 场同步信号 |
15 | ID3 | 地址码(显示器标识位3) |
由于VGA是模拟接口,所以需要把8位的红绿蓝像素信号转化为峰峰值0.7V的电压大小信号进行输出。
常见的VGA分辨率如下图所示
三、时序
1. 极性为positive
VGA显示主要取决于R、G、B三原色。根据R、G、B位宽的不同,VGA显示的效果也不同,常见有24bit(R/G/B各8bit)、16bit(R 5bit、G 6bit、B 6bit)、12bit(R/G/B各4bit)。
对于分辨率600×400@60Hz,该参数显示的是VGA有效数据参数,即VGA显示刷新率60帧,每帧600×400个像素点。VGA接口实际传输的数据比此数值要大。
VGA显示器逐行扫描原理:逐行扫描是扫描从屏幕左上角一点开始,从左至右逐点扫描,每扫描完一行,电子束回到屏幕的左边下一行的起始位置。在这期间,CRT对电子束进行消隐,每行结束时,用行同步信号HS进行同步;当扫描完所有的行,形成一帧,用场同步信号VS进行帧同步,并使扫描回到屏幕左上方,同时进行场消隐,开始下一帧。
从逐行扫描的原理中,我们可以了解,当一行扫描完成后,切换到下一行时,需要行同步信号及行消隐时间;当一帧扫描完成后,切换到下一帧时,需要场同步信号及场消隐时间,这样就形成了VGA扫描时序,如下图所示:
Sync为同步信号,Back Porch为显示后肩时间,Active Video为有效视频,Front Porch为显示前肩时间。Sync+Back Porch+Active Video+Front Porch构成一个完整行周期。
下图定义了一帧图像的时序定义。图中场信号的定义与行信号定义类似。行信号以像素点为单位,场信号以行为单位。VGA图像时序的编写即是对图4中各个时序段进行定时。
下面以1920×1080@60Hz分辨率介绍VGA时序定义。在VESA表准中给出了该分辨率的参数定义,如下图所示
在图5中,需要注意蓝色标记的HS/VS信号极性,这在VESA标准中有规定要求,图3只是给出了其中一种HS/VS信号极性,其他3种请参考VESA标准。
2. 极性为negetive
在VGA视频传输标准中,视频图像被分解为红、绿、蓝三色信号,在行同步(HSYNC)和场同步(VSYNC)信号的同步下分别在三个独立通道传输。VGA在传输过程中的同步时序分为行时序和场时序,如下图所示。
从上面两幅图中我们可以看到VGA传输过程中的行同步时序和场同步时序非常类似,一行或一场(又称一帧)数据都分为四个部分:低电平同步脉冲(消隐期,相当于还原扫描坐标)、显示后沿(消隐后肩,相当于准备开始扫描)、有效数据段(行显示期,扫描中,数据有效区域)以及显示前沿(行消隐前肩,完成扫描,相当于准备同步)。
行同步信号HSYNC在一个行扫描周期中完成一行图像的显示,其中在a段维持一段时间的低电平用于数据同步,其余时间拉高;在有效数据期间(c段),红绿蓝三原色数据通道上输出一行图像信号,其余时间数据无效。
与之类似,场同步信号在在一个场扫描周期中完成一帧图像的显示,不同的是行扫描周期的基本单位是像素点时钟,即完成一个像素点显示所需要的时间;而场扫描周期的基本单位是完成一行图像显示所需要的时间。
根据分辨率和刷新速率的不同,VGA又分为640*480@75、800*600@60等。不同的VGA显示时序是类似的,仅存在参数上的差异,如下图所示。
(由上图明显可直接读出完成一行需要的行同步时钟周期数e和完成一帧需要的列同步时钟数s)
有效显示区域效果如下:
(像素)时钟VGA_CLK计算公式:HS_total×VS_total×fps。
其中,HS_total为VGA_HS信号的一个周期内包含的VGA_CLK信号周期个数,即行周期数;VS_total为VGA_VS信号的一个周期内包含的VGA_HS信号周期个数,即列周期数;fps为每秒刷新率。分辨率640*480p@60Hz时,HS_total为800,VS_total为525, fps为60。
因此VGA_CLK信号的频率为800×525×60Hz=25.2MHz。
(以RGB888、640*480p@60Hz为例,有效区域内每个VGA_CLK输出一个像素数据(24位并行);每个VGA_HS包含800个VGA_CLK周期,显示一行的像素数据;每个VGA_VS包含525个VGA_CLK周期,显示一帧的像素数据)
三、总结
以640*480为例,如上图所示。其中行同步的数字是像素时钟的周期,列同步的数字是行同步的周期数。
在行同步和列同步都为高电平的有效时间段,每个像素时钟周期传输颜色数据(3*8bit的数字信号转化为3个电压大小信号,最终输出3个模拟信号)。
四、代码实现
module vga_driver
#(
parameter RED_WIDTH = 8 , // VGA红色分量位宽
BLUE_WIDTH = 8 , // VGA绿色分量位宽
GREEN_WIDTH = 8 , // VGA蓝色分量位宽
PIXEL = 25 // 代表时钟是25MHz,那么分辨率就是640*480@60
)
(
input clk , // 分辨率为640*480时,时钟为25.175MHz,近似使用25M即可
input rstn , // 系统复位
/*
input data , // 要显示的内容数据
*/
output reg [ RED_WIDTH - 1 : 0] red , // VGA红色分量
output reg [ GREEN_WIDTH - 1 : 0] green , // VGA绿色分量
output reg [ BLUE_WIDTH - 1 : 0] blue , // VGA蓝色分量
output hs , // VGA行同步信号
output vs // VGA场同步信号
);
// 行时序各个参数定义
reg [7:0] h_sync_pulse ; // 行低电平同步脉冲(消隐期,相当于还原扫描坐标)
reg [7:0] h_back_porch ; // 行显示后沿(消隐后肩,相当于准备开始扫描)
reg [10:0] h_active_time ; // 行有效数据段(行显示期,扫描中,数据有效区域)
reg [6:0] h_front_porch ; // 行显示前沿(行消隐前肩,完成扫描,相当于准备同步)
reg [10:0] h_line_total ; // 一行所需时钟周期总数
//场时序各个参数定义
reg [2:0] v_sync_pulse ; // 场低电平同步脉冲(消隐期,相当于还原扫描坐标)
reg [5:0] v_back_porch ; // 场显示后沿(消隐后肩,相当于准备开始扫描)
reg [10:0] v_active_time ; // 场有效数据段(行显示期,扫描中,数据有效区域)
reg [3:0] v_front_porch ; // 场显示前沿(行消隐前肩,完成扫描,相当于准备同步)
reg [10:0] v_frame_total ; // 一场所需时钟周期总数
// 分辨率为640*480时行时序各个参数定义
parameter H_SYNC_PULSE_640 = 96 ,
H_BACK_PORCH_640 = 48 ,
H_ACTIVE_TIME_640 = 640 ,
H_FRONT_PORCH_640 = 16 ,
H_LINE_TOTAL_640 = 800 ;
// 分辨率为640*480时场时序各个参数定义
parameter V_SYNC_PULSE_480 = 2 ,
V_BACK_PORCH_480 = 33 ,
V_ACTIVE_TIME_480 = 480 ,
V_FRONT_PORCH_480 = 10 ,
V_FRAME_TOTAL_480 = 525 ;
reg [11:0] hs_cnt ; // 行时序计数器(根据H_LINE_TOTAL决定)
reg [11:0] vs_cnt ; // 场时序计数器(根据V_FRAME_TOTAL决定)
wire active_flag ; // 激活标志,当这个信号为1时RGB的数据可以显示在屏幕上
wire [8:0] color_bar_width ; // 测试8个纯色列的宽度
//
// 功能:设置分辨率
//
always @(posedge clk or negedge rstn) begin
if(!rstn) begin
h_sync_pulse <= 8'b0 ;
h_back_porch <= 8'b0 ;
h_active_time <= 11'b0 ;
h_front_porch <= 7'b0 ;
h_line_total <= 11'b0 ;
v_sync_pulse <= 3'b0 ;
v_back_porch <= 6'b0 ;
v_active_time <= 11'b0 ;
v_front_porch <= 4'b0 ;
v_frame_total <= 11'b0 ;
end
else begin
case(PIXEL)
25: begin
h_sync_pulse <= H_SYNC_PULSE_640 ;
h_back_porch <= H_BACK_PORCH_640 ;
h_active_time <= H_ACTIVE_TIME_640 ;
h_front_porch <= H_FRONT_PORCH_640 ;
h_line_total <= H_LINE_TOTAL_640 ;
v_sync_pulse <= V_SYNC_PULSE_480 ;
v_back_porch <= V_BACK_PORCH_480 ;
v_active_time <= V_ACTIVE_TIME_480 ;
v_front_porch <= V_FRONT_PORCH_480 ;
v_frame_total <= V_FRAME_TOTAL_480 ;
end
default: begin
h_sync_pulse <= 8'b0 ;
h_back_porch <= 8'b0 ;
h_active_time <= 11'b0 ;
h_front_porch <= 7'b0 ;
h_line_total <= 11'b0 ;
v_sync_pulse <= 3'b0 ;
v_back_porch <= 6'b0 ;
v_active_time <= 11'b0 ;
v_front_porch <= 4'b0 ;
v_frame_total <= 11'b0 ;
end
endcase
end
end
//
// 功能:产生行时序
//
always @(posedge clk or negedge rstn) begin
if(!rstn)
hs_cnt <= 12'b0 ;
else if(hs_cnt == h_line_total - 1'b1)
hs_cnt <= 12'b0 ;
else
hs_cnt <= hs_cnt + 1'b1 ;
end
assign hs = (hs_cnt < h_sync_pulse) ? 1'b0 : 1'b1 ;
//
// 功能:产生场时序
//
always @(posedge clk or negedge rstn) begin
if(!rstn)
vs_cnt <= 12'd0 ;
else if(vs_cnt == v_frame_total - 1'b1)
vs_cnt <= 12'd0 ;
else if(hs_cnt == h_line_total - 1'b1)
vs_cnt <= vs_cnt + 1'b1 ;
else
vs_cnt <= vs_cnt ;
end
assign vs = (vs_cnt < v_sync_pulse) ? 1'b0 : 1'b1 ;
//
// 时序计数器必须在有效数据段范围内才可以显示
//
assign active_flag = (hs_cnt >= (h_sync_pulse + h_back_porch )) &&
(hs_cnt <= (h_sync_pulse + h_back_porch + h_active_time )) &&
(vs_cnt >= (v_sync_pulse + v_back_porch )) &&
(vs_cnt <= (v_sync_pulse + v_back_porch + v_active_time )) ;
//
// 功能:把显示器屏幕分成8个纵列,显示8种颜色,每个纵列的宽度相等
//
assign color_bar_width = h_active_time >> 3 ;
always @(posedge clk or negedge rstn) begin
if(!rstn)
begin
red <= {RED_WIDTH{1'b0}} ;
green <= {GREEN_WIDTH{1'b0}} ;
blue <= {BLUE_WIDTH{1'b0}} ;
end
else if(active_flag)
begin
if(hs_cnt < (h_sync_pulse + h_back_porch + color_bar_width)) // 红色彩条
begin
red <= ~ {RED_WIDTH{1'b0}} ; // 红色彩条把红色分量全部给1,绿色和蓝色给0
green <= {GREEN_WIDTH{1'b0}} ;
blue <= {BLUE_WIDTH{1'b0}} ;
end
else if(hs_cnt < (h_sync_pulse + h_back_porch + color_bar_width*2)) // 绿色彩条
begin
red <= {RED_WIDTH{1'b0}} ;
green <= ~{GREEN_WIDTH{1'b0}} ; // 绿色彩条把绿色分量全部给1,红色和蓝色分量给0
blue <= {BLUE_WIDTH{1'b0}} ;
end
else if(hs_cnt < (h_sync_pulse + h_back_porch + color_bar_width*3)) // 蓝色彩条
begin
red <= {RED_WIDTH{1'b0}} ;
green <= {GREEN_WIDTH{1'b0}} ;
blue <= ~ {BLUE_WIDTH{1'b0}} ; // 蓝色彩条把蓝色分量全部给1,红色和绿分量给0
end
else if(hs_cnt < (h_sync_pulse + h_back_porch + color_bar_width*4)) // 白色彩条
begin
red <= ~ {RED_WIDTH{1'b0}} ; // 白色彩条是有红绿蓝三基色混合而成
green <= ~{GREEN_WIDTH{1'b0}} ; // 所以白色彩条要把红绿蓝三个分量全部给1
blue <= ~ {BLUE_WIDTH{1'b0}} ;
end
else if(hs_cnt < (h_sync_pulse + h_back_porch + color_bar_width*5)) // 黑色彩条
begin
red <= {RED_WIDTH{1'b0}} ; // 黑色彩条就是把红绿蓝所有分量全部给0
green <= {GREEN_WIDTH{1'b0}} ;
blue <= {BLUE_WIDTH{1'b0}} ;
end
else if(hs_cnt < (h_sync_pulse + h_back_porch + color_bar_width*6)) // 黄色彩条
begin
red <= ~ {RED_WIDTH{1'b0}} ; // 黄色彩条是有红绿两种颜色混合而成
green <= ~{GREEN_WIDTH{1'b0}} ; // 所以黄色彩条要把红绿两个分量给1
blue <= {BLUE_WIDTH{1'b0}} ; // 蓝色分量给0
end
else if(hs_cnt < (h_sync_pulse + h_back_porch + color_bar_width*7)) // 紫色彩条
begin
red <= ~ {RED_WIDTH{1'b0}} ; // 紫色彩条是有红蓝两种颜色混合而成
green <= {GREEN_WIDTH{1'b0}} ; // 所以紫色彩条要把红蓝两个分量给1
blue <= ~ {BLUE_WIDTH{1'b0}} ; // 绿色分量给0
end
else // 青色彩条
begin
red <= {RED_WIDTH{1'b0}} ; // 青色彩条是由蓝绿两种颜色混合而成
green <= ~{GREEN_WIDTH{1'b0}} ; // 所以青色彩条要把蓝绿两个分量给1
blue <= ~ {BLUE_WIDTH{1'b0}} ; // 红色分量给0
end
end
else
begin
red <= {RED_WIDTH{1'b0}} ;
green <= {GREEN_WIDTH{1'b0}} ;
blue <= {BLUE_WIDTH{1'b0}} ;
end
end
endmodule