所有工程文件均已开源,获取地址:zikwq/FPGA_prj: FPGA_prj (github.com)
VGA介绍
1. 背景
VGA(视频图形阵列),是一种电脑显示标准,是IBM在1987年提出的一个使用类比讯号的电脑显示标准。个人电脑在加载自己独特的驱动程序之前,都必须支持VGA的标准,绝大多数显卡都带有这种接口。
说到VGA接口,相信很多朋友都不会陌生,因为这种接口是电脑显示器上最主要的接口,从块头巨大的CRT显示器时代开始,VGA接口就被使用,并且一直沿用至今,另外VGA接口还被称为D-Sub接口。
VGA支持在640x480的较高分辨率下同时显示16种色彩或256种灰度。同时在320x240分辨率下可以同时显示256种颜色。
VGA接口共有15针,分成3排,每排5针。传输红绿蓝模拟信号以及同步信号(场同步和行同步)。使用VGA连接设备,线缆长度最好不要超过10米。
2. 特征
1、是模拟信号的传输,随着显示器的分辨率越高画面就会越模糊。一般模拟信号在超过1280×1024分辨率以上的情况下就会出现明显的误差。
2、因为是模拟信息,信号极其容易受干扰。
3、不能传达音频。
4、一般不加放大器和转换器在30米左右,加放大器和放大转换器可以到150米。
3. 原理图
我在进行VGA显示器驱动时,因为我使用的FPGA开发板是至芯z1开发板,而我看到视频教程是在哔哩哔哩中野火FPGA的教程。当时我在跟着视频做完程序后,在配置引脚的时候发现我要传输出去的信号,野火定义了16位宽,而我的开发板原理图只留出来8个引脚,也就是我传输出的数据必须是8位宽的数据才行。
后来我找了一些资料才发现,野火教程里面使用的是RGB565协议,而我的原理图是RGB332协议,所以需要修改对应的输出数据的位宽以及重新使用8位宽定义输出不同的颜色。其实也很简单,只不过想在这里记录一下。因为当天晚上我回到宿舍还硬想了一晚上为啥不一样嘞。今天回到实验室我重新了原理图才明白哈哈
可以对比看出来,RGB332和RGB565的一些不一样的地方,但是在这两个原理图中,都是使用权电阻网络来驱动VGA的,还有一种方法就是使用AD模数转换芯片来输出模拟信号驱动VGA,常见的AD芯片如:AD7123
RGB信号在使用时的位宽有三种常见格式,以你的VGA解码芯片的配置有关。
1. RGB_8,R:G:B = 3:3:2,即RGB332
2. RGB_16,R:G:B = 5:6:5,即RGB565
3. RGB_24,R:G:B = 8:8:8,即RGB888
4. 时序图
VGA显示器扫描方式分为逐行扫描和隔行扫描:逐行扫描是扫描从屏幕左上角一点开始,从左像右逐点扫描,每扫描完一行,电子束回到屏幕的左边下一行的起始位置,在这期间,CRT对电子束进行消隐,每行结束时,用行同步信号进行同步;当扫描完所有的行,形成一帧,用场同步信号进行场同步,并使扫描回到屏幕左上方,同时进行场消隐,开始下一帧。隔行扫描是指电子束扫描时每隔一行扫一线,完成一屏后在返回来扫描剩下的线,隔行扫描的显示器闪烁的厉害,会让使用者的眼睛疲劳。因此我们一般都采用逐行扫描的方式。
VGA时序标准,主要注意的点就是需要一个行同步信号和场同步信号,并且在同步信号开始之后并不直接是有效图像信号,在一个完整的扫描周期中,分为同步、后沿、左边框、有效信号、右边框、前沿。只有在有效信号的部分才是有效信号(说了跟没说一样)
行扫描周期的单位是像素!
场扫描周期的单位是行!
程序设计
1. 系统框图
一个项目的设计,我们首先需要设计好整个系统的框图,将功能分成模块,然后自下而上一步一步分析。完成一个模块后,进行仿真验证
在这个系统框图里面,我们使用PLL锁相环IP核分出来一个25MHZ的时钟信号作为VGA的时钟,并且输出一个锁定信号locked,将锁定信号和复位信号使用与门连接,其输出作为其他模块的复位信号。
在vga_pic模块中,主要是产生8位宽的图像数据,是根据输入的(x,y)坐标进行确定的。因为我们要在一个显示屏幕上显示10种不同的颜色嘛,
vga_ctrl模块,顾名思义,就是一个控制模块,用来输出行场同步信号和图像数据信号
2. 程序设计
vga_pic文件
module vga_pic(
input wire vga_clk ,
input wire rst_n,
input wire [9:0] pix_x,
input wire [9:0] pix_y,
output reg [7:0]pix_data
);
parameter H_VALID = 10'd640,
V_VALID = 10'd480;
// RGB565协议生成颜色
// parameter RED = 16'hF800 ,
// ORANGE = 16'hFC00 ,
// YELLOW = 16'hFFE0 ,
// GREEN = 16'h07E0 ,
// CYAN = 16'h07EF ,
// BLUE = 16'h001F ,
// PURPPLE = 16'hF81F ,
// BLACK = 16'h0000 ,
// WHITE = 16'hFFFF ,
// GRAY = 16'hD69A ;
// RGB332协议生成颜色
parameter RED = 8'b11100000; // 16'hF800 -> 8'b11100000
parameter ORANGE = 8'b11111000; // 16'hFC00 -> 8'b11111000
parameter YELLOW = 8'b11111010; // 16'hFFE0 -> 8'b11111010
parameter GREEN = 8'b00100000; // 16'h07E0 -> 8'b00100000
parameter CYAN = 8'b00100011; // 16'h07EF -> 8'b00100011
parameter BLUE = 8'b00011000; // 16'h001F -> 8'b00011000
parameter PURPPLE = 8'b11100001; // 16'hF81F -> 8'b11100001
parameter BLACK = 8'b00000000; // 16'h0000 -> 8'b00000000
parameter WHITE = 8'b11111111; // 16'hFFFF -> 8'b11111111
parameter GRAY = 8'b11010110; // 16'hD69A -> 8'b11010110
always @(posedge vga_clk or negedge rst_n) begin
if(!rst_n)
pix_data <= 8'd0000;
else if((pix_x >=0) && (pix_x < (H_VALID / 10) * 1))
pix_data <= RED;
else if((pix_x >=(H_VALID / 10) * 1) && (pix_x < (H_VALID / 10) * 2))
pix_data <= ORANGE;
else if((pix_x >=(H_VALID / 10) * 2) && (pix_x < (H_VALID / 10) * 3))
pix_data <= YELLOW;
else if((pix_x >=(H_VALID / 10) * 3) && (pix_x < (H_VALID / 10) * 4))
pix_data <= GREEN;
else if((pix_x >=(H_VALID / 10) * 4) && (pix_x < (H_VALID / 10) * 5))
pix_data <= CYAN;
else if((pix_x >=(H_VALID / 10) * 5) && (pix_x < (H_VALID / 10) * 6))
pix_data <= BLUE;
else if((pix_x >=(H_VALID / 10) * 6) && (pix_x < (H_VALID / 10) * 7))
pix_data <= PURPPLE;
else if((pix_x >=(H_VALID / 10) * 7) && (pix_x < (H_VALID / 10) * 8))
pix_data <= BLACK;
else if((pix_x >=(H_VALID / 10) * 8) && (pix_x < (H_VALID / 10) * 9))
pix_data <= WHITE;
else if((pix_x >=(H_VALID / 10) * 9) && (pix_x < H_VALID))
pix_data <= GRAY;
else
pix_data <= BLACK;
end
endmodule
vga_ctrl文件
module vga_ctrl(
input wire vga_clk ,
input wire rstn ,
input wire [15:0] pix_data , // 显示数据
output wire [9:0] pix_x , // x轴有效坐标位置
output wire [9:0] pix_y , // y轴有效坐标位置
output wire hsync , // 行同步信号
output wire vsync , // 场同步信号
output wire [7:0] vga_rgb
);
parameter H_SYNC = 10'd96 ,
H_BACK = 10'd40 ,
H_LEFT = 10'd8 ,
H_VALID = 10'd640 ,
H_RIGHT = 10'd8 ,
H_FRONT = 10'd8 ,
H_TOTAL = 10'd800 ;
parameter V_SYNC = 10'd2 ,
V_BACK = 10'd25 ,
V_TOP = 10'd8 ,
V_VALID = 10'd480 ,
V_BOTTOM= 10'd8 ,
V_FRONT = 10'd2 ,
V_TOTAL = 10'd525 ;
reg [9:0] cnt_h ;
reg [9:0] cnt_v ;
wire rgb_valid ;
wire pix_data_req ;
// cnt_h行计数器,计数范围0-799
always @(posedge vga_clk or negedge rstn) begin
if(!rstn)
cnt_h <= 10'd0;
else if (cnt_h == H_TOTAL-1'd1)
cnt_h <= 10'd0;
else
cnt_h <= cnt_h + 1'd1;
end
// cnt_v列计数器,计数范围0-524并且行计数器计数到799
always @(posedge vga_clk or negedge rstn) begin
if(!rstn)
cnt_v <= 10'd0;
else if ((cnt_h == H_TOTAL-1'd1) && (cnt_v == V_TOTAL - 1'd1))
cnt_v <= 10'd0;
else if(cnt_h == H_TOTAL-1'd1)
cnt_v <= cnt_v + 1'd1;
else
cnt_v <= cnt_v;
end
// 定义有效数据范围,
assign rgb_valid = ((cnt_h >= H_SYNC + H_BACK + H_LEFT)
&& (cnt_h < H_SYNC + H_BACK + H_LEFT + H_VALID)
&& (cnt_v >= V_SYNC + V_BACK + V_TOP)
&& (cnt_v < V_SYNC + V_BACK + V_TOP + V_VALID)) ? 1'd1 : 1'd0;
assign pix_data_req = ((cnt_h >= H_SYNC + H_BACK + H_LEFT - 1'd1)
&& (cnt_h < H_SYNC + H_BACK + H_LEFT + H_VALID -1'd1)
&& (cnt_v >= V_SYNC + V_BACK + V_TOP)
&& (cnt_v < V_SYNC + V_BACK + V_TOP + V_VALID)) ? 1'd1 : 1'd0;
assign pix_x = (pix_data_req == 1'd1) ? (cnt_h - (H_SYNC + H_BACK + H_LEFT - 1'd1)) : 10'h3ff;
assign pix_y = (pix_data_req == 1'd1) ? (cnt_v - (V_SYNC + V_BACK + V_TOP)) : 10'h3ff;
assign hsync = (cnt_h <= H_SYNC - 1'd1) ? 1'd1 : 1'd0;
assign vsync = (cnt_v <= V_SYNC - 1'd1) ? 1'd1 : 1'd0;
assign vga_rgb = (rgb_valid == 1'd1) ? pix_data : 8'h0000;
endmodule
tb_vga_ctrl 仿真文件,在仿真的时候,将仿真时间设计成17ms即可
`timescale 1ns/1ps
module tb_vga_ctrl ();
reg clk ;
reg rstn ;
wire [15:0] pix_data;
wire vga_clk;
wire locked;
wire rst_n;
wire [9:0] pix_x ;
wire [9:0] pix_y ;
wire hsync ;
wire vsync ;
wire [15:0] vga_rgb ;
initial begin
clk = 1'd1;
rstn = 1'd0;
# 20
rstn = 1'd1;
end
always #10 clk = ~clk;
// always @(posedge vga_clk or negedge rstn) begin
// if(!rstn)
// pix_data <= 16'h0000;
// else if((pix_x >=0 && pix_x <= 639)
// && (pix_y >= 0 && pix_y <= 479))
// pix_data <= 16'hffff;
// else
// pix_data <= 16'h0000;
// end
// 实例化PLL
clk_gen clk_gen_inst
(
.areset ( ~rstn ),
.inclk0 ( clk ),
.c0 ( vga_clk ),
.locked ( locked )
);
assign rst_n = locked && rstn;
// 实例化控制模块
vga_ctrl vga_ctrl_init(
.vga_clk (vga_clk) ,
.rstn (rst_n) ,
.pix_data (pix_data) , // 显示数据
.pix_x (pix_x ) , // x轴有效坐标位置
.pix_y (pix_y ) , // y轴有效坐标位置
.hsync (hsync ) , // 行同步信号
.vsync (vsync ) , // 场同步信号
.vga_rgb (vga_rgb)
);
vga_pic vga_pic_inst(
. vga_clk(vga_clk) ,
. rst_n (rst_n) ,
. pix_x (pix_x) ,
. pix_y (pix_y) ,
.pix_data(pix_data)
);
endmodule
顶层文件:vga_colorb.v
module vga_colorb(
input wire clk,
input wire rstn,
output wire hsync,
output wire vsync,
output wire [7:0] vga_rgb
);
wire vga_clk ;
wire locked ;
wire rst_n ;
wire [9:0] pix_x ;
wire [9:0] pix_y ;
wire [7:0] pix_data;
assign rst_n = (rstn && locked);
// 实例化PLL
clk_gen clk_gen_inst
(
.areset ( ~rstn ),
.inclk0 ( clk ),
.c0 ( vga_clk ),
.locked ( locked )
);
// 实例化控制模块
vga_ctrl vga_ctrl_init(
.vga_clk (vga_clk) ,
.rstn (rst_n) ,
.pix_data (pix_data) , // 显示数据
.pix_x (pix_x ) , // x轴有效坐标位置
.pix_y (pix_y ) , // y轴有效坐标位置
.hsync (hsync ) , // 行同步信号
.vsync (vsync ) , // 场同步信号
.vga_rgb (vga_rgb)
);
vga_pic vga_pic_inst(
. vga_clk(vga_clk) ,
. rst_n (rst_n) ,
. pix_x (pix_x) ,
. pix_y (pix_y) ,
.pix_data(pix_data)
);
endmodule
参考资料
VGA、DVI、HDMI区别 - 知乎 (zhihu.com)