文章目录
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显示。