FPGA_Verilog学习之旅(2)---浅谈VGA


前言

前段时间用开发板玩了玩VGA,显示了一些彩条、汉字、图片等,后期想用VGA做一些复杂点的东西,比如贪吃蛇游戏等,暂且先总结一下这几个显示小实验,其中详细一下谈图片显示的部分,用到了片内的ROM_IP核
---------此篇文章主要内容:FPGA-VGA学习笔记


一、VGA是什么

VGA(Video Graphics Array):视频图像阵列,一种使用模拟信号进行视频传输的标准。
在这里插入图片描述

  • 接口定义

在这里插入图片描述

VGA接口定义如下:

引脚定义引脚名称功能说明
Pin1RED红色
Pin2GREEN绿色
Pin3BLUE蓝色
Pin4ID2地址码2
Pin5GND行同步地
Pin6RGND红色地
Pin7GGND绿色地
Pin8BGND蓝色地
Pin9KEY保留
Pin10GND场同步地
Pin11ID0地址码0
Pin12ID1地址码1
Pin13HSYNC行同步
Pin14VSYNC场同步
Pin15ID3地址码3

注: 在引脚1、2、3上输出视频的三原色,是一个模拟信号,信号的电压变化范围在0-0.714V之间,可将数字信号进行数模转换,以模拟信号输出;引脚13、14可接TTL电平,即可以直接连接数字信号输出。

二、VGA–工作流程

1、VGA扫描方式

扫描从屏幕的左上方开始,从左到右,从上到下进行扫描,每扫描完一行,电子束都回到屏幕的左边下一行的起始位置,在这期间,CRT对电子束进行消隐。每行结束时,用行同步信号进行同步;扫描完所有行(整屏一帧图像),用场同步信号进行场同步,并使扫描回到屏幕的左上方,同时进行场消隐,预备下一场的扫描。
在这里插入图片描述

2、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;   // 场扫描周期

在这里插入图片描述

3、VGA常用分辨率

在这里插入图片描述

  • 以640x480@60分辨率为例:

640表示每行640个像素点,480表示480行,60表示VGA显示每秒刷新60次(每秒显示60帧图像),25.175表示VGA的驱动时钟为25.175MHz。
VGA工业标准所要求的频率为:

名称频率
时钟频率25.175MHz
行频率31469Hz
场频率59.94Hz

VGA工业标准显示模式要求:行同步、场同步都为负极性,即同步脉冲通过要求是负脉冲。
其中,25.175MHz是这样计算的:800x525x59.94=25.175MHz,计算中包括了行消隐和场消隐。
当我们采用25MHz时钟驱动VGA时:显示器的刷新频率为25MHz/(800x525)=59.52Hz,接近工业标准场频率59.94Hz。
注:我们使用 25MHz 的时钟代替 25.175MHz 的时钟,不会对实验造成影响。

4、VGA工作流程小结

代码设计中,VGA在每一帧图像显示时,可用两个计数器进行计数(行、场扫描计数器),行计数器的驱动时钟为25MHz(可由PLL_IP核分频得到),场计数器的驱动时钟为行计数的溢出信号。计数器同时控制行、场同步信号输出,并在适当的时候送出像素点数据,就可以了显示相应的图像。
其实,简单来说,就是在显示的有效区内,根据VGA时钟(采用25MHz),按照从左到右,由上至下的扫描顺序,在适当的VGA时钟周期依次填充每个像素点的颜色数据值。
在这里插入图片描述

三、FPGA–VGA硬件设计

VGA显示实验基于DE2-115开发板,其VGA硬件部分如下:
在这里插入图片描述
DE2-115开发板提供了一个24位高质量VGA视频输出模块,该模块使用专用是DAC芯片ADV7123,实现24位动态色彩输出。

  • ADV7123部分引脚介绍:
