FPGA实验记录四:基于FPGA的VGA协议实现

FPGA实验记录四:基于FPGA的VGA协议实现

板子:DE2-115

IDE:QuartusII 18.1(Lite)

仿真软件:Modelsim-Alterl

芯片系列:Cydone IV E

芯片名称:EP4CE115F29C7

芯片核心:EP4CE115

DE2-115

〇、VGA协议

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

一、外部接口

img

img

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

二、色彩原理

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

img

设计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
  2. RGB_24,R:G:B = 8:8:8,即RGB888

三、扫描方式

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

img

四、行场信号(重点)

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

img

img

img

img

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

img

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

行场同步信号代码:

vga_ctrl.v

`include "vga_param.v"
// `define vga_640_480
// `define vga_1920_1080



/**
 *
 * VGA控制模块
*/
module vga_ctrl(
    input   wire            clk         ,
    input   wire            rst_n       ,
    input   wire [23:0]     data_display,   // 要显示的数据

    output  reg [10:0]      h_addr      ,   // 行地址--数据有效显示区域行地址
    output  reg [10:0]      v_addr      ,   // 场地址--数据有效显示区域场地址
    
    output  reg             hsync       ,
    output  reg             vsync       ,
    output  reg [7:0]       vga_r       ,    // R
    output  reg [7:0]       vga_g       ,    // G
    output  reg [7:0]       vga_b       ,    // B
    // output  wire            vga_sync_n  ,
    output  reg             vga_black   ,
    output  wire            vga_clk          // 时钟
);

    
    parameter           H_SYNC_START = 1;                        // 行同步开始
    parameter           H_SYNC_STOP  = `H_Sync_Time;         // 行同步结束
    parameter           H_DATA_START = `H_Sync_Time + `H_Back_Porch + `H_Left_Border; 
    parameter           H_DATA_STOP  = `H_Sync_Time + `H_Back_Porch + `H_Left_Border + `H_Data_Time;
    
    parameter           V_SYNC_START = 1;                        // 场同步开始
    parameter           V_SYNC_STOP  = `V_Sync_Time;         // 场同步结束
    parameter           V_DATA_START = `V_Sync_Time + `V_Back_Porch + `V_Top_Border; 
    parameter           V_DATA_STOP  = `V_Sync_Time + `V_Back_Porch + `V_Top_Border + `V_Data_Time;


    // parameter           V_BLANK      = `V_Total_Time;        //场空白信号总周期长
    // parameter           H_BLANK      = `H_Total_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)
                cnt_h_addr <= 12'd0;
            else
                cnt_h_addr <= cnt_h_addr + 1'b1;
        end

        else begin
            cnt_h_addr <= 12'd0;
        end
    end
    assign add_h_addr = 1'b1;
    assign end_h_addr = ((cnt_h_addr>=`H_Total_Time-1) && add_h_addr);


// 场地址计数
    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)
                cnt_v_addr <= 12'd0;
            else
                cnt_v_addr <= cnt_v_addr + 1'b1;
        end

        else begin
            cnt_v_addr <= cnt_v_addr;
        end
    end
    assign add_v_addr = end_h_addr;
    assign end_v_addr = ((cnt_v_addr>=`V_Total_Time-1) && add_v_addr);
// 行同步信号  低有效
    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            hsync <= 1'b1;
        end
    
        else if (cnt_h_addr == H_SYNC_START - 1) begin  // 行同步开始
            hsync <= 1'b0;
        end

        else if (cnt_h_addr == H_SYNC_STOP - 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_START - 1) begin
            vsync <= 1'b0;
        end

        else if (cnt_v_addr == V_SYNC_STOP - 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_START - 1)) && (cnt_h_addr <= H_DATA_STOP)) begin
            h_addr <= cnt_h_addr - H_DATA_START - 1;  // 总的 减去 前面的多余部分
        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_h_addr >= (V_DATA_START - 1)) && (cnt_h_addr <= V_DATA_STOP)) begin
            v_addr <= cnt_v_addr - V_DATA_START - 1;
        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;
        end
    
        else if (  ((cnt_h_addr >= (H_DATA_START - 1)) && (cnt_h_addr <= H_DATA_STOP)) && 
                    ((cnt_v_addr >= (V_DATA_START - 1)) && (cnt_v_addr <= V_DATA_STOP))  ) begin
            vga_r <= data_display[23:16];
            vga_g <= data_display[15:8];
            vga_b <= data_display[7:0];
            vga_black <= 1'b1;
        end
    
        else begin
            vga_r <= 8'd0;
            vga_g <= 8'd0; 
            vga_b <= 8'd0;
            vga_black <= 1'b0;
        end
    end

endmodule

时序仿真:

img

五、规格参数

`ifdef vga_640_480
    `define H_Right_Border 8
    `define H_Front_Border 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_Borde 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

    //执行操作A
`elsif vga_1920_1080
    `define H_Right_Border 0
    `define H_Front_Border 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_Borde 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
    //执行操作B
    
`elsif vga_800_480
    `define H_Right_Border 0
    `define H_Front_Border 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_Borde 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
    //执行操作B
    
`elsif vga_800_600
    `define H_Right_Border 0
    `define H_Front_Border 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_Borde 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
    //执行操作B
    
`elsif vga_1024_600
    `define H_Right_Border 0
    `define H_Front_Border 24
    `define H_Sync_Time    136
    `define H_Back_porch   160
    `define H_Left_Border  0
    `define H_Data_Time    1024
    `define H_Total_Time   1344
    `define V_Bottom_Borde 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
    //执行操作B
    
`elsif vga_1024_768
    `define H_Right_Border 0
    `define H_Front_Border 24
    `define H_Sync_Time    136
    `define H_Back_porch   160
    `define H_Left_Border  0
    `define H_Data_Time    1024
    `define H_Total_Time   1344
    `define V_Bottom_Borde 0
    `define V_Front_Porch  3
    `define V_Sync_Time    6
    `define V_Back_porch   29
    `define V_Top_Border   0
    `define V_Data_Time    768
    `define V_Total_Time   806
    //执行操作B
    
`elsif vga_1280_720
    `define H_Right_Border 0
    `define H_Front_Border 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_Borde 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
    //执行操作B
    
`elsif vga_1920_1080
    `define H_Right_Border 0
    `define H_Front_Border 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_Borde 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
    //执行操作B
`else  
    //默认,可以不写

`endif 

一、显示彩条

image-20220611221945853

一、时钟

640×480 60HZ和800×600 72HZ,对应时钟分别为25M和50M,这里可以直接调用IP核里的PPL分频时钟也可以自己书写分频时钟

自写分频时钟:

module clk_25M_get(
  input   wire          clk           , //VGA时
  input   wire          rst_n         , //复位
  output  reg         clk_25M         
);

always @(posedge clk or negedge rst_n) begin
     if (!rst_n) begin
         clk_25M <= 1'b0     ; 
     end 
     else  begin
         clk_25M <=  ~clk_25M;
     end 
end
endmodule

IP核调用PLL

  • QuartuasII的IP核栏中搜索PLL点击ALTPLL进入始终设置

image-20220611222423642

  • page1系统输入时钟选择50M

    image-20220611222622079

  • page2取消locked输出使能(目前不需要这个信号,精简一点更好)

    image-20220611222700903

  • page c1设置分频25M,将division factor参数修改为2

    image-20220611222814797

  • 勾选生成inst文件(方便调用)点击finish

二、代码

彩条显示代码:

data_generate.v

