御神楽的学习记录之基于FPGA的VGA协议实现


VGA协议简介

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

1.VGA显示原理

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

在这里插入图片描述

2.行场信号

在这里插入图片描述
SYNC是“信号同步”,Back proch和Left border常常加在⼀起称为“显⽰后沿”,Addressablevideo为“显⽰区域”,Right porder和Front porch常常加在⼀起称为“显⽰前沿”,⼀个时序其实就是先拉⾼⼀段较短的“信号同步”时间,然后拉低⼀段很长的时间,这就是⼀个回合。同时需要注意,其实也可以完全相反。即先拉低⼀段时间“信号同步”时间,然后拉⾼⼀段很长的时间。
具体显示区域如下图:
在这里插入图片描述


一、彩色条纹显示

1.VGA控制模块编写

1、编写vga的参数模块vga_param.v用于存储不同分辨率下的参数

`define vga_640_480

`ifdef  vga_640_480
    //执行操作A
    `define H_Right_Border 8
    `define H_Front_Proch  8
    `define H_Sync_Time    96
    `define H_Back_Proch   40
    `define H_Left_Border  8
    `define H_Data_Time    640
    `define H_Total_Time   800

    `define V_Bottom_Border 8
    `define V_Front_Proch   2
    `define V_Sync_Time     2
    `define V_Back_Proch    25
    `define V_Top_Border    8
    `define V_Data_Time     480
    `define V_Total_Time    525

`else

`endif

2、编写vga_ctrl.v模块,主要负责行场信号的计数以及将数据输出到vga当中

`define vga_640_480
`include "vga_param.v"
module vga_ctrl (
    input  wire       clk     ,
    input  wire       rst_n   ,
    input  wire [15:0]data_dis,

    output reg [10:0]h_addr ,//数字有效显示区域行地址
    output reg [10:0]v_addr ,//数字有效显示区域场地址
    output            vga_blank,
    output            vga_sync,     
    output reg        v_sync  ,
    output reg        h_sync  ,
    output reg  [4:0] vga_r   ,
    output reg  [5:0] vga_g   ,
    output reg  [4:0] vga_b   ,
    output wire       vga_clk 
    
);
   parameter 	H_SYNC_STA=`H_Sync_Time,
				H_SYNC_STO=`H_Total_Time,
				H_DATA_STA=`H_Sync_Time+`H_Back_Proch+`H_Left_Border,
				H_DATA_STO=`H_Sync_Time+`H_Back_Proch+`H_Left_Border+`H_Data_Time,
				
				V_SYNC_STA =`V_Sync_Time,
				V_SYNC_STO =`V_Total_Time,
				V_DATA_STA =`V_Sync_Time+`V_Back_Proch+`V_Top_Border,
				V_DATA_STO =`V_Sync_Time+`V_Back_Proch+`V_Top_Border+`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;
    
    reg CLK_25;
    always@(posedge clk)
    begin
        if (!rst_n) begin
            CLK_25<= 0;
        end
        else begin
            CLK_25=~CLK_25;         //时钟
        end 
        
    end

    assign vga_sync = 1'b0;
    assign vga_blank = ~((h_addr<160)||(v_addr<44));

    always @(posedge CLK_25 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 <= 12'd0;
       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_25 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_25 or negedge rst_n) begin
       if (!rst_n) begin
            h_sync <= 1'b1;            
       end
       else if (cnt_h_addr == H_SYNC_STA - 1) begin
            h_sync <= 1'b0;            
       end
        else if (cnt_h_addr == H_SYNC_STO - 1) begin
            h_sync <= 1'b1;            
       end
       else begin
            h_sync <= h_sync;            
       end
    end

    always @(posedge CLK_25 or negedge rst_n) begin
       if (!rst_n) begin
            v_sync <= 1'b1;            
       end
       else if (cnt_v_addr == V_SYNC_STA - 1) begin
            v_sync <= 1'b0;            
       end
        else if (cnt_v_addr == V_SYNC_STO - 1) begin
            v_sync <= 1'b1;            
       end
       else begin
            v_sync <= v_sync;            
       end
    end

    assign vga_clk = CLK_25;

    //数据有效显示区域定义
    always @(posedge CLK_25 or negedge rst_n) begin
       if (!rst_n) begin
           h_addr <= 11'd0 ;             
       end
       else if (cnt_h_addr >= H_DATA_STO - 1) begin
           h_addr <= 11'd0;
       end
       else if (cnt_h_addr >= H_DATA_STA - 1 ) begin
           h_addr <= cnt_h_addr - ( H_DATA_STA-1 );             
       end
       else begin
           h_addr <= 11'd0;              
       end
    end
    always @(posedge CLK_25 or negedge rst_n) begin
       if (!rst_n) begin
           v_addr <= 11'd0 ;             
       end
       else if (cnt_v_addr >= V_DATA_STO - 1) begin
           v_addr <= 11'd0;
       end
       else if (cnt_v_addr >= V_DATA_STA - 1 ) begin
           v_addr <= cnt_v_addr - ( V_DATA_STA -1 );             
       end
       else begin
           v_addr <= 11'd0;              
       end
    end
    //显示数据
    always @(posedge CLK_25 or negedge rst_n) begin
       if (!rst_n) begin
            vga_r <= 5'd0;  
            vga_g <= 6'd0; 
            vga_b <= 5'd0;            
       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[15:11];  
            vga_g <= data_dis[10:5]; 
            vga_b <= data_dis[4:0];            
       end
       else begin
            vga_r <= 5'd0;  
            vga_g <= 6'd0; 
            vga_b <= 5'd0;            
       end
    end
