附一个VGA不同分辨率的时序标准的网页,个人觉得还是挺有用的。http://tinyvga.com/vga-timing
经过前面的介绍,我们知道了要实现VGA传输时序,有3个基本脉冲要控制好:像素时钟脉冲clk、行同步信号脉冲Hsync、场同步信号脉冲Vsync;其中Hsync的周期为clk的整数倍,Vsync的周期为Hsync的整数倍。
Clk、Hsync、Vsync的频率可以根据选定的分辨率以及刷新时间通过查找VGA的标准资料得到。
我们先设定一些基本的时间点名称,如下图所示。(当该图像表示行信号时,时间点名称前面加h,如hsync_end;表示场信号时,前面加v,如vsync_end)
好,接下来实践一下。
效果是在一个16:9的VGA显示器上显示一条绿色的纵条纹和一条红色的横条纹。如下图所示。
我们先用VGA线把FPGA开发板与显示器连接起来。其中VGA的R,G,B模拟信号脚仅提供0v、3.3v两种状态,所以能够显示的颜色数量为2*2*2=8(包括黑白两色)。
首先,打开上面给的网址,确定好我们需要的分辨率以及刷新频率。这里我用的显示器为16:9的,所以选了一个1368*768分辨率的。得到我们需要的数据如下:
先建立好工程,分配好引脚等。
module VGA( clock, disp_RGB, hsync, vsync, rst_n
); input clock; //系统输入时钟 50MHz output [2:0]disp_RGB; //VGA数据输出 output hsync; //VGA行同步信号 output vsync; //VGA场同步信号 input rst_n; //复位信号,低电平有效 reg [10:0] hcount; //VGA行扫描计数器,用于时间点匹配 reg [10:0] vcount; //VGA场扫描计数器,用于时间点匹配 reg [2:0] data; //VGA数据寄存器,后面会把该寄存器与disp_RGB输出相连起来,控制data就控制了颜色 reg [2:0] h_dat; //行颜色 reg [2:0] v_dat; //列颜色 //reg flag; wire hcount_ov; //行信号结束标志 wire vcount_ov; //场信号结束标志 wire dat_act; //是否应该输出RGB信号标志 wire vga_clk; //经过PLL倍频后的VGA时钟 |
这里我们利用FPGA自带的PLL来得到满足频率要求的脉冲信号。关于如何调用FPGA自带的PLL,不同的FPGA应该不一样,我这里使用的软件是Quartus II,调用的方法请参考:http://wenku.baidu.com/link?url=n7I9CN7AFEFDl9qqJKEvKAw7S_3_K4OrTie5jWnyY582CDYwtTh6jOQgPheM1BX8H9xjYJ-FcSYvRfF2adMnUR-mMPHdtQxXsYWx34l5ipO
我们调用的代码如下
//倍频器,50M-->85.86M(85.8333) wire locked; PLL_ctrl PLL_ctrl_inst ( .areset ( ~rst_n), .inclk0 ( clock ), .c0 ( vga_clk ), .locked ( locked ) ); |
好,现在我们顺利得到了需要的像素时钟信号。虽然实际得到的信号的频率与标准的频率有一定的偏差,但是在实际的使用中,显示器依然可以识别,估计显示器有一定的容错能力。
在实现行信号以及场信号之前,我们先来定义一些参数。这些参数的作用是设置好前面所提到的时间点的具体值。
//VGA行、场扫描时序参数表,选择分辨率为1368X768 60Hz parameter hSync_pls = 11'd144, //同步头脉冲长度 hBackporch_pls = 11'd216, //后沿长度 hVisible_pls = 11'd1368,//有效像素信号长度 hFrontporch_pls = 11'd72, //前沿长度
vSync_pls = 11'd3, vBackporch_pls = 11'd23, vVisible_pls = 11'd768, vFrontporch_pls = 11'd1;
parameter hsync_end = hSync_pls-1, //同步头脉冲结束时间点 hdat_begin = hsync_end+hBackporch_pls, //有效像素信号开始时间点 hdat_end = hdat_begin+hVisible_pls, //有效像素信号结束时间点 hpixel_end = hdat_end+hFrontporch_pls, //行信号结束时间点
vsync_end = vSync_pls-1, vdat_begin = vsync_end+vBackporch_pls, vdat_end = vdat_begin+vVisible_pls, vline_end = vdat_end+vFrontporch_pls; |
接下来我们实现行信号。
我们先来分析一下行信号的周期以及上升沿出现在什么位置。
假设我们来产生图一中的脉冲。那么根据我们查找的资料,行信号的周期为1800个像素脉冲周期,其中上升沿出现在hsync_end处。之前我们已经实现了像素时钟信号vga_clk,而行信号是以vga时钟为基本长度来产生的,因此:
//行扫描 always @(posedge vga_clk) begin if (hcount_ov) hcount <= 10'd0; else hcount <= hcount + 10'd1; end assign hcount_ov = (hcount == hpixel_end); //是否到达溢出点 assign hsync = (hcount > hsync_end); //通过对时序图进行分析,可以知道在一个周期内,hsync的上升沿发生在hsync_end处 |
接下来实现场信号。
场信号的周期为795个行信号周期,其中上升沿出现在vsync_end处。因此,我们写下如下代码。Ok,场信号完成。
//场扫描 always @(posedge vga_clk) begin if (hcount_ov) //行扫描完成 begin if (vcount_ov) vcount <= 10'd0; else vcount <= vcount + 10'd1; end end assign vcount_ov = (vcount == vline_end); //是否到达溢出点 assign vsync = (vcount > vsync_end); //通过对时序图进行分析,可以知道在一个周期内,vsync的上升沿发生在vsync_end处 |
至此我们已经把基本像素时钟、行信号、场信号都顺利实现了。
根据VGA的标准,在非有效像素信号区间时,vga不应该输出RGB信号。所以,需要添加以下逻辑。
//数据、同步信号输 assign dat_act = ((hcount >= hdat_begin) && (hcount < hdat_end)) && ((vcount >= vdat_begin) && (vcount < vdat_end)); //是否应该传输视频信号标志位 assign disp_RGB = (dat_act) ? data : 3'h00; // 应该输出时就输出数据,否则输出0 |
假如此时你把程序下载到FPGA上,然后运行。可以看到显示器识别到了VGA信号,但是是全黑。
假如你修改一下data寄存器的值就能够让显示器显示不同的颜色了。
最后,我们来实现横竖条纹。
always @(posedge vga_clk) begin data=h_dat|v_dat; //rgb的最终输出为行列颜色的或运算 end always @(posedge vga_clk) begin //产生起点为(0,500)高度为100,宽度为全屏的横条纹 if((vcount>(vdat_begin+500)) &&(vcount<(vdat_begin+500+100))) h_dat <= 3'h1; //红 else h_dat <= 3'h0; //黑 //产生起点为(900,0)高度为全屏,宽度为100的竖条纹 if((hcount>(hdat_begin+900)) &&(hcount<(hdat_begin+900+100))) v_dat <= 3'h2; //绿 else v_dat <= 3'h0; //黑 end |
产生横竖条纹的原理无非是把每一行或每一列的某些区间的颜色填充一种颜色,而其他区域填充另一种颜色,然后表现在二维上就是条纹了。
附上代码文件。
文章评论(0条评论)
登录后参与讨论