基于FPGA的VGA协议实现

本文详细介绍了VGA(Video Graphics Array)的外部接口、色彩原理、扫描方式和行场信号,并展示了如何使用FPGA进行VGA驱动的工程编写,包括彩条、点阵和图片的输出。通过调整参数实现不同分辨率的显示,并在实际开发板上进行了验证。
摘要由CSDN通过智能技术生成

1、VGA概述

VGA(Video Graphics Array)视频图形阵列是IBM于1987年提出的一个使用模拟信号的电脑显示标准。VGA接口即电脑采用VGA标准输出数据的专用接口。VGA接口共有15针,分成3排,每排5个孔,显卡上应用最为广泛的接口类型,绝大多数显卡都带有此种接口。它传输红、绿、蓝模拟信号以及同步信号(水平和垂直信号)。

1.1、外部接口

实物图:
在这里插入图片描述
电子原理图:
在这里插入图片描述
由电路图可以看到,VGA并没有特殊的外部芯⽚,我们需要关注的其实只有5个信号:HS⾏同步信号,VS场同步信号,R红基⾊,G 绿基⾊,B蓝基⾊。

1.2、色彩原理

三基⾊是指通过其他颜⾊的混合⽆法得到的“基本⾊”由于⼈的⾁眼有感知红、绿、蓝三种不同颜⾊的锥体细胞,因此⾊彩空间通常可以由三种基本⾊来表达。这是⾊度学的最基本原理,即 三基⾊原理。三种基⾊是相互独⽴的,任何⼀种基⾊都不能有其它两种颜⾊合成。红绿蓝是三基⾊,这三种颜⾊合成的颜⾊范围最为⼴泛。我们的RGB信号真是三基⾊的运⽤,对这三个信号赋予不同的数值,混合起来便是不同的⾊彩。
在这里插入图片描述
设计RGB信号时,既可以R信号、G信号和B信号独⽴的赋值,最后连到端⼝上,也可以直接⽤RGB当做⼀个整体信号,RGB信号在使
⽤时的位宽有三种常见格式。

  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

1.3、扫描方式

GA显⽰器扫描⽅式分为逐⾏扫描和隔⾏扫描:逐⾏扫描是扫描从屏幕左上⾓⼀点开始,从左像右逐点扫描,每扫描完⼀⾏,电⼦束回 到屏幕的左边下⼀⾏的起始位置,在这期间,CRT对电⼦束进⾏消隐,每⾏结束时,⽤⾏同步信号进⾏同步;当扫描完所有的⾏,形成⼀ 帧,⽤场同步信号进⾏场同步,并使扫描回到屏幕左上⽅,同时进⾏场消隐,开始下⼀帧。隔⾏扫描是指电⼦束扫描时每隔⼀⾏扫⼀线,完成

⼀屏后在返回来扫描剩下的线,隔⾏扫描的显⽰器闪烁的厉害,会让使⽤者的眼睛疲劳。因此我们⼀般都采⽤逐⾏扫描的⽅式。

扫描原理如下所⽰:
在这里插入图片描述

1.4、行场信号

在这里插入图片描述
⼀开始看这个时序图可能看不懂,它是把⾏场信号绘制在同⼀张图⾥,说明⾏场信号的控制是相似的,只是时间参数不⼀样⽽已。如果展开的话,其实时序是这样的:
在这里插入图片描述
若⼲个HS信号才组合⽽成⼀个VS,如果在⼀副图⽚中,那正确的时序表⽰⽅式应该如下图这样:
在这里插入图片描述
SYNC是“信号同步”,Back proch和Left border常常加在⼀起称为“显⽰后沿”,Addressable video为“显⽰区域”,Right porder和Front porch常常加在⼀起称为“显⽰前沿”,⼀个时序其实就是先拉⾼⼀段较短的“信号同步”时间,然后拉低⼀段很长的时间,这就是⼀个回合。同时需要注意,其实也可以完全相反。即先拉低⼀段时间“信号同步”时间,然后拉⾼⼀段很长的时间。

2、工程编写

参数定义:

`define vga_640_480
//`define vga_800_600

`ifdef vga_640_480
    `define  h_right_border  8
    `define  h_front_porch   8
    `define  h_sync_time     96
    `define  h_back_porch    40
    `define  h_left_border   8
    `define  h_data_time     640
    `define  h_total_time    800

    `define  v_bottom_border 8
    `define  v_front_porch   2
    `define  v_sync_time     2
    `define  v_back_porch    25
    `define  v_top_border    8
    `define  v_data_time     480
    `define  v_total_time    525
`elsif vga_1280_720
    `define  h_right_border  0
    `define  h_front_porch   110
    `define  h_sync_time     40
    `define  h_back_porch    220
    `define  h_left_border   0
    `define  h_data_time     1280
    `define  h_total_time    1650

    `define  v_bottom_border 0
    `define  v_front_porch   5
    `define  v_sync_time     5
    `define  v_back_porch    20
    `define  v_top_border    0
    `define  v_data_time     720
    `define  v_total_time    750
`elsif vga_1920_1080
    `define  h_right_border  0
    `define  h_front_porch   88
    `define  h_sync_time     44
    `define  h_back_porch    148
    `define  h_left_border   0
    `define  h_data_time     1920
    `define  h_total_time    2200

    `define  v_bottom_border 0
    `define  v_front_porch   4
    `define  v_sync_time     5
    `define  v_back_porch    36
    `define  v_top_border    0
    `define  v_data_time     1080
    `define  v_total_time    1125
`elsif vga_800_600
    `define  h_right_border  0
    `define  h_front_porch   40
    `define  h_sync_time     128
    `define  h_back_porch    88
    `define  h_left_border   0
    `define  h_data_time     800
    `define  h_total_time    1056

    `define  v_bottom_border 0
    `define  v_front_porch   1
    `define  v_sync_time     4
    `define  v_back_porch    23
    `define  v_top_border    0
    `define  v_data_time     600
    `define  v_total_time    628
`endif 

VGA驱动:

`include "vga_par.v" 

module vga_ctrl(
    input   wire                  clk         ,//VGA时钟25.2MHz
    input   wire                  rst_n       ,//复位信号
    input   wire [23:00]          data_dis    ,//

    output  reg [10:00]           h_addr      ,//数据有效显示区域行地址
    output  reg [10:00]           v_addr      ,//数据有效显示区域场地址

    output  reg                   hsync       ,//
    output  reg                   vsync       ,//

    output  reg  [07:00]          vga_r       ,//
    output  reg  [07:00]          vga_g       ,//
    output  reg  [07:00]          vga_b       , //
    output  reg                   vga_blk     ,//vga消隐信号
    output  wire                  vga_clk      //
);

//参数定义
    
    parameter  h_sync_sta =  1,
               h_sync_sto = `h_sync_time,
               h_data_sta = `h_left_border  + `h_front_porch +`h_sync_time,
               h_data_sto = `h_left_border  + `h_front_porch +`h_sync_time + `h_data_time,

               v_sync_sta = 1,
               v_sync_sto = `v_sync_time,
               v_data_sta = `v_top_border + `v_back_porch +`v_sync_time,
               v_data_sto = `v_top_border + `v_back_porch +`v_sync_time + `v_data_time;

//信号定义
    reg     [11:0]  cnt_h_addr;//行地址计数器
    wire            add_h_addr;
    wire            end_h_addr;

    reg     [11:0]  cnt_v_addr;//场地址计数器
    wire            add_v_addr;
    wire            end_v_addr;
//
    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            cnt_h_addr <= 12'd0;
        end
        else if (add_h_addr) begin
            if (end_h_addr) begin
                cnt_h_addr <= 12'd0;
            end
            else begin
                cnt_h_addr <= cnt_h_addr + 12'd1;
            end
        end
        else begin
          cnt_h_addr <= cnt_h_addr;
        end
    end

    assign add_h_addr = 1'b1;
    assign end_h_addr = add_h_addr && cnt_h_addr >= `h_total_time - 1;

    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            cnt_v_addr <= 12'd0;
        end
        else if (add_v_addr) begin
            if (end_v_addr) begin
                cnt_v_addr <= 12'd0;
            end
            else begin
                cnt_v_addr <= cnt_v_addr + 12'd1;
            end
        end
        else begin
          cnt_v_addr <= cnt_v_addr;
        end
    end

    assign add_v_addr = end_h_addr;
    assign end_v_addr = add_v_addr && cnt_v_addr >= `v_total_time - 1;

    //行场同步信号
    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            hsync <= 1'b1;
        end
        else if(cnt_h_addr == h_sync_sta -1) begin  
            hsync <= 1'd0;
        end
        else if(cnt_h_addr == h_sync_sto -1)begin
            hsync <= 1'b1;
        end
        else begin
            hsync <= hsync;
        end
    end

    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            vsync <= 1'b1;
        end
        else if(cnt_v_addr == v_sync_sta -1) begin  
            vsync <= 1'd0;
        end
        else if(cnt_v_addr == v_sync_sto -1)begin
            vsync <= 1'b1;
        end
        else begin
            vsync <= vsync;
        end
    end

    assign vga_clk = ~clk;

    //数据有效显示区域定义
    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            h_addr <= 11'd0;
        end
        else if((cnt_h_addr >= h_data_sta ) && (cnt_h_addr <= h_data_sto) )begin
            h_addr <= cnt_h_addr - h_data_sta;
        end
        else begin
            h_addr <= 11'd0;
        end
    end

   always @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            v_addr <= 11'd0;
        end
        else if((cnt_v_addr >= v_data_sta ) && (cnt_v_addr <= v_data_sto))begin
            v_addr <= cnt_v_addr - v_data_sta;
        end
        else begin
            v_addr <= 11'd0;
        end
    end

    //显示数据
    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            vga_r   <= 8'd0;
            vga_g   <= 8'd0;
            vga_b   <= 8'd0;
            vga_blk <= 1'b0;
        end
        else if((cnt_h_addr >= h_data_sta -1    ) 
            &&  (cnt_h_addr <= h_data_sto -1    ) 
            &&  (cnt_v_addr >= v_data_sta -1    ) 
            &&  (cnt_v_addr <= v_data_sto -1    ))begin
            vga_r <= data_dis[23-:08];
            vga_g <= data_dis[15-:08];
            vga_b <= data_dis[07-:08];
           vga_blk <= 1'b1;

        end
        else begin
            vga_r <= 8'd0;
            vga_g <= 8'd0;
            vga_b <= 8'd0;
            vga_blk <= 1'b0;
        end
    end

    //assign sync = 1'b0;
