• 协议——VGA


      VGA(Video Graphics Array)是IBM在1987年随PS/2机一起推出的一种视频传输标准,具有分辨率高、显示速率快、颜色丰富等优点,在彩色显示器领域得到了广泛的应用。不支持热插拔,不支持音频传输。对于一些嵌入式VGA显示系统,可以在不使用VGA显示卡和计算机的情况下,实现VGA图像的显示和控制。VGA显示器具有成本低、结构简单、应用灵活的优点。对于一名FPGA工程师,尤其是视频图像的方向的学习者,VGA协议是必须要掌握的。

    一、外部接口

      由电路图可以看到,VGA并没有特殊的外部芯片,我们需要关注的其实只有5个信号:HS行同步信号,VS场同步信号,R红基色,G绿基色,B蓝基色。下面慢慢解释这些信号。

    二、色彩原理

      经过九年义务教育的我们都应该听过三基色,还给老师了的那就在再复习一下。三基色是指通过其他颜色的混合无法得到的“基本色”由于人的肉眼有感知红、绿、蓝三种不同颜色的锥体细胞,因此色彩空间通常可以由三种基本色来表达。这是色度学的最基本原理,即三基色原理。三种基色是相互独立的,任何一种基色都不能有其它两种颜色合成。红绿蓝是三基色,这三种颜色合成的颜色范围最为广泛。我们的RGB信号真是三基色的运用,对这三个信号赋予不同的数值,混合起来便是不同的色彩。

      设计RGB信号时,既可以R信号、G信号和B信号独立的赋值,最后连到端口上,也可以直接用RGB当做一个整体信号,RGB信号在使用时的位宽有三种常见格式,以你的VGA解码芯片的配置有关。

      1. RGB_8,R:G:B = 3:3:2,即RGB332

      2. RGB_16,R:G:B = 5:6:5,即RGB565

      3. RGB_24,R:G:B = 8:8:8,即RGB888

    三、扫描方式

      VGA显示器扫描方式分为逐行扫描和隔行扫描:逐行扫描是扫描从屏幕左上角一点开始,从左像右逐点扫描,每扫描完一行,电子束回到屏幕的左边下一行的起始位置,在这期间,CRT对电子束进行消隐,每行结束时,用行同步信号进行同步;当扫描完所有的行,形成一帧,用场同步信号进行场同步,并使扫描回到屏幕左上方,同时进行场消隐,开始下一帧。隔行扫描是指电子束扫描时每隔一行扫一线,完成一屏后在返回来扫描剩下的线,隔行扫描的显示器闪烁的厉害,会让使用者的眼睛疲劳。因此我们一般都采用逐行扫描的方式。

      扫描原理如下所示:

    四、行场信号

      行场信号共有 4 种模式,即 hsync 和 vsync 的高低状态不同,如下所示:

      一开始看这些时序图可能看不懂,它是把行场信号绘制在同一张图里,说明行场信号的控制是相似的,只是时间参数不一样而已。如果展开的话,其实时序是这样的:

      这样就清楚了,大致是若干个HS信号才组合而成一个VS,如果在一副图片中,那正确的时序表示方式应该如下图这样。

      现在稍稍解释一下这些参数。SYNC是“信号同步”,Back proch和Left border常常加在一起称为“显示后沿”,Addressable video为“显示区域”,Right porder和Front porch常常加在一起称为“显示前沿”,一个时序其实就是先拉高一段较短的“信号同步”时间,然后拉低一段很长的时间,这就是一个回合。同时需要注意,其实也可以完全相反。即先拉低一段时间“信号同步”时间,然后拉高一段很长的时间。

      具体这些时间参数是怎么来的呢?且看下文。

    五、规格参数

      直接拿数据手册说话!

      以上是 640x480 @60Hz 的参数表,对着这个表即可确定时间。如果为了嫌麻烦,也可以先计算好写在代码里。

    //**************************************************************************
    // *** 名称 : VGA_driver.v
    // *** 作者 : xianyu_FPGA
    // *** 博客 : https://www.cnblogs.com/xianyufpga/
    // *** 日期 : 2019-06-26
    // *** 描述 : VGA驱动模块,VGA_req和VGA_x、VGA_y信号一般不同时使用
    //**************************************************************************
    
    module VGA_driver
    //========================< 端口 >==========================================
    (
    //system ----------------------------------------
    input   wire                clk                 , //时钟,25Mhz
    input   wire                rst_n               , //复位,低电平有效
    //VGA_display -----------------------------------
    input   wire  [15:0]        VGA_din             , //得到图像数据
    output  wire                VGA_req             , //请求图像数据
    output  wire  [10:0]        VGA_x               , //请求显示区域横坐标
    output  wire  [10:0]        VGA_y               , //请求显示区域纵坐标
    //VGA output ------------------------------------
    output  wire                VGA_clk             , //VGA接口时钟信号
    output  wire                VGA_blank           , //VGA接口空白信号,低有效
    output  wire                VGA_de                , //VGA接口使能信号,高有效
    output  wire                VGA_hsync           , //VGA接口行信号
    output  wire                VGA_vsync           , //VGA接口场信号
    output  wire  [15:0]        VGA_data              //VGA接口数据信号
    );
    //========================< 参数 >==========================================
    /*                      640x480 @60Hz 25Mhz
    //---------------------------------------------------------------
    parameter H_TOTAL           = 800                               ; //行扫描周期
    parameter H_ADDR            = 640                               ; //行有效数据
    parameter H_RIGHT_BORDER    = 8                                 ; 
    parameter H_FRONT_PORCH     = 8                                 ;
    parameter H_FRONT           = H_RIGHT_BORDER + H_FRONT_PORCH    ; //行显示前沿
    parameter H_SYNC            = 96                                ; //行同步
    parameter H_BACK_PORCH      = 40                                ;
    parameter H_LEFT_BORDER     = 8                                 ;
    parameter H_BACK            = H_BACK_PORCH + H_LEFT_BORDER      ; //行显示后沿
    //-----------------------
    parameter V_TOTAL           = 525                               ; //场扫描周期
    parameter V_ADDR            = 480                               ; //场有效数据
    parameter V_BOTTOM_BORDER   = 8                                 ;
    parameter V_FRONT_PORCH     = 2                                 ;
    parameter V_FRONT           = V_BOTTOM_BORDER + V_FRONT_PORCH   ; //场显示前沿
    parameter V_SYNC            = 2                                 ; //场同步
    parameter V_BACK_PORCH      = 25                                ;
    parameter V_TOP_BORDER      = 8                                 ;
    parameter V_BACK            = V_BACK_PORCH + V_TOP_BORDER       ; //场显示后沿
    //--------------------------------------------------------------- */
    //                      1024x768 @60Hz 65Mhz
    //---------------------------------------------------------------
    parameter H_TOTAL           = 1344                              ; //行扫描周期
    parameter H_ADDR            = 1024                              ; //行有效数据
    parameter H_RIGHT_BORDER    = 0                                 ; 
    parameter H_FRONT_PORCH     = 24                                ;
    parameter H_FRONT           = H_RIGHT_BORDER + H_FRONT_PORCH    ; //行显示前沿
    parameter H_SYNC            = 136                               ; //行同步
    parameter H_BACK_PORCH      = 160                               ;
    parameter H_LEFT_BORDER     = 0                                 ;
    parameter H_BACK            = H_BACK_PORCH + H_LEFT_BORDER      ; //行显示后沿
    //-----------------------
    parameter V_TOTAL           = 806                               ; //场扫描周期
    parameter V_ADDR            = 768                               ; //场有效数据
    parameter V_BOTTOM_BORDER   = 0                                 ;
    parameter V_FRONT_PORCH     = 3                                 ;
    parameter V_FRONT           = V_BOTTOM_BORDER + V_FRONT_PORCH   ; //场显示前沿
    parameter V_SYNC            = 6                                 ; //场同步
    parameter V_BACK_PORCH      = 29                                ;
    parameter V_TOP_BORDER      = 0                                 ;
    parameter V_BACK            = V_BACK_PORCH + V_TOP_BORDER       ; //场显示后沿
    //---------------------------------------------------------------
    /*                      1280x720 @60Hz 74.25Mhz
    //---------------------------------------------------------------
    parameter H_TOTAL           = 1650                              ; //行扫描周期
    parameter H_ADDR            = 1280                              ; //行有效数据
    parameter H_RIGHT_BORDER    = 0                                 ; 
    parameter H_FRONT_PORCH     = 110                               ;
    parameter H_FRONT           = H_RIGHT_BORDER + H_FRONT_PORCH    ; //行显示前沿
    parameter H_SYNC            = 40                                ; //行同步
    parameter H_BACK_PORCH      = 220                               ;
    parameter H_LEFT_BORDER     = 0                                 ;
    parameter H_BACK            = H_BACK_PORCH + H_LEFT_BORDER      ; //行显示后沿
    //-----------------------
    parameter V_TOTAL           = 750                               ; //场扫描周期
    parameter V_ADDR            = 720                               ; //场有效数据
    parameter V_BOTTOM_BORDER   = 0                                 ;
    parameter V_FRONT_PORCH     = 5                                 ;
    parameter V_FRONT           = V_BOTTOM_BORDER + V_FRONT_PORCH   ; //场显示前沿
    parameter V_SYNC            = 5                                 ; //场同步
    parameter V_BACK_PORCH      = 20                                ;
    parameter V_TOP_BORDER      = 0                                 ;
    parameter V_BACK            = V_BACK_PORCH + V_TOP_BORDER       ; //场显示后沿
    //--------------------------------------------------------------- */
    //========================< 信号 >==========================================
    reg   [10:0]                cnt_h               ;
    wire                        add_cnt_h           ;
    wire                        end_cnt_h           ;
    reg   [10:0]                cnt_v               ;
    wire                        add_cnt_v           ;
    wire                        end_cnt_v           ;
    //==========================================================================
    //==    行、场计数
    //==========================================================================
    always @(posedge clk or negedge rst_n) begin
        if(!rst_n)
            cnt_h <= 0;
        else if(add_cnt_h) begin
            if(end_cnt_h)
                cnt_h <= 0;
            else
                cnt_h <= cnt_h + 1;
        end
    end
    
    assign add_cnt_h = 1;
    assign end_cnt_h = add_cnt_h && cnt_h==H_TOTAL-1;
    
    always @(posedge clk or negedge rst_n) begin 
        if(!rst_n)
            cnt_v <= 0;
        else if(add_cnt_v) begin
            if(end_cnt_v)
                cnt_v <= 0;
            else
                cnt_v <= cnt_v + 1;
        end
    end
    
    assign add_cnt_v = end_cnt_h;
    assign end_cnt_v = add_cnt_v && cnt_v==V_TOTAL-1;
    //==========================================================================
    //==    数据请求和数据坐标
    //==========================================================================
    //VGA请求
    assign VGA_req = (cnt_h >= H_SYNC + H_BACK - 1) && (cnt_h < H_SYNC + H_BACK + H_ADDR - 1) &&
                     (cnt_v >= V_SYNC + V_BACK    ) && (cnt_v < V_SYNC + V_BACK + V_ADDR    )
                     ? 1 : 0;
    //VGA坐标
    assign VGA_x = VGA_req ? (cnt_h - (H_SYNC + H_BACK - 1'b1)) : 10'd0;
    assign VGA_y = VGA_req ? (cnt_v - (V_SYNC + V_BACK - 1'b1)) : 10'd0;
    //==========================================================================
    //==    VGA output
    //==========================================================================
    //时钟
    assign VGA_clk = ~clk;
    
    //行场
    assign VGA_hsync = (cnt_h < H_SYNC) ? 0 : 1;
    assign VGA_vsync = (cnt_v < V_SYNC) ? 0 : 1;
    
    //空白,低有效,可以看成是de
    assign VGA_blank = VGA_hsync & VGA_vsync; //或=VGA_de
    
    //使能,高有效,非VGA必须,但大多数LCD屏都需要
    assign VGA_de = (cnt_h >= H_SYNC + H_BACK) && (cnt_h < H_SYNC + H_BACK + H_ADDR) &&
                    (cnt_v >= V_SYNC + V_BACK) && (cnt_v < V_SYNC + V_BACK + V_ADDR)
                    ? 1 : 0;
    
    //数据
    assign VGA_data = VGA_de ? VGA_din : 16'b0;
    
    
    
    
    endmodule

      注意一下,VGA 的驱动电路常用的有 2 种:

    (1) R-2R 电阻模拟电路设计方案

      该方案更便宜,在 1024x768@60hz 及以下的分辨率条件下稳定运行,多见于 16 位的 VGA 接口中,通常不需要 VGA_clk 和 VGA_blank 信号。

    (2)专用视频转换 DAC 芯片实现 VGA电路方案

      各方面都更牛逼,常见于 24 位的 VGA 接口中,通常需要 VGA_clk 和 VGA_blank 信号,有些还有 VGA_sync 信号,但该信号一般在电路上就接地了。

      本代码中对这些信号都进行了设计,要用就用,不用就不连到 FPGA 引脚就行。如果需要 24 位,只需要修改输入输出的位宽即可。

    六、实例讲解

      最近使用的开发板带了一个TFT屏,分辨率为480x272,其显示原理和VGA接口完全相同,因此拿这个屏幕编写一段程序看看。

      1 //==========================================================================
      2 // --- 名称 : TFT_driver.v
      3 // --- 作者 : xianyu_FPGA
      4 // --- 日期 : 2019-01-03
      5 // --- 描述 : TFT显示屏控制器,分辨率480x272,显示三个竖着的彩条
      6 //==========================================================================
      7 
      8 module TFT_driver
      9 //=====================<端口声明>===========================================
     10 (
     11 //input -------------------------------------
     12 input  wire             clk                 , //时钟,9Mhz
     13 input  wire             rst_n               , //复位,低电平有效
     14 //user interfaces ---------------------------
     15 output wire             TFT_req             , //输出请求信号
     16 input  wire [15:0]      data                , //得到图像数据
     17 //output ------------------------------------
     18 output wire             TFT_clk             , //TFT像素时钟
     19 output wire             TFT_de              , //TFT使能
     20 output wire             TFT_pwm             , //TFT背光控制
     21 output wire             TFT_hsync           , //TFT行同步信号
     22 output wire             TFT_vsync           , //TFT场同步信号
     23 output reg  [15:0]      TFT_rgb               //TFT像素输出
     24 );
     25 //=====================<参数定义>===========================================
     26 //480x272 @60 9Mhz --------------------------
     27 parameter H_TOTAL       = 525               ; //行扫描周期
     28 parameter H_ADDR        = 480               ; //行有效数据
     29 parameter H_FRONT       = 2                 ; //行显示前沿
     30 parameter H_SYNC        = 41                ; //行同步
     31 parameter H_BACK        = 2                 ; //行显示后沿
     32 parameter V_TOTAL       = 286               ; //场扫描周期
     33 parameter V_ADDR        = 272               ; //场有效数据
     34 parameter V_FRONT       = 2                 ; //场显示前沿
     35 parameter V_SYNC        = 10                ; //场同步
     36 parameter V_BACK        = 2                 ; //场显示后沿
     37 
     38 //=====================<信号定义>===========================================
     39 //行场信号
     40 reg  [9:0]              cnt_h               ;
     41 wire                    add_cnt_h           ;
     42 wire                    end_cnt_h           ;
     43 reg  [9:0]              cnt_v               ;
     44 wire                    add_cnt_v           ;
     45 wire                    end_cnt_v           ;
     46 reg                     TFT_en              ;
     47 wire                    red_area            ;
     48 wire                    green_area          ;
     49 wire                    blue_area           ;
     50 
     51 //--------------------------------------------------------------------------
     52 //--   行、场计数
     53 //--------------------------------------------------------------------------
     54 always @(posedge clk or negedge rst_n) begin
     55     if(!rst_n)
     56         cnt_h <= 0;
     57     else if(add_cnt_h) begin
     58         if(end_cnt_h)
     59             cnt_h <= 0;
     60         else
     61             cnt_h <= cnt_h + 1;
     62     end
     63 end
     64 
     65 assign add_cnt_h = 1;
     66 assign end_cnt_h = add_cnt_h && cnt_h==H_TOTAL-1;
     67 
     68 always @(posedge clk or negedge rst_n) begin 
     69     if(!rst_n)
     70         cnt_v <= 0;
     71     else if(add_cnt_v) begin
     72         if(end_cnt_v)
     73             cnt_v <= 0;
     74         else
     75             cnt_v <= cnt_v + 1;
     76     end
     77 end
     78 
     79 assign add_cnt_v = end_cnt_h;
     80 assign end_cnt_v = add_cnt_v && cnt_v==V_TOTAL-1;
     81 
     82 //--------------------------------------------------------------------------
     83 //--    TFT请求信号和使能信号,注意时序的对齐
     84 //--------------------------------------------------------------------------
     85 assign TFT_req = (cnt_h >= H_SYNC + H_BACK - 1) && (cnt_h < H_SYNC + H_BACK + H_ADDR - 1) &&
     86                  (cnt_v >= V_SYNC + V_BACK    ) && (cnt_v < V_SYNC + V_BACK + V_ADDR    )
     87                  ? 1 : 0;
     88 
     89 always @(posedge clk) begin
     90     TFT_en <= TFT_req;
     91 end
     92 
     93 //--------------------------------------------------------------------------
     94 //--   行场信号
     95 //--------------------------------------------------------------------------
     96 assign TFT_hsync = (cnt_h < H_SYNC) ? 0 : 1;
     97 assign TFT_vsync = (cnt_v < V_SYNC) ? 0 : 1;
     98 
     99 //--------------------------------------------------------------------------
    100 //--    其他信号
    101 //--------------------------------------------------------------------------
    102 assign TFT_clk = clk;
    103 assign TFT_de  = TFT_en;
    104 assign TFT_pwm = rst_n;
    105 
    106 //--------------------------------------------------------------------------
    107 //--   rgb信号
    108 //--------------------------------------------------------------------------
    109 //assign TFT_rgb = TFT_en ? data : 0;
    110 
    111 always @(*) begin
    112     if(TFT_en) begin
    113 
    114         if(red_area) begin                      //红色区域
    115             TFT_rgb <= 16'b11111_000000_00000;
    116         end
    117         else if(green_area) begin               //绿色区域
    118             TFT_rgb <= 16'b00000_111111_00000;
    119         end
    120         else if(blue_area) begin                //蓝色区域
    121             TFT_rgb <= 16'b00000_000000_11111;
    122         end
    123         
    124     end
    125     else begin                                  //非显示区域
    126         TFT_rgb <= 0;
    127     end
    128 end
    129 
    130 
    131 assign red_area   = cnt_h >= (H_SYNC + H_BACK) && cnt_h < (H_SYNC + H_BACK + H_ADDR*1/3) && 
    132                     cnt_v >= (V_SYNC + V_BACK) && cnt_v < (V_SYNC + V_BACK + V_ADDR);
    133 assign green_area = cnt_h >= (H_SYNC + H_BACK) && cnt_h < (H_SYNC + H_BACK + H_ADDR*2/3) && 
    134                     cnt_v >= (V_SYNC + V_BACK) && cnt_v < (V_SYNC + V_BACK + V_ADDR);
    135 assign blue_area  = cnt_h >= (H_SYNC + H_BACK) && cnt_h < (H_SYNC + H_BACK + H_ADDR*3/3) && 
    136                     cnt_v >= (V_SYNC + V_BACK) && cnt_v < (V_SYNC + V_BACK + V_ADDR);
    137 
    138 
    139                                  
    140 
    141 endmodule

      这个工程还包括顶层top模块,pll分频模块,这些就不展示了。还一点是接口处的user interfaces的信号没有使用到,而是自己通过代码赋的值。工程最终正常运行,显示出从左到右的三个竖彩条,其效果如下所示:

    七、后记

      这样只是简单的使用了VGA,最终还是要以显示视频或图像为目标,这就涉及到模块之间的交互问题,下次再总结吧!

    参考资料:

    [1]开源骚客.VGA系列之一:VGA显示驱动篇

    [2]NingHeChuan.基于FPGA的VGA显示静态图片

    [3]威三学院FPGA教程

    [4]袁玉卓, 曾凯锋, 梅雪松. FPGA自学笔记:设计与验证[M]. 北京航空航天出版社, 2017.

  • 相关阅读:
    12小时制时间
    sqlserver 安装和配置
    建议71:区分异步和多线程应用场景
    AVD管理器提示:PANIC:Could not open:AVD名称 解决办法
    一道看似复杂但是简单的c#面试题
    XML Schema 配置文件自动生成c#类设计案例子
    VS2010中的调试技巧 断点
    文章已被删除
    使用MONO使.net程序脱离.net框架运行
    5个很好用的.net 分析工具
  • 原文地址:https://www.cnblogs.com/xianyufpga/p/11128817.html
Copyright © 2020-2023  润新知