基于FPGA的VGA协议实现
一、VGA介绍
VGA(Video Graphics Array)视频图形阵列是IBM于1987年提出的一个使用模拟信号的电脑显示标准。VGA接口即电脑采用VGA标准输出数据的专用接口。VGA接口共有15针,分成3排,每排5个孔,显卡上应用最为广泛的接口类型,绝大多数显卡都带有此种接口。它传输红、绿、蓝模拟信号以及同步信号(水平和垂直信号)。
VGA驱动显示器用的是扫描的方式,一般是逐行扫描。
逐行扫描是扫描从屏幕左上角一点开始,从左像右逐点扫描,每扫描完一行,电子束回到屏幕的左边下一行的起始位置,在这期间,CRT对电子束进行消隐,每行结束时,用行同步信号进行同步;
当扫描完所有的行,形成一帧后,用场同步信号进行场同步,并使扫描回到屏幕左上方,同时进行场消隐,开始下一帧。
以下是vs 和 hs的时序简图。
可以看出,VGA一直在扫描,每一场的扫描包括了若干行扫描,如此循环。
综上我们可以知道,一个标准的VGA接口应该有以下的端口:
红色信号 r
绿色信号 g
蓝色信号 b
行同步信号 hs
场同步信号 vs
以及很多的地屏蔽
管脚定义:
管脚 | 定义 |
---|---|
1 | 红基色 |
2 | 绿基色 |
3 | 蓝基色 |
4 | 地址码 ID Bit |
5 | 自测试 |
6 | 红地 |
7 | 绿地 |
8 | 蓝地 |
9 | 保留(各家定义不同) |
10 | 数字码 |
11 | 地址码 |
12 | 地址码 |
13 | 行同步 |
14 | 场同步 |
15 | 地址码(各家定义不同) |
二、VGA显示彩色条纹
首先,VGA需要25M的时钟:
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
屏幕相关信息定义:
`define vga_640_480
`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_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_480
`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 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_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
`elsif vga_1024_600
`define H_Right_Border 0
`define H_Front_Porch 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_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
`elsif vga_1024_768
`define H_Right_Border 0
`define H_Front_Porch 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_Border 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
`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
`else
`endif
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
// end
//
// else if (add_v_addr) begin
// end
//
// else begin
// end
// end
// assign vga_sync_n = 1'b0; //符合同步控制信号 行时序和场时序都要产生同步脉冲
//assign vga_black = ~((cnt_h_addr < H_BLANK) || (cnt_v_addr < V_BLANK)); //当行计数器小于行空白总长或场计数器小于场空白总长时,空白信号低电平
// 行同步信号 低有效
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_v_addr >= (V_DATA_START - 1)) && (cnt_v_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
vga_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;
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_top.v
:
/**
*
* 顶层文件
*/
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_sync_n ,
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;
clk_25M_get u_clk_25M_get(
.clk (clk ), //VGA时
.rst_n (rst_n ), //复位
.clk_25M (clk_25M)
);
data_generate u_data_generate(
/*input wire */ .clk (clk_25M ),
/*input wire */ .rst_n (rst_n ),
/*input wire [10:0] */ .h_addr (h_addr ), // 行地址--数据有效显示区域行地址
/*input wire [10:0] */ .v_addr (v_addr ), // 场地址--数据有效显示区域场地址
/*output wire [23:0] */ .data_display (data_display ) // 要显示的数据
);![请添加图片描述](https://img-blog.csdnimg.cn/1660334f7ecd4160b71c835ce6129c95.png)
// 显示字符
// data_gen_char u_data_gen_char(
// /*input wire */ .clk (clk_25M ),
// /*input wire */ .rst_n (rst_n ),
// /*input wire [10:0] */ .h_addr (h_addr ), // 行地址--数据有效显示区域行地址
// /*input wire [10:0] */ .v_addr (v_addr ), // 场地址--数据有效显示区域场地址
// /*output wire [23:0] */ .data_display (data_display ) // 要显示的数据
);
vga_ctrl u_vga_ctrl(
/*input wire */ .clk (clk_25M ),
/*input wire */ .rst_n (rst_n ),
/*input wire [23:0]*/ .data_display (data_display), // 要显示的数据
/*output wire [10:0]*/ .h_addr (h_addr ), // 行地址--数据有效显示区域行地址
/*output wire [10:0]*/ .v_addr (v_addr ), // 场地址--数据有效显示区域场地址
/*output reg */ .hsync (hsync ),
/*output reg */ .vsync (vsync ),
/*output reg [7:0] */ .vga_r (vga_r ), // R
/*output reg [7:0] */ .vga_g (vga_g ), // G
/*output reg [7:0] */ .vga_b (vga_b ), // B
// .vga_sync_n (vga_sync_n ),
.vga_black (vga_black ),
/*output wire */ .vga_clk (vga_clk ) // 时钟
);
endmodule
运行效果:
三、VGA显示字符
只需替换data_generate.v
文件:
`include "vga_param.v"
module data_gen_char(
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 WIDTH_LEN = 64; // 字符总长度
parameter HEIGHT_LEN = 16; // 字符总高度
parameter WIDTH_REMAIN = (`H_Data_Time - WIDTH_LEN) / 2; //两边应该留的宽度
parameter HEIGHT_REMAIN = (`V_Data_Time - HEIGHT_LEN) / 2; //上下应该留的高度 232
// 每一行应该显示的数据(bit为1则显示)
reg [63:0] char_lines [15:0];
reg [4:0] now_line; // 当前为字符的第几行,0为不在
wire in_charline; // 在字符行
wire in_charcolumn; // 在字符列
reg [6:0] now_column; // 当前为字符的第几列
// 颜色
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;
// 字符数据
always @(*) begin
char_lines[0 ] = 64'h0040_0000_0000_0200;
char_lines[1 ] = 64'h2040_00FC_FFFE_0200;
char_lines[2 ] = 64'h17FC_7C84_0100_0200;
char_lines[3 ] = 64'h1040_4484_0100_0200;
char_lines[4 ] = 64'h83F8_4484_0100_0200;
char_lines[5 ] = 64'h4040_44FC_7FFC_0200;
char_lines[6 ] = 64'h47FE_7C84_4104_03F8;
char_lines[7 ] = 64'h1000_4484_4104_0200;
char_lines[8 ] = 64'h13F8_4484_4944_0200;
char_lines[9 ] = 64'h2208_44FC_4524_0200;
char_lines[10] = 64'hE3F8_7C84_4104_0200;
char_lines[11] = 64'h2208_4484_4944_0200;
char_lines[12] = 64'h23F8_0104_4524_0200;
char_lines[13] = 64'h2208_0104_4104_0200;
char_lines[14] = 64'h2228_0214_4114_FEFE;
char_lines[15] = 64'h0210_0408_4008_0000;
end
// 字符第几行的判定
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
now_line <= 5'd0;
end
else if (in_charline && in_charcolumn) begin // 在字符范围内
now_line <= v_addr - HEIGHT_REMAIN + 1;
end
else begin
now_line <= 5'd0;
end
end
// 在字符行、列内
assign in_charcolumn = ((h_addr >= WIDTH_REMAIN) && (h_addr < WIDTH_REMAIN + WIDTH_LEN)) ;
assign in_charline = ((v_addr >= HEIGHT_REMAIN) && (v_addr < HEIGHT_REMAIN + HEIGHT_LEN)) ;
// 字符第几列的判定
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
now_column <= 7'd0;
end
else if (in_charline && in_charcolumn) begin // 在字符范围内
now_column <= h_addr - WIDTH_REMAIN;
end
else begin
now_column <= 7'd0;
end
end
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
data_display <= WHITE;
end
else if (now_line != 5'd0 && now_column != 7'd0) begin // 在字符范围内
if (char_lines[now_line-1][WIDTH_LEN - now_column]) begin
// data_display <= {$random};
data_display <= CYANRAY;
end
else begin
data_display <= BLACK;
end
end
else begin
data_display <= BLACK;
end
end
endmodule
显示效果:
三、VGA显示图像
将bmp文件转换为mif文件:
将生成的mif文件保存在ROM中。此时我们的ROM的位宽度为16位,深度的设置一定要大于图片的大小就行:
data_drive.v
:
module data_drive (
input wire vga_clk,
input wire rst_n,
input wire [ 10:0 ] addr_h,
input wire [ 10:0 ] addr_v,
output reg [ 15:0 ] rgb_data
);
localparam black = 16'd0;
parameter height = 209; // 图片高度
parameter width = 102; // 图片宽度
reg [ 20:0 ] rom_address ; // ROM地址
wire [ 20:0 ] rom_data ; // 图片数据
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
rgb_data = black;
end
else if ( flag_enable_out2 ) begin
rgb_data = rom_data;
end
else begin
rgb_data = black;
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 == height * width - 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 = flag_begin_h && flag_begin_v;
//实例化ROM
rom_mif rom_inst (
.address ( rom_address ),
.clock ( vga_clk ),
.q ( rom_data )
);
endmodule
修改顶层文件:
/**
*
* 顶层文件
*/
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_sync_n ,
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;
clk_25M_get u_clk_25M_get(
.clk (clk ), //VGA时
.rst_n (rst_n ), //复位
.clk_25M (clk_25M)
);
// data_generate u_data_generate(
// /*input wire */ .clk (clk_25M ),
// /*input wire */ .rst_n (rst_n ),
// /*input wire [10:0] */ .h_addr (h_addr ), // 行地址--数据有效显示区域行地址
// /*input wire [10:0] */ .v_addr (v_addr ), // 场地址--数据有效显示区域场地址
// /*output wire [23:0] */ .data_display (data_display ) // 要显示的数据
// );
// 显示字符
// data_gen_char u_data_gen_char(
// /*input wire */ .clk (clk_25M ),
// /*input wire */ .rst_n (rst_n ),
// /*input wire [10:0] */ .h_addr (h_addr ), // 行地址--数据有效显示区域行地址
// /*input wire [10:0] */ .v_addr (v_addr ), // 场地址--数据有效显示区域场地址
// /*output wire [23:0] */ .data_display (data_display ) // 要显示的数据
// );
data_drive u_data_drive(
.vga_clk (clk_25M),
.rst_n (rst_n),
.addr_h (h_addr),
.addr_v (v_addr),
.rgb_data (data_display)
);
vga_ctrl u_vga_ctrl(
/*input wire */ .clk (clk_25M ),
/*input wire */ .rst_n (rst_n ),
/*input wire [23:0]*/ .data_display (data_display), // 要显示的数据
/*output wire [10:0]*/ .h_addr (h_addr ), // 行地址--数据有效显示区域行地址
/*output wire [10:0]*/ .v_addr (v_addr ), // 场地址--数据有效显示区域场地址
/*output reg */ .hsync (hsync ),
/*output reg */ .vsync (vsync ),
/*output reg [7:0] */ .vga_r (vga_r ), // R
/*output reg [7:0] */ .vga_g (vga_g ), // G
/*output reg [7:0] */ .vga_b (vga_b ), // B
// .vga_sync_n (vga_sync_n ),
.vga_black (vga_black ),
/*output wire */ .vga_clk (vga_clk ) // 时钟
);
endmodule
显示: