尝试用Verilog驱动VGA

本文有些地方是从
http://www.eepw.com.cn/article/275552.htm
或http://www.cnblogs.com/spartan/archive/2011/08/16/2140546.html
直接搬运过来的,这些博客都写的很详细,可以去看下。

VGA原理

VGA驱动显示器用的是扫描的方式,一般是逐行扫描
逐行扫描是扫描从屏幕左上角一点开始,从左像右逐点扫描,每扫描完一行,电子束回到屏幕的左边下一行的起始位置,在这期间,CRT对电子束进行消隐,每行结束时,用行同步信号进行同步
当扫描完所有的行,形成一帧后,用场同步信号进行场同步,并使扫描回到屏幕左上方,同时进行场消隐,开始下一帧。
以下是vs 和 hs的时序简图。
这里写图片描述
可以看出,VGA一直在扫描,每一场的扫描包括了若干行扫描,如此循环。

综上我们可以知道,一个标准的VGA接口应该有以下的端口:

红色信号 r
绿色信号 g
蓝色信号 b
行同步信号 hs
场同步信号 vs
以及很多的地屏蔽

尝试用Verilog驱动VGA_第1张图片

需要注意的是,r g b信号都是模拟信号,而 hs 和 vs 则是数字信号
一般而言如果要用数字电路驱动rgb模拟信号端口的话,可以有如下几种办法:

  • 直接用0/1数字信号驱动
    尝试用Verilog驱动VGA_第2张图片
    因为虽说rgb都是模拟信号驱动的,但是数字信号也是有电压的,可以看做是一个要么恒定不变,要么就跳变的模拟信号。我用的开发板就是这样做的,因为开发板的核心甚至不是FPGA,只是个CPLD,不过胜在不要钱→_→,直接找师兄要的。
    这么做的好处是,不需要在数字电路输出端和VGA接口之间加入模拟电路;
    坏处则是,这样的输出只能有8种(2种电压,3个端口,共8个组合)。

  • 在数字信号输出和VGA接口之间加入一段简单的DA电路
    尝试用Verilog驱动VGA_第3张图片
    利用不同的阻值电阻分压,把数字信号转化为模拟信号,然后再输入到VGA的端口。上图是Digilent公司的Basys3 FPGA开发板原理图的VGA部分。
    这样做的好处是,可以用多个数字信号端口输出,组合出更多的模拟信号,增加可输出的颜色种类;
    而这样做的坏处是,……反正除了要多搭几个电阻之外我想不到有什么麻烦的地方。

说完颜色信号,就该是同步信号了。

完成一行扫描的时间称为水平扫描时间,其倒数称为行频率;完成一帧(整屏)扫描的时间称为垂直扫描时间,其倒数称为场频率,即刷新一屏的频率,常见的有60Hz,75Hz等等。标准的VGA显示的场频60Hz,行频31.5KHz。
  行场消隐信号:是针对老式显像管的成像扫描电路而言的。电子枪所发出的电子束从屏幕的左上角开始向右扫描,一行扫完需将电子束从右边移回到左边以便扫描第二行。在移动期间就必须有一个信号加到电路上,使得电子束不能发出。不然这个回扫线会破坏屏幕图像的。这个阻止回扫线产生的信号就叫作消隐信号,场信号的消隐也是一个道理。
  显示带宽:带宽指的显示器可以处理的频率范围。如果是60Hz刷新频率的VGA,其带宽达640x480x60=18.4MHz,70Hz的刷新频率1024x768分辨率的SVGA,其带宽达1024x768x70=55.1MHz。
  时钟频率:[email protected](60Hz)为例,每场对应525个行周期(525=10+2+480+33),其中480为显示行。每场有场同步信号,该脉冲宽度为2个行周期的负脉冲,每显示行包括800点时钟,其中640点为有效显示区,每一行有一个行同步信号,该脉冲宽度为96个点时钟。由此可知:行频为525*59.94=31469Hz,需要点时钟频率:525*800*59.94约25MHz.

所以并不是分辨率多高,我们就需要扫描多少行/场;实际上我们需要扫描的比分辨率数值大,示意图见下图:
尝试用Verilog驱动VGA_第4张图片

最坑的是。。。我并没有找到消隐区大小跟分辨率之间有什么数学关系,虽然每个分辨率都有对应唯一的消隐区大小,但是总觉得很坑。。。如果要改变分辨率的话怎么办?

Verilog代码

第一次接触VGA,并没有任何头绪,于是先跑板子的例程,结果发现我那1440*900的联想屏幕并不能显示,倒是感应到了有数据,证明确实有输出,但是一直提示“输入信号超出范围”。结果后来换了个1024*768的BENQ的小屏幕,虽然还是提示“输入信号超出范围”,但是能看到提示框后面显示出了例程输出的色块,但是输出不全。现在想来应该也是消隐区的问题,例程代码并不能看懂,但是貌似并没有考虑消隐的问题,一直在做分频处理,分出好一些时钟驱动同步端口和数据端口。一开始我以为是板子上的12MHz晶振跟例程里写的50MHz频率不符,后来发现板子自带PLL,输入的原始时钟确实是50MHz,反正一直调参数,调了一整天,调得也很盲目,现在想想自己简直就是个傻缺= =||
后来上github找了很多人的开源代码,好不容易终于找到了一个能用的,readme里说这个分辨率是800*600,但是在1440*900屏下也没有出现问题,可能是屏幕能自动适应分辨率吧。代码如下:
效果是在屏幕上显示8个宽度相等的竖条色块,颜色各不相同,反正直接驱动能输出的也就8种颜色。

module Main(
    CLK,
    VGA_HS,
    VGA_VS,
    VGA_R,
    VGA_G,
    VGA_B
    );

    input CLK;
    output VGA_HS,VGA_VS, VGA_R, VGA_G, VGA_B;

 reg[10:0] x_counter;
 reg[10:0] y_counter;

 reg [3:1] GRBX;

    initial begin
        x_counter = 0;
        y_counter = 0;
    end

// Always block to drive drawing, {front|back}-doors, and syncs.
    always @(posedge CLK) begin
        if(x_counter == 1055)
        begin
            x_counter = 0;

            if(y_counter == 627)
                y_counter = 0;
            else
                y_counter = y_counter + 1;
        end
        else
            x_counter = x_counter + 1;

    end

    always @(x_counter or y_counter)
    begin
        if (x_counter<100)  GRBX<=3'b111;
            else if (x_counter<200)   GRBX<=3'b110;
            else if (x_counter<300)   GRBX<=3'b101;
            else if (x_counter<400)  GRBX<=3'b100;
            else if (x_counter<500)   GRBX<=3'b011;
            else if (x_counter<600)   GRBX<=3'b010;
            else if (x_counter<700)   GRBX<=3'b001;
            else  GRBX<=3'b000 ; 
        end

    assign  VGA_R=GRBX[2];
    assign  VGA_G=GRBX[3];
    assign  VGA_B=GRBX[1];

    assign VGA_HS = x_counter > 839 && x_counter < 968;
    assign VGA_VS = y_counter > 600 && y_counter < 605;

endmodule

这是把https://github.com/shiirish/FPGA—VGA里面的main.v改了一下改出来的。令我非常惊喜的是,这个代码真的能动,而且并没有严格要求时钟是多少,也没有试图分频分到60Hz,这里就是考虑了消隐的问题。
后来本来还想用Matlab把一些图片的RGB二值化,生成一系列RGB值放进IP核的ROM里,用 x_counter 和 y_counter 作为地址,输出数据到屏幕,这样就可以显示出图片。
然而。。。CPLD并不能用IP核 T_T,就连ISE的core generator按钮都是灰色的。。。然后心血来潮,用Matlab写了个脚本,生成了四万多行的if。。。强行生成个ROM,就为了显示一张静态图= =||
然而。。。直到现在都还在Synthesize。。。诶都是泪啊。。。

你可能感兴趣的:(尝试用Verilog驱动VGA)