注:由于没有系统的学过FPGA和Verilog语言,都是用到了才查的,所以对照参考的实验有很多删减的地方。参考链接见文末。
1.本实验是用Zedboard实现VGA接口的使用,通过PL端的资源,根据对应引脚连接信号线,完成VGA的使用。
实验分为几个小节,首先介绍VGA的引脚,然后了解VGA的时序,再根据硬件电路图,完成Verilog程序的设计,接着烧写bit流到开发板,测试。
2.介绍
参考:米联------- ZYNQ VGA 硬件驱动设计
VGA 是 Video Graphics Array 的简称, 也叫 D-Sub 接口。 目前显示器都配备 VGA接口, 有的显示器还配备 DVI(Digital Visual Interface) 和 HDMI(High Definition
Multimedia Interface) 接口。 VGA 是 IBM 在 1987 年随 PS/2 机一起推出的一种视频传输标准, 具有分辨率高、 显示速率快、 颜色丰富等优点, 在彩色显示器领域得到了广
泛的应用。 根据不同分辨率, VGA 分为 VGA(640x480) 、 SVGA(800x600) 、 XGA(1024x768) 、SXGA(1280x1024) 等。 目前 VGA 已经成为一种标准, 广泛应用于显示器、 TFT 液晶屏中。
VGA 接口分为公头和母头, 一般显示器都自带一根公头线, MIS603 开发平台上是母头, VGA 接口一共 15 根线, 分为 3 排, 标号如图所示:
引脚定义
在这里强调一下, VGA 接口的 RED、 GREEN 和 BLUE 传输的是模拟信号, 峰峰值为 0V~0.714V, 0V 代表无色, 0.714V 代表满色; HSYNC 和 VSYNC 传输的是数字信号, 为 TTL 电平。 VGA 模拟信号传输如图 6.1-2 所示, 源端和终端匹配电阻均为 75欧姆。
- 时序
参考:https://www.cnblogs.com/spartan/archive/2011/08/16/2140546.html
显示器扫描方式分为逐行扫描和隔行扫描:逐行扫描是扫描从屏幕左上角一点开始,从左像右逐点扫描,每扫描完一行,电子束回到屏幕的左边下一行的起始位置,在这期间,CRT对电子束进行消隐,每行结束时,用行同步信号进行同步;当扫描完所有的行,形成一帧,用场同步信号进行场同步,并使扫描回到屏幕左上方,同时进行场消隐,开始下一帧。隔行扫描是指电子束扫描时每隔一行扫一线,完成一屏后在返回来扫描剩下的线,隔行扫描的显示器闪烁的厉害,会让使用者的眼睛疲劳。
完成一行扫描的时间称为水平扫描时间,其倒数称为行频率;完成一帧(整屏)扫描的时间称为垂直扫描时间,其倒数称为场频率,即刷新一屏的频率,常见的有60Hz,75Hz等等。标准的VGA显示的场频60Hz,行频31.5KHz。
行场消隐信号:是针对老式显像管的成像扫描电路而言的。电子枪所发出的电子束从屏幕的左上角开始向右扫描,一行扫完需将电子束从右边移回到左边以便扫描第二行。在移动期间就必须有一个信号加到电路上,使得电子束不能发出。不然这个回扫线会破坏屏幕图像的。这个阻止回扫线产生的信号就叫作消隐信号,场信号的消隐也是一个道理。
注:这个图很重要,决定了编程的结构,一定要理解到,没看懂时序的要去看上面的参考链接,那里写的更详细。
4. VGA硬件图
由图可知,颜色信号共12个bit可以显示4096个色号。
VGA 接口信号和Zedboard引脚连接如下表所示:
在VIVADO里面编写好了Verilog程序后,要编写约束文件,就要把对应的信号线与引脚连接起来。
5. 接着就是在VIVADO 里面开始创建工程,不会的去翻以前的文章,里面有讲。然后添加三个文件,一个产生VG信号的.v文件,一个顶层封装文件,一个就是约束文件。由于VGA信号是800×[email protected],我们还需要一个像素时钟40MHZ。zedboard的PL部分默认的时钟是100MHZ,所以我们还需要一个时钟单元来产生40MHZ的特定时钟。分频的方法不稳定,不推荐。
接下来是全部代码。
vga_data_gen.v(添加注释版)
module vga_data_gen(
input pixel_clk, //像素时钟
input rst, //复位按键,高电平复位
output [11:0] vga_r, //vga信号红色视频信号
output [11:0] vga_g, //vga信号绿色视频信号
output [11:0] vga_b, //vga信号蓝色视频信号
output vga_hs, //行同步信号
output vga_vs, //场同步信号
output vga_de, //vga信号有效,如当前时刻传输数据有效,即为1;否则为0;
input turn_mode, //按键。按下为高电平,切换vga显示模式
output [3:0] mode //当前显示模式,用4位编码,也就是LED
);
//---------------------------------//
//使用的是800*600的分辨率
// 水平扫描参数的设定 800*600 60HZ
//行同步信号的参数
//--------------------------------//
parameter H_Total = 1056 - 1; //一行分为四个段a,b,c,d总共1056个像素
parameter H_Sync = 128 - 1; //a同步段-拉低的128个像素
parameter H_Back = 88 - 1; //b后廊段--拉高的88个像素
parameter H_Active = 800 - 1; //c**段--拉高的800个像素****
parameter H_Front = 40 - 1; //d前廊段--拉高的40个像素
parameter H_Start = 216 - 1; //从第三段开始
parameter H_End = 1016 - 1; //到第三段结尾,才是是要上机显示使用的像素点
//-------------------------------//
// 垂直扫描参数的设定
//场同步信号的参数--(同上)
//-------------------------------//
parameter V_Total = 628 - 1;
parameter V_Sync = 4 - 1;
parameter V_Back = 23 - 1;
parameter V_Active = 600 - 1;
parameter V_Front = 1 - 1;
parameter V_Start = 27 - 1;
parameter V_End = 627 - 1;
//行信号计数器
//计数每一行的信号,计到最大值H_Total则清零
reg [11:0] x_cnt;
always @(posedge pixel_clk or posedge rst)
begin
if(rst)
x_cnt <= 12'd0;
else if(x_cnt == H_Total)
x_cnt <= 12'd0;
else
x_cnt <= x_cnt + 1'b1;
end
//根据时序图参数,产生行同步信号
//如果复位或者则行同步信号赋高电平(低电平有效,每行结束时,用行同步信号进行同步)
reg hsync_r;
always @(posedge pixel_clk or posedge rst)
begin
if(rst)
hsync_r <= 1'b1;
else if(x_cnt>=0 && x_cnt < H_Sync)
hsync_r <= 1'b0; //有效
else
hsync_r <= 1'b1;
end
//行信号有效,即当前数据有效(高电平有效)
reg hs_de;
always @(posedge pixel_clk or posedge rst)
begin
if(rst)
hs_de <= 1'b0;
else if(x_cnt>=H_Start && x_cnt<H_End)
hs_de <= 1'b1;
else
hs_de <= 1'b0;
end
//场信号计数器
//计数每一列的信号(即有多少列),计到最大值V_Total则清零
reg [11:0] y_cnt;
always @(posedge pixel_clk or posedge rst)
begin
if(rst)
y_cnt <= 12'd0;
else if(y_cnt == V_Total)
y_cnt <= 12'd0;
else if(x_cnt == H_Total)
y_cnt <= y_cnt + 1'b1;
end
//根据时序图参数,产生场同步信号
//如果复位或者则场同步信号赋高电平(低电平有效,每帧结束时,用场同步信号进行同步)
reg vsync_r;
always @(posedge pixel_clk or posedge rst)
begin
if(rst)
vsync_r <= 1'b1;
else if(y_cnt>=0 && y_cnt<V_Sync)
vsync_r <= 1'b0;
else
vsync_r <= 1'b1;
end
//列信号有效,即当前数据有效
reg vs_de;
always @(posedge pixel_clk or posedge rst)
begin
if(rst)
vs_de <= 1'b0;
else if(y_cnt>=V_Start && y_cnt<V_End)
vs_de <= 1'b1;
else
vs_de <= 1'b0;
end
//按键消抖,按下后延时一段时间,再检测是否按下
reg [16:0] key_counter;
always @(posedge pixel_clk)
begin
if(turn_mode) //接到按键,按下为高电平
key_counter <= 17'd0; //按下后初始化为0
else if((turn_mode == 1'b0) && (key_counter <= 17'h11704))
key_counter <= key_counter + 1'b1; //如果按键未抖动,并且key_counter的值小于17'h11704,则key_counter寄存器的值加一,
//每一个时钟上升沿加一次,直到key_counter等于17'h11704(估计是按照时钟频率来算的)
end
//显示模式技术,每次按下按键,模式改变,最多有12种显示模式
//并且将每一种模式的输出到LED 对应的值上面,调用4个LED可以显示16种模式
reg [3:0] dis_mode;
assign mode = dis_mode;
always @(posedge pixel_clk)
begin
if(key_counter == 17'h11704) //相当于按键按下
begin
if(dis_mode == 4'd6) //判断模式是否为6,如果是的,归零
dis_mode <= 4'd0;
else
dis_mode <= dis_mode +1'b1; //否则,按一下,模式加一
end
end
//产生小格子,黑白棋盘
reg [11:0] grid_data_1;
always @(posedge pixel_clk)
begin
if((x_cnt[4] == 1'b0) ^ (y_cnt[4] == 1'b0)) //两个寄存器第五位的值异或为1则为黑色,否则为白
grid_data_1 <= 12'h000;
else
grid_data_1 <= 12'hfff;
end
//产生大格子,黑白棋盘
reg [11:0] grid_data_2;
always @(posedge pixel_clk)
begin
if((x_cnt[6] == 1'b0) ^ (y_cnt[6] == 1'b0)) //两个寄存器第七位的值异或为1则为黑色,否则为白
grid_data_2 <= 12'h000;
else
grid_data_2 <= 12'hfff;
end
//根据显示模式传输rgb信号到缓存器中
reg [11:0] vga_r_reg;
reg [11:0] vga_g_reg;
reg [11:0] vga_b_reg;
always @(posedge pixel_clk or posedge rst)
begin
if(rst)
begin
vga_r_reg <= 12'd0;
vga_g_reg <= 12'd0;
vga_b_reg <= 12'd0;
end
else
begin
case(dis_mode)
4'd0: //全黑
begin
vga_r_reg <= 12'd0;
vga_g_reg <= 12'd0;
vga_b_reg <= 12'd0;
end
4'd1: //全白
begin
vga_r_reg <= 12'b1111_1111_1111;
vga_g_reg <= 12'b1111_1111_1111;
vga_b_reg <= 12'b1111_1111_1111;
end
4'd2: //全红
begin
vga_r_reg <= 12'b1111_1111_1111;
vga_g_reg <= 12'd0;
vga_b_reg <= 12'd0;
end
4'd3: //全绿
begin
vga_r_reg <= 12'd0;
vga_g_reg <= 12'b1111_1111_1111;
vga_b_reg <= 12'd0;
end
4'd4: //全蓝
begin
vga_r_reg <= 12'd0;
vga_g_reg <= 12'd0;
vga_b_reg <= 12'b1111_1111_1111;
end
4'd5: //小格子,黑白棋盘
begin
vga_r_reg <= grid_data_1;
vga_g_reg <= grid_data_1;
vga_b_reg <= grid_data_1;
end
4'd6: //大格子,黑白棋盘
begin
vga_r_reg <= grid_data_2;
vga_g_reg <= grid_data_2;
vga_b_reg <= grid_data_2;
end
endcase
end
end
//将这些寄存器中的信号输出
assign vga_hs = hsync_r;
assign vga_vs = vsync_r;
assign vga_de = hs_de & vs_de; //只有当行信号和场信号同时有效时数据才有效
assign vga_r = (hs_de & vs_de) ? vga_r_reg : 12'h0;
assign vga_g = (hs_de & vs_de) ? vga_g_reg : 12'h0;
assign vga_b = (hs_de & vs_de) ? vga_b_reg : 12'h0;
endmodule
vga_disp.v顶层文件
我的理解是顶层文件的作用是把底层的端口隐射到顶层,实现了一个调用底层文件的作用。
`timescale 1ns / 1ps
//////////////////////////////////////////////////////////////////////////////////
// Company:
// Engineer:
//
// Create Date: 2018/12/13 15:18:47
// Design Name:
// Module Name: vga_disp
// Project Name:
// Target Devices:
// Tool Versions:
// Description:
//
// Dependencies:
//
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
//
//////////////////////////////////////////////////////////////////////////////////
module vga_disp(
input clk,
input rst,
input turn_mode, //KEY
output [3:0] vga_r,
output [3:0] vga_g,
output [3:0] vga_b,
output vga_hs,
output vga_vs,
output [3:0] mode //LED
);
wire pixel_clk;
wire [7:0] R, G, B;
wire HS, VS, DE;
assign vga_r = R[7:4];
assign vga_g = G[7:4];
assign vga_b = B[7:4];
assign vga_hs = HS;
assign vga_vs = VS;
vga_data_gen vga_data_gen(
.pixel_clk(pixel_clk),
.rst(rst),
.vga_r(R),
.vga_g(G),
.vga_b(B),
.vga_hs(HS),
.vga_vs(VS),
.vga_de(DE),
.turn_mode(turn_mode),
.mode(mode)
);
clk_wiz_0 clk
(
.clk_out1(pixel_clk),
.reset(1'b0),
.locked(),
.clk_in1(clk)
);
endmodule
约束文件 vga.xdc
# Clk(板子上的GCLK)
# Clk(板子上的GCLK)
set_property PACKAGE_PIN Y9 [get_ports {clk}]
set_property IOSTANDARD LVCMOS33 [get_ports {clk}]
# Rst(板子上的BTNU)
set_property PACKAGE_PIN T18 [get_ports {rst}]
set_property IOSTANDARD LVCMOS33 [get_ports {rst}]
# key 板子上的按键
set_property PACKAGE_PIN P16 [get_ports {turn_mode}]
set_property IOSTANDARD LVCMOS33 [get_ports {turn_mode}]
# Led0
set_property PACKAGE_PIN T22 [get_ports {mode[0]}]
set_property IOSTANDARD LVCMOS33 [get_ports {mode[0]}]
# Led1
set_property PACKAGE_PIN T21 [get_ports {mode[1]}]
set_property IOSTANDARD LVCMOS33 [get_ports {mode[1]}]
# Led2
set_property PACKAGE_PIN U22 [get_ports {mode[2]}]
set_property IOSTANDARD LVCMOS33 [get_ports {mode[2]}]
# Led3
set_property PACKAGE_PIN U21 [get_ports {mode[3]}]
set_property IOSTANDARD LVCMOS33 [get_ports {mode[3]}]
set_property PACKAGE_PIN Y19 [get_ports {vga_vs}]
set_property IOSTANDARD LVCMOS33 [get_ports {vga_vs}]
set_property PACKAGE_PIN AA19 [get_ports {vga_hs}]
set_property IOSTANDARD LVCMOS33 [get_ports {vga_hs}]
set_property PACKAGE_PIN V20 [get_ports {vga_r[0]}]
set_property IOSTANDARD LVCMOS33 [get_ports {vga_r[0]}]
set_property PACKAGE_PIN U20 [get_ports {vga_r[1]}]
set_property IOSTANDARD LVCMOS33 [get_ports {vga_r[1]}]
set_property PACKAGE_PIN V19 [get_ports {vga_r[2]}]
set_property IOSTANDARD LVCMOS33 [get_ports {vga_r[2]}]
set_property PACKAGE_PIN V18 [get_ports {vga_r[3]}]
set_property IOSTANDARD LVCMOS33 [get_ports {vga_r[3]}]
set_property PACKAGE_PIN AB22 [get_ports {vga_g[0]}]
set_property IOSTANDARD LVCMOS33 [get_ports {vga_g[0]}]
set_property PACKAGE_PIN AA22 [get_ports {vga_g[1]}]
set_property IOSTANDARD LVCMOS33 [get_ports {vga_g[1]}]
set_property PACKAGE_PIN AB21 [get_ports {vga_g[2]}]
set_property IOSTANDARD LVCMOS33 [get_ports {vga_g[2]}]
set_property PACKAGE_PIN AA21 [get_ports {vga_g[3]}]
set_property IOSTANDARD LVCMOS33 [get_ports {vga_g[3]}]
set_property PACKAGE_PIN Y21 [get_ports {vga_b[0]}]
set_property IOSTANDARD LVCMOS33 [get_ports {vga_b[0]}]
set_property PACKAGE_PIN Y20 [get_ports {vga_b[1]}]
set_property IOSTANDARD LVCMOS33 [get_ports {vga_b[1]}]
set_property PACKAGE_PIN AB20 [get_ports {vga_b[2]}]
set_property IOSTANDARD LVCMOS33 [get_ports {vga_b[2]}]
set_property PACKAGE_PIN AB19 [get_ports {vga_b[3]}]
set_property IOSTANDARD LVCMOS33 [get_ports {vga_b[3]}]
接下来的事就很简单了,生成比特流,下载,然后测试。
测试的时候我是录得视频,并未拍照,下面的图的借的,但是是一致的。
连上vga线,可以看到现象如下:
按动按键,依次看见
测试结束。很感谢参考的文章,学到了就要教人,拿到了就要给人。
参考文章:https://blog.csdn.net/hongbin_xu/article/details/76582103