基于FPGA的VGA协议实现

一 VGA相关介绍

以下内容来源百度以及https://blog.csdn.net/Learning1232/article/details/131126352?spm=1001.2014.3001.5502博客,详细内容可参考原文!!!

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

在这里插入图片描述
在这里插入图片描述
VGA显示图像使用扫描的方式,从左上角到右下角逐渐填充,通过这种方式形成一帧完整的图像。当扫描速度足够快,就可以看到一副完整的图片,而不是闪烁的像素点。
本次实验选用的分辨率为640*480@60Hz,表示的含义是:

有效画面横向由像素点640个,列像素点由480个,刷新的频率是60Hz,即1s电脑显示60张图片。也就是从左上角到右下角的过程持续了60次。

VGA驱动波形:
直接贴640*480@60Hz的波形图
请添加图片描述
这个图中表示的含义同下表中对应分辨率的数值:
请添加图片描述

可以看出Hsync的完整的行扫描周期(扫描完一行的像素点个数)是由同步,后沿,左边框,有效图像,右边框,前沿组成的。VSync同理。最终经过完整的一帧扫描后形成的范围如下:

请添加图片描述
最后是VGA的时序波形图:
请添加图片描述

从上述内容可以看出对于640480@60Hz的显示器,实际行扫描周期为800,场扫描周期为525。所以一秒钟下来,总共会经过800525*60=25200000个周期。得到频率为25.2MHz,也就是25MHz。考虑到FPGA的基频为50MHz,需要进行一定的时钟分频。

二 结果演示

在这里插入图片描述

三 思路整理

1、时钟分频(50MHz—>25MHz)进行扫描
2、设计Data驱动准备RGB的数据,提供给VGA发送
3、设计VGA驱动(需要对外发送出某行某列的像素值到显示屏处理)

四 代码编写

根据DEC-115数据手册可以判断出采用的是RGB888。
在这里插入图片描述

3.1 时钟分频

可以使用quartus中的IP核,直接调用PLL锁相环进行时钟分频
详细了解可参考【锁相环PLL背景意义,原理,介绍,应用】
打开quartus工程项目工作空间,在右侧IP Catalog添加pll
在这里插入图片描述
需要一个50MHz的和25MHz,先设定基频为50MHz
在这里插入图片描述
设置c0输出50MHz
在这里插入图片描述
设置c1输出25MHz
在这里插入图片描述
选择生成文件
在这里插入图片描述
可以看见文件夹中产生了如下文件:
在这里插入图片描述

3.2 添加ROM IP

因为后续要显示图片,需要使用ROM进行存放图片数据
在这里插入图片描述
应为RGB888,输出数据位24位,所以从rom中也应该读取出24位,rtl可以从左侧看出来。下面是问需要多少个24bit的空间。就要大于等于你的图片大小(width*height)。在使用过程中我第一张图片有一个参数是奇数,显示出来的图片就很奇怪。所以尽量可以使用偶数参数。

在这里插入图片描述
在这里插入图片描述
选择你由图片生成的hex或者mif文件(可以使用win10自带的画图转换图片为bitmap,然后再使用BMP2MIF软件转换成hex或者mif)。
在这里插入图片描述
在这里插入图片描述
可以在工作空间左上看见如下:
在这里插入图片描述
后续修改可双击。

3.2 VGA驱动

