目录
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;在上一篇《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; 同时,也方便生成同步信号。
有了行计数器,就方便生成行同步信号了
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; endy_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