VGA

1、VGA接口

  VGA(Video Graphics Array)即视频图形阵列,是IBM在1987年推出的使用模拟信号的一种视频传输标准,在当时具有分辨率高、显示速率快、颜色丰富等优点,在彩色显示器领域得到了广泛的应用。这个标准对于现今的个人电脑市场已经十分过时。VGA可分为,VGA硬件接口,和VGA协议。VGA硬件接口没有什么好学的,VGA时序才是正点

  上图左边带的叫VGA公头,右边带的叫VGA母头。VGA接口不支持热插拔,不支持音频传输。

  VGA并没有特殊的外部芯片,我们需要关注的其实只有5个信号:HS行同步信号,VS场同步信号,R红基色,G绿基色,B蓝基色(13、14、1、2、3引脚)

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

  2. RGB_16,R:G:B = 5:6:5,即RGB565,2^16=65536色

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

 

2、VGA协议

  VGA 显示器扫描方式从屏幕左上角一点开始,从左向右逐点扫描,每扫描完一行,电子束回到屏幕的左边下一行的起始位置,在这期间,CRT 对电子束进行消隐,每行结束时,用行同步信号进行同步;当扫描完所有的行,形成一帧,用场同步信号进行场同步,并使扫描回到屏幕左上方,同时进行场消隐,开始下一帧。完成一行扫描的时间称为水平扫描时间,其倒数称为行频率;完成一帧(整屏)扫描的时间称为垂直扫描时间,其倒数称为场频率,即屏幕的刷新频率,常见的有 60Hz,75Hz 等等。

   行消隐(HBlank)在将光信号转换为电信号的扫描过程中,扫描总是从图像的左上角开始,水平向前行进,同时扫描点也以较慢的速率向下移动。当扫描点到达图像右侧边缘时,扫描点快速返回左侧,重新开始进行第2行扫描,行与行之间的返回过程称为水平消隐。在移动期间就必须有一个信号加到电路上,使得电子束不能发出。不然这个回扫线会破坏屏幕图像的。

  扫描点扫描完一帧后,要从图像的右下角返回到图像的左上角,开始新一帧的扫描,这一时间间隔,叫做垂直消隐,也称场消隐(VBlank)。

3、行-场信号

       行场信号共有 4 种模式,即 hsync 和 vsync 的高低状态不同,可以理解为00、01、10、11 。这里直接给出几种信号的组合

  ( 来源于“VESA and Industry Standards and Guidelines for Computer Display Monitor Timing (DMT)” )分辨率VESA标准。链接:https://pan.baidu.com/s/1F_6FR3lMtfmGjFORhJHohQ  提取码:1234

  上图看起来较为难理解,其实在实际应用中,应该是如下图所示,若干个HSync信号才会有一个VSync信号。

 

  总帧时序如下: Blanking为消隐部分,Border为边界,最中央为实际显示区域。

  行时序是以”像素”(Pixels)为单位的, 场时序是以”行”(Lines)为单位的。参考以640x480@60Hz这种分辨率格式来说,像素时钟频率为:25.175Mhz ≈ 60*(640+16+96+48)*(480+10+2+33)=60*800*525

  分辨率VESA标准中有如下参数设置:

  一般情况下,可以用a、b、c、d、e简化这个表达:以行同步为例,a=96,b=40+8=48,c=640,d=8+8=16 。

 

