最近一直在玩转自己板子上的VGA模块,前几天做了一个基于Verilog的VGA显示控制,拿出来和大家分享一下。
一、VGA时序
下面的图是本人画了一个晚上的结果,个人认为能够比较详细的阐述VGA的信号时序。
VGA的时序根据不同的显示分辨率和刷新频率会有变化,具体各种类型的时序信息可以参考下面的网站,这里非常详细的说明的每一种显示模式的VGA时序信息。
http://www.tinyvga.com/vga-timing
<?xml:namespace prefix = o ns = "urn:schemas-microsoft-com:office:office" />
二、VGA电平
VSYNC,HSYNC为标准TTL电平,0V~3.3V。
RGB的电平在0V~0.7V之间(0V为黑色,0.7V为全色)。
三、程序顶层框图
VGA产生行同步(HSYNC),场同步信号(VSYNC),并产生每个像素的地址输入单口ROM(显存)中,ROM输出该点需要显示的颜色值。
四、单口ROM(显存)设计
程序的显示模式为800*600,72Hz刷新频率,像素频率为50MHz。每个像素需要显示的颜色存储在单口RAM中,每种颜色用8个字节表示,则如果要显示800*600分辨率,则需要800*600字节(480KB)的单口ROM,由于FPGA内部没有这么大的RAM(我用的是ep<?xml:namespace prefix = st1 ns = "urn:schemas-microsoft-com:office:smarttags" />2c8),因此我把屏幕上100*100个像素组成的矩形作为一个逻辑像素(即显示同一种颜色),这样只要8*6字节(48字节),用FPGA自带的RAM是很容易实现的。
ROM中颜色存储地址表
将全屏划分成8*6的方格,每个方格的颜色存储在ROM中,VGA控制器不断产生行坐标(ROM水平地址)和场坐标(ROM垂直地址),最后组合成ROM实际地址输入ROM中,ROM输出该地址的颜色值,显示在LCD中。
五、程序设计
VGA控制器程序
module VGA(clk,rst_n,hsync,vsync,vga_r,vga_g,vga_b);
input clk; //50MHz
input rst_n; //复位信号
output hsync; //行同步信号
output vsync; //场同步信号
// R、G、B信号输出
output[1:0] vga_r;
output[2:0] vga_g;
output[2:0] vga_b;
//--------------------------------------------------
reg[10:0] x_cnt; //行坐标(这里包括了行同步、后沿、有效数据区、前沿)
reg[9:0] y_cnt; //列坐标(这里包括了场同步、后沿、有效数据区、前沿)
reg[5:0] Xcoloradd;
reg[2:0] Ycoloradd;
parameter
Left = 184,
PixelWidth = 100,
Top = 29;
always @ (posedge clk or negedge rst_n)
if(!rst_n) x_cnt <= 10'd0;
else if(x_cnt == 11'd1040) x_cnt <= 10'd0; //行计数记到1040
else x_cnt <= x_cnt+1'b1;
always @ (posedge clk or negedge rst_n)//产生行地址(ROM水平地址)
if(!rst_n) Xcoloradd <= 6'b000000;
else if(x_cnt >= Left && x_cnt <Left + PixelWidth) Xcoloradd <= 6'b000000;
else if(x_cnt >= Left + PixelWidth && x_cnt <Left + 2*PixelWidth) Xcoloradd <= 6'b000001;
else if(x_cnt >= Left + 2*PixelWidth && x_cnt <Left + 3*PixelWidth) Xcoloradd <= 6'b000010;
else if(x_cnt >= Left + 3*PixelWidth && x_cnt <Left + 4*PixelWidth) Xcoloradd <= 6'b000011;
else if(x_cnt >= Left + 4*PixelWidth && x_cnt <Left + 5*PixelWidth) Xcoloradd <= 6'b000100;
else if(x_cnt >= Left + 5*PixelWidth && x_cnt <Left + 6*PixelWidth) Xcoloradd <= 6'b000101;
else if(x_cnt >= Left + 6*PixelWidth && x_cnt <Left + 7*PixelWidth) Xcoloradd <= 6'b000110;
else if(x_cnt >= Left + 7*PixelWidth && x_cnt <Left + 8*PixelWidth) Xcoloradd <= 6'b000111;
else Xcoloradd <= 6'b110000;//背景颜色地址
always @ (posedge clk or negedge rst_n)
if(!rst_n) y_cnt <= 10'd0;
else if(y_cnt == 10'd666) y_cnt <= 10'd0; //场同步记到666
else if(x_cnt == 11'd1040) y_cnt <= y_cnt+1'b1;//每计数完一行,场同步就加一
always @ (posedge clk or negedge rst_n)//产生列地址(ROM垂直地址)
if(!rst_n) Ycoloradd <= 3'b000;
else if(y_cnt >= Top && y_cnt < Top + PixelWidth) Ycoloradd <= 3'b000;
else if(y_cnt >= Top + PixelWidth && y_cnt < Top + 2*PixelWidth) Ycoloradd <= 3'b001;
else if(y_cnt >= Top + 2*PixelWidth && y_cnt < Top + 3*PixelWidth) Ycoloradd <= 3'b010;
else if(y_cnt >= Top + 3*PixelWidth && y_cnt < Top + 4*PixelWidth) Ycoloradd <= 3'b011;
else if(y_cnt >= Top + 4*PixelWidth && y_cnt < Top + 5*PixelWidth) Ycoloradd <= 3'b100;
else if(y_cnt >= Top + 5*PixelWidth && y_cnt < Top + 6*PixelWidth) Ycoloradd <= 3'b101;
else Ycoloradd <= 3'b110;//背景颜色地址
//--------------------------------------------------
// signal port ROM
wire[7:0] color;
wire[5:0] coloradd;
assign coloradd = {Ycoloradd,3'b000}|Xcoloradd;//将水平地址和垂直地址合成ROM实际地址
sprom u1(coloradd,clk,color);
//---------------------------------------------------
wire valid; //有效数据显示区标志,就是你在液晶屏幕上可以看到的区域
assign valid = (x_cnt > 10'd184) && (x_cnt < 10'd984)
&& (y_cnt > 10'd29) && (y_cnt < 10'd629);
//--------------------------------------------------
reg hsync_r,vsync_r;
always @ (posedge clk or negedge rst_n)
if (!rst_n) begin
hsync_r <= 1'b0;
vsync_r <= 1'b0;
end
else begin
hsync_r <= x_cnt >= 10'd120; //产生hsync信号(行同步)when x_cnt>=50,then hsync_r=1,else 0;低电平同步
vsync_r <= y_cnt >= 10'd6; //产生vsync信号(场同步)my LCD is low sync
end
assign hsync = hsync_r;
assign vsync = vsync_r;
//--------------------------------------------------
//颜色输出
assign vga_r[1] = valid ? color[7] : 1'b0;
assign vga_r[0] = valid ? color[6] : 1'b0;
assign vga_g[2] = valid ? color[5] : 1'b0;
assign vga_g[1] = valid ? color[4] : 1'b0;
assign vga_g[0] = valid ? color[3] : 1'b0;
assign vga_b[2] = valid ? color[2] : 1'b0;
assign vga_b[1] = valid ? color[1] : 1'b0;
assign vga_b[0] = valid ? color[0] : 1'b0;
endmodule
六、运行结果
相机不好,拍的不太清晰,大家将就看看。
七、后记
在这次程序中只在ROM中存储了一些随机的数,因此显示出来是一些小方格,如果ROM做的更大,完全可以存储一幅图像,显示在LCD中。
不过由于由于用ROM做为显存,每次只能显示一幅静态的图像,而且没有加入字符库,不能显示字符,在下次的文章中,我将使用双口RAM,加上Nios II处理器,这样可以方便的显示各种字符。
用户377235 2013-4-28 22:07
不错的文章
用户377235 2012-12-13 20:45
用户333178 2011-6-14 08:54
jinkeyou112_614503351 2010-1-28 21:41
用户1506654 2009-10-6 19:46
用户1506654 2009-10-6 19:39
用户251222 2009-9-14 16:54
用户251222 2009-9-12 15:05
用户198286 2009-5-1 00:25
用户1554589 2009-4-23 18:15