endmodule

2.VGA数据产生模块编写

module data_gen (
    input         clk    ,
    input         rst_n  ,
    input  wire [10:0] h_addr ,
    input  wire [10:0] v_addr ,
    output  reg [15:0] data_dis
);

    parameter   black    = 16'd00000;
    parameter   orange   = 16'd64768;
    parameter   indigo   = 16'd18448;
    parameter   red      = 16'd63488;
    parameter   green    = 16'd01024;
    parameter   blue     = 16'd00031;
    parameter	yellow   = 16'd65472;
    parameter	sky_blue = 16'd34397;
    parameter	purple   = 16'd32784;
    parameter	gary     = 16'd33808;
    parameter	white    = 16'd65503;
    parameter   max_cnt  = 10000 ;
  
    //彩条显示
 always @( posedge clk or negedge rst_n ) begin
         if ( !rst_n ) begin
             data_dis <= black;
         end
         else if(h_addr>=0 && h_addr <=80) begin
             data_dis <= red;
         end
         else if(h_addr>= 81 && h_addr<= 160) begin
             data_dis <= orange;
         end
         else if(h_addr>=161 && h_addr<=240) begin
             data_dis <= yellow;
         end
         else if(h_addr>= 241 && h_addr<=320) begin
             data_dis <= green;
         end
         else if(h_addr>= 321 && h_addr<= 400) begin
             data_dis <= blue;
         end
         else if(h_addr>=401 && h_addr<=480) begin
             data_dis <= indigo;
         end
         else if(h_addr>=481 && h_addr <=560) begin
             data_dis <= purple;
         end
         else if(h_addr>=561 && h_addr<=640) begin
             data_dis <= white;
         end
         else begin
             data_dis <= black;
        end
     end
endmodule

3.实际效果

请添加图片描述

二、自定义的汉字字符

1.取字模

使用软件PCtoLCD取字模
在这里插入图片描述
先将字符保存为BMP位图,然后再用PCtoLCD打开BMP位图生成字模
在这里插入图片描述
生成字模
在这里插入图片描述

2.修改data_gen.v

