VGA显示器工作原理
vga显示器显示图像,是采用电子枪逐行扫描的方式,在行场同步信号作用下,将构成图像的像素点从左到右,从上到下一点一点地显示在屏幕上,只要扫描速率够快,人眼看到的就是一整幅图像。下面结合行场同步信号进行讲解。
行同步信号
行同步信号包含以下阶段:同步、后沿、左边框、有效图像、右边框、前沿
行同步信号的工作时钟周期为像素周期,也就是显示一个像素点所需要的时间。
行同步信号控制电子枪从左到右的扫描,当每扫描完一行后,电子枪指向显示器的最右端(不是屏幕的最右端,下面会解释),在同步脉冲的作用下,电子枪从最右端回到最左端,同步脉冲的持续时间就是电子枪返回所需要的时间,在这段时间电子枪处于关闭状态。电子枪返回最左端后,关闭状态需要持续一段时间才能开启扫描,这段时间称为后沿。开启扫描后,并不是立即扫描屏幕上的像素点,而是先要扫描经过一个左边框(想象一下以前台式电视机,这个左边框就是包裹着屏幕的物理边框),在经过左边框后电子枪就扫描到了屏幕上,这时可以输出有效图像了,在有效图像输出完毕后继续扫描经过一个右边框(也是物理边框)后电子枪会关闭(这时电子枪指向显示器最右端),从电子枪关闭到下一个同步脉冲之间的时间间隔称为前沿。在前沿、后沿、同步这3个阶段电子枪都是关闭的,用以实现消隐。
场同步信号
场同步信号包含以下阶段:同步、后沿、上边框、有效图像、底边框、前沿
场同步信号的工作时钟周期是行扫描周期,也就是完成一行扫描所需要的时间。
场同步信号控制电子枪从上到下的扫描,每当一帧图像扫描完毕后,电子枪指向显示器的最下端,在同步脉冲的作用下,电子枪从最下端回到最上端,同步脉冲的持续时间就是电子枪返回所需要的时间,在这段时间电子枪处于关闭状态。电子枪返回最上端后,关闭状态需要持续一段时间才能开启扫描,这段时间称为后沿。开启扫描后,并不是立即扫描屏幕上的像素点,而是先要扫描经过一个上边框,在经过上边框后电子枪就扫描到了屏幕上,这时可以输出有效图像了,在有效图像输出完毕后继续扫描经过一个下边框后电子枪会关闭,从电子枪关闭到下一个同步脉冲之间的时间间隔称为前沿。在前沿、后沿、同步这3个阶段电子枪都是关闭的,用以实现消隐。
VGA565模块框图
Clk_gen:锁相环模块,产生时钟
Vga_ctrl:vga控制模块,产生行场同步信号和坐标数据并传输行场同步信号和图像数据,坐标数据需要提前产生
Vga_pic:根据接收到的坐标经过1个像素时钟后输出数据
像素时钟计算
表1
采用640x480@60模式,像素时钟频率Vga_clk = 800x525x60 = 25200000 ≈25.175Mhz(误差忽略不计)
输入50Mhz,用锁相环2分频,输出25Mhz
一、vga_ctrl
先上我(火)哥的代码,慢慢拆析
module vga_ctrl
(
input wire vga_clk ,
input wire sys_rst_n ,
input wire [15:0] pix_data ,
output wire [9:0] pix_x ,
output wire [9:0] pix_y ,
output wire hsync ,
output wire vsync ,
output wire [15:0] vga_rgb
);
parameter H_SYNC = 10'd96 ,//同步
H_BACK = 10'd40 ,//后沿
H_LEFT = 10'd8 ,//左边框
H_VALID = 10'd640,//有效图像
H_RIGHT = 10'd8 ,//右边框
H_FRONT = 10'd8 ,//前沿
H_TOTAL = 10'd800;//同步+后沿+左边框+有效图像+右边框+前沿
parameter V_SYNC = 10'd2 ,//同步
V_BACK = 10'd25 ,//后沿
V_TOP = 10'd8 ,//上边框
V_VALID = 10'd480,//有效图像
V_BOTTOM= 10'd8 ,//底边框
V_FRONT = 10'd2 ,//前沿
V_TOTAL = 10'd525;//总共
reg [9:0] cnt_h ;
reg [9:0] cnt_v ;
wire pix_data_req;
wire rgb_valid ;
always@(posedge vga_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
cnt_h <= 10'd0;
else if(cnt_h == H_TOTAL - 1'b1)
cnt_h <= 10'd0;
else
cnt_h <= cnt_h + 1'b1;
always@(posedge vga_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
cnt_v <= 10'd0;
else if((cnt_v == V_TOTAL - 1'b1) && (cnt_h == H_TOTAL - 1'b1))
cnt_v <= 10'd0;
else if(cnt_h == H_TOTAL - 1'b1)
cnt_v <= cnt_v + 1'b1;
else
cnt_v <= cnt_v;
assign rgb_valid = ((cnt_h >= H_SYNC + H_BACK + H_LEFT)
&& (cnt_h < H_SYNC + H_BACK + H_LEFT + H_VALID)
&&(cnt_v >= V_SYNC + V_BACK + V_TOP)
&&(cnt_v < V_SYNC + V_BACK + V_TOP + V_VALID))
? 1'b1 : 1'b0;
assign pix_data_req = ((cnt_h >= H_SYNC + H_BACK + H_LEFT - 1'b1)
&& (cnt_h < H_SYNC + H_BACK + H_LEFT + H_VALID - 1'b1)
&&(cnt_v >= V_SYNC + V_BACK + V_TOP)
&&(cnt_v < V_SYNC + V_BACK + V_TOP + V_VALID))
? 1'b1 : 1'b0;
assign pix_x = (pix_data_req == 1'b1) ? (cnt_h - (H_SYNC + H_BACK + H_LEFT - 1'b1)) : 10'h3ff;
assign pix_y = (pix_data_req == 1'b1) ? (cnt_v - (V_SYNC + V_BACK + V_TOP)) : 10'h3ff;
assign hsync = (cnt_h <= H_SYNC - 1'b1) ? 1'b1 : 1'b0;
assign vsync = (cnt_v <= V_SYNC - 1'b1) ? 1'b1 : 1'b0;
assign vga_rgb = (rgb_valid == 1'b1) ? pix_data : 16'h0000;
endmodule
总代码
行同步信号hsync和场同步信号vsync需要一个行计数器cnt_h和一个场计数器cnt_v
根据表1,640x480@60模式需要行计数器cnt_h计数800个,场计数器cnt_v计数525个,其中,计数器计数过程中可分为各个阶段,根据表1,定义各个阶段参数
行计数器参数
parameter H_SYNC = 10'd96 ,//同步
H_BACK = 10'd40 ,//后沿
H_LEFT = 10'd8 ,//左边框
H_VALID = 10'd640,//有效图像
H_RIGHT = 10'd8 ,//右边框
H_FRONT = 10'd8 ,//前沿
H_TOTAL = 10'd800;//同步+后沿+左边框+有效图像+右边框+前沿
场计数器参数
parameter V_SYNC = 10'd2 ,//同步
V_BACK = 10'd25 ,//后沿
V_TOP = 10'd8 ,//上边框
V_VALID = 10'd480,//有效图像
V_BOTTOM= 10'd8 ,//底边框
V_FRONT = 10'd2 ,//前沿
V_TOTAL = 10'd525;//总共
行计数器
always@(posedge vga_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
cnt_h <= 10'd0;
else if(cnt_h == H_TOTAL - 1'b1)
cnt_h <= 10'd0;
else
cnt_h <= cnt_h + 1'b1;
场计数器
always@(posedge vga_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
cnt_v <= 10'd0;
else if((cnt_v == V_TOTAL - 1'b1) && (cnt_h == H_TOTAL - 1'b1))
cnt_v <= 10'd0;
else if(cnt_h == H_TOTAL - 1'b1)
cnt_v <= cnt_v + 1'b1;
else
cnt_v <= cnt_v;
根据计数器产生行场同步信号:
assign hsync = (cnt_h <= H_SYNC - 1'b1) ? 1'b1 : 1'b0;
assign vsync = (cnt_v <= V_SYNC - 1'b1) ? 1'b1 : 1'b0;
信号有效区间
行计数器和场计数器都计数到有效图像阶段信号有效
行有效区间(列坐标有效区间):
cnt_h >= H_SYNC + H_BACK + H_LEFT
cnt_h < H_SYNC + H_BACK + H_LEFT + H_VALID
场有效区间(行坐标有效区间):
cnt_v >= V_SYNC + V_BACK + V_TOP
cnt_v < V_SYNC + V_BACK + V_TOP + V_VALID
assign rgb_valid = ((cnt_h >= H_SYNC + H_BACK + H_LEFT)
&& (cnt_h < H_SYNC + H_BACK + H_LEFT + H_VALID)
&&(cnt_v >= V_SYNC + V_BACK + V_TOP)
&&(cnt_v < V_SYNC + V_BACK + V_TOP + V_VALID))
? 1'b1 : 1'b0;
在有效区间内,rgb_valid为高电平,其用来控制数据从输入到输出的传输,要求数据提前准备好
assign vga_rgb = (rgb_valid == 1'b1) ? pix_data : 16'h0000;
要提前准备数据,就要提前1个像素周期产生坐标信号,因为Vga_pic需要经过1个像素周期来生成数据。
像素坐标的生成:
表1
信号在有效区内输出坐标信号,在无效区内输出无效坐标信号(全1)以示无效。进入有效区内坐标从(0,0)开始计数。
若不提前产生坐标数据,根据表1,列坐标=行计数器-(同步+后沿+左边框) ;这点好理解,若同步+后沿+左边框=96+40+8=144,行计数器=144,那相减后为0,表示第0列。同理,行坐标=场计数器-(同步+后沿+上边框)。
若要提前产生坐标数据,重点在于操作行计数器。比如,对于640x480@60,若当前坐标为(1,1),那么下一个坐标就是(1,2),改变的是列,而行不变,若当前正在输出坐标(0,0)的数据,那么当前的坐标输出就应为(0,1),以通知Vga_pic模块提前准备好(0,1)的数据。若当前坐标为(0,639),下一个坐标就应为(1,0)。若当前坐标为(439,639),下一个坐标就应为(0,0)。那么如何提前产生像素坐标呢?
每切换一行,行计数器都是从0开始计数,行坐标不变,对应的列坐标从无效区逐渐计数进入有效区,可以将列坐标有效区向左平移一个像素周期,以提前产生像素坐标,如图1。
图1
此时的行有效区间:
cnt_h >= H_SYNC + H_BACK + H_LEFT-1
cnt_h < H_SYNC + H_BACK + H_LEFT + H_VALID-1
此时的场有效区间(不变):
cnt_v >= V_SYNC + V_BACK + V_TOP
cnt_v < V_SYNC + V_BACK + V_TOP + V_VALID
此时的有效坐标信号:
列坐标=行计数器-(同步+后沿+左边框-1)
行坐标=场计数器-(同步+后沿+上边框)
进入坐标有效区域内需要一个指示信号,在有效区内指示信号拉高
assign pix_data_req = ((cnt_h >= H_SYNC + H_BACK + H_LEFT - 1'b1)
&& (cnt_h < H_SYNC + H_BACK + H_LEFT + H_VALID - 1'b1)
&&(cnt_v >= V_SYNC + V_BACK + V_TOP)
&&(cnt_v < V_SYNC + V_BACK + V_TOP + V_VALID))
? 1'b1 : 1'b0;
该指示信号用来控制坐标输出
assign pix_x = (pix_data_req == 1'b1) ? (cnt_h - (H_SYNC + H_BACK + H_LEFT - 1'b1)) : 10'h3ff;
assign pix_y = (pix_data_req == 1'b1) ? (cnt_v - (V_SYNC + V_BACK + V_TOP)) : 10'h3ff;
至此,vga_ctrl讲解完毕。
二、vga_pic
该模块的功能就是根据输入的坐标数据经过一个像素时钟后输出对应坐标的图像数据,具体实现五花八门。若觉得一个像素周期时间太少,可修改vga_ctrl模块。