基于Verilog编程实现VGA的图像显示

目录

一、VGA协议简介

1、概念认识

  • VGA(Video Graphics Array),中文称视频图形阵列,是IBM于1987年提出的一个使用模拟信号的电脑显示标准;
  • VGA这个术语直接用于指称640×480的分辨率。其接口采用VGA标准输出数据的专用接口,共有15针,分成3排,每排5个孔,是显卡上应用最为广泛的接口类型,绝大多数显卡都带有此种接口。它传输红、绿、蓝模拟信号以及同步信号(水平和垂直信号)。
    VGA接口样式
    在这里插入图片描述

2、显示模式

  • VGA最早指的是显示器640X480这种显示模式;
  • VGA技术的应用还主要基于VGA显示卡的计算机、笔记本等设备,而在一些既要求显示彩色高分辨率图像又没有必要使用计算机的设备上,VGA技术的应用却很少见到;
  • 对于一些嵌入式VGA显示系统,可以在不使用VGA显示卡和计算机的情况下,实现VGA图像的显示和控制。系统具有成本低、结构简单、应用灵活的优点,可广泛应用于超市、车站、飞机场等公共场所的广告宣传和提示信息显示,也可应用于工厂车间生产过程中的操作信息显示,还能以多媒体形式应用于日常生活。

3、基础原理

显示与时序
通用VGA显示卡系统主要由控制电路、显示缓存区和视频BIOS(Basic Input Output System即基本输入输出系统)程序三个部分组成。
在这里插入图片描述
控制电路主要完成时序发生、显示缓冲区数据操作、主时钟选择和D/A(Digital to Analog即将数字信号转换为模拟信号)转换等功能;显示缓冲区提供显示数据缓存空间;视频BIOS作为控制程序固化在显示卡的ROM(Read-Only Memory即只读存储器)中。
VGA时序分析
通过对VGA显示卡基本工作原理的分析可知,要实现VGA显示就要解决数据来源、数据存储、时序实现等问题,其中关键还是如何实现VGA时序。
在这里插入图片描述
行时序和帧时序都需要产生同步脉冲(Sync a)、显示后沿(Back porch b)、显示时序段(Display interval c)和显示前沿(Front porch d)四个部分。
行同步时序
在这里插入图片描述
场同步时序
在这里插入图片描述
VGA时序实现
首先,根据刷新频率确定主时钟频率,然后由主时钟频率和图像分辨率计算出行总周期数,再把表1中给出的a、b、c、d各时序段的时间按照主计数脉冲源频率折算成时钟周期数。在CPLD中利用计数器和RS触发器,以计算出的各时序段时钟周期数为基准,产生不同宽度和周期的脉冲信号,再利用它们的逻辑组合构成图2中的a、b、c、d各时序段以及D/A转换器的空白信号BLANK和同步信号SYNC。
SRAM地址
主时钟作为像素点计数脉冲信号,同时提供显存SRAM的读信号和D/A转换时钟,它所驱动的计数器的输出端作为读SRAM的低位地址。行同步信号作为行数计数脉冲信号,它所驱动的计数器的输出端作为读SRAM的高位地址。由于采用两片SRAM,所以最高位地址作为SRAM的片选使用。
数据
如果VGA显示真彩色BMP图像,则要R、G、B三个分量各8位,即24位表示一个像素值,很多情况下还采用32位表示一个像素值。为了节省显存的存储空间,可采用高彩色图像,即每个像素值由16位表示,R、G、B三个分量分别使用5位、6位、5位,比真彩色图像数据量减少一半,同时又能满足显示效果。

4、功能设计

实现VGA显示,除了实现时序控制,还必须有其他功能单元的支持才能实现完整的图像显示。