4、实例练习

  ① VGA单色显示

  通过查阅标准手册可知,800*600@60Hz的VGA时序参数如上所示。以下设计可以实现由开发板按钮控制VGA单色显示,一秒更换一种颜色。(参考B站圈养孤独滴滴答

module vga(
    input clk,          //以800*600@60hz为例,即40Mhz时钟
    input rst_n,
    output reg vga_hs,      //行同步
    output reg vga_vs,      //列同步
    output en
    //output en_rgb
);

    parameter H_total = 1056,    //行总和
              H_a     = 128,     //行同步sync   
              H_b     = 88,     //行back_porch + left_border
              H_c     = 800,    //行实际像素
              H_d     = 40,     //行front_porch + right_border
              
              V_total = 628,    //列总和
              V_a     = 4,     //列同步sync   
              V_b     = 23,     //列back_porch + top_border
              V_c     = 600,    //列实际行
              V_d     = 1;     //列front_porch + bottom_border
    
    reg [12:0]cnt_h;
    reg [12:0]cnt_v;

//***************行列计数***********************//
    always @(posedge clk or negedge rst_n)begin     //行计数
        if(!rst_n)
            cnt_h <= 11'b0;
        else if(cnt_h == H_total-1'b1)              //计数一直加,直到加到H_total就置零
            cnt_h <= 11'b0;
        else
            cnt_h <= cnt_h + 1'b1;    
    end
    
    always @(posedge clk or negedge rst_n)begin
        if(!rst_n)
            cnt_v <= 11'b0;
        else if(cnt_v == V_total-1'b1)
            cnt_v <= 11'b0;
        else if(cnt_h == H_total-1'b1)
            cnt_v <= cnt_v + 1'b1;
        else
            cnt_v <= cnt_v;
    end
    
//************行列同步**********************//
    always @(posedge clk or negedge rst_n)begin
        if(!rst_n)
            vga_hs <= 1'b0;
        else if(cnt_h == H_total-1'b1)
            vga_hs <= 1'b1;
        else if(cnt_h == H_a)
            vga_hs <= 1'b0;
        else
            vga_hs <= vga_hs;
    end
    
    always @(posedge clk or negedge rst_n)begin
        if(!rst_n)
            vga_vs <= 1'b0;
        else if(cnt_v == V_total-1'b1)
            vga_vs <= 1'b1;
        else if(cnt_v == V_a)
            vga_vs <= 1'b0;
        else
            vga_vs <= vga_vs;
    end

//*************限定有效显示区域***************//
    wire [12:0]en1;
    wire [12:0]en2;
    assign en1 = (cnt_h >= (H_a+H_b) && cnt_h <= (H_a+H_b+H_c))? 
                 (cnt_h - H_a - H_b) : 12'b0;
    assign en2 = (cnt_v >= (V_a+V_b) && cnt_v <= (V_a+V_b+V_c))?    //当计数到C段就把C的值赋给en
                 (cnt_v - V_a - V_b) : 12'b0;                             //比如,当前cnt_h计到145,处于C段,就把超过的1赋值给en1,
                                                                    //表示显示区域的第一位,一直叠加到640个像素,一行结束
                                                                    //即cnt_h=96+48+640,超过C段后en1置零
    assign en = (en1 > 0 && en2 > 0)? 1'b1:1'b0;          //行列同时有效

/*    wire en_r,en_g,en_b;
    assign en_r = (en && ( cnt_v >= (V_a+V_b) && cnt_v <= (V_a+V_b+10'd160) ) )? 1'b1:1'b0;     //红色有效
    assign en_g = (en && ( cnt_v >= (V_a+V_b+10'd160) && cnt_v <= (V_a+V_b+10'd320) ) )? 1'b1:1'b0;//绿色有效
    assign en_b = (en && ( cnt_v >= (V_a+V_b+10'd320) && cnt_v <= (V_a+V_b+10'd480) ) )? 1'b1:1'b0;//蓝色有效

    assign en_rgb = {en_r,en_g,en_b};
*/   
    
endmodule 
VGA
module rgb(
    input en,
    input rst_n,
    input clk,
    output reg[4:0] vga_r,
    output reg[5:0] vga_g,
    output reg[4:0] vga_b,
    input key
);
    reg [2:0]i=0;
    reg [25:0]cnt;
    reg clk1;
    
    always @(posedge clk or negedge rst_n)begin
        if(!rst_n)begin
            cnt <= 'b0;
            clk1 <= 1'b0;
         end
        else if(cnt==24_999_999) begin
            cnt <= 'b0;
            clk1 <= ~clk1;
        end
        else
            cnt <= cnt +1'b1;
    end
    
    always @(posedge clk1)begin
        if(!key)
            i <= i + 1'b1;
        else if(i==7)
            i <= 3'b0;
            
    end
    
    always @(*)begin
    if(en) begin
        case(i)
            0:      vga_r = 5'b1_1111;
            1:      vga_r = 5'b1_1111;
            2:      vga_r = 5'b0_0000;
            3:      vga_r = 5'b0_0000;
            4:      vga_r = 5'b1_1111;
            5:      vga_r = 5'b0_0000;
            6:      vga_r = 5'b1_1111;
            default:   vga_r = vga_r;
        endcase
        end
     else vga_r = 5'b0_0000;
    end
    
    always @(*)begin
        if(en) begin
        case(i)
            0:      vga_g = 6'b00_0000;
            1:      vga_g = 6'b00_0000;
            2:      vga_g = 6'b11_1111;
            3:      vga_g = 6'b00_0000;
            4:      vga_g = 6'b11_1111;
            5:      vga_g = 6'b11_1111;
            6:      vga_g = 6'b11_1111;
            default:    vga_g = vga_g;
        endcase
        end
        else vga_g = 6'b00_0000;
    end
    
    always @(*)begin
    if(en) begin
        case(i)
            0:      vga_b = 5'b1_1111;
            1:      vga_b = 5'b0_0000;
            2:      vga_b = 5'b0_0000;
            3:      vga_b = 5'b1_1111;
            4:      vga_b = 5'b0_0000;
            5:      vga_b = 5'b1_1111;
            6:      vga_b = 5'b1_1111;
            default:    vga_b = vga_b;
        endcase
        end
     else vga_b = 5'b0_0000;
    end
    
  

endmodule 
rgb
module top(
    input clk,
    input rst_n,
    output vga_hs,      //行同步
    output vga_vs,      //列同步
    output [4:0] vga_r,
    output [5:0] vga_g,
    output [4:0] vga_b,
    input key
    
);
    wire en;
    wire c0;
    
    vga vga_inst(
    .clk(c0),          
    .rst_n(rst_n),
    .vga_hs(vga_hs),      //行同步
    .vga_vs(vga_vs),      //列同步
    .en(en)
    );

    rgb rgb_inst(
    .en(en),
    .clk(clk),
    .vga_r(vga_r),
    .vga_g(vga_g),
    .vga_b(vga_b),
    .key(key),
    .rst_n(rst_n)
    );
    
    pll    pll_inst (
    .areset (~rst_n),
    .inclk0 (clk),
    .c0 (c0)
    );
    

endmodule 
View Code

  该设计还存在缺点,但功能能实现,考虑后期优化。

 

  ② VGA彩条显示

  使用VGA实现1920*1080@60Hz参数下的彩条显示,代码如下:

  

//*****vga控制模块,以1920*1080@60Hz参数为例********//
module vga_driver(
    input clk_in,
    input rst_n,
    
    input [15:0]data_in,    //输入的需要显示的RGB565数据
    
    output [4:0]vga_r,      //rgb三色输出,以rgb565为例
    output [5:0]vga_g,
    output [4:0]vga_b,
    
    output [11:0]vga_x,     //横纵坐标显示
    output [11:0]vga_y,
    
    output vga_hs,          //行场同步信号
    output vga_vs,
    
    output vga_req,
    output vga_de,
    output vga_clk,
    output vga_blank,
    output vga_sync
);

    parameter ha      = 12'd44,
              hb      = 12'd148,
              hc      = 12'd1920,
              hd      = 12'd88,
              h_total = 12'd2200;
    parameter va      = 12'd5,
              vb      = 12'd36,
              vc      = 12'd1080,
              vd      = 12'd4,
              v_total = 12'd1125;

              
    reg [11:0]cnt_h;        //行场计数
    reg [11:0]cnt_v;

//**************行场计数*******************************//
    always @(posedge clk_in or negedge rst_n)begin
        if(!rst_n)
            cnt_h <= 12'b0;
        else if(cnt_h == h_total-1'b1)
            cnt_h <= 12'b0;
        else
            cnt_h <= cnt_h +1'b1;
    end
    always @(posedge clk_in or negedge rst_n)begin
        if(!rst_n)
            cnt_v <= 12'b0;
        else if(cnt_h == h_total-1'b1)begin
            if(cnt_v == v_total-1'b1)
                cnt_v <= 12'b0;
            else 
                cnt_v <= cnt_v +1'b1;
        end
        else
            cnt_v <= cnt_v; 
    end

//****************行场同步信号********************// 
//vesa标准中指出**Hor Sync Polarity = POSITIVE***//
//***************Ver Sync Polarity = POSITIVE***// 
    assign vga_hs = (cnt_h < ha)? 1'b1:1'b0;
    assign vga_vs = (cnt_v < va)? 1'b1:1'b0;
    
//****************vga有效显示数据区域************************//
    assign vga_de = ((cnt_h >= ha+hb) && (cnt_h <= ha+hb+hc) &&
                     (cnt_v >= va+vb) && (cnt_v <= va+vb+vc))
                     ? 1'b1:1'b0;

//****************rgb数据分量******************************//
    assign vga_r = vga_de? data_in[15:11] : 5'b0;
    assign vga_g = vga_de? data_in[10: 5] : 6'b0;
    assign vga_b = vga_de? data_in[ 4: 0] : 5'b0;

//****************vga请求,比数据data_in提前一拍***********************//
    assign vga_req = ((cnt_h >= ha+hb-1'b1) && (cnt_h <= ha+hb+hc-1'b1) &&
                     (cnt_v >= va+vb) && (cnt_v <= va+vb+vc))
                     ? 1'b1:1'b0;

//****************xy坐标,比数据data_in提前一拍********************************//
    assign vga_x = vga_req? (cnt_h-(ha+hb-1'b1)) : 12'd0;
    assign vga_y = vga_req? (cnt_v-(va+vb)) : 12'd0;

//**********************其他信号*********************//
    assign vga_clk   = clk_in;
    assign vga_blank = vga_de;
    assign vga_sync  = 1'b0; 
    
endmodule   
vga_driver
//********rgb显示模块*****************//
module rgb(
    input clk_in,
    input rst_n,
    
    input [11:0]vga_x,          //横纵坐标显示
    input [11:0]vga_y,
    
    output reg [15:0]data_out       //输出rgb组合数据
);
    
    parameter hc = 12'd1920;    //这里使用竖向显示彩条,将行像素分为8份
  //parameter vc = 12'd1080;
    parameter RED        =    16'b11111_000000_00000,        
              GREEN        =    16'b00000_111111_00000,
              BLUE        =    16'b00000_000000_11111,
              YELLOW    =    16'b11111_111111_00000,
              PURPLE    =    16'b11111_000000_11111,
              CYAN        =    16'b00000_111111_11111,
              WHITE     =    16'b11111_111111_11111,
              BLACK        =    16'b00000_000000_00000;
 
//*******************把vga显示分为8份,显示8种颜色************************// 
    always @(posedge clk_in or negedge rst_n)begin
        if(!rst_n)
            data_out <= 16'b0;
        else if(vga_x < (hc/8))                             //显示红色
            data_out <= RED;
            
        else if((vga_x >= (hc/8)*1) && (vga_x <= (hc/8)*2)) //显示绿色
            data_out <= GREEN;
            
        else if((vga_x >= (hc/8)*2) && (vga_x <= (hc/8)*3)) //显示蓝色
            data_out <= BLUE;
            
        else if((vga_x >= (hc/8)*3) && (vga_x <= (hc/8)*4)) //显示黄色
            data_out <= YELLOW;
            
        else if((vga_x >= (hc/8)*4) && (vga_x <= (hc/8)*5)) //显示紫色
            data_out <= PURPLE;
            
        else if((vga_x >= (hc/8)*5) && (vga_x <= (hc/8)*6)) //显示青色
            data_out<=CYAN;
            
        else if((vga_x >= (hc/8)*6) && (vga_x <= (hc/8)*7)) //显示黑色
            data_out <= BLACK;
            
        else if((vga_x>= (hc/8)*7) && (vga_x <= hc))        //显示白色
            data_out <= WHITE;
    
    end
  
  

endmodule   
rgb
//*****作者:DOVI666************************//
//*****功能:实现vga的彩条显示***************//
//*****顶层模块****************************//
module vga_top(
    input clk_sys,
    input reset_n,
    
    output [4:0]vga_r,      //rgb三色输出,以rgb565为例
    output [5:0]vga_g,
    output [4:0]vga_b,
    
    output vga_hs,          //行场同步信号
    output vga_vs
);
    
    wire c0;
    wire rst_n;
    wire locked;
    wire [15:0]data;
    wire [11:0]vga_x;
    wire [11:0]vga_y;
    
    assign rst_n = locked & reset_n;
    
//***********************************//
    pll    pll_inst (
    .areset   ( ~reset_n ),
    .inclk0   ( clk_sys ),
    .c0       ( c0 ),
    .locked   ( locked )
    );
    
//**********************************//    
    rgb rgb_inst(
    .clk_in   ( c0 ),
    .rst_n    ( rst_n ),
     
    .vga_x    ( vga_x ),          //横纵坐标显示
    .vga_y    ( vga_y ),
     
    .data_out ( data )           //输出rgb组合数据
    );
    
//**********************************//    
    vga_driver vga_driver_inst(
    .clk_in   ( c0 ),
    .rst_n    ( rst_n ),
    
    .data_in  ( data ),
    
    .vga_r    ( vga_r ),        //rgb三色输出,以rgb565为例
    .vga_g    ( vga_g ),
    .vga_b    ( vga_b ),
    
    .vga_x    ( vga_x ),        //横纵坐标显示
    .vga_y    ( vga_y ),
    
    .vga_hs   ( vga_hs ),       //行场同步信号
    .vga_vs   ( vga_vs ),
    
    .vga_req  (  ),             //没有用到的端口,不接
    .vga_de   (  ),
    .vga_clk  (  ),
    .vga_blank(  ),
    .vga_sync (  )
    );
//**********************************//

endmodule   
vga_top

  RTL视图如下

 

  最终效果如下图所示

 

 

   ③VGA图片显示

  图片的尺寸是指一幅图片长度和宽度各占多少像素,我们平常说的一张640×480的图片指的就是这张图片的长度有640个像素点,宽度有480个像素点。

  位深度是指图片的每个像素是用多少位(bit)来表示的。比如黑白二色的图像是数字图像中最简单的一种,它只有黑、白两种颜色,也就是说它的每个像素只有1位颜色,位深度是1,用2的零次幂来表示;

  8位颜色的图,位深度就是8,用2的8次幂表示,它含有256种颜色 ( 或256种灰度等级 )。

  24位颜色可称之为真彩色,位深度是24,它能组合成2的24次幂种颜色,即:16777216种颜色 ( 或称千万种颜色 )。当我们用24位来记录颜色时,实际上是以2^(8×3),即红、绿、蓝 ( RGB ) 三基色各以2的8次幂,256种颜色而存在的,三色组合就形成一千六百万种颜色。

  除了上面这几种情况以外,有的图片的位深度是16位,其中红基色占5位,绿基色占6位,蓝基色占5位,他们一共可以组成2^16中颜色。

  在电脑上,通过查看图片属性可以观察到这几种参数:

 

  VGA显示图片可以先将图片数据存储在FPGA中,在彩条显示代码的基础上,我添加了一个rom ip核存储待显示的图片数据。这里存储的数据是一种可以存储图片信息的.mif文件(quartus软件中使用.mif文件,Xilinx软件中使用.coe文件)。通过查阅FPGA板载存储大小,将图片调整至合适的分辨率(可通过电脑自带软件 “ 画图 ” 调整),使用软件或者matlab即可生成需要格式的.mif文件。为了简便,这里使用了正点原子“PicToMif_V1.0.exe”直接生成mif文件。

  vga控制驱动部分直接使用彩条显示中给出的即可,顶层top做些许修改即可实现。

//********rgb显示模块*****************//
module rgb(
    input clk_in,
    input rst_n,
    
    input [11:0]vga_x,          //横纵坐标显示
    input [11:0]vga_y,
    input key,
    
    output reg[15:0]data_out       //输出rgb组合数据
);
    
    parameter hc = 12'd1920;    //这里使用竖向显示彩条,将行像素分为8份
  //parameter vc = 12'd1080;
    parameter RED        =    16'b11111_000000_00000,        
              GREEN        =    16'b00000_111111_00000,
              BLUE        =    16'b00000_000000_11111,
              YELLOW    =    16'b11111_111111_00000,
              PURPLE    =    16'b11111_000000_11111,
              CYAN        =    16'b00000_111111_11111,
              WHITE     =    16'b11111_111111_11111,
              BLACK        =    16'b00000_000000_00000;
    parameter IMG_WIDTH         = 12'd80           ; //图片宽度
    parameter IMG_HEIGHT        = 12'd80           ; //图片高度
 
//*******************把vga显示分为8份,显示8种颜色************************// 
/*    always @(posedge clk_in or negedge rst_n)begin
        if(!rst_n)
            data_out <= 16'b0;
        else if(vga_x < (hc/8))                             //显示红色
            data_out <= RED;
            
        else if((vga_x >= (hc/8)*1) && (vga_x <= (hc/8)*2)) //显示绿色
            data_out <= GREEN;
            
        else if((vga_x >= (hc/8)*2) && (vga_x <= (hc/8)*3)) //显示蓝色
            data_out <= BLUE;
            
        else if((vga_x >= (hc/8)*3) && (vga_x <= (hc/8)*4)) //显示黄色
            data_out <= YELLOW;
            
        else if((vga_x >= (hc/8)*4) && (vga_x <= (hc/8)*5)) //显示紫色
            data_out <= PURPLE;
            
        else if((vga_x >= (hc/8)*5) && (vga_x <= (hc/8)*6)) //显示青色
            data_out<=CYAN;
            
        else if((vga_x >= (hc/8)*6) && (vga_x <= (hc/8)*7)) //显示黑色
            data_out <= BLACK;
            
        else if((vga_x>= (hc/8)*7) && (vga_x <= hc))        //显示白色
            data_out <= WHITE;
    
    end
*/    

    wire [15:0]data; 
    reg [15:0]addr;
    wire rd_en;
    reg  rd_en_r;
                                                                            //将图片显示在显示器中央
    assign rd_en = (vga_x >= 920) && (vga_x < 920 + IMG_WIDTH ) &&  
               (vga_y >= 500) && (vga_y < 500 + IMG_HEIGHT)
               ? 1'b1 : 1'b0;
    
    always @(posedge clk_in or negedge rst_n)begin
        if(!rst_n)
            addr <= 16'b0;
        else if(rd_en)begin 
            if(addr == IMG_WIDTH*IMG_HEIGHT-1'b1)
                addr <= 16'b0;
            else
                addr <= addr + 1'b1;
        end
    end
    
    always @(posedge clk_in or negedge rst_n)begin
        if(!rst_n)
            rd_en_r <= 16'b0;
        else
            rd_en_r <= rd_en;
    end
    
    always @(posedge clk_in or negedge rst_n)begin
        if(!rst_n)
            data_out <= 16'b0;
        else if(rd_en_r && key=='b0)
            data_out <= data;
        else
            data_out <= WHITE;
    end
    
    rom    rom_inst (
    .address ( addr ),
    .clock ( clk_in ),
    .q ( data )
    );

  
  

endmodule   
rgb

  效果如下:   通过按下fpga开发板上的按钮,即可实现图片的显示,松开按钮即恢复白色底板界面。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 资料来源:

  ninghechuan、咸鱼IC、黑金动力社区、jgliu【接口时序】7、VGA接口原理与Verilog实现 - jgliu - 博客园 (cnblogs.com)

posted @ 2024-01-15 16:55  DOVI666  阅读(595)  评论(0)    收藏  举报