VGA(Video Graphics Array)视频图形阵列是IBM于1987年提出的一个使用模拟信号的电脑显示标准。VGA接口即电脑采用VGA标准输出数据的专用接口。VGA接口共有15针,分成3排,每排5个孔,显卡上应用最为广泛的接口类型,绝大多数显卡都带有此种接口。它传输红、绿、蓝模拟信号以及同步信号(水平和垂直信号)。
实物图:
电子原理图:
由电路图可以看到,VGA并没有特殊的外部芯⽚,我们需要关注的其实只有5个信号:HS⾏同步信号,VS场同步信号,R红基⾊,G 绿基⾊,B蓝基⾊。
三基⾊是指通过其他颜⾊的混合⽆法得到的“基本⾊”由于⼈的⾁眼有感知红、绿、蓝三种不同颜⾊的锥体细胞,因此⾊彩空间通常可以由三种基本⾊来表达。这是⾊度学的最基本原理,即 三基⾊原理。三种基⾊是相互独⽴的,任何⼀种基⾊都不能有其它两种颜⾊合成。红绿蓝是三基⾊,这三种颜⾊合成的颜⾊范围最为⼴泛。我们的RGB信号真是三基⾊的运⽤,对这三个信号赋予不同的数值,混合起来便是不同的⾊彩。
设计RGB信号时,既可以R信号、G信号和B信号独⽴的赋值,最后连到端⼝上,也可以直接⽤RGB当做⼀个整体信号,RGB信号在使
⽤时的位宽有三种常见格式。
GA显⽰器扫描⽅式分为逐⾏扫描和隔⾏扫描:逐⾏扫描是扫描从屏幕左上⾓⼀点开始,从左像右逐点扫描,每扫描完⼀⾏,电⼦束回 到屏幕的左边下⼀⾏的起始位置,在这期间,CRT对电⼦束进⾏消隐,每⾏结束时,⽤⾏同步信号进⾏同步;当扫描完所有的⾏,形成⼀ 帧,⽤场同步信号进⾏场同步,并使扫描回到屏幕左上⽅,同时进⾏场消隐,开始下⼀帧。隔⾏扫描是指电⼦束扫描时每隔⼀⾏扫⼀线,完成
⼀屏后在返回来扫描剩下的线,隔⾏扫描的显⽰器闪烁的厉害,会让使⽤者的眼睛疲劳。因此我们⼀般都采⽤逐⾏扫描的⽅式。
⼀开始看这个时序图可能看不懂,它是把⾏场信号绘制在同⼀张图⾥,说明⾏场信号的控制是相似的,只是时间参数不⼀样⽽已。如果展开的话,其实时序是这样的:
若⼲个HS信号才组合⽽成⼀个VS,如果在⼀副图⽚中,那正确的时序表⽰⽅式应该如下图这样:
SYNC是“信号同步”,Back proch和Left border常常加在⼀起称为“显⽰后沿”,Addressable video为“显⽰区域”,Right porder和Front porch常常加在⼀起称为“显⽰前沿”,⼀个时序其实就是先拉⾼⼀段较短的“信号同步”时间,然后拉低⼀段很长的时间,这就是⼀个回合。同时需要注意,其实也可以完全相反。即先拉低⼀段时间“信号同步”时间,然后拉⾼⼀段很长的时间。
参数定义:
`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
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
//初始化显示文字
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
*/
//ROM地址计数器
always @( posedge vga_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_out2 ) begin //在有效区域内+1
rom_address <= rom_address + 1;
end
else begin //无效区域保持
rom_address <= rom_address;
end
end
assign flag_clear_rom_address = rom_address == 50267;
rom rom_inst (
.address ( rom_address ),
.clock ( clk ),
.q ( rom_data)
);
这里我实验用的是EP4CE115F29C7
开发板,不同开发板根据手册绑定不同的引脚。
效果:
修改分辨率为800*600 时钟改为40MHz:
可以看到图形整体左移,基本符合预期。
点阵:
这里修改代码之后可以看见字符整体往左上方移动,基本符合预期。
图片:
VGA的实现在掌握VGA协议之后不算很难,主要是理解时序。还有确保硬件的问题,我最开始因为用的另一种开发板,外部接口有损,就算是编写正确工程也得不到想要的结果。之后换了开发板修改引脚,一下子就出来了。
VGA协议
VGA接口
基于FPGA的VGA显示彩条、字符、图片