控制器

  • VGA显示有多种模式,需要通过控制器实现模式间切换,还需要对显示的内容进行接收、处理和显示。所以控制器的性能越高,数据更新和显示效果就越好。
  • VGA显示要求显存速度快、容量大。读速度要达到65MHz以上,存储容量至少要2MB。可采用高速SRAM或SDRAM作为显示数据缓存。
    数模转换器
    DAC
    VGA显示对数模转换DAC有如下要求:
  • 一是高速转换,转换的速度应该在80MHz或以上;
  • 二是同步性好,能保证 R、G、B三路信号的同步性;
  • 三是有相应的精度。可选择一种包括3路8位高速D/A的专用视频芯片
    数据源
    要提高VGA显示的效率,就要不断更新数据,同时还要保证实时性,因此需要非常高的接口速度。VGA显示卡虽可达到100Mbps的数据更新速度,但是一般设备、特别是嵌入式设备达不到这么高的速度,而且大多数情况下也不需要这么高的数据更新率。
    在数据源为低速接口时,可以考虑采用 Flash或者SM存储卡等预先存储一些常用的图像显示数据和字库文件,在更新数据时直接应用这些数据,从而加快显示缓存的更新速度。这样既能满足高分辨率图像的显示,又能满足文字信息数据的快速更新。

二、基于Verilog的VGA彩条图案显示

1、硬件设计

VGA接口部分原理图
在这里插入图片描述
各端口信号的管脚分配
在这里插入图片描述

2、程序设计

芯片型号:EP4CE10F17C8
VGA时序包含三个要素:像素时钟、行场同步信号、以及图像数据。时钟分频模块负责产生像素时钟,VGA驱动模块产生行场同步信号,VGA显示模块输出图像数据。
在这里插入图片描述

FPGA部分包括四个模块,顶层模块(vga_colorbar)、时钟分频模块(vga_pll)、VGA显示模块(vga_display)以及VGA驱动模块(vga_driver)。其中在顶层模块中完成对另外三个模块的例化。

各模块端口及信号连接
在这里插入图片描述
顶层模块代码

 module vga_colorbar( 2 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; //PLL输出稳定信号
 wire rst_n_w; //内部复位信号
 wire [15:0] pixel_data_w; //像素点数据
 wire [ 9:0] pixel_xpos_w; //像素点横坐标
 wire [ 9:0] pixel_ypos_w; //像素点纵坐标 
 
 //*****************************************************
 //** main code
 //***************************************************** 
 //待PLL输出稳定之后,停止复位
 assign rst_n_w = sys_rst_n && locked_w;
 
 vga_pll u_vga_pll( //时钟分频模块
 .inclk0 (sys_clk), 
 .areset (~sys_rst_n),
 
 .c0 (vga_clk_w), //VGA时钟 25M
 .locked (locked_w)
 );

 vga_driver u_vga_
 .vga_clk (vga_clk_w), 
 .sys_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),
 .sys_rst_n (rst_n_w),
 
 .pixel_xpos (pixel_xpos_w),
 .pixel_ypos (pixel_ypos_w),
 .pixel_data (pixel_data_w)
 ); 
 
 endmodule

VGA驱动模块代码

module vga_driver( 2 input vga_clk, //VGA驱动时钟
input sys_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 
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;
//** main code
//VGA行场同步信号
assign vga_hs = (cnt_h <= H_SYNC - 1'b1) ? 1'b0 : 1'b1;
assign vga_vs = (cnt_v <= V_SYNC - 1'b1) ? 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 sys_rst_n) begin 
if (!sys_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
//场计数器对行计数
 always @(posedge vga_clk or negedge sys_rst_n) begin 
 if (!sys_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

VGA显示模块代码

module vga_display( 2 input vga_clk, //VGA驱动时钟
 input sys_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; //分辨率—列
localparam WHITE = 16'b11111_111111_11111; //RGB565白色
localparam BLACK = 16'b00000_000000_00000; //RGB565黑色
localparam RED = 16'b11111_000000_00000; //RGB565 红色
localparam GREEN = 16'b00000_111111_00000; //RGB565 绿色
localparam BLUE = 16'b00000_000000_11111; //RGB565 蓝色

//*****************************************************
//** main code
//*****************************************************
//根据当前像素点坐标指定当前像素点颜色数据,在屏幕上显示彩条
always @(posedge vga_clk or negedge sys_rst_n) begin 
if (!sys_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

3、下载验证

在工程所在的路径下打开vga_colorbar/par文件夹,在里面找到“vga_colorbar.qpf”并双击打开。

注意工程所在的路径名只能由字母、数字以及下划线组成,不能出现中文、空格以及特殊字符等.

在这里插入图片描述
实物连接
在这里插入图片描述
工程打开后通过点击工具栏中的“Programmer”图标打开下载界面,通过“Add File”按 钮选择vga_colorbar/par/output_files目录下的“vga_colorbar.sof”文件。开发板电源打开后,在程序下载界面点击“Hardware Setup”,在弹出的对话框中选择当前的硬件连接为“USB-Blaster[USB-0]”。然后点击“Start”将工程编译完成后得到的sof文件下载到开发板中
在这里插入图片描述

4、效果显示

下载完成后观察显示器显示的图案如图
在这里插入图片描述

三、贪吃蛇游戏设计

VGA显示&游戏状态控制模块
在这里插入图片描述

1、时序分析

  • Sync
  • Back Porch
  • Left Border(针对行同步信号)/Top Border(针对场同步信号)
  • Addr Time(数据有效时间,分辨率就是看这个参数)
  • Right Border(针对行同步信号)/Bottom Border(针对场同步信号)
  • Front Porch
    VGA 真正显示的区域是 VSync 的 Addr Time 和 HSync 的 Addr Time 同时有效区域(低有效)
    在这里插入图片描述

2、VGA参数设定

在这里插入图片描述
选用 640*480 x60Hz 分辨率

`define VGA_640_480_60Hz
//=====================================================================\
// ********** Define Parameter and Internal Signals *************
//=====================================================================/
`ifdef VGA_640_480_60Hz
parameter   Hor_Total_Time  =   800                             ;
parameter   Hor_Addr_Time   =   640                             ;
parameter   Hor_Sync_Time   =   96                              ;
parameter   Hor_Back_Porch  =   40                              ;
parameter   Hor_Left_Border =   8                               ;
parameter   Hor_Start       =   Hor_Sync_Time + Hor_Back_Porch + Hor_Left_Border; 
parameter   Hor_End         =   Hor_Start + Hor_Addr_Time       ;

parameter   Ver_Total_Time  =   525                             ;
parameter   Ver_Addr_Time   =   480                             ;
parameter   Ver_Sync_Time   =   2                               ;
parameter   Ver_Back_Porch  =   25                              ;
parameter   Ver_Top_Border  =   8                               ;
parameter   Ver_Start       =   Ver_Sync_Time + Ver_Back_Porch + Ver_Top_Border; 
parameter   Ver_End         =   Ver_Start + Ver_Addr_Time       ; 
`endif

parameter   Red_Wall        =   16                              ;//边界墙壁厚度
parameter   NONE            =   2'd0                            ;
parameter   HEAD            =   2'd1                            ;
parameter   BODY            =   2'd2 

3、主要代码

行列计数


//h_cnt
always @(posedge clk or negedge rst_n)begin
    if(!rst_n)begin
        h_cnt <= 0;
    end
    else if(add_h_cnt)begin
        if(end_h_cnt)
            h_cnt <= 0;
        else
            h_cnt <= h_cnt + 1;
    end
end

assign add_h_cnt     =       1'b1;
assign end_h_cnt     =       add_h_cnt && h_cnt== Hor_Total_Time-1;

//v_cnt
always @(posedge clk or negedge rst_n)begin 
    if(!rst_n)begin
        v_cnt <= 0;
    end
    else if(add_v_cnt)begin
        if(end_v_cnt)
            v_cnt <= 0;
        else
            v_cnt <= v_cnt + 1;
    end
end

assign add_v_cnt     =       end_h_cnt;
assign end_v_cnt     =       add_v_cnt && v_cnt== Ver_Total_Time-1;

信号产生

//vga_hs
always  @(posedge clk or negedge rst_n)begin
    if(rst_n==1'b0)begin
        vga_hs  <=      1'b0;
    end
    else if(add_h_cnt && h_cnt == Hor_Sync_Time-1)begin
        vga_hs  <=      1'b0;
    end
    else if(end_h_cnt)begin
        vga_hs  <=      1'b1;
    end
end

//vga_vs
always  @(posedge clk or negedge rst_n)begin
    if(rst_n==1'b0)begin
        vga_vs  <=      1'b0;
    end
    else if(add_v_cnt && v_cnt == Ver_Sync_Time-1)begin
        vga_vs  <=      1'b0;
    end
    else if(end_v_cnt)begin
        vga_vs  <=      1'b1;
    end
end

有效区域设定

//vga显示有效区域,由于vga_r/vga_g/vga_b获取数据需要消耗一拍,所以这里要提前一拍
//v_cnt是多周期的,所以不用提前  
assign  display_area = (h_cnt >= Hor_Start - 1 && h_cnt < Hor_End - 1 && v_cnt >= Ver_Start && v_cnt < Ver_End);
//定义墙壁区域
assign  wall_area = ((h_cnt >= Hor_Start - 1 && h_cnt < Hor_Start - 1 + Red_Wall) || (h_cnt >= Hor_End - 1 - Red_Wall && h_cnt < Hor_End - 1)) || ((v_cnt >= Ver_Start && v_cnt < Ver_Start + Red_Wall) || (v_cnt >= Ver_End - Red_Wall && v_cnt < Ver_End));

有效区内像素点的坐标扫描

//pixel_x,pixel_y vga扫描坐标
always  @(posedge clk or negedge rst_n)begin
    if(rst_n == 1'b0)begin
        pixel_x <=  10'd0;
        pixel_y <=  10'd0;
    end
    else if(display_area)begin
        pixel_x <=  h_cnt - Hor_Start + 2;//这里提前了两个时钟,因为前后判断各耽搁一个时钟,具体自己画一下时序图
        pixel_y <=  v_cnt - Ver_Start;//因为v_cnt数据是多个时钟保持的,持续时间长,所以不会耽搁,故不能加2
    end
    else begin
        pixel_x <=  10'd0;
        pixel_y <=  10'd0;
    end
end

像素点输出显示

//vga_r,vga_g,vga_b
always  @(posedge clk or negedge rst_n)begin
    if(rst_n == 1'b0)begin
        vga_r   <=  5'd0;
        vga_g   <=  6'd0;
        vga_b   <=  5'd0;
    end
    else if(wall_area)begin//扫描到墙
        vga_r   <=  5'h1f;
        vga_g   <=  6'h00;
        vga_b   <=  5'h00;
    end
    else if(pixel_x[9:4] == apple_x && pixel_y[9:4] == apple_y)begin//扫描到苹果
        vga_r   <=  5'h1f;
        vga_g   <=  6'h3f;
        vga_b   <=  5'h00;
    end
    else if(object == HEAD)begin//扫描到蛇头
        vga_r   <=  5'h00;
        vga_g   <=  6'h3f;
        vga_b   <=  5'h00;
    end
    else if(object == BODY)begin//扫描到蛇身体
        case({pixel_x[3:0],pixel_y[3:0]})//蛇身四个角做特效处理,
            8'h00,8'h0f,8'hf0,8'hff:begin
                vga_r   <=  5'h00;
                vga_g   <=  6'h00;
                vga_b   <=  5'h00;
            end
            default:begin
                vga_r   <=  5'h00;
                vga_g   <=  6'h3f;
                vga_b   <=  5'h1f;
            end
        endcase
    end//其他显示区域为黑色
    else begin
        vga_r   <=  5'd0;
        vga_g   <=  6'd0;
        vga_b   <=  5'd0;
    end
end

4、效果显示

成型效果如图
在这里插入图片描述

四、总结与参考资料

1、总结

由于笔者也是初次接触,在很多方面,对相关的知识理解与认识不到位,需要在后续的学习过程中逐步加深理解。大家有不知道的,可以自行查阅相关资料。

2、参考资料

基于DE2-115 FPGA开发板的VGA显示.
基于FPGA的VGA显示,简单的历程和注释(DE2-115).
尝试用Verilog驱动VGA.
基于FPGA的贪吃蛇游戏设计(2)VGA显示&游戏状态控制模块的子模块a:play_vga.
Verilog——基于FPGA的贪吃蛇游戏(VGA显示).

猜你喜欢

转载自blog.csdn.net/QWERTYzxw/article/details/117379738