endmodule

顶层文件:

module vga_top(
    input   wire                    clk         ,
    input   wire                    rst_n       ,
    output  wire                    hsync       ,//
    output  wire                    vsync       ,
    output  wire  [07:00]           vga_r       ,//
    output  wire  [07:00]           vga_g       ,//
    output  wire  [07:00]           vga_b       ,//
    output  wire                    vga_blk     ,
    output  wire                    vga_clk      //
);

wire [10:00]          h_addr      ;
wire [10:00]          v_addr      ;
wire [23:00]          data_dis    ;


pll1	pll1_inst (
	.areset ( ~rst_n ),
	.inclk0 ( clk ),
	.c0 ( vga_clk ),
	.c1 ( clk1 )
	);

data_gen u_data_gen(
    .clk         (vga_clk  ),//VGA时钟25.2MHz
    .rst_n       (rst_n    ),//复位信号
    .h_addr      (h_addr   ),//数据有效显示区域行地址
    .v_addr      (v_addr   ),//数据有效显示区域场地址
    .vga_blk     (vga_blk  ),
    .data_dis    (data_dis )//
);

vga_ctrl u_vga_ctrl(
    .clk         (vga_clk  ),//VGA时钟25.2MHz
    .rst_n       (rst_n    ),//复位信号
    .data_dis    (data_dis ),//
    .h_addr      (h_addr   ),//数据有效显示区域行地址
    .v_addr      (v_addr   ),//数据有效显示区域场地址
    .hsync       (hsync    ),//
    .vsync       (vsync    ),//
    .vga_r       (vga_r    ),//
    .vga_g       (vga_g    ),//
    .vga_b       (vga_b    ), //
    .vga_blk     (vga_blk  )
);
endmodule

2.1、彩条输出

module data_gen(
    input   wire                  clk         ,//VGA时钟25.2MHz
    input   wire                  rst_n       ,//复位信号
    input   wire [10:00]          h_addr      ,//数据有效显示区域行地址
    input   wire [10:00]          v_addr      ,//数据有效显示区域场地址
    input   wire                  vga_blk     ,

    output  reg [23:00]           data_dis     //
);
    parameter 
        BLACK       = 24'H000000,
        RED         = 24'HFF0000,
        GREEN       = 24'H00FF00,
        BLUE        = 24'H0000FF,
        YELLOW      = 24'HFFFF00,
        SKY_BULE    = 24'H00FFFF,
        PURPLE      = 24'HFF00FF,
        GRAY        = 24'HC0C0C0,
        WHITE       = 24'HFFFFFF;
    parameter 
        h_vld       = 640,
        v_vld       = 480,

        pic_w       =272,
        pic_h       =16,

        x_start     =   (h_vld - pic_w >>1 ) -1,
        y_start     =   (v_vld - pic_h >>1 ) -1;
    reg [10:00] pix_x,pix_y;
    reg [ 271:0 ] char_line[ 15:0 ];   //272*16
    reg [15:00]     rom_address;

//彩条
always @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            data_dis <= WHITE; 
        end
        else  begin
            case (h_addr)
                0       :   data_dis <= BLACK   ;
                80      :   data_dis <= RED     ;
                160     :   data_dis <= GREEN   ;    
                240     :   data_dis <= BLUE    ;    
                320     :   data_dis <= YELLOW  ;    
                400     :   data_dis <= SKY_BULE;        
                480     :   data_dis <= PURPLE  ;    
                560     :   data_dis <= GRAY    ;    
                default :   data_dis <= data_dis;        
            endcase
        end
    end
endmodule

2.2、点阵输出

