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源碼:
`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彩條閃動就做好了: