目录
VGA
1 VGA彩条显示实验
2、VGA方块移动实验
3 VGA字符显示实验
VGA是IBM公司在1987年推出的一种视频传输标准,具有分辨率高,显示速率快、颜色丰富等优点。VGA的全称是Video Graphics Array,即视频图形阵列,是一个使用模拟信号进行视频传输的标准。
VGA接口定义及各引脚功能说明如图 18.1.2所示,我们一般只用到其中的1(RED)、2(GREEN)、3(BLUE)、13(HSYNC)、14(VSYNC)信号。引脚1、2、3分别输出红、绿、蓝三原色模拟信号,电压变化范围为 0~0.714V,0V代表无色,0.714V 代表满色;引脚13、14输出TTL电平标准的行/场同步信号。
在VGA视频传输标准中,视频图像被分解为红、绿、蓝三原色信号,经过数模转换之后,在行同步(HSYNC)和场同步(VSYNC)信号的同步下分别在三个独立通道传输。VGA在传输过程中的同步时序分为行时序和场时序,
行同步时序
场同步时序
一行或一场(又称一帧)数据都分为四个部分:低电平同步脉冲、显示后沿、有效数据段以及显示前沿。
行同步信号HSYNC在一个行扫描周期中完成一行图像的显示,其中在a段维持一段时间的低电平用于数据同步,其余时间拉高;在有效数据期间(c段),红绿蓝三原色数据通道上输出一行图像信号,其余时间数据无效。
场同步信号在一个场扫描周期中完成一帧图像的显示,不同的是行扫描周期的基本单位是像素点时钟,即完成一个像素点显示所需要的时间;而场扫描周期的基本单位是完成一行图像显示所需要的时间。
640*480@60表示每一行有640个像素,一帧有480行,每秒显示60帧图像
不同分辨率的VGA时序参数
开拓者开发板上VGA接口,FPGA管脚输出的颜色数据位宽为16bit,数据格式为RGB565,即数据高5位表示红色,中间6位表示绿色,低5位表示蓝色。RGB565格式的数据一共可表示65536种颜色,此外常用的颜色数据格式还有RGB888,数据位宽越大,可以表示的颜色种类就越丰富。VGA传输的是模拟信号,因此需要对FPGA输出的RGB565颜色数据进行数模转换。可以通过专用的视频转换芯片(如ADV7123)来实现,也可以采用利用电阻匹配网络来实现数字信号到模拟信号的转换。
VGA接口原理图
实验任务:
使用开拓者开发板上的VGA接口在显示器上显示彩条,要求分辨率为640*480,刷新速率为60hz。
程序设计:
VGA时序包含三个要素:像素时钟、行场同步信号、以及图像数据,时钟分频模块负责产生像素时钟,VGA驱动模块产生行场同步信号,VGA显示模块输出图像数据。
VGA彩条显示系统框图
由系统框图可知,FPGA部分包括四个模块,顶层模块(vga_colorbar)、时钟分频模块(vga_pll)、VGA显示模块(vga_display)以及VGA驱动模块(vga_driver)。其中在顶层模块中完成对另外三个模块的例化。
时钟分频模块(vga_pll)通过调用锁相环(PLL) IP核来实现。根据实验任务要求的分辨率及刷新速率,通过图可以得知本次实验中VGA显示用到的像素时钟为25.175Mhz,因为分辨率不是很高,我们可以设置锁相环IP核让其输出25Mhz的时钟作为像素时钟
VGA驱动模块(vga_driver)在像素时钟的驱动下,根据VGA时序的参数输出行同步(vga_hs)、场同步(vga_vs)信号。同时VGA驱动模块还需要输出像素点的纵横坐标,供VGA显示模块(vga_display)调用,以绘制彩条图案。
顶层模块原理图
顶层模块的代码如下:
module vga_colorbar( input sys_clk, input sys_rst_n, //VGA接口 output vga_hs,//行同步信号 output vga_vs,//场同步信号 output [15:0] vga_rgb//红绿蓝三原色输出 ); //wire define wire vga_clk_w;//PLL分频得到25Mhz时钟 wire locked_w; //lock信号拉高表示PLL输出稳定信号 wire rst_n_w; //内部复位信号 wire [15:0] pixel_data_w;//像素点数据 wire [9:0] pixel_xpos_w;//像素点横坐标 wire [9:0] pixel_ypos_w;//像素点纵坐标 //待PLL输出稳定后,停止复位 assign rst_n_w=sys_rst_n&&locked_w; //例化时钟分频模块 pll u_pll( .areset(~sys_rst_n), .inclk0(sys_clk), .c0(vga_clk_w),//VGA时钟 25M .locked(locked_w) ); vga_driver u_vga_driver( .vga_clk(vga_clk_w), .rst_n(rst_n_w), .vga_hs(vga_hs), .vga_vs(vga_vs), .vga_rgb(vga_rgb), .pixel_data(pixel_data_w), .pixel_xpos(pixel_xpos_w), .pixel_ypos(pixel_ypos_w) ); vga_display u_vga_display( .vga_clk(vga_clk_w), .rst_n(rst_n_w), .pixel_xpos(pixel_xpos_w), .pixel_ypos(pixel_ypos_w), .pixel_data(pixel_data_w) ); endmodule |
利用IP核进行时钟分频时, 系统上电复位后PLL输出的25Mhz时钟需要经过一段时间才能到达稳定状态。 在PLL输出稳定后,标志信号locked拉高。
由于VGA驱动模块及显示模块均由PLL输出的像素时钟驱动,因此在PLL输出稳定之前,其余模块应保持复位状态。通过将系统复位信号sys_rst_n和PLL输出稳定标志信号locked进行“与”操作,得到内部复位信号rst_n_w。将该信号作为VGA驱动模块及显示模块的复位信号,可避免由于系统复位后像素时钟不稳定造成的VGA时序错误。
VGA驱动模块代码如下
module vga_driver( input vga_clk, input rst_n, //VGA接口 output vga_hs, //行同步信号 output vga_vs, //场同步信号 output [15:0] vga_rgb,//红绿蓝三原色输出
input [15:0] pixel_data,//像素点数据 output [9:0] pixel_xpos,//像素点横坐标 output [9:0] pixel_ypos //像素点纵坐标 ); //parameter define //定义了分辨率为640*480@60时VGA时序中的各个参数 parameter H_SYNC=10'd96;//行同步 parameter H_BACK=10'd48;//行后沿显示 parameter H_DISP=10'd640;//行有效数据 parameter H_FRONT=10'd16;//行显示前沿 parameter H_TOTAL=10'd800;//行扫描周期 parameter V_SYNC=10'd2;//场同步 parameter V_BACK=10'd33;//场显示后沿 parameter V_DISP=10'd480;//场有效数据 parameter V_FRONT=10'd10;//场显示前沿 parameter V_TOTAL=10'd525;//场扫描周期 //reg define reg [9:0] cnt_h; reg [9:0] cnt_v; //wire define wire vga_en; wire data_req; //VGA行场同步信号 assign vga_hs=(cnt_h<H_SYNC)?1'b0:1'b1; assign vga_vs=(cnt_v<V_SYNC)?1'b0:1'b1; //RGB565数据输出使能信号 assign vga_en=(((cnt_h>=H_SYNC+H_BACK)&&(cnt_h<H_SYNC+H_BACK+H_DISP)) &&((cnt_v>=V_SYNC+V_BACK)&&(cnt_v<V_SYNC+V_BACK+V_DISP))) ?1'b1:1'b0; //RGB565数据输出 assign vga_rgb=vga_en?pixel_data:16'd0; //像素点颜色数据输入请求信号 assign data_req=(((cnt_h>=H_SYNC+H_BACK-1'b1)&&(cnt_h<H_SYNC+H_BACK+H_DISP-1'b1)) &&((cnt_v>=V_SYNC+V_BACK)&&(cnt_v<V_SYNC+V_BACK+V_DISP))) ?1'b1:1'b0; //像素点坐标 assign pixel_xpos=data_req?(cnt_h-(H_SYNC+H_BACK-1'b1)):10'd0; assign pixel_ypos=data_req?(cnt_v-(V_SYNC+V_BACK-1'b1)):10'd0; //行计数器对像素时钟计数 always @(posedge vga_clk or negedge rst_n) begin if(!rst_n) cnt_h<=10'd0; else begin if(cnt_h<H_TOTAL-1'b1) cnt_h<=cnt_h+1'b1; else cnt_h<=10'd0; end end //场计数器对行计数, //扫描一个行后cnt_v加1,计满一个行扫描周期后清零并重新计数 always@(posedge vga_clk or negedge rst_n) begin if(!rst_n) cnt_v<=10'd0; else if(cnt_h==H_TOTAL-1'b1) begin if(cnt_v<V_TOTAL-1'b1) cnt_v<=cnt_v+1'b1; else cnt_v<=10'd0; end end endmodule |
由于坐标输出后下一个时钟周期才能接收到像素点的颜色数据,因此数据请求信号data_req比数据输出使能信号vga_en提前一个时钟周期。
VGA显示模块的代码
module vga_display( input vga_clk,//VGA驱动时钟 input rst_n,//复位信号
input [9:0] pixel_xpos,//像素点横坐标 input [9:0] pixel_ypos,//像素点纵坐标 output reg [15:0] pixel_data //像素点数据 ); parameter H_DISP=10'd640; //分辨率一行 parameter V_DISP=10'd480; //分辨率一列 parameter WHITE=16'b11111_111111_11111;//RGB565白色 parameter BLACK=16'd00000_000000_00000;//RGB565黑色 parameter RED=16'd11111_000000_00000;//红色 parameter GREEN=16'b00000_111111_00000;//绿色 parameter BLUE=16'b00000_000000_11111;//蓝色 //根据当前像素点坐标指定当前像素点颜色数据,在屏幕上显示彩条 always @(posedge vga_clk or negedge rst_n) begin if(!rst_n) pixel_data<=16'd0; else begin if((pixel_xpos>=0)&&(pixel_xpos<(H_DISP/5)*1)) pixel_data<=WHITE; else if((pixel_xpos>=(H_DISP/5)*1)&&(pixel_xpos<(H_DISP/5)*2)) pixel_data<=BLACK; else if((pixel_xpos>=(H_DISP/5)*2)&&(pixel_xpos<(H_DISP/5)*3)) pixel_data<=RED; else if((pixel_xpos>=(H_DISP/5)*3)&&(pixel_xpos<(H_DISP/5)*4)) pixel_data<=GREEN; else pixel_data<=BLUE;
end end endmodule |
实验任务:
使用开拓者开发板上的VGA接口在显示器上显示一个不停移动的方块,要求方块移动到边界处时能够改变移动方向。显示分辨率为640*480@60;
程序设计:
在“VGA彩条显示实验”中,我们利用VGA驱动模块输出的像素点的横坐标,在VGA显示模块中完成了彩条图案的绘制。而在本次实验中,为了完成方块的显示,需要同时使用像素点的横坐标和纵坐标来绘制方块所在的矩形区域,另外还需要知道矩形区域左上角的顶点坐标。由于VGA显示的图像在行场同步信号的同步下不停的刷新,因此只要连续改变方块左上角顶点的坐标,并在新的坐标点处重新绘制方块,即可实现方块移动的效果。
VGA显示模块代码如下:
module vga_display( input vga_clk,//VGA驱动时钟 input rst_n,//复位信号
input [9:0] pixel_xpos,//像素点横坐标 input [9:0] pixel_ypos,//像素点纵坐标 output reg [15:0] pixel_data //像素点数据 ); parameter H_DISP=10'd640; //分辨率一行 parameter V_DISP=10'd480; //分辨率一列 parameter SIDE_W=10'd40; //边框宽度 parameter BLOCK_W=10'd40; //方框宽度 parameter WHITE=16'b11111_111111_11111;//背景颜色白色 parameter BLACK=16'd00000_000000_00000;//方块颜色黑色 parameter BLUE=16'b00000_000000_11111; //边框颜色蓝色 //reg define reg [9:0] block_x; //方块左上角横坐标 reg [9:0] block_y; //方块左上角纵坐标 reg [21:0] div_cnt; //时钟分频计数器 reg h_direct; //方块水平移动方向,1:右移,0:左移 reg v_direct; //方块竖直移动方向,1:向下,0:向上 //wire define wire move_en; //方块移动使能信号,频率为100hz assign move_en=(div_cnt==22'd250000-1'b1)?1'b1:1'b0; //通过对vga驱动时钟计数,实现时钟分频,频率为100hz always @(posedge vga_clk or negedge rst_n) begin if(!rst_n) div_cnt<=22'd0; else begin if(div_cnt<22'd25000-1'b1) div_cnt<=div_cnt+1'b1; else div_cnt<=22'd0; //计数达10ms清零 end end //当方块移动到边界时,改变移动方向 always @(posedge vga_clk or negedge rst_n) begin if(!rst_n) begin h_direct<=1'b1; //方块初始水平向右移动 v_direct<=1'b1; //方块初始竖直向下移动 end else begin if(block_x==SIDE_W-1'b1) //到达左边界时,水平向右 h_direct<=1'b1; else if(block_x==H_DISP-SIDE_W-BLOCK_W) //到达右边界时,水平向左 h_direct<=1'b0; else h_direct<=h_direct; if(block_y==SIDE_W-1'b1) //到达上边界时,竖直向下 v_direct<=1'b1; else if(block_y==H_DISP-SIDE_W-BLOCK_W) //到达下边界时,竖直向上 v_direct<=1'b0; else v_direct<=v_direct; end end //根据方块移动方向,改变其纵横坐标 always@(posedge vga_clk or negedge rst_n) begin if(!rst_n) begin block_x<=22'd100;//方块初始位置横坐标 block_y<=22'd100;//方块初始位置纵坐标 end else if(move_en) begin if(h_direct) block_x<=block_x+1'b1;//方块向右移动 else block_y<=block_y+1'b1;//方块向左移动 if(v_direct) block_y<=block_y+1'b1;//方向向下移动 else block_y<=block_y-1'b1;//方向向上移动 end else begin block_x<=block_x; block_y<=block_y; end end //给不同的区域绘制不同的颜色 always@(posedge vga_clk or negedge rst_n) begin if(!rst_n) pixel_data<=BLACK; else begin if((pixel_xpos<SIDE_W)||(pixel_xpos>=H_DISP-SIDE_W) ||(pixel_ypos<SIDE_W)||(pixel_ypos>=V_DISP-SIDE_W)) pixel_data<=BLUE;//绘制边框为蓝色 else if((pixel_xpos>=block_x)||(pixel_xpos<block_x+BLOCK_W) ||(pixel_ypos>=block_y)||(pixel_ypos<block_y+BLOCK_W)) pixel_data<=BLACK;//绘制边框为黑色 else pixel_data<=WHITE;//绘制背景为白色 end end endmodule |
由于VGA驱动时钟相对于方块移动速度而言过快,我们通过计数器对时钟计数,得到一个频率为100hz的脉冲信号move_en,用它作为使能信号来控制方块的移动。根据方块的移动方向,在使能信号move_en到来时改变其左上角顶点的纵横坐标值。当move_en的频率为100hz时,方块每秒钟在水平和竖直方向上分别移动100个像素点的距离,也可以通过调整move_en的频率,来加快或减慢方块移动的速度。
实验任务:
实验任务是使用开拓者开发板上的VGA接口在显示器的屏幕中心位置显示汉字“天津科技大学”。显示分辨率为640*480,刷新速率为60hz,每个汉字的大小为16*16。
字符(包括汉字、字母和符号等)的本质都是点阵,在VGA屏幕上体现为字符显示区域内像素点的集合。字符的大小决定了字符显示区域内像素点的数目,而字符的样式(字体、颜色等)则决定了各像素点的颜色值。因此,在显示字符之前,我们需要先指定字符的大小、样式,然后获取该字符的点阵,这个过程我们称之为“提取字模”,或简称“取模”。
一般使用0和1的组合来描述字符的点阵排列:点阵中每个像素点用一位(1 bit)数据来表示, 其中用于表征字符的像素点用数字1来表示, 其他的像素点作为背景用数字0来表示。“每行显示数据”是以字节(Byte)为单位的,而一个字节的数据为8个bit,即可以表示一行点阵中的8个像素点。
实验程序如下:
module vga_display( input vga_clk,//VGA驱动时钟 input rst_n,//复位信号
input [9:0] pixel_xpos,//像素点横坐标 input [9:0] pixel_ypos,//像素点纵坐标 output reg [15:0] pixel_data //像素点数据 ); parameter H_DISP=10'd640; //分辨率一行 parameter V_DISP=10'd480; //分辨率一列 parameter POS_X=10'd288; //字符区域起始点横坐标 parameter POS_Y=10'd232; //字符区域起始点纵坐标 parameter WIDTH=10'd96;//字符区域宽度 parameter HEIGHT=10'd16;//字符区域高度 parameter RED=16'b11111_000000_00000;//字符颜色红色 parameter BLACK=16'd00000_000000_00000;//屏幕颜色黑色 parameter BLUE=16'b00000_000000_11111; //字符域背景颜色蓝色 //reg define reg [95:0] char [15:0]; //二维字符数组 //wire define wire [9:0] x_cnt; wire [9:0] y_cnt; assign x_cnt=pixel_xpos-POS_X;//像素点相对于字符区域起始点水平坐标 assign y_cnt=pixel_ypos-POS_Y;//像素点相对于字符区域起始点竖直坐标 //给字符数组赋值,显示汉字“天津科技大学”,汉字大小为16*16 always @(posedge vga_clk) begin char[0]<=96'h000000400810102001002208; char[1]<=96'h3FF820401D10102001001108; char[2]<=96'h010013F8F090102001001110; char[3]<=96'h01001048109013FE01000020; char[4]<=96'h010087FE1010FC2001007FFE; char[5]<=96'h01004048FD101020FFFE4002; char[6]<=96'hFFFE4BF81090102001008004; char[7]<=96'h01000840389015FC01001FE0; char[8]<=96'h028010403410188402800040; char[9]<=96'h028013F8501E308802800180; char[10]<=96'h0440E04053F0D0480440FFFE; char[11]<=96'h044020409010105004400100; char[12]<=96'h082027FC1010102008200100; char[13]<=96'h101020401010105010100100; char[14]<=96'h200820401010518820080500; char[15]<=96'hC006004010102606C0060200;
end //给不同的区域绘制不同的颜色 always@(posedge vga_clk or negedge rst_n) begin if(!rst_n) pixel_data<=BLACK; else begin if((pixel_xpos>=POS_X)||(pixel_xpos<POS_X+WIDTH) ||(pixel_ypos>=POS_Y)||(pixel_ypos<POS_Y+HEIGHT))begin if(char[y_cnt][10'd95-x_cnt]) pixel_data<=RED;//绘制字符为蓝色 else pixel_data<=BLUE;//绘制字符区域背景为黑色 end else pixel_data<=BLACK;//绘制屏幕为黑色 end end endmodule |