module vga_dirve (input			wire						clk,            //系统时钟
                input			wire						rst_n,          //复位
                input			wire		[ 23:0 ]		rgb_data,       //16位RGB对应值
                output			wire							vga_clk,    //vga时钟 25M
                output			reg							h_sync,     //行同步信号
                output			reg							v_sync,     //场同步信号
                output			reg		[ 11:0 ]				addr_h, //行地址
                output			reg		[ 11:0 ]				addr_v,  //列地址
                output			wire		[ 7:0 ]				rgb_r,  //红基色
                output			wire		[ 7:0 ]				rgb_g,  //绿基色
                output			wire		[ 7:0 ]				rgb_b  //蓝基色
);
    
    // 640 * 480 60HZ
    localparam	 H_FRONT = 16; // 行同步前沿信号周期长
    localparam	 H_SYNC  = 96; // 行同步信号周期长
    localparam	 H_BLACK = 48; // 行同步后沿信号周期长
    localparam	 H_ACT   = 640; // 行显示周期长
    localparam	 V_FRONT = 11; // 场同步前沿信号周期长
    localparam	 V_SYNC  = 2; // 场同步信号周期长
    localparam	 V_BLACK = 31; // 场同步后沿信号周期长
    localparam	 V_ACT   = 480; // 场显示周期长
    
    // 800 * 600 72HZ
    // localparam	 H_FRONT = 40; // 行同步前沿信号周期长
    // localparam	 H_SYNC  = 120; // 行同步信号周期长
    // localparam	 H_BLACK = 88; // 行同步后沿信号周期长
    // localparam	 H_ACT   = 800; // 行显示周期长
    // localparam	 V_FRONT = 37; // 场同步前沿信号周期长
    // localparam	 V_SYNC  = 6; // 场同步信号周期长
    // localparam	 V_BLACK = 23; // 场同步后沿信号周期长
    // localparam	 V_ACT   = 600; // 场显示周期长
    
    
    localparam	H_TOTAL = H_FRONT + H_SYNC + H_BLACK + H_ACT; // 行周期
    localparam	V_TOTAL = V_FRONT + V_SYNC + V_BLACK + V_ACT; // 列周期
    
    reg			[ 11:0 ]			cnt_h			; // 行计数器
    reg			[ 11:0 ]			cnt_v			; // 场计数器
    reg			[ 23:0 ]			rgb			; // 对应显示颜色值
    
    // 对应计数器开始、结束、计数信号
    wire							flag_enable_cnt_h			;
    wire							flag_clear_cnt_h			;
    wire							flag_enable_cnt_v			;
    wire							flag_clear_cnt_v			;
    wire							flag_add_cnt_v  			;
    wire							valid_area      			;
    
    
    // 25M时钟 行周期*场周期*刷新率 = 800 * 525* 60
    wire							clk_25			;
    // 50M时钟 1040 * 666 * 72
    wire							clk_50			;
    //
    pll	pll_inst (
        .areset ( ~rst_n ),
        .inclk0 ( clk ),
        .c0 ( clk_50 ), //50M
        .c1 ( clk_25 ), //25M
        );
    //根据不同分配率选择不同频率时钟
    assign vga_clk = clk_25;
    
    
    // 行计数
    always @( posedge vga_clk or negedge rst_n ) begin
        if ( !rst_n ) begin
            cnt_h <= 0;
        end
        else if ( flag_enable_cnt_h ) begin
            if ( flag_clear_cnt_h ) begin
                cnt_h <= 0;
            end
            else begin
                cnt_h <= cnt_h + 1;
            end
        end
        else begin
            cnt_h <= 0;
        end
    end
    assign flag_enable_cnt_h = 1;
    assign flag_clear_cnt_h  = cnt_h == H_TOTAL - 1;
    
    // 行同步信号
    always @( posedge vga_clk or negedge rst_n ) begin
        if ( !rst_n ) begin
            h_sync <= 0;
        end
        else if ( cnt_h == H_SYNC - 1 ) begin // 同步周期时为1
            h_sync <= 1;
        end
            else if ( flag_clear_cnt_h ) begin // 其余为0
            h_sync <= 0;
            end
        else begin
            h_sync <= h_sync;
        end
    end
    
    // 场计数
    always @( posedge vga_clk or negedge rst_n ) begin
        if ( !rst_n ) begin
            cnt_v <= 0;
        end
        else if ( flag_enable_cnt_v ) begin
            if ( flag_clear_cnt_v ) begin
                cnt_v <= 0;
            end
            else if ( flag_add_cnt_v ) begin
                cnt_v <= cnt_v + 1;
            end
            else begin
                cnt_v <= cnt_v;
            end
        end
        else begin
            cnt_v <= 0;
        end
    end
    assign flag_enable_cnt_v = flag_enable_cnt_h;
    assign flag_clear_cnt_v  = cnt_v == V_TOTAL - 1;
    assign flag_add_cnt_v    = flag_clear_cnt_h;
    
    // 场同步信号
    always @( posedge vga_clk or negedge rst_n ) begin
        if ( !rst_n ) begin
            v_sync <= 0;
        end
        else if ( cnt_v == V_SYNC - 1 ) begin
            v_sync <= 1;
        end
            else if ( flag_clear_cnt_v ) begin
            v_sync <= 0;
            end
        else begin
            v_sync <= v_sync;
        end
    end
    
    // 对应有效区域行地址 1-640
    always @( posedge vga_clk or negedge rst_n ) begin
        if ( !rst_n ) begin
            addr_h <= 0;
        end
        else if ( valid_area ) begin
            addr_h <= cnt_h - H_SYNC - H_BLACK + 1;
        end
        else begin
            addr_h <= 0;
        end
    end
    // 对应有效区域列地址 1-480
    always @( posedge vga_clk or negedge rst_n ) begin
        if ( !rst_n ) begin
            addr_v <= 0;
        end
        else if ( valid_area ) begin
            addr_v <= cnt_v -V_SYNC - V_BLACK + 1;
        end
        else begin
            addr_v <= 0;
        end
    end
    // 有效显示区域
    assign valid_area = cnt_h >= H_SYNC + H_BLACK && cnt_h <= H_SYNC + H_BLACK + H_ACT && cnt_v >= V_SYNC + V_BLACK && cnt_v <= V_SYNC + V_BLACK + V_ACT;


    // 显示颜色
    always @( posedge vga_clk or negedge rst_n ) begin
        if ( !rst_n ) begin
            rgb <= 24'h0;
        end
        else if ( valid_area ) begin
            rgb <= rgb_data;
        end
        else begin
            rgb <= 24'b0;
        end
    end
    assign rgb_r = rgb[ 23:16 ];
    assign rgb_g = rgb[ 15:8 ];
    assign rgb_b = rgb[ 7:0 ];