module data_generate(
    input   wire            clk         ,
    input   wire            rst_n       ,

    input   wire [10:0]     h_addr      ,   // 行地址--数据有效显示区域行地址
    input   wire [10:0]     v_addr      ,   // 场地址--数据有效显示区域场地址

    output  reg [23:0]      data_display   // 要显示的数据
);


    parameter 
        BLACK    = 24'h000000,
        RED      = 24'hFF0000,
        GREEN    = 24'h00FF00,
        BLUE     = 24'h0000FF,
        YELLOW   = 24'hFFFF00,
        CYANRAY  = 24'h00FFFF,
        PURPLE   = 24'hFF00FF,
        GRAY     = 24'hC0C0C0,
        WHITE    = 24'hFFFFFF;

    reg [ 383:0 ] char_line[ 64:0 ];
    wire           state_words;

    assign         state_words = 1;
    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            data_display <= WHITE;
        end
        else begin
            case (h_addr)
                0:          data_display <= BLACK  ;
                80:         data_display <= RED    ;
                160:        data_display <= GREEN  ;
                240:        data_display <= BLUE   ;
                320:        data_display <= YELLOW ;
                400:        data_display <= CYANRAY;
                480:        data_display <= PURPLE ;
                560:        data_display <= GRAY   ;
                default:    data_display <= data_display;
            endcase
        end

    end
endmodule

使用时将其与行场控制模块代码vga_ctrl放在同一个top文件里就好了

module vga_top(
    input   wire        clk,
    input   wire        rst_n,

    output  wire             hsync       ,
    output  wire             vsync       ,
    output  wire [7:0]       vga_r       ,
    output  wire [7:0]       vga_g       ,
    output  wire [7:0]       vga_b       ,
    output  wire             vga_black   ,
    output  wire             vga_clk      
);

    wire [23:0]     data_display;   // 要显示的数据

    wire [10:0]     h_addr      ;   // 行地址--数据有效显示区域行地
    wire [10:0]     v_addr      ;   // 场地址--数据有效显示区域场地

    
    wire    clk_25M;

    pll_25m	pll_25m_inst (
        .areset ( ~rst_n ),
        .inclk0 ( clk ),
        .c0 ( clk_25M )
	);



    data_generate u_data_generate(
            .clk            (clk_25M      ),
            .rst_n          (rst_n        ),

            .h_addr         (h_addr       ),   // 行地址--数据有效显示区域行地址
            .v_addr         (v_addr       ),   // 场地址--数据有效显示区域场地址

            .data_display   (data_display )    // 要显示的数据
    );



    vga_ctrl u_vga_ctrl(
            .clk            (clk_25M     ),
            .rst_n          (rst_n       ),
            .data_display   (data_display),   // 要显示的数据

            .h_addr         (h_addr      ),   // 行地址--数据有效显示区域行地址
            .v_addr         (v_addr      ),   // 场地址--数据有效显示区域场地址

            .hsync          (hsync       ),
            .vsync          (vsync       ),
            .vga_r          (vga_r       ),    // R
            .vga_g          (vga_g       ),    // G
            .vga_b          (vga_b       ),    // B
            // .vga_sync_n     (vga_sync_n  ),
            .vga_black      (vga_black   ),
            .vga_clk        (vga_clk     )  // 时钟
    )
endmodule

原理就是修改data_generate模块内的参数使其输出彩色条纹信号再通过vga_ctrl模块翻译成VGA协议的帧格式就可以了。

二、显示文字/BMP双色图片

image-20220611225549204

一、原理

因为这个方法实际上是把文字做成指定分辨率的图片,比如接下来通过PCL字模工具,我将六个字做成了一张384*64分辨率的BMP双色图片(黑白)

image-20220611224439121

字符大小设置为64*64,然后另存为BPM格式的图片,接下来再用PCL工具打开进行字模输出

image-20220611224645360

字模输出格式设置一下

image-20220611224814377

点击生成字模后得到以下文件:

image-20220611224912600

二、代码

同样我们直接修改数据生成模块即可

该图片分辨率为384*64,所以我们需要一个一维数组,并给他赋予初始值,即刚才得到的那堆数据。

image-20220611225027299

赋予初始值

always@( posedge clk or negedge rst_n ) begin
    if ( !rst_n ) begin
        char_line[ 0 ]  = 384'h000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000;
        char_line[ 1 ]  = 384'h000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000;
        char_line[ 2 ]  = 384'h000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000;
        char_line[ 3 ]  = 384'h000000000000000000000018000000000000000000004000000000000000020000000200020000000000000000000000;
        char_line[ 4 ]  = 384'h00000000000000000000000C00000000000000000003E000000000004000038000000380038000000000000000000000;
        char_line[ 5 ]  = 384'h0000000000000000000000070000000000000000003FF00000000000E00003F0000003E003E000000000000000000800;
        char_line[ 6 ]  = 384'h000000000000000000000007800000000000000007FFF80000000001F00003C0000003C003C000000000000000001C00;
        char_line[ 7 ]  = 384'h000000000000000000000003C000000000000001FFFFE00007FFFFFFF80003C00000038003C000000000000000003E00;
        char_line[ 8 ]  = 384'h000000000000000000000003E0000000000001FFFFE0000003FFFFFFFC0003C00000038003C0000003FFFFFFFFFFFF00;
        char_line[ 9 ]  = 384'h000000000000000000000001E0000000001FFFFFC000000001800C00000003C00000038003C0010001FFFFFFFFFFFF80;
        char_line[ 10 ] = 384'h000000000000000000180001E000000001FFE00F0000000000001E00000003C00000038003C0038000C0078000F00000;
        char_line[ 11 ] = 384'h000000000000000000180001C00002000000001E0000000000001F80002003C00000038003C00FC00000078000F00000;
        char_line[ 12 ] = 384'h000000000000000000180000800007000000001E0000000000003F00003803C01FFFFFFFFFFFFFE00000078000F00000;
        char_line[ 13 ] = 384'h0000000000000000001FFFFFFFFFFF800000001E0000000000003C00003E03C00FFFFFFFFFFFFFF00000078000F00000;
        char_line[ 14 ] = 384'h0000000000000000003FFFFFFFFFFFC00000001C0000C00000007800003E03C00400038003C000000000078000F00000;
        char_line[ 15 ] = 384'h00000000000000000038000000000FE00000003C0003E0000000F020003C03C00000038003C000000000078000F00000;
        char_line[ 16 ] = 384'h00000000000000000078000000001F00007FFFFFFFFFF0000000E030003C03C00000038003C000000000078000F00000;
        char_line[ 17 ] = 384'h00000000000000000078000000001C00003FFFFFFFFFF8000001C01C003C03C00000038003C000000000078000F00000;
        char_line[ 18 ] = 384'h000000000000000000F000000000380000100078000000000003800E003C03C00000038003C000000000078000F00000;
        char_line[ 19 ] = 384'h000000000000000001F0000000003000000000780000000000070007003C03C00000038003C000000000078000F00000;
        char_line[ 20 ] = 384'h000000000000000003F0000000006000000000F00000000000060007803C03C000000300030000000000078000F00000;
        char_line[ 21 ] = 384'h000000000000000003E0000000004000000000F000000080000C0003E03C03C000000400000000000000078000F00000;
        char_line[ 22 ] = 384'h00000000000000000000000000002000000001E0000001C000380001F03C03C000000C00000000000000078000F00000;
        char_line[ 23 ] = 384'h00000000000000000000000000007000000001E0000003E000F0003FF83C03C000001F00000000000000078000F00000;
        char_line[ 24 ] = 384'h0000000000000300000000000000F8003FFFFFFFFFFFFFF001FFFFFFF83C03C000001F00100000000000078000F00000;
        char_line[ 25 ] = 384'h0000000000000F8000FFFFFFFFFFFC001FFFFFFFFFFFFFF800FFFE00FC3C03C000003E001C0000000000078000F00180;
        char_line[ 26 ] = 384'h0000000000001FC0007FFFFFFFFFFE000C0007800000000000FE0E007C3C03C000003C001F0000000000078000F003C0;
        char_line[ 27 ] = 384'h0000000000003FE000200003C0000000000007800000000000600F807C3C03C0000078001F000C000000078000F007E0;
        char_line[ 28 ] = 384'h1FFFFFFFFFFFFFF000000003C000000000000F000000000000000F00383C03C0000078001E001E001FFFFFFFFFFFFFF0;
        char_line[ 29 ] = 384'h0FFFFFFFFFFFFFF800000003C000000000000E000000000000000E00303C03C00000F0001E003F000FFFFFFFFFFFFFF8;
        char_line[ 30 ] = 384'h07C000000000000000000003C000000000001E000002000000000E00003C03C00000F0001E007F800400070000F00000;
        char_line[ 31 ] = 384'h000000000000000000000003C000000000001C000003800000000E00003C03C00001E0001E00FC000000070000F00000;
        char_line[ 32 ] = 384'h000000000000000000008003C000000000003FFFFFFFC00000000E00003C03C00001C0001E01F8000000070000F00000;
        char_line[ 33 ] = 384'h00000000000000000000E003C000000000007FFFFFFFC00000000E00003C03C00003F8001E03E0000000070000F00000;
        char_line[ 34 ] = 384'h00000000000000000000F803C00000000000FE000007800000000E00003C03C00007F0001E0FC00000000F0000F00000;
        char_line[ 35 ] = 384'h00000000000000000001F803C00000000000FE000007800000000E00C03C03C00007E0001E1F000000000F0000F00000;
        char_line[ 36 ] = 384'h00000000000000000001F003C00080000001DE000007800000000E01E03C03C0000FE0001E3E000000000F0000F00000;
        char_line[ 37 ] = 384'h00000000000000000001F003C001C00000039E000007800007FFFFFFF03C03C0001EE0001EF8000000000F0000F00000;
        char_line[ 38 ] = 384'h00000000000000000001E003C003E00000079E000007800003FFFFFFF83C03C0003CE0001FF0000000000F0000F00000;
        char_line[ 39 ] = 384'h00000000000000000001E003FFFFF000000F1E000007800000000E00003C03C00078E0001FC0000000000E0000F00000;
        char_line[ 40 ] = 384'h00000000000000000001E003FFFFF800001E1FFFFFFF800000000E00003C03C000F0E0001F00000000001E0000F00000;
        char_line[ 41 ] = 384'h00000000000000000001C003C0000000003C1FFFFFFF800000000E00003C03C000C0E0007E00000000001E0000F00000;
        char_line[ 42 ] = 384'h00000000000000000003C003C000000000701E000007800000000E00003C03C00380E001FE00000000001C0000F00000;
        char_line[ 43 ] = 384'h00000000000000000003E003C000000000E01E000007800000000E00003C03C00700E007DE00000000003C0000F00000;
        char_line[ 44 ] = 384'h00000000000000000003F003C000000001C01E000007800000000E00003C03C00E00E01F1E00000000003C0000F00000;
        char_line[ 45 ] = 384'h000000000000000000039003C000000003801E000007800000000E00003C03C01800E0781E0000400000380000F00000;
        char_line[ 46 ] = 384'h000000000000000000079803C000000006001E000007800000000E00003003C01000E1C01E0000400000780000F00000;
        char_line[ 47 ] = 384'h000000000000000000070C03C00000000C001E000007800000000E00000003C00000E1001E0000400000700000F00000;
        char_line[ 48 ] = 384'h0000000000000000000F0603C000000010001E000007800000000E000C0003C00000E0001E0000400000E00000F00000;
        char_line[ 49 ] = 384'h0000000000000000000E0703C000000000001FFFFFFF800000000E01FC0003C00000E0001E0000400001E00000F00000;
        char_line[ 50 ] = 384'h0000000000000000001E03C3C000000000001FFFFFFF800000000E3FC00003C00000E0001E0000400001C00000F00000;
        char_line[ 51 ] = 384'h0000000000000000001C01E3C000000000001E000007800000000FFC000003C00000E0001E0000C00003800000F00000;
        char_line[ 52 ] = 384'h0000000000000000003C00FBC000000000001E00000780000001FFC0000003C00000E0001E0000E00007000000F00000;
        char_line[ 53 ] = 384'h00000000000000000038007FC000000000001E000007800000FFFE00000003C00000E0001E0000E0000E000000F00000;
        char_line[ 54 ] = 384'h00000000000000000070001FF800000000001E000007800007FFE000000C07800000E0001E0000F0001C000000F00000;
        char_line[ 55 ] = 384'h000000000000000000E0000FFFFFFFF000001E000007800007FF0000000FFF800000E0000F0001F80038000000F00000;
        char_line[ 56 ] = 384'h000000000000000000C00001FFFFFF8000001FFFFFFF800003F800000001FF800000E0000FFFFFF00070000000F00000;
        char_line[ 57 ] = 384'h0000000000000000018000003FFFFF0000001FFFFFFF800003C0000000007F800000E0000FFFFFF000C0000000F00000;
        char_line[ 58 ] = 384'h00000000000000000300000001FFFE0000001E00000780000100000000001F000000E00003FFFFC00380000000F00000;
        char_line[ 59 ] = 384'h0000000000000000060000000000020000001E00000780000000000000001E000000E000000000000600000000E00000;
        char_line[ 60 ] = 384'h0000000000000000080000000000000000001E0000070000000000000000080000008000000000000800000000000000;
        char_line[ 61 ] = 384'h000000000000000000000000000000000000100000040000000000000000000000000000000000000000000000000000;
        char_line[ 62 ] = 384'h000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000;
        char_line[ 63 ] = 384'h000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000;
    end
