VGA(Video Graphics Array)是IBM在1987年随PS/2机一起推出的一种视频传输标准,具有分辨率高、显示速率快、颜色丰富等优点,在彩色显示器领域得到了广泛的应用。不支持热插拔,不支持音频传输。对于一些嵌入式VGA显示系统,可以在不使用VGA显示卡和计算机的情况下,实现VGA图像的显示和控制。VGA显示器具有成本低、结构简单、应用灵活的优点。对于一名FPGA工程师,尤其是视频图像的方向的学习者,VGA协议是必须要掌握的。
一、外部接口
由电路图可以看到,VGA并没有特殊的外部芯片,我们需要关注的其实只有5个信号:HS行同步信号,VS场同步信号,R红基色,G绿基色,B蓝基色。下面慢慢解释这些信号。
二、色彩原理
经过九年义务教育的我们都应该听过三基色,还给老师了的那就在再复习一下。三基色是指通过其他颜色的混合无法得到的“基本色”由于人的肉眼有感知红、绿、蓝三种不同颜色的锥体细胞,因此色彩空间通常可以由三种基本色来表达。这是色度学的最基本原理,即三基色原理。三种基色是相互独立的,任何一种基色都不能有其它两种颜色合成。红绿蓝是三基色,这三种颜色合成的颜色范围最为广泛。我们的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
三、扫描方式
VGA显示器扫描方式分为逐行扫描和隔行扫描:逐行扫描是扫描从屏幕左上角一点开始,从左像右逐点扫描,每扫描完一行,电子束回到屏幕的左边下一行的起始位置,在这期间,CRT对电子束进行消隐,每行结束时,用行同步信号进行同步;当扫描完所有的行,形成一帧,用场同步信号进行场同步,并使扫描回到屏幕左上方,同时进行场消隐,开始下一帧。隔行扫描是指电子束扫描时每隔一行扫一线,完成一屏后在返回来扫描剩下的线,隔行扫描的显示器闪烁的厉害,会让使用者的眼睛疲劳。因此我们一般都采用逐行扫描的方式。
扫描原理如下所示:
四、行场信号
一开始看这个时序图可能看不懂,它是把行场信号绘制在同一张图里,说明行场信号的控制是相似的,只是时间参数不一样而已。如果展开的话,其实时序是这样的:
这样就清楚了,大致是若干个HS信号才组合而成一个VS,如果在一副图片中,那正确的时序表示方式应该如下图这样。
现在稍稍解释一下这些参数。SYNC是“信号同步”,Back proch和Left border常常加在一起称为“显示后沿”,Addressable video为“显示区域”,Right porder和Front porch常常加在一起称为“显示前沿”,一个时序其实就是先拉高一段较短的“信号同步”时间,然后拉低一段很长的时间,这就是一个回合。同时需要注意,其实也可以完全相反。即先拉低一段时间“信号同步”时间,然后拉高一段很长的时间。
具体这些时间参数是怎么来的呢?且看下文。
五、规格参数
直接拿数据手册说话!
以上是 640x480 @60Hz 的规格参数表,对着这个表即可确定时间。如果为了嫌麻烦,也可以先计算好写在代码里。
//**************************************************************************
// *** 名称 : VGA_driver.v
// *** 作者 : xianyu_FPGA
// *** 博客 : https://www.cnblogs.com/xianyufpga/
// *** 日期 : 2019-06-26
// *** 描述 : VGA驱动模块,pixel_req和pixel_x、pixel_y信号一般不同时使用
//**************************************************************************
module VGA_driver
//========================< 端口 >==========================================
(
//system ----------------------------------------
input wire clk , //时钟,25Mhz
input wire rst_n , //复位,低电平有效
//vga_display -----------------------------------
input wire [:] pixel_data , //得到图像数据
output wire pixel_req , //请求图像数据
output wire [ :] pixel_x , //请求显示区域横坐标
output wire [ :] pixel_y , //请求显示区域纵坐标
//vga output ------------------------------------
output reg vga_de , //VGA接口使能
output wire vga_hsync , //VGA接口行信号
output wire vga_vsync , //VGA接口场信号
output wire [:] vga_data //VGA接口rgb数据信号
);
//========================< 参数 >==========================================
//640x480 @60Hz 25Mhz -----------------------------------------
parameter H_TOTAL = ; //行扫描周期
parameter H_ADDR = ; //行有效数据
parameter H_RIGHT_BORDER = ;
parameter H_FRONT_PORCH = ;
parameter H_FRONT = H_RIGHT_BORDER + H_FRONT_PORCH ; //行显示前沿
parameter H_SYNC = ; //行同步
parameter H_BACK_PORCH = ;
parameter H_LEFT_BORDER = ;
parameter H_BACK = H_BACK_PORCH + H_LEFT_BORDER ; //行显示后沿
//-------------------------------------------------------------
parameter V_TOTAL = ; //场扫描周期
parameter V_ADDR = ; //场有效数据
parameter V_BOTTOM_BORDER = ;
parameter V_FRONT_PORCH = ;
parameter V_FRONT = V_BOTTOM_BORDER + V_FRONT_PORCH ; //场显示前沿
parameter V_SYNC = ; //场同步
parameter V_BACK_PORCH = ;
parameter V_TOP_BORDER = ;
parameter V_BACK = V_BACK_PORCH + V_TOP_BORDER ; //场显示后沿
//========================< 信号 >==========================================
reg [ :] cnt_h ;
wire add_cnt_h ;
wire end_cnt_h ;
reg [ :] cnt_v ;
wire add_cnt_v ;
wire end_cnt_v ;
//==========================================================================
//== 行、场计数
//==========================================================================
always @(posedge clk or negedge rst_n) begin
if(!rst_n)
cnt_h <= ;
else if(add_cnt_h) begin
if(end_cnt_h)
cnt_h <= ;
else
cnt_h <= cnt_h + ;
end
end
assign add_cnt_h = ;
assign end_cnt_h = add_cnt_h && cnt_h==H_TOTAL-;
always @(posedge clk or negedge rst_n) begin
if(!rst_n)
cnt_v <= ;
else if(add_cnt_v) begin
if(end_cnt_v)
cnt_v <= ;
else
cnt_v <= cnt_v + ;
end
end
assign add_cnt_v = end_cnt_h;
assign end_cnt_v = add_cnt_v && cnt_v==V_TOTAL-;
//==========================================================================
//== VGA display
//==========================================================================
//vga请求
assign pixel_req = (cnt_h >= H_SYNC + H_BACK - ) && (cnt_h < H_SYNC + H_BACK + H_ADDR - ) &&
(cnt_v >= V_SYNC + V_BACK ) && (cnt_v < V_SYNC + V_BACK + V_ADDR )
? : ;
//vga坐标
assign pixel_x = pixel_req ? (cnt_h - (H_SYNC + H_BACK - 'b1)) : 10'd0;
assign pixel_y = pixel_req ? (cnt_v - (V_SYNC + V_BACK - 'b1)) : 10'd0;
//==========================================================================
//== VGA output
//==========================================================================
always @(posedge clk) begin
vga_de <= pixel_req;
end
assign vga_hsync = (cnt_h < H_SYNC) ? : ;
assign vga_vsync = (cnt_v < V_SYNC) ? : ;
assign vga_data = vga_de ? pixel_data : 'b0;
endmodule
六、实例讲解
最近使用的开发板带了一个TFT屏,分辨率为480x272,其显示原理和VGA接口完全相同,因此拿这个屏幕编写一段程序看看。
//==========================================================================
// --- 名称 : TFT_driver.v
// --- 作者 : xianyu_FPGA
// --- 日期 : 2019-01-03
// --- 描述 : TFT显示屏控制器,分辨率480x272,显示三个竖着的彩条
//==========================================================================
module TFT_driver
//=====================<端口声明>===========================================
(
//input -------------------------------------
input wire clk , //时钟,9Mhz
input wire rst_n , //复位,低电平有效
//user interfaces ---------------------------
output wire TFT_req , //输出请求信号
input wire [:] data , //得到图像数据
//output ------------------------------------
output wire TFT_clk , //TFT像素时钟
output wire TFT_de , //TFT使能
output wire TFT_pwm , //TFT背光控制
output wire TFT_hsync , //TFT行同步信号
output wire TFT_vsync , //TFT场同步信号
output reg [:] TFT_rgb //TFT像素输出
);
//=====================<参数定义>===========================================
//480x272 @60 9Mhz --------------------------
parameter H_TOTAL = ; //行扫描周期
parameter H_ADDR = ; //行有效数据
parameter H_FRONT = ; //行显示前沿
parameter H_SYNC = ; //行同步
parameter H_BACK = ; //行显示后沿
parameter V_TOTAL = ; //场扫描周期
parameter V_ADDR = ; //场有效数据
parameter V_FRONT = ; //场显示前沿
parameter V_SYNC = ; //场同步
parameter V_BACK = ; //场显示后沿
//=====================<信号定义>===========================================
//行场信号
reg [:] cnt_h ;
wire add_cnt_h ;
wire end_cnt_h ;
reg [:] cnt_v ;
wire add_cnt_v ;
wire end_cnt_v ;
reg TFT_en ;
wire red_area ;
wire green_area ;
wire blue_area ;
//--------------------------------------------------------------------------
//-- 行、场计数
//--------------------------------------------------------------------------
always @(posedge clk or negedge rst_n) begin
if(!rst_n)
cnt_h <= ;
else if(add_cnt_h) begin
if(end_cnt_h)
cnt_h <= ;
else
cnt_h <= cnt_h + ;
end
end
assign add_cnt_h = ;
assign end_cnt_h = add_cnt_h && cnt_h==H_TOTAL-;
always @(posedge clk or negedge rst_n) begin
if(!rst_n)
cnt_v <= ;
else if(add_cnt_v) begin
if(end_cnt_v)
cnt_v <= ;
else
cnt_v <= cnt_v + ;
end
end
assign add_cnt_v = end_cnt_h;
assign end_cnt_v = add_cnt_v && cnt_v==V_TOTAL-;
//--------------------------------------------------------------------------
//-- TFT请求信号和使能信号,注意时序的对齐
//--------------------------------------------------------------------------
assign TFT_req = (cnt_h >= H_SYNC + H_BACK - ) && (cnt_h < H_SYNC + H_BACK + H_ADDR - ) &&
(cnt_v >= V_SYNC + V_BACK ) && (cnt_v < V_SYNC + V_BACK + V_ADDR )
? : ;
always @(posedge clk) begin
TFT_en <= TFT_req;
end
//--------------------------------------------------------------------------
//-- 行场信号
//--------------------------------------------------------------------------
assign TFT_hsync = (cnt_h < H_SYNC) ? : ;
assign TFT_vsync = (cnt_v < V_SYNC) ? : ;
//--------------------------------------------------------------------------
//-- 其他信号
//--------------------------------------------------------------------------
assign TFT_clk = clk;
assign TFT_de = TFT_en;
assign TFT_pwm = rst_n;
//--------------------------------------------------------------------------
//-- rgb信号
//--------------------------------------------------------------------------
//assign TFT_rgb = TFT_en ? data : 0;
always @(*) begin
if(TFT_en) begin
if(red\_area) begin //红色区域
TFT\_rgb <= 'b11111\_000000\_00000;
end
else if(green\_area) begin //绿色区域
TFT\_rgb <= 'b00000\_111111\_00000;
end
else if(blue\_area) begin //蓝色区域
TFT\_rgb <= 'b00000\_000000\_11111;
end
end
else begin //非显示区域
TFT\_rgb <= ;
end
end
assign red_area = cnt_h >= (H_SYNC + H_BACK) && cnt_h < (H_SYNC + H_BACK + H_ADDR*/) && cnt_v >= (V_SYNC + V_BACK) && cnt_v < (V_SYNC + V_BACK + V_ADDR); assign green_area = cnt_h >= (H_SYNC + H_BACK) && cnt_h < (H_SYNC + H_BACK + H_ADDR*/) && cnt_v >= (V_SYNC + V_BACK) && cnt_v < (V_SYNC + V_BACK + V_ADDR); assign blue_area = cnt_h >= (H_SYNC + H_BACK) && cnt_h < (H_SYNC + H_BACK + H_ADDR*/) && cnt_v >= (V_SYNC + V_BACK) && cnt_v < (V_SYNC + V_BACK + V_ADDR);
endmodule
这个工程还包括顶层top模块,pll分频模块,这些就不展示了。还一点是接口处的user interfaces的信号没有使用到,而是自己通过代码赋的值。工程最终正常运行,显示出从左到右的三个竖彩条,其效果如下所示:
七、后记
这样只是简单的使用了VGA,最终还是要以显示视频或图像为目标,这就涉及到模块之间的交互问题,下次再总结吧!
参考资料:
[1]开源骚客.VGA系列之一:VGA显示驱动篇
[2]NingHeChuan.基于FPGA的VGA显示静态图片
[3]威三学院FPGA教程
[4]袁玉卓, 曾凯锋, 梅雪松. FPGA自学笔记:设计与验证[M]. 北京航空航天出版社, 2017.
手机扫一扫
移动阅读更方便
你可能感兴趣的文章