endmodule // vga_dirve


3.3 Data驱动

module data_drive (input			wire						vga_clk,
                input			wire						rst_n,
                input			wire		[ 11:0 ]		addr_h,//当前有效面积处的位置
                input			wire		[ 11:0 ]		addr_v,
                input			wire		[ 2:0 ]		 key,
                output			reg		[ 23:0 ]				rgb_data);

    localparam orange = 24'd16750899;   // 11111100_11111111_00001000
    localparam yellow = 24'd16776960;   // 11111111_11111111_10000000
    localparam green  = 24'd65280;      // 00111111_11111111_00000000
    localparam blue   = 24'd255;        // 00000001_11111111_00000000
    localparam indigo = 24'd10027161;    // 01111100_10000000_00000000
    localparam purple = 24'd16738047;      // 00001111_11111111_00000000
    localparam white  = 24'd16777215;   // 11111111_11111111_11111111
    localparam black  = 24'd0;          // 00000000_00000000_00000000
    localparam red    = 24'd16711936;   // 11111111_11111111_11110000


    // 分辨率为 640*480 时行时序各个参数定义
    parameter       C_H_SYNC_PULSE      =   96  ,
                    C_H_BACK_PORCH      =   48  ,
                    C_H_ACTIVE_TIME     =   640 ,
                    C_H_FRONT_PORCH     =   16  ,
                    C_H_LINE_PERIOD     =   800 ;

    // 分辨率为 640*480 时场时序各个参数定义
    parameter       C_V_SYNC_PULSE      =   2   , 
                    C_V_BACK_PORCH      =   33  ,
                    C_V_ACTIVE_TIME     =   480 ,
                    C_V_FRONT_PORCH     =   10  ,
                    C_V_FRAME_PERIOD    =   525 ;
