VGA驱动
VGA接口是一个常用到的接口,今天笔者带大家来探讨下怎么样编写VGA驱动。从数据手册上我们可以看到VGA接口一共有15个引脚,但其实真正需要我们去操作的只有5个引脚,他们分别是行同步信号,场同步信号,以及RGB三基色这三个信号。我们需要通过FPGA去模拟行同步信号以及场同步信号的时序,并且控制RGB的信号,来输出图像到显示屏上。
一个需要提醒的点就是,我们使用FPGA输出的是数字信号,而驱动显示屏的RGB信号必须是模拟信号,所以在这里,我们需要一个DA转换芯片,或者使用电阻网络来完成DA转换,把数字信号转化为模拟信号,才可以获得彩色的信号。
好了,我们以显示一张800*600*60Hz的图像为例,首先我们来看一下VGA的时序是怎么样的
从上图可以看到,对于行同步信号,分为同步脉冲a,显示后沿b,显示时序段c和显示前沿d。一共有a,b,c和d四个段,其中a段是需要把电平拉低,而b,c,d段则需要把电平拉高,而且我们看到只有在c段,视频帧才是高电平的,证明在这时,RGB信号才有效。
对于场同步信号,分析也与行同步信号类似。
从这里我们可以看到,对于一张800*600*60Hz的图像来说,其一行的像素点应该为1056个,有效的像素段为c段的800个,而一列的像素点为628个,其有效的像素段为c段的600个。通过计算 1056*628*60=39790080=39.79008M,我们可以得知,一秒需要刷新接近40M个像素点,所以我们需要一个40MHz的时钟信号。对于行同步信号来说,他的一个时序周期为1056个时钟脉宽长度,而对于场同步信号来说,他的一个时序周期则为1056*628=663168个时钟脉宽长度。
下面笔者以一个屏幕显示蓝绿红彩条的图像为例,这个让笔者想起了翡翠台的台标。
我们首先以Top-down的设计方法,设计出整个工程的模块框图
整个设计分为3个模块,第一个模块为PLL模块,把50MHz的时钟4倍频为200MHz,再5分频得到40MHz的时钟,输入到VGA控制模块以及输出模块。
第2个模块为VGA控制模块,主要是模拟行同步信号HS和场同步信号VS的时序,并告诉VGA输出模块,什么时候该输出RGB信号。
第3个模块则是VGA输出模块,在VGA控制模块的操控下,有序输出RGB信号。
对于PLL模块,我们使用IP核得到1个40MHz的时钟,这是非常简单的。
对于VGA控制模块,代码具体如下:
module vga_ctrl(clk,rst_n,hs,vs,valid,x,y);
input clk;
input rst_n;
output hs;//输出行同步信号
output vs;//输出场同步信号
output valid;//行同步信号和场同步信号在都在c段时拉高
output [10:0] x,y;//行坐标x为1-800,列坐标为y为1-600
reg [10:0]count_h;//行同步信号计时器
reg [10:0]count_v;//场同步信号计时器
reg valid;
always @(posedge clk or negedge rst_n)
if (!rst_n)
count_h<=11'd0;
else if (count_h==11'd1055)
count_h<=11'd0;
else
count_h<=count_h+11'd1;
always @(posedge clk or negedge rst_n)
if (!rst_n)
count_v<=11'd0;
else if (count_v==11'd627)
count_v<=11'd0;
else if(count_h==11'd1055)
count_v<=count_v+11'd1;
always @(posedge clk or negedge rst_n)
if (!rst_n)
valid<=1'b0;
else if(count_h>=11'd215&&count_h<11'd1015&&count_v>=11'd3&&count_v<11'd626)
valid<=1'b1;
else
valid<=1'b0;
assign hs=(count_h<=11'd127)? 1'b0:1'b1;//a段拉低,b,c,d拉高
assign vs=(count_v<=11'd3)? 1'b0:1'b1;//a段拉低,b,c,d拉高
assign x=valid?count_h-11'd215:11'd0;//x坐标,仅c段有效
assign y=valid?count_v-11'd2:11'd0;//y坐标,仅c段有效
endmodule
对于 always @(posedge clk or negedge rst_n)
if (!rst_n)
count_h<=11'd0;
else if (count_h==11'd1055)
count_h<=11'd0;
else
count_h<=count_h+11'd1;
always @(posedge clk or negedge rst_n)
if (!rst_n)
count_v<=11'd0;
else if (count_v==11'd627)
count_v<=11'd0;
else if(count_h==11'd1055)
count_v<=count_v+11'd1;
这一段代码,count_h计数器在时钟每来一次便自加1,周期为前面所说的1056个时钟脉冲宽度,count_v,也一样,不过是在count_h完成一次周期时才自加1,所以他的周期为1056*628=663168个时钟脉冲宽度。
always @(posedge clk or negedge rst_n)
if (!rst_n)
valid<=1'b0;
else if(count_h>=11'd215&&count_h<11'd1015&&count_v>=11'd3&&count_v<11'd626)
valid<=1'b1;
else
valid<=1'b0;
这一段代码valid仅在行同步信号和场同步信号均在c段时才为高电平,否则为低电平。
VGA的控制模块分析就到此结束,下面看一下VGA输出模块的代码实现
module vga_output(clk,rst_n,x,y,valid,rs,gs,bs);
input clk,rst_n;
input [10:0]x,y;
input valid;
output [4:0]rs;//RPB565信号,R信号为5bit
output [5:0]gs;//RPB565信号,G信号为6bit
output [4:0]bs;//RPB565信号,B信号为5bit
reg [4:0]rs;
reg [5:0]gs;
reg [4:0]bs;
always @(posedge clk or negedge rst_n)
if (!rst_n)
begin
rs<=5'd0;
gs<=5'd0;
bs<=5'd0;
end
else if (valid&&x>=11'd0&&x<=11'd800)
begin
if (y>=11'd0&&y<=11'd200)
begin
rs<=5'd0;
gs<=6'd0;
bs<=5'b11111;
end
else if (y>11'd200&&y<=11'd400)
begin
rs<=5'd0;
gs<=6'b111111;
bs<=5'd0;
end
else if (y>11'd400&&y<=11'd600)
begin
rs<=5'b11111;
gs<=6'd0;
bs<=5'd0;
end
end
endmodule
因为我们实现的是蓝绿红彩条,最上面的矩形为蓝色,矩形范围为y从0到200,蓝基色5个位全部都取1,而其他两个基色则为0。中间的矩形为绿色,矩形范围为y从200到400,绿基色6个位全部取1,而其他两个基色则为0。最下面的矩形则为蓝色,矩形范围为y从400到600,红基色5个位全部取1,则可以完成彩条的逻辑了。
最后把模块例化
module vga(clk,rst_n,vs,hs,rs,gs,bs);
input clk;
input rst_n;
output vs,hs;
wire clk_40MHz;
wire [10:0]x,y;
wire valid;
output [4:0]rs;
output [5:0]gs;
output [4:0]bs;
pll_ctrl pll_ctrl_inst (
.inclk0 ( clk ),
.c0 ( clk_40MHz )
);
vga_ctrl i1(
.clk( clk_40MHz ),
.rst_n( rst_n ),
.hs( hs ),
.vs( vs ),
.valid( valid),
.x( x ),
.y( y )
);
vga_output i2(
.clk( clk_40MHz ),
.rst_n( rst_n ),
.x( x ),
.y( y ),
.valid( valid ),
.rs( rs ),
.gs( gs ),
.bs( bs )
);
endmodule
实验的效果如图所示
广州老伯 2015-11-30 23:04
用户1836944 2015-11-30 10:30
博主麻烦问下,为什么在行时序里面行后沿反而在行前沿的前面。
用户377235 2015-11-12 19:07
用户1573777 2015-11-11 17:19
用户1136505 2015-11-11 10:21