FPGA驱动VGA接口显示1024*768@60Hz彩条图像到带VGA接口的显示屏。
VGA的全称是Video Graphics Array,即视频图形阵列,是一个使用模拟信号进行视频传输的标准。早期的CRT显示器由于设计制造上的原因,只能接收模拟信号输入,因此计算机内部的显卡负责进行数模转换,而VGA接口就是显卡上输出模拟信号的接口。如今液晶显示器虽然可以直接接收数字信号,但是为了兼容显卡上的VGA接口,也大都支持VGA标准。(在液晶屏内部又做了一次AD转换)。如图所示,图像数据是模拟信号,而行场同步信号可以直接连接到VGA接口上。
早期的VGA特指分辨率为640X480的显示模式,后来根据分辨率的不同,VGA又分为VGA(640x480)、SVGA(800x600)、XGA(1024x768)、SXGA(1280x1024)等。不同分辨率的VGA显示时序是类似的,仅存在参数上的差异
该芯片主要将数字信号转换成模拟信号连接VGA接口。其内部有三路10bitAD转换通道,刚好分别连接R、G、B三路信号,具体FPGA程序如何连接管脚,应该看具体的硬件设计。例如,本次例程使用一个VGA转接板连接,设计很奇葩,AD的低两位直接接地,原本可以支持RGB888,但是强行通过转接板删减成RGB565,R[4:2],G[3:2],B[4:2]直接接到了一起,于是只能逻辑上将这个地方接0,即代码中的vga_dummy信号。原理图如下所示。
驱动VGA接口的逻辑,其实是驱动ADV芯片的逻辑,这与RGB接口是一致的,只有一点不同是RGB接口时序参数是由制作LCD的厂家定义的,而VGA接口的时序参数是由制定VGA接口标准的协会制定的,是通用的。不同分辨率和刷新率能决定不同的显示参数和像素时钟。例程中,采用的是1024*768@60Hz,像素时钟为时钟为65MHz,常见的VGA接口的参数如下,其中a(o)对应同步脉冲,b(p)对应后沿脉冲,c(q)为有效数据,d(r)为前沿脉冲,e(s)为总长度。
对于VGA的驱动编写很简单,用一句话概括就是按照VGA显示该分辨率帧频图像时要求的准确时刻置位行场同步信号,并送出模拟图像有效信号到VGA管脚。如下图所示只需要修改相应的时序参数就可以了。关于RGB接口的时序可以参考F2—TFT显示彩条测试
对应工程源码
效果如下:
代码如下:
Top模块,例化驱动模块和逻辑控制模块
module Vga_top(
input sys_clk_i,//Y18
input key,//S4 B21
output [15:0]vga_data, //VGA数据 RGB565信号
output vga_hs, //VGA行同步信
output vga_vs, //VGA场同步信
output vga_clk, //VGA像素时钟
output vga_blank_n, //VGA数据使能
output vga_sync_n, //JIE 0
output [2:0]vga_dummy //VGA背光控制
);
wire clk_65m;
wire clk_rst;
wire vga_de;
assign vga_blank_n=vga_de;
assign vga_sync_n = 1'b0;
assign vga_dummy = 3'b0;
clk_wiz_0 clk_tree(.clk_out1(clk_65m),.reset(1'b0),.locked(clk_rst),.clk_in1(sys_clk_i));
wire [15:0]user_data;
wire data_req;
wire [11:0]x_addr;
wire [11:0]y_addr;
vga_driver my_driver(
.clk_65m (clk_65m),
.rst_n (clk_rst),
.start_show (key),
.user_data (user_data),
.data_req (data_req),
.addr_x (x_addr),
.addr_y (y_addr),
.vga_data (vga_data),
.vga_hs (vga_hs),
.vga_vs (vga_vs),
.vga_clk (vga_clk),
.vga_de (vga_de)
);
vga_ctrl my_ctrl(
.x_addr(x_addr),
.y_addr(y_addr),
.data_req(data_req),
.pix_data(user_data)
);
endmodule
Driver模块,接收数据源并且实现VGA接口驱动
module vga_driver(
input clk_65m,
input rst_n,
input start_show,
input [15:0]user_data,
output data_req,
output [11:0]addr_x,
output [11:0]addr_y,
output[15:0]vga_data,
output vga_hs,
output vga_vs,
output vga_clk,
output vga_de//背光使能信号
);
//VGA时序参数 Timing 1024*768 & 65MHz & 60Hz
parameter H_Total_Time = 12'd1344;
parameter H_Right_Border = 12'd0;
parameter H_Front_Porch = 12'd24;
parameter H_Sync_Time = 12'd136;
parameter H_Back_Porch = 12'd160;
parameter H_Left_Border = 12'd0;
parameter V_Total_Time = 12'd806;
parameter V_Bottom_Border= 12'd0;//border信号是人为定义的上下边框
parameter V_Front_Porch = 12'd3;
parameter V_Sync_Time = 12'd6;
parameter V_Back_Porch = 12'd29;
parameter V_Top_Border = 12'd0;
reg [11:0] h_cnt;//行计数
reg [11:0] v_cnt;//场计数
wire h_pulse;
/***********RGB 接口时序输出****************/
assign vga_hs = (h_cnt >= H_Sync_Time);//行场同步在数据无效的时候也可能有效
assign vga_vs = (v_cnt >= V_Sync_Time);
assign vga_data = vga_de ? user_data : 16'd0;
//数据有效区域,使能背光并且使能计数器
assign vga_de = (h_cnt >= H_Sync_Time + H_Back_Porch + H_Left_Border -1)&&
(h_cnt < H_Total_Time- H_Right_Border - H_Front_Porch-1)&&
(v_cnt >= V_Sync_Time + V_Back_Porch + V_Top_Border -1)&&
(v_cnt < V_Total_Time - V_Bottom_Border - V_Front_Porch-1);
assign data_req = vga_de;
assign vga_clk = clk_65m;//这是刷新率和屏幕参数决定的
//行同步计数
always@(posedge clk_65m or negedge rst_n)
if(!rst_n)
h_cnt <= 0;
else if(start_show)begin
if(h_pulse)
h_cnt <= 0;
else
h_cnt <= h_cnt+1;
end else
h_cnt <= 0;
assign h_pulse = (h_cnt >= H_Total_Time-1);//计一行产生一个脉冲 用于刷新行计数器和场计数器
//场同步计数
always@(posedge clk_65m or negedge rst_n)
if(!rst_n)
v_cnt <= 0;
else if(start_show)begin
if(h_pulse)begin
if(v_cnt == V_Total_Time - 1)
v_cnt <= 0;
else
v_cnt <= v_cnt+1;
end else
v_cnt <= v_cnt;
end else
v_cnt <= 0;
//x坐标返回
assign addr_x = vga_de?(h_cnt - H_Sync_Time - H_Back_Porch - H_Left_Border +1):12'd0;;
//y坐标返回
assign addr_y = vga_de?(v_cnt - V_Sync_Time - V_Back_Porch - V_Top_Border +1):12'b0;
endmodule
ctrl模块,提供数据源,整合可将数据源替换即可
module vga_ctrl(
input [11:0]x_addr,
input [11:0]y_addr,
input data_req,
output reg [15:0]pix_data
);
//提供显示数据
wire s_1;//指示四块区域
wire s_2;
wire s_3;
wire s_4;
wire [3:0]s_list;
localparam
BLACK = 16'h0000, //黑色
BLUE = 16'h001F, //蓝色
RED = 16'hF800, //红色
PURPPLE = 16'hF81F; //紫色
//将1920个像素点分成四个部分,
assign s_1 = data_req && x_addr >= 0 && x_addr < 256; //正在扫描第1列条纹;
assign s_2 = data_req && x_addr >= 256 && x_addr < 512;//正在扫描第2列条纹;
assign s_3 = data_req && x_addr >= 512 && x_addr < 768;//正在扫描第3列条纹;
assign s_4 = data_req && x_addr >= 768&& x_addr < 1024;//正在扫描第4列条纹;
assign s_list = {s_1,s_2,s_3,s_4};
always @(*) begin
case (s_list)
4'b0001: pix_data = BLACK;
4'b0010: pix_data = BLUE;
4'b0100: pix_data = RED;
4'b1000: pix_data = PURPPLE;
default: pix_data = BLACK;
endcase
end
endmodule