目录
Verilog设计
1.接口设计
2. 时序参数设置
3. 内部信号
4. PLL(VGA_CLK)
5.行计数器
6. 行同步信号
7.列计数器
8.显示方块
显示彩条
VGA图像数据选择输出
按键控制程序
学习的过程都是由浅入深,多数教程都是选择从某一点引入,逐步拓展,因此这里也是从VGA的显示出发开始学习(最简单了)。
该VGA显示例程已经黑金AX309开发板(Spartan6)验证。采用的常用的RGB565格式进行VGA显示,5bit的R,6bit的G,5bitd B。共16bit,可标识65536色。
该显示例程只是用来说明VGA的显示原理,因此并没有视频数据源(摄像头等),而是内部产生一些数据进行测试使用。FPGA上的VGA处理的是数字信号,而VGA显示器是模拟信号,因此之间需要一个DAC的过程,会有一个ADV/GM7123芯片来实现。AX309开发板上似乎没有用,而是用电阻匹配网络来实现RGB数字信号到模拟信号的转换。
因为这里没有视频源,因此该模块并没有数据的输入,而数据就是RGB三种分量以及同步信号。为了实现多种显示模式的切换还需要一个按键开关。
`timescale 1ns / 1ps
//
// Module Name: vga_test
//
//
module vga_test(
input rst_n,
input fpga_gclk,
output vga_hs,
output vga_vs,
output [4:0] vga_r,
output [5:0] vga_g,
output [4:0] vga_b,
input key1 //按键key1
);
为了实现分辨率可配置,可以 将分辨率参数化:
//-----------------------------------------------------------//
// 水平扫描参数的设定1024*768 60Hz VGA
//-----------------------------------------------------------//
parameter LinePeriod =1344; //行周期数
parameter H_SyncPulse=136; //行同步脉冲(Sync a)
parameter H_BackPorch=160; //显示后沿(Back porch b)
parameter H_ActivePix=1024; //显示时序段(Display interval c)
parameter H_FrontPorch=24; //显示前沿(Front porch d)
parameter Hde_start=296;
parameter Hde_end=1320;
//-----------------------------------------------------------//
// 垂直扫描参数的设定1024*768 60Hz VGA
//-----------------------------------------------------------//
parameter FramePeriod =806; //列周期数
parameter V_SyncPulse=6; //列同步脉冲(Sync o)
parameter V_BackPorch=29; //显示后沿(Back porch p)
parameter V_ActivePix=768; //显示时序段(Display interval q)
parameter V_FrontPorch=3; //显示前沿(Front porch r)
parameter Vde_start=35;
parameter Vde_end=803;
//-----------------------------------------------------------//
// 水平扫描参数的设定800*600 VGA
//-----------------------------------------------------------//
//parameter LinePeriod =1056; //行周期数
//parameter H_SyncPulse=128; //行同步脉冲(Sync a)
//parameter H_BackPorch=88; //显示后沿(Back porch b)
//parameter H_ActivePix=800; //显示时序段(Display interval c)
//parameter H_FrontPorch=40; //显示前沿(Front porch d)
//-----------------------------------------------------------//
// 垂直扫描参数的设定800*600 VGA
//-----------------------------------------------------------//
//parameter FramePeriod =628; //列周期数
//parameter V_SyncPulse=4; //列同步脉冲(Sync o)
//parameter V_BackPorch=23; //显示后沿(Back porch p)
//parameter V_ActivePix=600; //显示时序段(Display interval q)
//parameter V_FrontPorch=1; //显示前沿(Front porch r)
//-----------------------------------------------------------//
// 水平扫描参数的设定640*480 VGA
//-----------------------------------------------------------//
//parameter LinePeriod =800; //行周期数
//parameter H_SyncPulse=96; //行同步脉冲(Sync a)
//parameter H_BackPorch=48; //显示后沿(Back porch b)
//parameter H_ActivePix=640; //显示时序段(Display interval c)
//parameter H_FrontPorch=16; //显示前沿(Front porch d)
//Parameter Hde_start=144;
//Parameter Hde_end =784;
//-----------------------------------------------------------//
// 垂直扫描参数的设定640*480 VGA
//-----------------------------------------------------------//
//parameter FramePeriod =525; //列周期数
//parameter V_SyncPulse=2; //列同步脉冲(Sync o)
//parameter V_BackPorch=31; //显示后沿(Back porch p)
//parameter V_ActivePix=480; //显示时序段(Display interval q)
//parameter V_FrontPorch=11; //显示前沿(Front porch r)
//parameter Vde_start=33;
//parameter Vde_end =513;
reg[10 : 0] x_cnt; //行计数器即水平计数
reg[9 : 0] y_cnt; //列计数器即垂直计数
reg[15 : 0] grid_data_1; //小格子数据寄存器
reg[15 : 0] grid_data_2; //大方格数据寄存器
reg[15 : 0] bar_data; //彩条数据寄存器
reg[3 : 0] vga_dis_mode; //VGA图像显示模式寄存器
reg[4 : 0] vga_r_reg;
reg[5 : 0] vga_g_reg;
reg[4 : 0] vga_b_reg;
reg hsync_r;
reg vsync_r;
reg hsync_de;
reg vsync_de;
reg [15:0] key1_counter; //按键检测寄存器
wire vga_clk;
wire CLK_OUT1;
在上一篇《VGA入门学习》中讲过,VGA驱动时钟与图像的分辨率有关,并提供了计算方式:800*525*60=约25.2近似25M。工业上帧率常用59.94fps。AX309开发板上提供50M外部时钟,需要经PLL分频拿到需要的时钟。
//25.2Mhz for 640x480(60hz)/ 40.0Mhz for 800x600(60hz) / 65.0Mhz for 1024x768(60hz)/108.0Mhz for 1280x1024(60hz)
pll1 pll1_inst
(// Clock in ports
.CLK_IN1(fpga_gclk), // IN
.CLK_OUT1(CLK_OUT1), // 25.0Mhz for 640x480(60hz)
.RESET(~rst_n), // reset input
.LOCKED()); // OUT
为了实现横向或竖向或者不同尺寸的方块,首先要能够对一帧图像的行和列进行定位(好比在图像上建立一个坐标系),最直接的方法就是设置行/列计数器x_cnt和y_cnt; 同时,也方便生成同步信号。
//----------------------------------------------------------------
// 水平扫描计数
//----------------------------------------------------------------
always @ (posedge vga_clk)
if(~rst_n) x_cnt <= 1;
else if(x_cnt == LinePeriod) x_cnt <= 1;
else x_cnt <= x_cnt+ 1;
//----------------------------------------------------------------
有了行计数器,就方便生成行同步信号了
Hsync 和Hsync_de 。 Hync_de信号就是图像行上的data_valid信号,这与Vsync_de信号共同确定有效像素显示区域,后续介绍。
// 水平扫描信号hsync,hsync_de产生
//----------------------------------------------------------------
always @ (posedge vga_clk)
begin
if(~rst_n) hsync_r <= 1'b1;
else if(x_cnt == 1) hsync_r <= 1'b0; //产生hsync信号
else if(x_cnt == H_SyncPulse) hsync_r <= 1'b1;
if(~rst_n) hsync_de <= 1'b0;
else if(x_cnt == Hde_start) hsync_de <= 1'b1; //产生hsync_de信号
else if(x_cnt == Hde_end) hsync_de <= 1'b0;
end
y_cnt;列计数器需要依赖行计数器来驱动,因为每当行计数器计满即传输一行时,下会到下一列。
//----------------------------------------------------------------
// 垂直扫描计数
//----------------------------------------------------------------
always @ (posedge vga_clk)
if(~rst_n) y_cnt <= 1;
else if(y_cnt == FramePeriod) y_cnt <= 1;
else if(x_cnt == LinePeriod) y_cnt <= y_cnt+1;
同样,有了列计数器,列同步信号和列数据使能信号也就随之产生了:
//----------------------------------------------------------------
// 垂直扫描信号vsync, vsync_de产生
//----------------------------------------------------------------
always @ (posedge vga_clk)
begin
if(~rst_n) vsync_r <= 1'b1;
else if(y_cnt == 1) vsync_r <= 1'b0; //产生vsync信号
else if(y_cnt == V_SyncPulse) vsync_r <= 1'b1;
if(~rst_n) vsync_de <= 1'b0;
else if(y_cnt == Vde_start) vsync_de <= 1'b1; //产生vsync_de信号
else if(y_cnt == Vde_end) vsync_de <= 1'b0;
end
---------------------------------------------------------------------------分割线------------------------------------------------------------------------------------
以上都是前期准备工作,图像定位问题,驱动时钟,以及对应分辨率下的同步信号什么的都准备好 ,就差输出图像数据了。因为没有视频源,下面就考虑想要显示什么以及怎么显示的问题了。比较简单的就是一些彩条,方块的显示了,虽然简单,但方便理解啊。
行列计数器对方块间隔进行控制,这里有两种间隔即方块的大小,其数据分别放在grid_data_1和grid_data_2中。需要注意的是两种方块都在跟着计数器的变化生成,只不过在最后显示的时候根据需要,进行模式切换来选择其中一个罢了。
//----------------------------------------------------------------
// 格子测试图像产生
//----------------------------------------------------------------
always @(negedge vga_clk)
begin
if ((x_cnt[4]==1'b1) ^ (y_cnt[4]==1'b1)) //产生格子1图像
grid_data_1<= 16'h0000;
else
grid_data_1<= 16'hffff;
if ((x_cnt[6]==1'b1) ^ (y_cnt[6]==1'b1)) //产生格子2图像
grid_data_2<=16'h0000;
else
grid_data_2<=16'hffff;
end
需要什么颜色的彩条就将对应的颜色填入数据中即可,x_cnt来控制彩条宽度。也可以选择将下面的各种颜色进行宏定义,这样方便使用。
//----------------------------------------------------------------
// 彩色条测试图像产生
//----------------------------------------------------------------
always @(negedge vga_clk)
begin
if (x_cnt==300)
bar_data<= 16'hf800;
else if (x_cnt==420)
bar_data<= 16'h07e0;
else if (x_cnt==540)
bar_data<=16'h001f;
else if (x_cnt==660)
bar_data<=16'hf81f;
else if (x_cnt==780)
bar_data<=16'hffe0;
else if (x_cnt==900)
bar_data<=16'h07ff;
else if (x_cnt==1020)
bar_data<=16'hffff;
else if (x_cnt==1140)
bar_data<=16'hfc00;
else if (x_cnt==1260)
bar_data<=16'h0000;
end
可以设置多种的输出模式vga_dis_mode,这里就准备了多种模式的VGA图像数据,但是一次只能显示一种。那如何进行选择呢,本例程采用按键控制。
//----------------------------------------------------------------
// VGA图像选择输出
//----------------------------------------------------------------
//LCD数据信号选择
always @(negedge vga_clk)
if(~rst_n) begin
vga_r_reg<=0;
vga_g_reg<=0;
vga_b_reg<=0;
end
else
case(vga_dis_mode)
4'b0000:begin
vga_r_reg<=0; //VGA显示全黑
vga_g_reg<=0;
vga_b_reg<=0;
end
4'b0001:begin
vga_r_reg<=5'b11111; //VGA显示全白
vga_g_reg<=6'b111111;
vga_b_reg<=5'b11111;
end
4'b0010:begin
vga_r_reg<=5'b11111; //VGA显示全红
vga_g_reg<=0;
vga_b_reg<=0;
end
4'b0011:begin
vga_r_reg<=0; //VGA显示全绿
vga_g_reg<=6'b111111;
vga_b_reg<=0;
end
4'b0100:begin
vga_r_reg<=0; //VGA显示全蓝
vga_g_reg<=0;
vga_b_reg<=5'b11111;
end
4'b0101:begin
vga_r_reg<=grid_data_1[15:11]; // VGA显示方格1
vga_g_reg<=grid_data_1[10:5];
vga_b_reg<=grid_data_1[4:0];
end
4'b0110:begin
vga_r_reg<=grid_data_2[15:11]; // VGA显示方格2
vga_g_reg<=grid_data_2[10:5];
vga_b_reg<=grid_data_2[4:0];
end
4'b0111:begin
vga_r_reg<=x_cnt[6:2]; //VGA显示水平渐变色
vga_g_reg<=x_cnt[6:1];
vga_b_reg<=x_cnt[6:2];
end
4'b1000:begin
vga_r_reg<=y_cnt[6:2]; //VGA显示垂直渐变色
vga_g_reg<=y_cnt[6:1];
vga_b_reg<=y_cnt[6:2];
end
4'b1001:begin
vga_r_reg<=x_cnt[6:2]; //VGA显示红水平渐变色
vga_g_reg<=0;
vga_b_reg<=0;
end
4'b1010:begin
vga_r_reg<=0; //VGA显示绿水平渐变色
vga_g_reg<=x_cnt[6:1];
vga_b_reg<=0;
end
4'b1011:begin
vga_r_reg<=0; //VGA显示蓝水平渐变色
vga_g_reg<=0;
vga_b_reg<=x_cnt[6:2];
end
4'b1100:begin
vga_r_reg<=bar_data[15:11]; //VGA显示彩色条
vga_g_reg<=bar_data[10:5];
vga_b_reg<=bar_data[4:0];
end
default:begin
vga_r_reg<=5'b11111; //VGA显示全白
vga_g_reg<=6'b111111;
vga_b_reg<=5'b11111;
end
endcase
assign vga_hs = hsync_r;
assign vga_vs = vsync_r;
assign vga_r = (hsync_de & vsync_de)?vga_r_reg:5'b00000;
assign vga_g = (hsync_de & vsync_de)?vga_g_reg:6'b000000;
assign vga_b = (hsync_de & vsync_de)?vga_b_reg:5'b00000;
assign vga_clk = CLK_OUT1;
选择性的放图:
按键消抖和模式的切换。按键本身只能有两种状态,但是通过每一次案件操作映射模式寄存器对应的vga_dis_mode寄存器值就能实现多种显示状态的切换。
//按钮处理程序
always @(posedge vga_clk)
begin
if (key1==1'b0) //如果按钮没有按下,寄存器为0
key1_counter<=0;
else if ((key1==1'b1)& (key1_counter<=16'hc350)) //如果按钮按下并按下时间少于1ms,计数
key1_counter<=key1_counter+1'b1;
if (key1_counter==16'hc349) //一次按钮有效,改变显示模式
begin
if(vga_dis_mode==4'b1101)
vga_dis_mode<=4'b0000;
else
vga_dis_mode<=vga_dis_mode+1'b1;
end
end
endmodule