引脚描述
VGA_R[0-7]红色信号
VGA_G[0-7]绿色信号
VGA_B[0-7]蓝色信号
VGA_CLKVGA驱动时钟
VGA_SYNC_N同步控制信号输入,当SYNC为低电平时,关闭IRE电流源,该信号仅允许在行消隐期间被设置。如果绿色通道不需要同步信号,可以将该信号接到低电平(这里不需要,直接接低电平)
VGA_BLANK_N消隐控制信号,当该信号为0时,所有的RGB输入数据将被忽略,因此如果希望正常输出VGA信号,则要保证在数据区,BLANK信号为高电平

另外,FPGA芯片与VGA接口直接相连了行同步信号与场同步信号。

引脚描述
VGA_ HS行同步信号
VGA_ VS场同步信号

四、FPGA–VGA程序设计

1、VGA驱动模块

VGA驱动模块主要完成:VGA时序的控制、输出扫描到当前像素点的X、Y坐标、输入要显示的像素点数据,并驱动VGA进行图像显示。(采用RGB888格式)

- 此模块的输入输出接口:

    input         vga_clk_25,   // VGA驱动时钟---25MHz
    input         rst_n,        // 复位信号(低有效)
    input  [23:0] pixel_data,   // RGB888要显示的像素点数据      
                
    output [23:0] vga_rgb,      // RGB888红绿蓝三原色输出
    output        vga_hs,       // 行同步信号
    output        vga_vs,       // 场同步信号
    output        vga_blank,    // 场消隐信号
    output [ 9:0] pixel_xpos,   // 像素点横坐标     
    output [ 9:0] pixel_ypos    // 像素点纵坐标

-行、场同步信号:

// H_cnt 为行计数器;  H_SYNC   =  10'd96;    
// V_cnt 为场计数器;  V_SYNC   =  10'd2;     
assign vga_hs = (H_cnt >= H_SYNC) ? 1'b1 : 1'b0; // 每个行周期起始时:VGA_HS为96个(0-95)低电平VGA时钟周期
assign vga_vs = (V_cnt >= V_SYNC) ? 1'b1 : 1'b0; // 每个场周期起始时:VGA_VS为2个(0-1)低电平的行溢出周期

-行、场计数器:

// 行计数器,其中---H_TOTAL  =  10'd800;->行扫描周期
always @(posedge vga_clk_25 or negedge rst_n)
begin
    if(!rst_n) begin
        H_cnt <= 10'd0;
    end
    else begin
        if(H_cnt == H_TOTAL - 10'd1) 
            H_cnt <= 10'd0;
        else 
            H_cnt <= H_cnt + 10'd1;
    end
end

// 场计数器,其中---V_TOTAL  =  10'd525;->场扫描周期
always @(posedge vga_clk_25 or negedge rst_n)
begin
    if(!rst_n) begin
        V_cnt <= 10'd0;
    end
    else if(H_cnt == H_TOTAL - 10'd1) begin
        if(V_cnt == V_TOTAL - 10'd1) 
            V_cnt <= 10'd0;
        else 
            V_cnt <= V_cnt + 10'd1;
    end
    else 
        V_cnt <= V_cnt;
end

-在图像有效的显示区,输出有效像素数据:

// vga_en在图像有效显示区为1,在消隐期间为0
assign vga_en = (((H_cnt >= (H_SYNC+H_BACK)) && (H_cnt < (H_TOTAL-H_FRONT)))
                 && ((V_cnt >= (V_SYNC+V_BACK)) && (V_cnt < (V_TOTAL-V_FRONT))))
                 ? 1'b1 : 1'b0;
assign vga_blank = vga_en;  // 有效区时,消隐控制信号置1,正常输出VGA信号;其余,置0,忽略RGB输入的数据                                     
assign vga_rgb = vga_en ? pixel_data : 24'd0; // 有效显示区,输出像素数据

-返回像素点坐标值:

// 这里对于每行的X坐标信号提前了一个VGA时钟周期发出申请,为其他模块在判断使用X、Y坐标,并送出像素点数据时提供了一个VGA时钟周期的缓冲
assign pixel_req = (((H_cnt >= (H_SYNC+H_BACK-10'd1)) && (H_cnt < (H_TOTAL-H_FRONT-10'd1)))
                 && ((V_cnt >= (V_SYNC+V_BACK)) && (V_cnt < (V_TOTAL-V_FRONT))))
                 ? 1'b1 : 1'b0;
assign pixel_xpos = pixel_req ? (H_cnt - (H_SYNC+H_BACK-10'd1)) : 10'd0;    
assign pixel_ypos = pixel_req ? (V_cnt - (V_SYNC+V_BACK)) : 10'd0;          

2、VGA驱动模块的Modelsim仿真

-Testbench设计:

`timescale 1 ns/ 1 ns

module VGA_Driver_tb();

// 输入变量
reg vga_clk_25;
reg rst_n;
reg [23:0] pixel_data;
// 输出变量                                              
wire vga_blank;
wire vga_hs;
wire [23:0] vga_rgb;
wire vga_vs; 
wire [ 9:0] pixel_xpos;      
wire [ 9:0] pixel_ypos;   
// 接收到指定显示区范围使能信号  
wire       recv_en;   

localparam  WHITE   = 24'b11111111_11111111_11111111;  // RGB888 白色
localparam  BLANK   = 24'b00000000_00000000_00000000;  // RGB888 黑色
localparam  RED     = 24'b11111111_00000000_00000000;  // RGB888 红色
localparam  GREEN   = 24'b00000000_11111111_00000000;  // RGB888 绿色
localparam  BLUE    = 24'b00000000_00000000_11111111;  // RGB888 蓝色         

VGA_Driver u_VGA_Driver(
    .vga_clk_25   (vga_clk_25),
    .rst_n        (rst_n     ),
    .pixel_data   (pixel_data),     
                
    .vga_rgb      (vga_rgb   ),
    .vga_hs       (vga_hs    ),
    .vga_vs       (vga_vs    ),
    .vga_blank    (vga_blank ),
    .pixel_xpos   (pixel_xpos),  
    .pixel_ypos   (pixel_ypos)
);

initial                                                
begin                                                  
   vga_clk_25 = 1'b0;
   rst_n      = 1'b0;
   #80 rst_n  = 1'b1; 
   #100000000 $stop;
end                                                    
   
always  #20 vga_clk_25 = ~vga_clk_25;   
// 设置0-9行,每行1-9像素点位置为有效显示区
assign recv_en = ( ((pixel_xpos >= 10'd1) && (pixel_xpos < 10'd10))
                 && ((pixel_ypos >= 10'd0) && (pixel_ypos < 10'd10)) )
                 ? 1'b1 : 1'b0;
                  
// 在显示有效区中,进行有效像素点输出
always @(posedge vga_clk_25 or negedge rst_n)
begin
    if(!rst_n)
        pixel_data <= BLANK;
    else begin
        if(recv_en)
            pixel_data <= WHITE;  
        else 
            pixel_data <= GREEN;  
    end
end

-modelsim仿真波形:
在这里插入图片描述
可观察到,在Testbench中收到X、Y坐标后,经过一个VGA时钟周期的后,才输出了有效的像素点数据,故在VGA驱动模块中提前一个VGA时钟周期送出坐标值(主要是X坐标),方便其他模块捕捉坐标值处理并将像素数据回传给VGA驱动模块,在图像有效区进行像素点坐标对应显示。
简单来说,比如模块A要在坐标X=10,Y=23,这个像素点位置进行颜色显示。若模块A用always语句判断已经到了这个像素点的坐标位置,即(pixel_xpos >= 10'd10) && (pixel_xpos >= 10'd23), 直接进行像素数据输出,则正好对应驱动模块要输出图像位置(10,23)的像素点数据。

注意:如果模块A全都使用assign语句进行坐标位置的判断和像素数据输出,则将不存在一个VGA时钟周期的处理时间,在VGA驱动模块设计中,可去除提前一个VGA时钟周期的坐标申请。即

// 去除了提前一个VGA时钟周期发起坐标申请
assign pixel_req = (((H_cnt >= (H_SYNC+H_BACK)) && (H_cnt < (H_TOTAL-H_FRONT)))
                 && ((V_cnt >= (V_SYNC+V_BACK)) && (V_cnt < (V_TOTAL-V_FRONT))))
                 ? 1'b1 : 1'b0;
assign pixel_xpos = pixel_req ? (H_cnt - (H_SYNC+H_BACK)) : 10'd0;    
assign pixel_ypos = pixel_req ? (V_cnt - (V_SYNC+V_BACK)) : 10'd0;          

五、FPGA–VGA图片显示实验

  • 任务:使用开发板通过VGA接口在显示屏上显示一个100x100大小的图片
  • 步骤:首先将图片数据初始化到FPGA内嵌的ROM中,然后在选定的显示区处读取ROM中的数据,送到VGA驱动模块进行显示

1、ROM_IP核的创建

  • 此次图显实验创建一个单端口ROM_IP核,并创建一个读使能信号rden

输出位宽:使用RGB888格式,故位宽选择24bits
存储深度:要显示图片为100x100,即共需存储10000个像素点数据,选择10000以上即可,在此选择16384words
存储类型:保持默认Auto
时钟类型:单时钟

在这里插入图片描述

创建一个读使能信号,且输出无需寄存

在这里插入图片描述

把图片文件转换成mif格式,放入ROM的初始化中
注意:若要对ROM_IP核进行Modelsim仿真,则要把mif文件放到工程文件目录下,否则仿真时ROM的输出将会一直是0

在这里插入图片描述

2、图片显示模块代码设计

  • 在此模块中例化刚才创建的ROM_IP核
pic_rom	u_pic_rom(
    .clock      (vga_clk_25),
	.address    (rom_addr  ),   // 读ROM地址
	.rden       (rom_rd_en ),   // 读ROM使能信号
    
	.q          (rom_data  )    // ROM输出数据
);
  • 扫描到图片显示区时,拉高读使能信号
// START_X ->图片区域起始横坐标;    START_Y ->图片区域起始纵坐标
// PHOTO_H ->待显图片的水平宽度;   PHOTO_V ->待显图片的垂直高度
assign rom_rd_en = ( ((pixel_xpos >= START_X) && (pixel_xpos < START_X + PHOTO_H))
                   && ((pixel_ypos >= START_Y) && (pixel_ypos < START_Y + PHOTO_V)) )
                   ? 1'b1 : 1'b0;
  • 控制ROM读数据的地址
// 读地址范围0-9999;  PHOTO = 14'd10000; ->图片区域总像素数
always @(posedge vga_clk_25 or negedge rst_n)
begin
    if(!rst_n)
        rom_addr <= 14'd0;
    else begin
        if(rom_rd_en) begin
            if(rom_addr == PHOTO - 1'b1)
                rom_addr <= 14'd0;              // 读到ROM有效末地址后,从首地址重新开始读操作
            else    
                rom_addr <= rom_addr + 14'd1;   // 每次ROM数据后,读地址+1
        end
        else 
            rom_addr <= rom_addr;
    end
end
  • 对读使能信号延时一个VGA时钟周期
// 由于对ROM写入读地址和读使能后,数据会在下一个VGA时钟周期输出
// 故此对读使能信号也延迟一个VGA时钟周期,使rom_valid与ROM有效数据输出的时钟保持一致
always @(posedge vga_clk_25 or negedge rst_n)
begin
    if(!rst_n)
        rom_valid <= 1'b0;
    else 
        rom_valid <= rom_rd_en;
end
  • 在图片显示区,把ROM中数据进行输出,VGA驱动模块接收此数据进行图片显示
// 在图片显示区,进行ROM数据输出,显示屏背景色为白色
// pixel_data ->24位像素数据,送到VGA驱动模块进行图像显示
assign pixel_data = rom_valid ? rom_data : WHITE;

总结

此文大致总结了本人使用DE2-115开发板进行的FPGA—VGA显示实验,全部笔记内容均为纯手打(包括上一篇浅谈串口也都是纯手工码字,每篇基本都花费了整下午+整晚上的时间,累啊!),其中也包括了个人的一些理解及总结,若文中有描述有误的地方,欢迎留言指正。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值