//初始化显示文字
always@( posedge clk or negedge rst_n ) begin
    if ( !rst_n ) begin
      char_line[ 0 ]  = 272'h010010010000000000000000000000000000000000000000000000000000;
      char_line[ 1 ]  = 272'h010021000804000000000000000000000000000000000000000000000000;
      char_line[ 2 ]  = 272'h028011fc7f78000000000000000000000000000000000000000000000000;
      char_line[ 3 ]  = 272'h04401200004018003c000800380018007e0018001800180008003c007e00;
      char_line[ 4 ]  = 272'h082085f82240240042003800440024004200240024002400380042004200;
      char_line[ 5 ]  = 272'h101041081440400042000800420042000400420040004200080042000400;
      char_line[ 6 ]  = 272'h2fe84948ff7e400002000800420042000400420040004200080042000400;
      char_line[ 7 ]  = 272'hc106092808485c000400080042004200080042005c004200080002000800;
      char_line[ 8 ]  = 272'h010017fe0848620018000800460042000800420062004200080004000800;
      char_line[ 9 ]  = 272'h3ff811087f484200040008003a0042001000420042004200080008001000;
      char_line[ 10 ] = 272'h0100e2480848420002000800020042001000420042004200080010001000;
      char_line[ 11 ] = 272'h111022282a48420042000800020042001000420042004200080020001000;
      char_line[ 12 ] = 272'h110823fc4948220042000800240024001000240022002400080042001000;
      char_line[ 13 ] = 272'h2104200888881c003c003e0018001800100018001c0018003e007e001000;
      char_line[ 14 ] = 272'h450420502888000000000000000000000000000000000000000000000000;
      char_line[ 15 ] = 272'h020000201108000000000000000000000000000000000000000000000000;
    end
end

always @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
        pix_x <= 11'd0;
        pix_y <= 11'd0;
    end
    else if ((h_addr >= x_start && h_addr < x_start +pic_w)
           &&(v_addr >= y_start && v_addr < y_start + pic_h)) begin
        pix_x <= h_addr - x_start;
        pix_y <= v_addr - y_start;
    end
    else begin
        pix_x <= 11'h7ff;
        pix_y <= 11'h7ff;
    end
end

always @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            data_dis <= BLACK; 
        end
        else if (pix_x != 11'h7FF && pix_y != 11'h7FF) begin
            if(char_line[pix_y][271 - pix_x]== 1'b1)begin
                data_dis <= WHITE;
            end
            else begin
                data_dis <= BLUE;
            end
        end 
        else begin
        data_dis <= data_dis;
        end
end

2.3、图片输出

always @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            data_dis <= BLACK; 
        end
        else if(flag_enable_out)begin
                data_dis <= rom_data;
            end
        else begin
            data_dis <= BLACK;
        end
        

end

//ROM地址计数器
always @( posedge clk or negedge rst_n ) begin
    if ( !rst_n ) begin
        rom_address <= 0;
    end
    else if ( flag_clear_rom_address ) begin //计数满清零
            rom_address <= 0;
    end
    else if ( flag_enable_out ) begin  //在有效区域内+1
        rom_address <= rom_address + 1;
    end
    else begin  //无效区域保持
        rom_address <= rom_address;
    end
end
assign flag_clear_rom_address = rom_address == 52*52 -1;
assign flag_begin_h     = h_addr > ( ( 640 - 52 ) / 2 ) && h_addr < ( ( 640 - 52 ) / 2 ) + 52 + 1;
assign flag_begin_v     = v_addr > ( ( 480 - 52 )/2 ) && v_addr <( ( 480 - 52 )/2 ) + 52 + 1;
assign flag_enable_out = flag_begin_h && flag_begin_v;

rom	rom_inst (
	.address ( rom_address ),
	.clock ( clk ),
	.q (rom_data)
	);

3、效果实现

3.1、引脚绑定

这里我实验用的是EP4CE115F29C7开发板,不同开发板根据手册绑定不同的引脚。
在这里插入图片描述

3.2、上板验证

效果:
在这里插入图片描述
修改分辨率为800*600 时钟改为40MHz:
在这里插入图片描述
可以看到图形整体左移,基本符合预期。
点阵:
在这里插入图片描述
这里修改代码之后可以看见字符整体往左上方移动,基本符合预期。
在这里插入图片描述
图片:
在这里插入图片描述
原图:
在这里插入图片描述

4、总结

VGA的实现在掌握VGA协议之后不算很难,主要是理解时序。还有确保硬件的问题,我最开始因为用的另一种开发板,外部接口有损,就算是编写正确工程也得不到想要的结果。之后换了开发板修改引脚,一下子就出来了。

5、参考链接

VGA协议
VGA接口
基于FPGA的VGA显示彩条、字符、图片

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值