//


    reg [11:0]  x;
    reg [11:0]  y;

    reg turn_flag_x;
    reg turn_flag_y;

    
    //方块的长和宽
    parameter LENGTH1 = 200;
    parameter WIDTH1  = 200;
    
    reg frame_flag;             		//帧结束标志


    //帧结束标志
    always @(posedge vga_clk or negedge rst_n) begin
        if (!rst_n) begin
            frame_flag <= 1'b0;
        end
        else if ((addr_h == 23) && (addr_v == 23)) begin
            frame_flag <= 1'b1;
        end
        else begin
            frame_flag <= 1'b0;
        end
    end
    
    //变量y
    always @(posedge vga_clk or negedge rst_n) begin
        if (!rst_n) begin
            y <= 'd0;
        end
        else if ((turn_flag_y == 1'b0 && frame_flag == 1'b1 && (y == 480 - WIDTH1 - 1)) || (turn_flag_y == 1'b1 && frame_flag == 1'b1 && (y == 'd0)))begin
            y <= y;
        end
        else if (turn_flag_y == 1'b0 && frame_flag == 1'b1) begin
            y <= y + 1'b1;
        end
        else if (turn_flag_y == 1'b1 && frame_flag == 1'b1) begin
            y <= y - 1'b1;
        end
    end

    //位置变量y翻转标志
    always @(posedge vga_clk or negedge rst_n) begin
        if (!rst_n) begin
            turn_flag_y <= 1'b0;
        end
        else if ((y == 480 - WIDTH1 - 1) && frame_flag == 1'b1 && turn_flag_y == 1'b0) begin
            turn_flag_y <= 1'b1;
        end
        else if (y == 'd0 && frame_flag == 1'b1 && turn_flag_y == 1'b1) begin
            turn_flag_y <= 1'b0;
        end
    end

    //变量x
    always @(posedge vga_clk or negedge rst_n) begin
        if (!rst_n) begin
            x <= 'd0;
        end
        else if((turn_flag_x == 1'b0 && frame_flag == 1'b1 && x == (640 - LENGTH1 - 1'b1)) || (turn_flag_x == 1'b1 && frame_flag == 1'b1 && x == 'd0)) begin
            x <= x;
        end
        else if (turn_flag_x == 1'b0 && frame_flag == 1'b1) begin
            x <= x + 1'b1;
        end
        else if (turn_flag_x == 1'b1 && frame_flag == 1'b1) begin
            x <= x - 1'b1;
        end
    end

    //位置变量x翻转标志
    always @(posedge vga_clk or negedge rst_n) begin
        if (!rst_n) begin
            turn_flag_x <= 1'b0;
        end
        else if (turn_flag_x == 1'b0 && frame_flag == 1'b1 && x == 640 - LENGTH1 - 1'b1) begin
            turn_flag_x <= 1'b1;
        end
        else if (turn_flag_x == 1'b1 && frame_flag == 1'b1 && x == 'd0) begin
            turn_flag_x <= 1'b0;
        end
    end
//



    reg [ 303:0 ] char_line[ 31:0 ];
    localparam	states_1 = 1; // 彩条
    localparam	states_2 = 2; // 字符
    localparam	states_3 = 3; // 图片
    parameter	height = 286; // 图片高度
    parameter	width  = 300; // 图片宽度
    reg			[ 1:0 ]			states_current			; // 当前状态
    reg			[ 1:0 ]			states_next			    ; // 下个状态
    reg			[ 17:0 ]		rom_address				; // ROM地址
    wire			[ 23:0 ]		rom_data				; // 图片数据
    wire							flag_enable_out1			; // 文字有效区域
    wire							flag_enable_out2			; // 图片有效区域
    wire							flag_clear_rom_address		; // 地址清零
    wire							flag_begin_h			    ; // 图片显示行
    wire							flag_begin_v			    ; // 图片显示列
    //状态转移
    always @( posedge vga_clk or negedge rst_n ) begin
        if ( !rst_n ) begin
            states_current <= states_1;
        end
        else begin
            states_current <= states_next;
        end
    end
    //状态判断
    always @( posedge vga_clk or negedge rst_n ) begin
        if ( !rst_n ) begin
            states_next <= states_1;
        end
        else if ( key[ 0 ] ) begin
            states_next <= states_1;
        end
            else if ( key[ 1 ] ) begin
            states_next <= states_2;
            end
            else if ( key[ 2 ] ) begin
            states_next <= states_3;
            end
        else begin
            states_next <= states_next;
        end
    end
    //状态输出
    always @( * ) begin
        case ( states_current )
            states_1 : begin
                if ( addr_h == 0 ) begin
                    rgb_data = black;
                end
                else if(addr_h >=  x && addr_h < x + LENGTH1 && addr_v >=  y && addr_v <  y + WIDTH1)
                    rgb_data = white;
                else if ( addr_h >0 && addr_h <81 ) begin
                    rgb_data = red;
                end
                else if ( addr_h >80 && addr_h <161 ) begin
                    rgb_data = orange;
                end
                else if ( addr_h >160 && addr_h <241 ) begin
                    rgb_data = yellow;
                end
                else if ( addr_h >240 && addr_h <321 ) begin
                    rgb_data = green;
                end
                else if ( addr_h >320 && addr_h <401 ) begin
                    rgb_data = blue;
                end
                else if ( addr_h >400 && addr_h <481 ) begin
                    rgb_data = indigo;
                end
                else if ( addr_h >480 && addr_h <561 ) begin
                    rgb_data = purple;
                end
                else if ( addr_h >560 && addr_h <641 ) begin
                    rgb_data = white;
                end
                else begin
                    rgb_data = black;
                end
                
            end
            states_2 : begin
                if ( flag_enable_out1 ) begin//显示字符
                //第几行,第几列
                    rgb_data = char_line[ addr_v-208 ][ 385 - addr_h ]? white:black;
                end
                else begin
                    rgb_data = black;
                end
            end
            states_3 : begin
                if ( flag_enable_out2 ) begin
                    rgb_data = rom_data;
                end
                else begin
                    rgb_data = black;
                end
                
            end
            default: begin
                case ( addr_h )
                    0 : rgb_data      = black;
                    1 : rgb_data      = red;
                    81 : rgb_data     = orange;
                    161: rgb_data     = yellow;
                    241: rgb_data     = green;
                    321: rgb_data     = blue;
                    401: rgb_data     = indigo;
                    481: rgb_data     = purple;
                    561: rgb_data     = white;
                    default: rgb_data = rgb_data;
                endcase
            end
        endcase
    end
    assign flag_enable_out1 = states_current == states_2 && addr_h > 80 && addr_h < 80+304+1 && addr_v > 208  && addr_v < 208+32+1 ;
    assign flag_begin_h     = addr_h > ( ( 640 - width ) / 2 ) && addr_h < ( ( 640 - width ) / 2 ) + width + 1;
    assign flag_begin_v     = addr_v > ( ( 480 - height )/2 ) && addr_v <( ( 480 - height )/2 ) + height + 1;
    assign flag_enable_out2 = states_current == states_3 && flag_begin_h && flag_begin_v;
    
    //ROM地址计数器
    always @( posedge vga_clk or negedge rst_n ) begin
        if ( !rst_n ) begin
            rom_address <= 0;
        end
        else if ( flag_clear_rom_address  ||  key[ 2 ] ) begin //计数满清零
            rom_address <= 0;
        end
            else if ( flag_enable_out2 ) begin  //在有效区域内+1
            rom_address <= rom_address + 1;
            end
        else begin  //无效区域保持
            rom_address <= rom_address;
        end
    end
    assign flag_clear_rom_address = rom_address == height * width - 1;
    //初始化显示文字
    always@( posedge vga_clk or negedge rst_n ) begin
        if ( !rst_n ) begin
            char_line[0] = 304'h0000000000000000000000000000000000000000000000000000000000000000000000000000;
            char_line[1] = 304'h0000000000000000000000000000000000000000000000000000000000000000000000000000;
            char_line[2] = 304'h0000000000000000000000000000000000000000000000000000000000000001000000010000;
            char_line[3] = 304'h0000000000000000000000000000000000000000000000000000000000000001C0000001C000;
            char_line[4] = 304'h0000000000000000000000000000000000000000000000000000000000200001800000018000;
            char_line[5] = 304'h0000000000000000000000000000000000000000000000000000000000700001800000018000;
            char_line[6] = 304'h01E007C007E0008003C01FFC03C001E003C0006007C007E000000FFFFFF00001800000018000;
            char_line[7] = 304'h061818600838018006201FFC0620061806200060186008380000000180000001800000018000;
            char_line[8] = 304'h0C18303010181F800C3010080C300C180C3000E0303010180000000180000001800000018000;
            char_line[9] = 304'h08183018200C01801818301018180818181800E03018200C0000000180000001803000018030;
            char_line[10] = 304'h18003018200C01801818201018181800181801603018200C0000000180003FFFFFF83FFFFFF8;
            char_line[11] = 304'h10003018300C01801808202018081000180801603018300C0000000180000007C0000007C000;
            char_line[12] = 304'h10000018300C0180300C0020300C1000300C02600018300C0000000180000007C0000007C000;
            char_line[13] = 304'h30000018000C0180300C0040300C3000300C04600018000C000000018000000DA000000DA000;
            char_line[14] = 304'h33E0003000180180300C0040300C33E0300C046000300018000000018000000DA000000DA000;
            char_line[15] = 304'h3630006000180180300C0040300C3630300C0860006000180000000180C0001DB000001DB000;
            char_line[16] = 304'h381803C000300180300C0080300C3818300C086003C00030000007FFFFE00019900000199000;
            char_line[17] = 304'h3808007000600180300C0080300C3808300C1060007000600000000180000031980000319800;
            char_line[18] = 304'h300C001800C00180300C0100300C300C300C3060001800C000000001800000318C0000318C00;
            char_line[19] = 304'h300C000801800180300C0100300C300C300C20600008018000000001800000618E0000618E00;
            char_line[20] = 304'h300C000C03000180300C0100300C300C300C4060000C030000000001800000C1870000C18700;
            char_line[21] = 304'h300C000C02000180300C0100300C300C300C7FFC000C02000000000180000181838001818380;
            char_line[22] = 304'h300C300C04040180180803001808300C18080060300C0404000000018000010181C0010181C0;
            char_line[23] = 304'h180C300C08040180181803001818180C18180060300C0804000000018000020180F0020180F0;
            char_line[24] = 304'h1808300810040180181803001818180818180060300810040000000180000401807E0401807E;
            char_line[25] = 304'h0C183018200C01800C3003000C300C180C3000603018200C0000000180000801803008018030;
            char_line[26] = 304'h0E3018303FF803C00620030006200E300620006018303FF80000000180003001800030018000;
            char_line[27] = 304'h03E007C03FF81FF803C0030003C003E003C003FC07C03FF80000000180184001800040018000;
            char_line[28] = 304'h00000000000000000000000000000000000000000000000000003FFFFFFC0001800000018000;
            char_line[29] = 304'h0000000000000000000000000000000000000000000000000000000000000001800000018000;
            char_line[30] = 304'h0000000000000000000000000000000000000000000000000000000000000001000000010000;
            char_line[31] = 304'h0000000000000000000000000000000000000000000000000000000000000000000000000000;
        end
    end
    
    //实例化ROM
    ROM1_port	ROM1_inst (
    .address ( rom_address ),
    .clock ( vga_clk ),
    .q ( rom_data )
    );
endmodule // data_drive

源码

https://gitee.com/quartfee/de115-vga

参考资料

1.【FPGA入门】第七篇、FPGA实现VGA接口驱动
2. 基于FPGA的VGA显示彩条、字符、图片

总结

通过本次实验了解了VGA协议,如何使用IP核简化开发步骤。图片的颜色显示出现了一些问题,后续需要进一步修改,并且再加上动图的实现。或者从串口发送数据到ROM,再从ROM中读到显示屏上。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值