一、VGA通信协议
VGA(Video Graphics Array)是IBM在1987年随PS/2机一起推出的一种视频传输标准,具有分辨率高、显示速率快、颜色丰富等优点,在彩色显示器领域得到了广泛的应用。不支持热插拔,不支持音频传输。对于一些嵌入式VGA显示系统,可以在不使用VGA显示卡和计算机的情况下,实现VGA图像的显示和控制。VGA显示器具有成本低、结构简单、应用灵活的优点。对于一名FPGA工程师,尤其是视频图像的方向的学习者,VGA协议是必须要掌握的。
1、外部接口
由电路图可以看到,VGA并没有特殊的外部芯片,我们需要关注的其实只有5个信号:HS行同步信号,VS场同步信号,R红基色,G绿基色,B蓝基色。
2、色彩原理
三基色是指通过其他颜色的混合无法得到的“基本色”由于人的肉眼有感知红、绿、蓝三种不同颜色的锥体细胞,因此色彩空间通常可以由三种基本色来表达。这是色度学的最基本原理,即三基色原理。三种基色是相互独立的,任何一种基色都不能有其它两种颜色合成。红绿蓝是三基色,这三种颜色合成的颜色范围最为广泛。我们的RGB信号真是三基色的运用,对这三个信号赋予不同的数值,混合起来便是不同的色彩。
设计RGB信号时,既可以R信号、G信号和B信号独立的赋值,最后连到端口上,也可以直接用RGB当做一个整体信号,RGB信号在使用时的位宽有三种常见格式,以你的VGA解码芯片的配置有关。
1. RGB_8,R:G:B = 3:3:2,即RGB332
2. RGB_16,R:G:B = 5:6:5,即RGB565
3. RGB_24,R:G:B = 8:8:8,即RGB888
3、扫描方式
VGA显示器扫描方式分为逐行扫描和隔行扫描:逐行扫描是扫描从屏幕左上角一点开始,从左像右逐点扫描,每扫描完一行,电子束回到屏幕的左边下一行的起始位置,在这期间,CRT对电子束进行消隐,每行结束时,用行同步信号进行同步;当扫描完所有的行,形成一帧,用场同步信号进行场同步,并使扫描回到屏幕左上方,同时进行场消隐,开始下一帧。隔行扫描是指电子束扫描时每隔一行扫一线,完成一屏后在返回来扫描剩下的线,隔行扫描的显示器闪烁的厉害,会让使用者的眼睛疲劳。因此我们一般都采用逐行扫描的方式。
扫描原理如下所示:
4、行场信号
行场信号共有 4 种模式,即 hsync 和 vsync 的高低状态不同,如下所示:
一开始看这些时序图可能看不懂,它是把行场信号绘制在同一张图里,说明行场信号的控制是相似的,只是时间参数不一样而已。如果展开的话,其实时序是这样的:
从VGA的时序图中, 可以看出,帧时序和行时序都产生了四部分,帧时序的四个部分分别是:同步脉冲(o),显示后沿(p),显示时序段(q)和显示前沿(r)。其中同步脉冲(o),显示后沿(p)和显示前沿(r)是消隐区,RGB信号无效,屏幕不显示数据。显示数据段(q)是有效数据区。行时序的四个部分是:同步脉冲(a),显示后沿(b),显示时序段(c)和显示前沿(d)。其中同步脉冲(a),显示后沿(b),显示前沿(d)是消隐区,RGB信号无效,屏幕不显示数据。显示时序段(c)是有效数据区。
从表中我们可以知道,不同的分辨率,它的时序是不一样的。以800600@60HZ分辨率为例。HSYNC是用来控制“行时序”,HSYNC的a是拉低128个列像素,b是拉高88个列像素,c是拉高800个列像素,而d是拉高40个列像素,e总共有1056个列像素。VSYNC用来控制“帧时序”,VSYNC的o是拉低4个行像素,p是拉高23个行像素,q是拉高600个行像素,而r是拉高1个行像素,s总共有628个行像素。每帧对应628个行周期(628=4+23+600+1),每显示行包括1056点时钟,可知,时钟频率:6281056*60约40MHZ。
如果在一副图片中,那正确的时序表示方式应该如下图这样。
现在稍稍解释一下这些参数。SYNC是“信号同步”,Back proch和Left border常常加在一起称为“显示后沿”,Addressable video为“显示区域”,Right porder和Front porch常常加在一起称为“显示前沿”,一个时序其实就是先拉高一段较短的“信号同步”时间,然后拉低一段很长的时间,这就是一个回合。同时需要注意,其实也可以完全相反。即先拉低一段时间“信号同步”时间,然后拉高一段很长的时间。
二、实验任务
任务要求:
通过Verilog编程,在640*480@60Hz显示模式,分别实现以下VGA显示,并对照VGA协议信号做时序分析:
1)屏幕上显示彩色条纹;
2)显示自定义的汉字字符(姓名-学号);
3)输出一幅彩色图像。
1、显示彩色条纹
使用开发板:EP4CE115F29C7
将多种显示模式对应帧时序和行时序进行定义,保存在一个单独的文件中。
参数定义文件:vga_para.v
`ifdef vga_640_480
`define H_Right_Border 8
`define H_Front_Porch 8
`define H_Sync_Time 96
`define H_Back_Porch 40
`define H_Left_Border 8
`define H_Data_Time 640
`define H_Total_Time 800
`define V_Bottom_Border 8
`define V_Front_Porch 2
`define V_Sync_Time 2
`define V_Back_Porch 25
`define V_Top_Border 8
`define V_Data_Time 480
`define V_Total_Time 525
`elsif vga_1920_1080
`define H_Right_Border 0
`define H_Front_Porch 88
`define H_Sync_Time 44
`define H_Back_Porch 148
`define H_Left_Border 0
`define H_Data_Time 1920
`define H_Total_Time 2200
`define V_Bottom_Border 0
`define V_Front_Porch 4
`define V_Sync_Time 5
`define V_Back_Porch 36
`define V_Top_Border 0
`define V_Data_Time 1080
`define V_Total_Time 1125
`elsif vga_1024_768
`define H_Right_Border 0
`define H_Front_Porch 24
`define H_Sync_Time 136
`define H_Back_Porch 160
`define H_Left_Border 0
`define H_Data_Time 1024
`define H_Total_Time 1344
`define V_Bottom_Border 0
`define V_Front_Porch 3
`define V_Sync_Time 6
`define V_Back_Porch 29
`define V_Top_Border 0
`define V_Data_Time 768
`define V_Total_Time 806
`else
`endif
顶层文件:
module vga_top(
input clk ,
input rst_n ,
output hsync ,
output vsync ,
output [7:0] vga_r ,//red
output [7:0] vga_g ,//green
output [7:0] vga_b ,//blue
output vga_clk ,
output sync ,
output vga_blk
);
wire [23:0] data_dis;
wire [10:0] h_addr ;
wire [10:0] v_addr ;
data_gen u_data_gen(
.clk (clk ),//时钟信号
.rst_n (rst_n ),//复位信号
.h_addr (h_addr ),//数据有效显示区域行地址
.v_addr (v_addr ),//数据有效显示区域场地址
.data_dis (data_dis )
);
vga_ctrl u_vga_ctrl(
.clk (clk ),//vga时钟信号 640*480 25.2MHZ
.rst_n (rst_n ),//复位信号
.data_dis (data_dis ),//
. h_addr ( h_addr ),//数据有效显示区域行地址
. v_addr ( v_addr ),//数据有效显示区域场地址
. hsync ( hsync ),
. vsync ( vsync ),
. vga_r ( vga_r ),//red
. vga_g ( vga_g ),//green
. vga_b ( vga_b ),//blue
. vga_blk ( vga_blk ),
.sync (sync ),
. vga_clk ( vga_clk )
);
endmodule
vga驱动设计:
(这里的25MHz是在50M时钟的上升沿取值进行分频的,还可以使用IP核PLL进行分频,会在后面的图片显示用IP核方法)
vga_crtl.v:
`define vga_640_480
//`define vga_1920_1080
//`define vga_1024_768
`include "vga_para.v"
module vga_ctrl(
input clk ,//vga时钟信号 640*480 25.2MHZ
input rst_n ,//复位信号
input [23:0] data_dis ,//
output reg [10:0] h_addr ,//数据有效显示区域行地址
output reg [10:0] v_addr ,//数据有效显示区域场地址
output reg hsync ,
output reg vsync ,
output reg [7:0] vga_r ,//red
output reg [7:0] vga_g ,//green
output reg [7:0] vga_b ,//blue
output vga_blk ,//VGA消隐信号
output sync ,
output vga_clk
);
reg clk_25;
parameter H_SYNC_STA = 1;
parameter H_SYNC_STO = `H_Sync_Time ;
parameter H_DATA_STA = `H_Left_Border + `H_Back_Porch + `H_Sync_Time ;
parameter H_DATA_STO = `H_Left_Border + `H_Back_Porch + `H_Sync_Time + `H_Data_Time ;
parameter V_SYNC_STA = 1;
parameter V_SYNC_STO = `V_Sync_Time;
parameter V_DATA_STA = `V_Top_Border + `V_Back_Porch + `V_Sync_Time ;
parameter V_DATA_STO = `V_Top_Border + `V_Back_Porch + `V_Sync_Time + `V_Data_Time ;
reg [11:0] cnt_h_addr ;//行地址计数器
wire add_h_addr ;
wire end_h_addr ;
reg [11:0] cnt_v_addr ;//场地址计数器
wire add_v_addr ;
wire end_v_addr ;
assign sync = 1'b0;
//分频25MHz
always@(posedge clk or negedge rst_n)begin
if(!rst_n)begin
clk_25 <= 0;
end
else begin
clk_25 <= ~clk_25;
end
end
assign vga_clk = clk_25;
//计数器
always@(posedge vga_clk or negedge rst_n)begin
if(!rst_n)begin
cnt_h_addr <= 12'd0;
end
else if(add_h_addr) begin
if(end_h_addr)begin
cnt_h_addr <= 12'd0;
end
else begin
cnt_h_addr <= cnt_h_addr + 12'd1;
end
end
else begin
cnt_h_addr <= 12'd0;
end
end
assign add_h_addr = 1'b1 ;//开启条件
assign end_h_addr = add_h_addr && cnt_h_addr >= `H_Total_Time - 1;//结束条件
assign vga_blk = ~((cnt_h_addr<(`H_Front_Porch+`H_Sync_Time+`H_Back_Porch))||(cnt_v_addr<`V_Front_Porch+`V_Sync_Time+`V_Back_Porch));
//计数器
always@(posedge vga_clk or negedge rst_n)begin
if(!rst_n)begin
cnt_v_addr <= 12'd0;
end
else if(add_v_addr) begin
if(end_v_addr)begin
cnt_v_addr <= 12'd0;
end
else begin
cnt_v_addr <= cnt_v_addr + 12'd1;
end