end

**时序逻辑:**在指定位置h[148,533]与v[208,273]的区间内,并且以黑底白字的样式显示,并且背景依然是彩条

    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            data_display <= WHITE;
        end
        else if (state_words &&h_addr > 148 &&h_addr < 533 && v_addr > 208  && v_addr < 273) begin
            data_display <= char_line[ v_addr-208 ][ 532 - h_addr ]? WHITE:BLACK;
        end
        else begin
            case (h_addr)
                0:          data_display <= BLACK  ;
                80:         data_display <= RED    ;
                160:        data_display <= GREEN  ;
                240:        data_display <= BLUE   ;
                320:        data_display <= YELLOW ;
                400:        data_display <= CYANRAY;
                480:        data_display <= PURPLE ;
                560:        data_display <= GRAY   ;
                default:    data_display <= data_display;
            endcase
        end
    end

其他地方不用更改,编译完成后直接烧录即可。

三、BMP图片显示

一、原理

一张640×480的24位的图片的大小超过了芯片内存,所以采用一张96*80的图片进行显示。

原图:

image-20220611230107776

这里使用BMP2Mif软件进行位图转换,将其以HEX文件模式输出

image-20220611230247734

选择RGB888的模式是因为DE2-115是RGB888版本

image-20220611230359793

图片数据太多所以我们需要ROM来存储

image-20220611230539402

设置位宽16,大小为96*80=7680

image-20220611230632462

不需要它

image-20220611230649803

找到之前的hex文件

image-20220611230727363

勾选inst后点击finish即可

二、代码

修改数据生成模块

reg			[ 13:0 ]		rom_address				; // ROM地址
wire			[ 15: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语句块中添加下列语句
            if ( flag_enable_out2 ) begin
                rgb_data = rom_data;
            end
            else begin
                rgb_data = black;
            end


image-20220611231126156

四、总结

学习并掌握了VGA协议。

五、参考文章

咸鱼FPGA: 协议——VGA

醉意丶千层梦: 基于FPGA的VGA显示彩条、字符、图片

六、源文件

https://github.com/Wattson1128/FPGA

猜你喜欢

转载自blog.csdn.net/ChenJ_1012/article/details/125240121