VGA电阻网络分压法硬件电路:https://blog.csdn.net/mr_liu_666/article/details/102761178
首先给出各部分的源码,有兴趣的朋友可以去下面看VGA的原理。
先是VGA驱动模块:
//学习&参考源:正点原子、开拓者FPGA
module vga_driver(
input vga_clk, //VGA驱动时钟
input sys_rst_n, //复位信号
//VGA接口
output vga_hs, //行同步信号
output vga_vs, //场同步信号
output [15:0] vga_rgb, //红绿蓝三原色输出
input [15:0] pixel_data, //像素点数据
output [ 9:0] pixel_xpos, //像素点横坐标
output [ 9:0] pixel_ypos //像素点纵坐标
);
//parameter define
parameter H_SYNC = 10'd96; //行同步
parameter H_BACK = 10'd48; //行显示后沿
parameter H_DISP = 10'd640; //行有效数据
parameter H_FRONT = 10'd16; //行显示前沿
parameter H_TOTAL = 10'd800; //行扫描周期
parameter V_SYNC = 10'd2; //场同步
parameter V_BACK = 10'd33; //场显示后沿
parameter V_DISP = 10'd480; //场有效数据
parameter V_FRONT = 10'd10; //场显示前沿
parameter V_TOTAL = 10'd525; //场扫描周期
//reg define
reg [9:0] cnt_h;
reg [9:0] cnt_v;
//wire define
wire vga_en;
wire data_req;
//VGA行场同步信号
/*正如我所说,行计数器对应行,在前96个像素时钟,或者说96个“无用像素点”时,行同步时钟输出低电平,达到“同步”的目的,除此之外,在48个“无用像素点”,640个有用像素和16个“无用像素”时,行同步时钟都是高电平,场同步时钟也是一个道理*/
assign vga_hs = (cnt_h <= H_SYNC - 1'b1) ? 1'b0 : 1'b1;
assign vga_vs = (cnt_v <= V_SYNC - 1'b1) ? 1'b0 : 1'b1;
//使能RGB565数据输出
/*其实“有用像素点”的数据什么时候输出都行,但是最好还是在有用像素点到来(有用时钟到来)的时候再输出,毕竟如果是显示图片或者是彩条,忘记这一点,竖线就会变成斜线,而且会有很多数据丢失(显示到屏幕外面去了),这一句的意思就是在640*480的范围内才允许有用数据输出*/
assign vga_en = (((cnt_h >= H_SYNC+H_BACK) && (cnt_h < H_SYNC+H_BACK+H_DISP))
&&((cnt_v >= V_SYNC+V_BACK) && (cnt_v < V_SYNC+V_BACK+V_DISP)))
? 1'b1 : 1'b0;
//RGB565数据输出
assign vga_rgb = vga_en ? pixel_data : 16'd0;
//请求像素点颜色数据输入
/*这句的意思也是在640*480的范围内的时候才会去读数据*/
assign data_req = (((cnt_h >= H_SYNC+H_BACK-1'b1) && (cnt_h < H_SYNC+H_BACK+H_DISP-1'b1))
&& ((cnt_v >= V_SYNC+V_BACK) && (cnt_v < V_SYNC+V_BACK+V_DISP)))
? 1'b1 : 1'b0;
//像素点坐标
/*只有在640*480之内读数据的时候才给出横纵坐标,其实也就是横纵周期*/
assign pixel_xpos = data_req ? (cnt_h - (H_SYNC + H_BACK - 1'b1)) : 10'd0;
assign pixel_ypos = data_req ? (cnt_v - (V_SYNC + V_BACK - 1'b1)) : 10'd0;
//行计数器对像素时钟计数
/*VGA_CLK是锁相环分频分出来的,频率是25M*/
always @(posedge vga_clk or negedge sys_rst_n) begin
if (!sys_rst_n)
cnt_h <= 10'd0;
else begin
if(cnt_h < H_TOTAL - 1'b1)
cnt_h <= cnt_h + 1'b1;
else
cnt_h <= 10'd0;
end
end
//场计数器对行计数
always @(posedge vga_clk or negedge sys_rst_n) begin
if (!sys_rst_n)
cnt_v <= 10'd0;
else if(cnt_h == H_TOTAL - 1'b1) begin
if(cnt_v < V_TOTAL - 1'b1)
cnt_v <= cnt_v + 1'b1;
else
cnt_v <= 10'd0;
end
end
endmodule
然后是彩条(数据产生)模块:
//学习&参考源:正点原子、开拓者FPGA
/*模块的意义就是在驱动模块给出横纵坐标之后返回一个RGB值以显示*/
module vga_display(
input vga_clk, //VGA驱动时钟
input sys_rst_n, //复位信号
input [ 9:0] pixel_xpos, //像素点横坐标
input [ 9:0] pixel_ypos, //像素点纵坐标
output reg [15:0] pixel_data //像素点数据
);
parameter H_DISP = 10'd640; //分辨率——行
parameter V_DISP = 10'd480; //分辨率——列
localparam WHITE = 16'b11101_111011_11101; //RGB565 白色
localparam BLACK = 16'b00000_000000_00000; //RGB565 黑色
localparam RED = 16'b11101_000000_00000; //RGB565 红色
localparam GREEN = 16'b00000_111011_00000; //RGB565 绿色
localparam BLUE = 16'b00000_000000_11101; //RGB565 蓝色
reg [23:0] counter_Change;
reg [23:0] counter_Compare;
/*这部分是彩条闪烁部分,通过对主时钟分频,获得一个数值不断变化的counter_Compare值,将它的18位到5位作为颜色输出值,变色频率就是25M/1M/32 = 0.78次/S*/
always @(posedge vga_clk)
begin
if(counter_Compare < 24'hffffff)
begin
if(counter_Change % 24'd1_000_000 == 0)//主时钟变1M次,颜色变一次
counter_Compare <= counter_Compare + 1'b1;
end
else
counter_Compare <= 24'd0;
end
always @(posedge vga_clk)
begin
if(counter_Change < 24'd2_000_000)
counter_Change <= counter_Change + 1'b1;
else
counter_Change <= 24'b0;
end
//*****************************************************
//** main code
//*****************************************************
//根据当前像素点坐标指定当前像素点颜色数据,在屏幕上显示彩条
always @(posedge vga_clk or negedge sys_rst_n) begin
if (!sys_rst_n)
pixel_data <= 16'd0;
else begin
/*为了使颜色更有规律又各不相同,采用了基色减去随机色的办法,这样颜色的就会在基色附近变动,不至于变得太远,失去规律*/
if((pixel_xpos >= 0) && (pixel_xpos <= (H_DISP/5)*1))
pixel_data <= WHITE - counter_Compare[17:4] % WHITE;
else if((pixel_xpos >= (H_DISP/5)*1) && (pixel_xpos < (H_DISP/5)*2))
pixel_data <= RED - counter_Compare[17:4] % RED;
else if((pixel_xpos >= (H_DISP/5)*2) && (pixel_xpos < (H_DISP/5)*3))
pixel_data <= GREEN - counter_Compare[17:4] % GREEN;
else if((pixel_xpos >= (H_DISP/5)*3) && (pixel_xpos < (H_DISP/5)*4))
pixel_data <= BLUE - counter_Compare[17:4] % BLUE;
else
begin
/*这里把最后一个竖条分为了5个横格,变化频率更慢*/
if((pixel_ypos >= 0) && (pixel_ypos <= (V_DISP/5)*1))
pixel_data <= WHITE - counter_Compare[22:7] % 11;
else if((pixel_ypos >= (V_DISP/5)*1) && (pixel_ypos < (V_DISP/5)*2))
pixel_data <= WHITE - counter_Compare[22:7] % 19;
else if((pixel_ypos >= (V_DISP/5)*2) && (pixel_ypos < (V_DISP/5)*3))
pixel_data <= WHITE - counter_Compare[22:7] % 29;
else if((pixel_ypos >= (V_DISP/5)*3) && (pixel_ypos < (V_DISP/5)*4))
pixel_data <= WHITE - counter_Compare[22:7] % 37;
else
pixel_data <= WHITE - counter_Compare[22:7] % 47;
end
end
end
endmodule
顶层文件:
//学习&参考源:正点原子、开拓者FPGA
//待PLL输出稳定之后,停止复位
assign rst_n_w = sys_rst_n && locked_w;
vga_pll u_vga_pll( //时钟分频模块
.inclk0 (sys_clk),
.areset (~sys_rst_n),
.c0 (vga_clk_w), //VGA时钟 25M
.locked (locked_w)
);
vga_driver u_vga_driver(
.vga_clk (vga_clk_w),
.sys_rst_n (rst_n_w),
.vga_hs (vga_hs),
.vga_vs (vga_vs),
.vga_rgb (vga_rgb),
.pixel_data (pixel_data_w),
.pixel_xpos (pixel_xpos_w),
.pixel_ypos (pixel_ypos_w)
);
vga_display u_vga_display(
.vga_clk (vga_clk_w),
.sys_rst_n (rst_n_w),
.pixel_xpos (pixel_xpos_w),
.pixel_ypos (pixel_ypos_w),
.pixel_data (pixel_data_w)
);
endmodule
PLL锁相环这个IP请自行编辑吧,频率为25M(对于640 * 480 * 60Hz)。
相应的testbench源码:工程链接https://download.csdn.net/download/mr_liu_666/11928995
`timescale 10ns/10ns
module vga_drivertb();
parameter T = 4;//12.5ns
reg sys_clk; //系统时钟
reg sys_rst_n; //复位信号
//reg [15:0] pixel_data; //像素点数据
wire vga_hs; //行同步信号
wire vga_vs; //场同步信号
wire [15:0] vga_rgb; //红绿蓝三原色输出
wire [9:0] pixel_xpos; //像素点横坐标
wire [9:0] pixel_ypos; //像素点纵坐标
integer i;
initial
begin
sys_clk = 'b0;
sys_rst_n = 'b1;
/* for(i = 0; i < 16'hFFFF ; i = i + 1)
begin
#(5*T) pixel_data <= i;
end
*/
end
always
begin
#(T/2) sys_clk = ~sys_clk;
end
vga_driver vga_driver0(
.vga_clk(sys_clk), //VGA驱动时钟
.sys_rst_n(sys_rst_n), //复位信号
//VGA接口
.vga_hs(vga_hs), //行同步信号
.vga_vs(vga_vs), //场同步信号
.vga_rgb(vga_rgb), //红绿蓝三原色输出
// .pixel_data(pixel_data), //像素点数据
.pixel_xpos(pixel_xpos), //像素点横坐标
.pixel_ypos(pixel_ypos) //像素点纵坐标
);
endmodule
被观察的driver模块:
//****************************************Copyright (c)***********************************//
//技术支持:www.openedv.com
//淘宝店铺:http://openedv.taobao.com
//关注微信公众平台微信号:"正点原子",免费获取FPGA & STM32资料。
//版权所有,盗版必究。
//Copyright(C) 正点原子 2018-2028
//All rights reserved
//----------------------------------------------------------------------------------------
// File name: vga_driver
// Last modified Date: 2018/1/30 11:12:36
// Last Version: V1.1
// Descriptions: vga驱动
//----------------------------------------------------------------------------------------
// Created by: 正点原子
// Created date: 2018/1/29 10:55:56
// Version: V1.0
// Descriptions: The original version
//
//----------------------------------------------------------------------------------------
// Modified by: 正点原子
// Modified date: 2018/1/30 11:12:36
// Version: V1.1
// Descriptions: vga驱动
//
//----------------------------------------------------------------------------------------
//****************************************************************************************//
module vga_driver(
input vga_clk, //VGA驱动时钟
input sys_rst_n, //复位信号
//VGA接口
output vga_hs, //行同步信号
output vga_vs, //场同步信号
output [15:0] vga_rgb, //红绿蓝三原色输出
// input [15:0] pixel_data, //像素点数据
output [ 9:0] pixel_xpos, //像素点横坐标
output [ 9:0] pixel_ypos //像素点纵坐标
);
//parameter define
parameter H_SYNC = 10'd96; //行同步
parameter H_BACK = 10'd48; //行显示后沿
parameter H_DISP = 10'd640; //行有效数据
parameter H_FRONT = 10'd16; //行显示前沿
parameter H_TOTAL = 10'd800; //行扫描周期
parameter V_SYNC = 10'd2; //场同步
parameter V_BACK = 10'd33; //场显示后沿
parameter V_DISP = 10'd480; //场有效数据
parameter V_FRONT = 10'd10; //场显示前沿
parameter V_TOTAL = 10'd525; //场扫描周期
localparam WHITE = 16'b11111_111111_11111; //RGB565 白色
localparam BLACK = 16'b00000_000000_00000; //RGB565 黑色
localparam RED = 16'b11111_000000_00000; //RGB565 红色
localparam GREEN = 16'b00000_111111_00000; //RGB565 绿色
localparam BLUE = 16'b00000_000000_11111; //RGB565 蓝色
//reg define
reg [9:0] cnt_h;
reg [9:0] cnt_v;
reg [15:0] pixel_data;
//wire define
wire vga_en;
wire data_req;
//*****************************************************
//** main code
//*****************************************************
//VGA行场同步信号
assign vga_hs = (cnt_h <= H_SYNC - 1'b1) ? 1'b0 : 1'b1;
assign vga_vs = (cnt_v <= V_SYNC - 1'b1) ? 1'b0 : 1'b1;
//使能RGB565数据输出
assign vga_en = (((cnt_h >= H_SYNC+H_BACK) && (cnt_h < H_SYNC+H_BACK+H_DISP))
&&((cnt_v >= V_SYNC+V_BACK) && (cnt_v < V_SYNC+V_BACK+V_DISP)))
? 1'b1 : 1'b0;
//RGB565数据输出
assign vga_rgb = vga_en ? pixel_data : 16'd0;
//请求像素点颜色数据输入
assign data_req = (((cnt_h >= H_SYNC+H_BACK-1'b1) && (cnt_h < H_SYNC+H_BACK+H_DISP-1'b1))
&& ((cnt_v >= V_SYNC+V_BACK) && (cnt_v < V_SYNC+V_BACK+V_DISP)))
? 1'b1 : 1'b0;
//像素点坐标
assign pixel_xpos = data_req ? (cnt_h - (H_SYNC + H_BACK - 1'b1)) : 10'd0;
assign pixel_ypos = data_req ? (cnt_v - (V_SYNC + V_BACK - 1'b1)) : 10'd0;
//行计数器对像素时钟计数
always @(posedge vga_clk or negedge sys_rst_n) begin
if (!sys_rst_n)
cnt_h <= 10'd0;
else begin
if(cnt_h < H_TOTAL - 1'b1)
cnt_h <= cnt_h + 1'b1;
else
cnt_h <= 10'd0;
end
end
//场计数器对行计数
always @(posedge vga_clk or negedge sys_rst_n) begin
if (!sys_rst_n)
cnt_v <= 10'd0;
else if(cnt_h == H_TOTAL - 1'b1) begin
if(cnt_v < V_TOTAL - 1'b1)
cnt_v <= cnt_v + 1'b1;
else
cnt_v <= 10'd0;
end
end
always @(posedge vga_clk or negedge sys_rst_n) begin
if (!sys_rst_n)
pixel_data <= 16'd0;
else begin
if((pixel_xpos >= 0) && (pixel_xpos <= (H_DISP/5)*1))
pixel_data <= WHITE;
else if((pixel_xpos >= (H_DISP/5)*1) && (pixel_xpos < (H_DISP/5)*2))
pixel_data <= BLACK;
else if((pixel_xpos >= (H_DISP/5)*2) && (pixel_xpos < (H_DISP/5)*3))
pixel_data <= RED;
else if((pixel_xpos >= (H_DISP/5)*3) && (pixel_xpos < (H_DISP/5)*4))
pixel_data <= GREEN;
else
pixel_data <= BLUE;
end
end
endmodule
连接了硬件电路之后,FPGA端的写法就是写一个提供行时钟、帧时钟和16bits颜色数据的输出接口,VGA显示原理如下(黑白图来自开拓者FPGA):
可见除了颜色和共地端以外,VGA接口还要求行同步和场同步(帧同步),地址吗0 1 2 3 没有用上。
以下的是行同步适中的时序图(横轴时间,纵轴0 1),行同步的意思就是在每一个HSYNC的周期里面,显示器的一行被刷新了,也就是一个周期把640个RGB像素点打到屏幕上(如果显示器是640*480的),同步和显示后沿、前沿期间的 数据 是没有意义的。
以下的是场同步适中的时序图(横轴时间,纵轴0 1),行同步的意思就是在每一个VSYNC的周期里面,显示器的一帧被刷新了,也就是一个周期把480行图像打到屏幕上(如果显示器是640*480的),也就是一个VSYNC周期同步和显示后沿、前沿期间的 数据 是没有意义的。
所以他的刷新方式就是:
两个VSYNC和HSYNC都是由一个主时钟分频出来的,主时钟就是像素时钟,一个主时钟一个像素点,800个主时钟就是一行,800*525个主时钟就是一帧。因为像素点和时钟是对应的,所以图像的横纵也完全可以和像素时钟一个个周期对应起来,如下图,一帧显示需要800*525个周期(像素点),在中间橙色以外的像素点可以输出,但是不会显示,只有横纵640*480的像素点才会完整的显示在屏幕上。
不同的显示器对应不同的像素时钟和时序,分辨率和刷新速度都是影响时钟的因素:
综上,一个VGA彩条闪动就做好了: