FPGA学习历程(八):VGA驱动模块


以下内容学习自开源骚客教程: SDRAM那些事儿第二季。

一、VGA时序标准

  VGA 显示器扫描方式为从屏幕左上角一点开始,从左向右逐点扫描。当扫描完一行后,电子束就会回到屏幕的左边下一行起始位置开始下一行的扫描,而在此期间,CRT 对电子束进行消隐,每行结束时,用行同步信号进行同步;当扫描完所有的行,形成一帧,则用场同步信号进行场同步,并使扫描点回到屏幕左上方,同时进行场消隐,开始下一帧。
  以行同步时序为例说明:“Active” Video 是一行视频的有效像素,大部分分辨率时钟中 Top/Left BorderBottom / Right Border 都是 0。“Blanking” 是一行的同步时间,“Blanking” 时间加上 “Active” Video 时间就是一行的时间。“Blanking” 又分为 “Front Porch”、“Sync”、“Back Porch” 三段。在这里插入图片描述
  完成一行扫描的时间称为水平扫描时间,其倒数称为行频率;完成一帧扫描的时间称为垂直扫描时间,其倒数称为场频率,即刷新一屏的频率,常见的有 60 Hz,75 Hz 等。标准的 VGA 显示场频为 60 Hz。
  时钟频率的确定:以 1024x768@59.94Hz(60Hz)为例,每场对应 806 个行周期,其中 768 为显示行。每显示行包括 1344 点时钟,其中 1024 点为有效显示区。由此可知:需要点时钟频率: 806 * 1344 * 60 约 65 MHz。
在这里插入图片描述
  这里附上 1024x768@60Hz 的时序参数(需求其余规格可自行在网上查找):
在这里插入图片描述

二、VGA模块设计

2.1 与教程不同的小差别

  这里开发板的 VGA 相关电路和教程里的板子不太一样,因为 ADV7123 的缘故多了个 VGA_BLACK 的信号(结果在网上找了找数据手册发现真名是 /BLANK ?),作用类似开关;R、G、B 三种通道数均被物理限制到了 888 规格。
在这里插入图片描述
在这里插入图片描述

PS:目前 VGA 模块仅实现彩条显示测试。

  那么在源码上自然也是要做一定的改动才行。此外,由于 1024x768 的 VGA 显示规格要求像素输入时钟为 65MHz,而开发板自带的晶振只有 50MHz 和 27MHz 两种,因此还需要使用到 PLL 锁相环对系统时钟进行分频/倍频才行。添加方法类似 FIFO。

2.2 使用 PLL IP核

  打开 Quartus II 软件,选择菜单栏 Tools ⟶ \longrightarrow MegaWizard Plug-In Manager:
在这里插入图片描述
  检索要生成的 IP 核,并指定存放位置,之后会进入到 IP 核属性的配置页面:
在这里插入图片描述
  之后配置好要输出的时钟频率即可(areset 信号和 locked 信号可酌情使用):
在这里插入图片描述

  最后还需要在 SDC 文件中添加 PLL 时钟信息,添加方法类似系统时钟,选择菜单栏 Constraints ⟶ \longrightarrow Derive PLL Clocks… 后更新时序网表即可看到新的 PLL 时钟,写入 SDC 即可完成时序约束的更新:
在这里插入图片描述

2.3 模块源码

module vga_driver(
    // system signals
	input 				    sclk	    ,       // 像素时钟 65MHz
	input                   s_rst_n     ,       // 复位信号,低电平有效
    
    // VGA
    output  wire            vga_hsync   ,       // 行同步信号
    output  wire            vga_vsync   ,       // 场同步信号
    output  wire            vga_blank   ,
    output  reg    [15:0]   vga_rgb             // RGB565
);

/**************************************************************************/
/***************** Define Parameter and Internal Signals ******************/
/**************************************************************************/
// 不需要定义 H_FRONT_PORCH 和 V_FRONT_PORCH 是因为直接使用 H_TOTAL_TIME 与 V_TOTAL_TIME 作为计数周期
localparam  H_TOTAL_TIME    =   1344;
localparam  H_ADD_TIME      =   1024;
localparam  H_SYNC_TIME     =   136;
localparam  H_BACK_PORCH    =   160;
localparam  V_TOTAL_TIME    =   806;
localparam  V_ADD_TIME      =   768;
localparam  V_SYNC_TIME     =   6;
localparam  V_BACK_PORCH    =   29;

reg     [10:0]      cnt_h;              // 从行同步信号拉高开始作为计数周期的起点
reg     [9:0]       cnt_v;              // 场同步计数同理

/**************************************************************************/
/******************************* Main Code ********************************/
/**************************************************************************/
always  @(posedge sclk or negedge s_rst_n)
begin
    if(s_rst_n == 1'b0)
        cnt_h <= 'd0;
    else if(cnt_h >= H_TOTAL_TIME)
        cnt_h <= 'd0;
    else
        cnt_h <= cnt_h + 1'b1;
end

always  @(posedge sclk or negedge s_rst_n)
begin
    if(s_rst_n == 1'b0)
        cnt_v <= 'd0;
    else if(cnt_v >= V_TOTAL_TIME && cnt_h >= H_TOTAL_TIME)     // 最后一行也扫描完了才算完整的一场
        cnt_v <= 'd0;
    else if(cnt_h >= H_TOTAL_TIME)
        cnt_v <= cnt_v + 1'b1;
end

// 彩条测试
always  @(posedge sclk or negedge s_rst_n)
begin
    if(s_rst_n == 1'b0)
        vga_rgb <=  'd0;
    else if(cnt_v >= (V_SYNC_TIME + V_BACK_PORCH) && cnt_v < (V_SYNC_TIME + V_BACK_PORCH + V_ADD_TIME))         // 场有效区域
    begin
        if(cnt_h >= (H_SYNC_TIME + H_BACK_PORCH - 1) && cnt_h < (H_SYNC_TIME + H_BACK_PORCH - 1 + 256))         // 第一个彩条
            vga_rgb <=  16'h0fff;
        else if(cnt_h >= (H_SYNC_TIME + H_BACK_PORCH - 1) && cnt_h < (H_SYNC_TIME + H_BACK_PORCH - 1 + 512))
            vga_rgb <=  16'hf0ff;
        else if(cnt_h >= (H_SYNC_TIME + H_BACK_PORCH- 1) && cnt_h < (H_SYNC_TIME + H_BACK_PORCH - 1 + 768))
            vga_rgb <=  16'hff0f;
        else if(cnt_h >= (H_SYNC_TIME + H_BACK_PORCH- 1) && cnt_h < (H_SYNC_TIME + H_BACK_PORCH - 1 + 1024))
            vga_rgb <=  16'hfff0;
        else
            vga_rgb <= 16'h0000;
    end
    else
        vga_rgb <= 16'h0000;
end

assign  vga_hsync   =   (cnt_h < H_SYNC_TIME) ? 1'b1 : 1'b0;
assign  vga_vsync   =   (cnt_v < V_SYNC_TIME) ? 1'b1 : 1'b0;
assign  vga_blank   =   ~(vga_hsync & vga_vsync);

endmodule 

  VGA 驱动比较简单,很好搞定,所以板级调试就不放图出来了(并不是因为测试的时候忘了拍照测试完了又不想专门为了拍照再测试一次)。

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值