module data_gen (
    input         clk    ,
    input         rst_n  ,
    input  wire [10:0] h_addr ,
    input  wire [10:0] v_addr ,
    output  reg [15:0] data_dis
);

    parameter   black    = 16'd00000;
    parameter   orange   = 16'd64768;
    parameter   indigo   = 16'd18448;
    parameter   red      = 16'd63488;
    parameter   green    = 16'd01024;
    parameter   blue     = 16'd00031;
    parameter	yellow   = 16'd65472;
    parameter	sky_blue = 16'd34397;
    parameter	purple   = 16'd32784;
    parameter	gary     = 16'd33808;
    parameter	white    = 16'd65503;
    parameter   max_cnt  = 10000 ;
  
 //字符显示
parameter  //点阵字模:每一行char_lineXX是显示的一行,共272列
         char_line00=320'h00000000000000000000000000000000000000000000000000000000000000000000000000000000,
         char_line01=320'h00000000000000000000000000000000000000000000000000000000000000000000000000000000,
         char_line02=320'h01040000010006000003C00000000000000000000000000000000000000000000000000000000000,
         char_line03=320'h03860000038006000003800000000000000000000000000000000000000000000000000000000000,
         char_line04=320'h038E03FC01C006000803800800000000000000000000000000000000000000000000000000000000,
         char_line05=320'h070C03FC01C006001C3FFC1C00000000000000000000000000000000000000000000000000000000,
         char_line06=320'h0E1FFB9C008006001E3FFC3C0000000000E007E001C00FE007E07FFE07E000E007E0003001C07FFE,
         char_line07=320'h0E1FFB9C0003FFFC0F380CF00000000000E00FF801C01FF00FF07FFE0FF000E00FF0007001C07FFE,
         char_line08=320'h1C39839C3FF3FFFC07380CE00000000001C01C3803C038381C38000E1C3801C01C38007003C0000E,
         char_line09=320'h3931839C3FF3061C02380C00000000000380381C07C0301C3838001C38380380383800F007C0001C,
         char_line0a=320'h1181839C0063061C003FFC00000000000380301C1DC0301C381C0018381C0380381C01F01DC00018,
         char_line0b=320'h01C1839C00E3061C003FFC40000000000700101C19C0301C301C0038301C0700301C01B019C00038,
         char_line0c=320'h03BFFB9C00C3061C01B80CE0000000000E00001C11C0301C700C0030700C0E00700C03B011C00030,
         char_line0d=320'h073FFB9C01C3061C07B80CF0000000000E00001801C03838700C0070700C0E00700C073001C00070,
         char_line0e=320'h0701839C0183FFFC0E380C3C0007C0001FF0007801C03C78700C0060700C1FF0700C0E3001C00060,
         char_line0f=320'h0E01839C0383FFFC3C3FFC1C0007C0001FF803E001C00FE0700C00E0700C1FF8700C0C3001C000E0,
         char_line10=320'h1E01839C07E3061C183FFC080007C0003C1C03F001C01FF0700C00C0700C3C1C700C1C3001C000C0,
         char_line11=320'h1E39839C0FE3061C1001C0000007C000380E007801C03C78700C01C0700C380E700C383001C001C0,
         char_line12=320'h3E39FB9C1FF3061C0001C0000007C000700E001C01C0781C700C01C0700C700E700C303001C001C0,
         char_line13=320'h3639FB9C3BBB061C3FFFFFFC00000000700E001C01C0701C700C0180700C700E700C703001C00180,
         char_line14=320'h0639839C3393FFFC3FFFFFFC00000000700E001C01C0600C700C0380700C700E700CFFFE01C00380,
         char_line15=320'h0639839C0383FFFC000FF00000000000700E100C01C0600C301C0380301C700E301CFFFE01C00380,
         char_line16=320'h0639839C0383FFFC000FF80000000000700E701C01C0700C381C0300381C700E381CFFFE01C00300,
         char_line17=320'h06398B9C0383061C001DDC0000000000380E381C01C0701C381807003818380E3818003001C00700,
         char_line18=320'h0639FBFC0383061C0079DE00000000003C1C3C3801C0783C1C3807001C383C1C1C38003001C00700,
         char_line19=320'h063FF3B80380060000F1CF00000000001FF81FF801C03FF81FF006001FF01FF81FF0003001C00600,
         char_line1a=320'h067F83A00380060003E1C3E0000000000FF00FE001C01FF007E00E0007E00FF007E0003001C00E00,
         char_line1b=320'h067C0380038006001F81C1FE0000000001C001800000038001800000018001C00180000000000000,
         char_line1c=320'h06200380038006003E01C07C00000000000000000000000000000000000000000000000000000000,
         char_line1d=320'h06000380038006001801C01800000000000000000000000000000000000000000000000000000000,
         char_line1e=320'h06000380038006000001C00000000000000000000000000000000000000000000000000000000000,
         char_line1f=320'h00000000000000000000000000000000000000000000000000000000000000000000000000000000;
 
         reg[8:0] char_bit;
     always@(posedge clk)
         if(h_addr==10'd144)char_bit<=9'd320;   //当显示到144像素时准备开始输出图像数据
         else if(h_addr>10'd144&&h_addr<10'd464)     //左边距屏幕144像素到474像素时    464=144+320(图像宽度)
             char_bit<=char_bit-1'b1;
     always@(posedge clk) 
             if(h_addr>10'd144&&h_addr<10'd464)    //X控制图像的横向显示边界:左边距屏幕左边144像素  右边界距屏幕左边界464像素
                 begin case(v_addr)            //Y控制图像的纵向显示边界:从距离屏幕顶部160像素开始显示第一行数据
                     10'd160:
                     if(char_line00[char_bit])data_dis<=sky_blue;  //如果该行有数据 则颜色为红色
                     else data_dis<=black;                      //否则为黑色
                     10'd162:
                     if(char_line01[char_bit])data_dis<=sky_blue;
                     else data_dis<=black;
                     10'd163:
                     if(char_line02[char_bit])data_dis<=sky_blue;
                     else data_dis<=black;
                     10'd164:
                     if(char_line03[char_bit])data_dis<=sky_blue;
                     else data_dis<=black;
                     10'd165:
                     if(char_line04[char_bit])data_dis<=sky_blue;
                     else data_dis<=black; 
                     10'd166:
                     if(char_line05[char_bit])data_dis<=sky_blue;
                     else data_dis<=black;
                     10'd167:
                     if(char_line06[char_bit])data_dis<=sky_blue;
                     else data_dis<=black; 
                     10'd168:
                     if(char_line07[char_bit])data_dis<=sky_blue;
                     else data_dis<=black;
                     10'd169:
                     if(char_line08[char_bit])data_dis<=sky_blue;
                     else data_dis<=black; 
                     10'd170:
                     if(char_line09[char_bit])data_dis<=sky_blue;
                     else data_dis<=black;
                     10'd171:
                     if(char_line0a[char_bit])data_dis<=sky_blue;
                     else data_dis<=black;
                     10'd172:
                     if(char_line0b[char_bit])data_dis<=sky_blue;
                     else data_dis<=black;
                     10'd173:
                     if(char_line0c[char_bit])data_dis<=sky_blue;
                     else data_dis<=black;
                     10'd174:
                     if(char_line0d[char_bit])data_dis<=sky_blue;
                     else data_dis<=black;
                     10'd175:
                     if(char_line0e[char_bit])data_dis<=sky_blue;
                     else data_dis<=black;
                     10'd176:
                     if(char_line0f[char_bit])data_dis<=sky_blue;
                     else data_dis<=black;
                     10'd177:
                     if(char_line10[char_bit])data_dis<=sky_blue;
                     else data_dis<=black;
                     10'd178:
                     if(char_line11[char_bit])data_dis<=sky_blue;
                     else data_dis<=black;
                     10'd179:
                     if(char_line12[char_bit])data_dis<=sky_blue;
                     else data_dis<=black;
                     10'd180:
                     if(char_line13[char_bit])data_dis<=sky_blue;
                     else data_dis<=black;
                     10'd181:
                     if(char_line14[char_bit])data_dis<=sky_blue;
                     else data_dis<=black;
                     10'd182:
                     if(char_line15[char_bit])data_dis<=sky_blue;
                     else data_dis<=black;
                     10'd183:
                     if(char_line16[char_bit])data_dis<=sky_blue;
                     else data_dis<=black;
                     10'd184:
                     if(char_line17[char_bit])data_dis<=sky_blue;
                     else data_dis<=black;
                     10'd185:
                     if(char_line18[char_bit])data_dis<=sky_blue;
                     else data_dis<=black;
                     10'd186:
                     if(char_line19[char_bit])data_dis<=sky_blue;
                     else data_dis<=black;
                     10'd187:
                     if(char_line1a[char_bit])data_dis<=sky_blue;
                     else data_dis<=black;
                     10'd188:
                     if(char_line1b[char_bit])data_dis<=sky_blue;
                     else data_dis<=black;
                     10'd189:
                     if(char_line1c[char_bit])data_dis<=sky_blue;
                     else data_dis<=black;
                     10'd190:
                     if(char_line1d[char_bit])data_dis<=sky_blue;
                     else data_dis<=black;
                     10'd191:
                     if(char_line1e[char_bit])data_dis<=sky_blue;
                     else data_dis<=black;
                     10'd192:
                     if(char_line1f[char_bit])data_dis<=sky_blue;
                     else data_dis<=black;
                     default:data_dis<=black;   //默认颜色黑色
                 endcase 
             end
         else data_dis<=black; 
endmodule

3.实际效果

请添加图片描述

三、彩色图像

1.将图片转为hex文件

这里使用BMP2MIf软件
在这里插入图片描述

2.使用rom ip核

1、搜索rom IP核添加
在这里插入图片描述
2、设置如下
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
直接点击完成即可

3.修改data_gen.v代码

module data_gen (
    input         clk    ,
    input         rst_n  ,
    input  wire [10:0] h_addr ,
    input  wire [10:0] v_addr ,
    output  reg [15:0] data_dis
);

    parameter   black    = 16'd00000;
    parameter   orange   = 16'd64768;
    parameter   indigo   = 16'd18448;
    parameter   red      = 16'd63488;
    parameter   green    = 16'd01024;
    parameter   blue     = 16'd00031;
    parameter	yellow   = 16'd65472;
    parameter	sky_blue = 16'd34397;
    parameter	purple   = 16'd32784;
    parameter	gary     = 16'd33808;
    parameter	white    = 16'd65503;
    parameter   max_cnt  = 10000 ;
    reg [13:0]  cnt_pic ;
    wire [15:0]  pic_data;
    reg [20:0] cnt_1s;
    wire      add_cnt;
    wire      end_cnt;
    
    
//图片显示
      rom	rom_inst (
  	.address ( cnt_pic ),
  	.clock ( clk ),
  	.q ( pic_data )
  	);
 always @(posedge clk or negedge rst_n) begin
         if (!rst_n) begin
             data_dis <= black;
                        
         end
         else if (h_addr >=100 && h_addr<200&& v_addr >=100 && v_addr  <200) begin
              data_dis <= pic_data;
              
         end
         else begin
              data_dis <= black;  
                         
         end
      end
  
  always @(posedge clk or negedge rst_n) begin 
     if (!rst_n) begin
        cnt_pic <= 0;
                      
     end
     else if (flag) begin
         cnt_pic <= 0;
     end
     else if (cnt_pic == max_cnt - 1) begin
        cnt_pic <= 0;
                      
     end
     else if ( h_addr >=100 && h_addr<200&& v_addr >=100 && v_addr  <200) begin
        cnt_pic <= cnt_pic + 1 ; 
     end
      else begin
        cnt_pic <= cnt_pic ;
        
      end                
  end
endmodule

4.显示效果

请添加图片描述


总结

VGA显示主要是对行场计数器的一个理解,及一行计数完毕则开始计数场。更重要的是对具体显示区域的一个理解。在正确的区域计数才能正确的驱动vga显示。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值