VGA驱动实验作为FPGA的入门实验,多少也有些挑战。VGA是什么?估计有接触过电脑的朋友多少也是知道。粗略认识VGA可分为,VGA硬件接口,和VGA协议。VGA硬件接口没有什么好学的,VGA时序才是正点。
VGA协议,主要有5个输入信号,亦是 HSYNC Signal, VSYNC Signal, RGB Signal。说简单一点,HSYNC Signal 是“列同步信号”, VSYNC Signal是“行同步信号”,RGB Signal是“红色-绿色-蓝色 颜色信号”。
VGA的扫描是固定的。一帧的屏幕是由“m行扫描”和“n列填充”组成。假设以 800 x 600 x 60Hz 为例的显示标准 ( 800宽 x 600 高 x 60Hz ), 那么宏观上它有 600 行 和 800列为一行。
扫描的次序如下:
扫描第0行 - 在第0行,列填充0~799。
扫描第1行 - 在第1行,列填充0 ~ 799。
扫描第2行 - 在第2行,列填充0 ~ 799。
扫描第m行 - 在第m行,列填充0 ~ 799。
扫描第598行 - 在第598行,列填充0 ~ 799
直到描第599行- 在第599行,列填充0 ~ 799
宏观上,一帧屏幕的显示是由600行从上至下扫描,800列从左至右填充(这也是为什么每当电脑几乎要当机的时候,视屏显示从上之下的延迟扫描)然而微观上,一行的行扫描是由超过800个列填充完成,一帧图像超过600行扫描。实际上是VGA的时序在作怪。
上图是有关 HSYNC 和 VSYCNC 的时序图,以 800 x 600 x 60Hz 为例,信息如下:
800 x 600 x 60Hz | a段 | b段 | c段 | d段 | e段-总共n个列像素 |
HSYNC Signal 列像素 | 128 | 88 | 800 | 40 | 1056 |
800 x 600 x 60Hz | o段 | p段 | q段 | r段 | s段-总共n个行像素 |
VSYNC Signal 行像素 | 4 | 23 | 600 | 1 | 628 |
HSYNC Signal 是用来控制“列填充”, 而一个HSYNC Signal 可以分为 4个段,也就是 a (同步段) , b(后廊段),c(激活段),d(前廊段)。HSYNC Signal 的a 是拉低的128 个列像素 ,b是拉高的88个列像素,至于c 是拉高的 800 个列像素,而最后的 d 是拉高的 40 个列像素。 一列总共有1056 个列像素。
VSYNC Signal 是用来控制“行扫描”。而一个 VSYNC Signal 同样可以分为 4 个段, 也是 o (同步段) , p(后廊段),q(激活段),r(前廊段)。VSYNC Signal 的o 是拉低的4个行像素 ,p是拉高的23 个行像素,至于q 是拉高的 600 个行像素,而最后的 r 是拉高的 1 个行像素。 一行总共有628 个行像素。
“一个行像素”是以“列像素为单位”来定义(以 800 x 600 x 60Hz 为例)如下所示 :
1个行像素 = 1056个列像素。
而“一个列像素”是以“时间位单位”来定义(以 800 x 600 x 60Hz 为例),如下所示:
1个列像素 = 25 ns。
1个行像素 = 1056个列像素 = 1056 x 25ns = 2.64us。
(以 800 x 600 x 60Hz 为例)上述内容读者可以发现一个事实,要完成一行的扫描,需要 1056 个列像素,也就是说需要 1056 x 25ns的时间。如果要完成所有行的扫描的话,需要628 x 1056 x 25ns 的时间。很遗憾的是,不是所有时间都用来显示图片,有一部分的时间是用来同步操作。
在上图表示了,HSYNC Signal 只有在的C段 (红色部分)和VSYNC Signal 的 q 段(黄色部分)的激活段,数据的输入才有效。换句话说,显示图片是发生在交叉(橘色部分)的“有效区域”下。
交叉部分的表达式可以如此描述:
列像素 > 216 && 列像素 < 1017 && 行像素 > 27 && 行像素 < 627。
接下来我们以简单的实验来说明VGA时序的规则。
vga_module.v 是组合模块,而且它包含了 pll_module.v , sync_module.v 和vga_control_module.v 。我们先说说 pll_module.v 的目的:
如果是要驱动VGA 为800 x 600 x 60Hz 的显示标准,我们知道这个显示标准需要的 “最小单位”也就是“列像素”所占用的时间周期--25ns。换句话说,至少需要 40MHz 的时钟频率,而黑金搭载的时钟为20Mhz,所以需要用到 pll_module.v 对黑金的源时钟进行翻两倍。
20Mhz = 50ns
40Mhz = 25ns
(pll是FPGA的重要硬件资源,pll主要的功能是对源时钟编程,翻频,分频等 )
而 sync_module.v 是对HSYNC Signal 和 VSYNC Signal 控制的“功能模块”。他扮演了“VGA驱动的核心”的角色。除此之外,sync_module.v还输出当前的 x地址(Column_Addr_Sig),y地址 ( Row_Addr_Sig ) 和有效区域信号(Ready_Sig)。
vga_control_module.v 是“图像控制的核心”控制模块,有关 Red, Green, Blue 信号的控制,x地址和y地址的控制,图像显示控制,帧控制,完全都是在这个模块中完成操作。
说简单点,sync_module.v 的工作是“显示标准控制”,然而 vga_control_module.v的工作是 “图像显示控制”。
由于时钟频率已经由pll_module.v 进行翻倍,由本来的20Mhz 变成 40Mhz ,所以时钟周期完全符合 800 x 600 x 60Hz 的显示标准。也就是说,时间周期达到一个列像素的时间,亦即 25ns。
第18行定义的 Count_H 是对“列像素”计数的寄存器。第20~26行表示了“列像素”每25ns就会累计,直到Count_H 达到1056值。
第30行定义了 Count_V,它是针对“行像素”计数的寄存器。而第32~38行描述了,“1个行像素等于 1056个列像素(以 800 x 600 x 60Hz 为例)” 因为每 1056个列像素,Count_V就会递增(第37行),而第35行表示了Count_V的最大数,亦即628。
在42~51行,表示了“有效区域”,也就是说数据输入有效的条件。isReady寄存器(42行)定义了“有效区域”的标志。
第55行表示了 VSYNC Signal 的 o 段。VSYNC Signal 在 o 段是保持低电平,当o 段之后就会拉高输出,而且o段保持低电平的时间是4个行像素。在56行是针对,HSYNC Signal 的 a 段。我们知道开始的 128 个列像素都是拉低 HSYNC Signal,当128个列像素之后就拉高 HSYNC Signal。第57行是“有效区域”的输出信号。
我们都知道屏幕是以“像素”计算。为了要很好的控制图片显示在屏幕的位置,sync_module.v 有义务输出当前的 x 地址(Column_Addr_Sig)和当前的 y 地址(Row_Addr_Sig)。x地址的计数是发生在 128个Count_H之后(62行),然而y地址计数是 27 个 Count_V之后(63行)开始计数。
Count_H的128 = HSYNC Signal 的 a段 + b段。
Count_V的27 = VSYNC Signal 的 o 段 + p段。
isReady 有效区域是 = HSYNC Signal 的 C段 * VSYNC Signal 的 q段。
Column_Addr_Sig = HSYNC Signal 的 b 段之后 800 个列像素。
Row_Addr_Sig = VSYNC Signal 的 p 段之后的 600 个行像素。
这个vga_control_module.v 比较简单,在第18行定义了一个名为 isRectangle 的标志寄存器。在第23行,表示了在 x地址之后,和在y地址100以内就设置isRectangle寄存器为逻辑1。第31~33行,所有颜色信号被赋予同样的表达式,这表示了在 800 x 100 的区域内显示白色的矩形。Ready_Sig 有时候可以作为保险。
vga_control_module.v 作为“图像显示控制”,在这个实验之中它显示了 高100 x 宽800的矩形。
这个实验主要是驱动 800 x 600 x 60Hz 显示标准,同时也描述了 sync_module.v在 vga_module.v 组合模块中扮演的角色。sync_module.v 直接对 HSYNC Signal 和 VSYNC Signal 控制。同时间,sync_module.v 也把当前的 x 地址和 y地址告诉 vga_control_module.v 好让这个模块作出相关的显示控制。
除了 800 x 600 x 60Hz 的显示标准以外,还有其他的显示标准可以参考。
(常用时钟 Mhz 的时间周期,亦即是1个列像素的时间)从上图我们可以得知 800 x 600 x 60Hz 显示标准的使用时钟是 40Mhz 。这也是实验九之一将20Mhz时钟翻倍至 40Mhz 的原因,因为800 x 600 x 60Hz 显示标准的1个列像素需要 25ns。
完成的扩展图:
实验九之一实现了简单的VGA驱动,同时实验也告诉我们配置 sync_module.v 等于是配置 VGA 的显示标准。 但是这个实验有一个严冲的弱点,因为关于源时钟20Mhz翻倍后是40Mhz , 悄悄好达到 1个列像素的时间单位 25ns。
如果翻倍的时间不是40Mhz 而是其他的什么,那么要如何是好!?
假设现在我们向源时钟 20Mhz 翻倍到 100Mhz,然而 640 x 480 x 60Hz 的显示标准需要的时钟频率是 25.175Mhz 也就是说1个列像素需要的时间是 39.7 ns。那么100Mhz 如何向下兼容 640 x 480 x 60Hz 的显示标准。
(聪明的朋友已经估计到解决的方法了吧 (∩_∩)!?)
vga_module.v 组合模块同样是没有任何改变。有改变的只是 pll_module.v 将源时钟20Mhz翻倍至 100Mhz ,和 sync_module.v 显示要求改变致 640 x 480 x 60Hz。
其实向下兼容的概念,也就是模仿“显示标准的主要时钟频率”。换一句说,如果 640 x 480 x 60Hz 的主要时钟频率是 25.175Mhz的话,亦即 1个列像素是 39.7ns,那么可以用更高的源时钟是求得 39.7ns的定时。说得简单一点就是配置一个定时器。
假设源时钟是100Mhz,定时器的计数 x 是 :
x = 39.7 ns / ( 1 / 100Mhz )
= 3.97
= 4
第18行定义了 40ns 的常量。而第22~30行是用于 40ns的定时。那样一来,我们不用同样的时钟频率也能向下驱动不同的显示标准(在41行的定时作用)。
除此之外,修改的地方还不少, 主要是关于:
最大个列像素(第39行),最大个行像素(第51行),一个行像素所占有的n个列像素(53行),有效区域条件(第63~64行),HSYNC Signal 的a段拉低时间(71行),VSYNC Signal 的 o 段拉低时间(72行),还有x地址和y地址的输出(第78~79行)。
vga_control_module.v 和实验九之一基本一样,没有任何改变,这里就不显示了。
vga_module.v 组合模块也就是修改了 CLK_100Mhz 的命名而已( 第18,23,34,47行)。其余的和实验九之一一样。
不是所有更高时钟的频率就能向下兼容,如实验九之二一样,100Mhz 的频率,为了兼容25.175Mhz 至少需要超过2被时间的区别。如果时钟频率是30Mhz,或者40Mhz 估计向下兼容的效果不怎么好 。
完成后扩展图:
无论是是实验九之一或者实验九之二,主要是强调了 1个列像素在驱动 VGA时所扮演的角色。因为1个列像素是最小的单位。只要掌握了 1个列像素,1个行像素,有效区域,x地址和y地址,HSYNC Signal 和 VSYNC Signal 时序等等概念以后,VGA的时自然而然会明白。
点阵这东西,一直以来都是我的大爱呀,我所有6成的实验都是与点阵有关。我相信有学过LED点阵的同学应该对点阵有所概念。点阵嘛~就是在 m x n 的面积中,实现
亮(逻辑1)和 灭(逻辑0)效果来产生一副图像。典型的实例如:交通灯的小红人。但是在VGA上实现点阵效果需要下一点功夫。因为VGA时序的关系,点阵都是1列的显示。不同于LED点阵,用户可以自定义点阵的位长度,扫描方式。
开始实验之前,我们再来复习一下VGA扫描的次序(以 800 x 600 x 60Hz 为例):
一行的扫描是由1056个列像素填充,换句话说就是1个行像素等于1056个列像素。然而图片显示的“有效区域”是在 HSYNC Signal 的 c 段和 VSYNC SIgnal 的 q段的交叉部分。
在点阵方面,RGB信号全为逻辑1代表“亮”,相反的RGB信号全为逻辑0代表“灭”。
上面是一张64 x 64 像素的比卡丘(我最喜欢了)。点阵编码的方式是“逐行式”+“高位在前”。“逐行式”也就是逐行扫描的意思。“高位在前”的表示如下:
假设有一行拥有16个列像素,那么编码会是如此:0xf738。
至于为什么要采用“高位在前”的编码方式?原因在于“符合人类思维”和“容易理解”。
要显示一副 64 x 64 像素点阵的比卡丘图像,所占用的空间是 64 Bit(宽)* 64Bit(高)* 1Bit(颜色)亦即 4096 Bit 空间容量。所以呀,要存放这一副图像, 64 bit x 64 words 的 rom 就行了。 因为这样的配置也使得设计简单化,毕竟1 words 就代表一行,1 Bit 就代表一列。假设我要读取第 32 行的第6列的值。我只要向rom发送32的地址,然后取出rom的值的第六位即可。
下面是 64 x 64 比卡丘图像的点阵信息
行
0 1 2 3
4 5 6 7
8 9 10 11
12 13 14 15
16 17 18 19
20 21 22 23
24 25 26 27
28 29 30 31 |
64位点阵信息
0000000000000000 0000000000000000 0000000000000000 0000000000000000
0000000000000000 0000000000000000 0000000000000000 0000000000000000
0000180000000000 0000380000000000 00003C0000000000 00007C0000000000
0000FC0000070000 0000FE00003F0000 0000E60001FF0000 0001860007FF0000
0001C6000C3E0000 000101F8701C0000 00018FFFE0380000 0001840780300000
0001600000607800 0001000000C0C800 0003000001C0CF00 0003003803010780
000F004C160300C0 000D807C1C0300E0 000F007C0C060060 001720380C060060
001A6001060600C0 00180003820C0180 001CFFC7C30C0300 001C7707C1B80E00 |
行
32 33 34 35
36 37 38 39
40 41 42 43
44 45 46 47
48 49 50 51
52 53 54 55
56 57 58 59
60 61 62 63
|
64位点阵信息
001C3F07C1181800 00BC1E01F1B01800 00EC0003B1D83000 018200071BCC3C00
0181000219C61600 00C1000031660E00 0060000020371C00 0028000000AC3000
003C0001E1F0E000 0006001B5BFCC000 0003001F03FCE000 0003803581FC6000
00010031807FC000 00038030E07F8000 0001000840F80000 0001C008C0F00000
0000C018C3C00000 0000C01D83000000 0000600E8C000000 000034066C000000
00001E03F0000000 0000070380000000 000001CF00000000 000000E700000000
0000006300000000 0000007E00000000 0000000000000000 0000000000000000
0000000000000000 0000000000000000 0000000000000000 0000000000000000 |
图上的vga_module.v 是基于实验九之一的vga_module.v。主要是对vga_control_module进行扩展的修改,支持从 rom_module.v 读取数据。除此之外 vga_control_module.v 还控制对图片的输出。具体的操作直接看代码吧。
基本上和实验九之一一模一样。显示标准是 800 x 600 x 60Hz。
在第5行,第14~15行,是针对rom进行修改,主要是加了 Rom_Data 和 Rom_Addr。
第23~29行表示了m寄存器是读取 当前的y地址(Row_Addr_Signal),亦即当前的行。
而第33~39行的n寄存器则是读取当前的x地址(Column_Addr_Sig),也就是当前的列。
在43行中,m寄存器作为“行寻址”将值赋予Rom_Addr。最后45~47行,n寄存器作为“列寻址”,对rom读取的值 Rom_Data,从最高位到最低位判断目前的第m行第n列是“点亮”还是“点灭”。
假设一个情况,我要显示图片第0行。那么图片第0行的值,当然是存放在 rom 的0地址(详细的rom配置和创建过程请参考实验配置笔记)。如果我要在屏幕的“第0 y地址”和“第0 x地址”显示开始显示图片的话,我们可以利用 “当前y地址(Row_Addr_Sig)”和 “当前x地址(Column_Addr_Sig)”。因为告诉了我们,当前的显示进度。
当“当前y地址”是0时,直接利用“当前y地址”对rom进行“行寻址”从rom读取图片第0行的信息,然后再利“当前x地址”对读出图片的第0行信息进行“列寻址”,从高位到低位进行判断,在“当前显示的 x y 地址”是否是“点亮”还是点“点灭”。
如:图片第0行的信息全部都是逻辑0, 所以在“当前y地址”0,和“当前x地址”从0~63都是“点灭”的操作。上述的过程会一直重复直到 64行的图片信息扫描完毕。
第43~54行主要是多了rom的实例化,然后在第54~72行 vga_control_module.v 实例化中,对rom扩展 Rom_Data 和 Rom_Addr 的输入输出口。
注意:
第45行定义了位宽为64位的Rom_Data的wire。然而第56行定义了位宽为6的Rom_Addr的wire。这表示了rom_module.v 的1字数据是64位宽,Rom_Addr
位宽为6表示可以支持到64个words的寻址。
实验九之三主要是对“点阵”的概念叙述一番。此外还说明了“屏幕”的x地址和y地址在图片显示上的作用。
如果要将一张图片显示在屏幕上任一一个位置,需要好好利用从 sync_module.v 输出的 Column_Addr_Sig和 Row_Addr_Sig信号,因为这两个信号表示了当前显示的x地址和y地址(当前显示m行n列)。如一张只有 64 x 64 像素的比卡丘图片,在实验九之三中,将显示起始地址为(x,y)( 0,0 )。
此外实验九之三还很有效的利用 Row_Addr_Sig 对 rom_module.v 进行“行寻址”, 然后利用 Column_Addr_Sig 对执行“列寻址”的操作。
完成后的扩展图:
实验九之三有一点使许多新手容易产生问题的,就是如何为一张图片执行“点阵编码“?
说一点题外话,在LED点阵中,因为LED点阵的扫描次序完全是用户自定义的,所以“点阵”编码的方式很多样化。但是VGA的扫描次序都是固定,故为了很好降低设计的猥琐度,编码的方式尽量偏向“容易理解”。
如VGA 扫描方式是逐行对列填充,也就是说列填充是从左至右。“高位在前”的“编码方式”既然而然很符合VGA“从左至右的列填充”的扫描次序。
还有一点问题点就是,关于“用什么方式去储存图片”?如果你的图片是16x16 像素,那么我很建议你在模块里定义图片每一行信息的常量。如果图片是超过这样 16 x 16 像素,呵呵!还是建立一个rom来存放图片信息。不要像C语言那样,建立一个库文件,然后调用。Verilog HDL语言不适合这一套。
将一张有颜色信息的图片现在屏幕上,传统通常都会用“颜色编码”的方式。假设一个例子,如果有一个VGA驱动电路可以支持8中颜色,也就是说每一列“点阵”都使用3Bit 来代表。颜色支持如下:
颜色 | Red Signal | Green Signal | Blue Signal |
黑色 | 0 | 0 | 0 |
蓝色 | 0 | 0 | 1 |
绿色 | 0 | 1 | 0 |
浅蓝色 | 0 | 1 | 1 |
红色 | 1 | 0 | 0 |
紫色 | 1 | 0 | 1 |
黄色 | 1 | 1 | 0 |
白色 | 1 | 1 | 1 |
假设我有一行4个列像素的信息(4个列点阵):
|
|
|
|
那么一行的长度需要12位,编码结果是:100 101 111 000。对于“颜色编码”的方式,对于硬件来说,无论是储存编码信息,还是处理编码信息,实在是有点过分。所以不怎么推荐。
在这里我们还用另一种方式可以显示颜色图片,就是“图层概念”。
举一个例子,假设我要显示 64 x 64 只带有颜色的比卡丘(爱!爱!):
3色层集合 | 红色层 | 绿色层 | 蓝色层 |
基本上我会将该图片分为“基本3色的图层”,亦即“红色层”,“绿色层”和“蓝色层”。
颜色的支持如下:
颜色 | Red Signal | Green Signal | Blue Signal |
红色 | 1 | 0 | 0 |
黄色 | 1 | 1 | 0 |
黑色 | 1 | 1 | 1 |
白色 | 0 | 0 | 0 |
又假设我有一行4个列像素的信息(4个列点阵):
|
|
|
|
颜色层 | 第四位 | 第三位 | 第二位 | 第一位 |
红色层 | 1 | 1 | 0 | 1 |
绿色层 | 0 | 1 | 0 | 1 |
蓝色层 | 0 | 0 | 0 | 1 |
看简单点,红色的输出,只要红色层即可。黄色的输出需要红色层和绿色层相叠加。白色也等于没有颜色,这时候不需要任何层的叠加。则黑色需要3个层叠加才能显示。
红色层:4'b1101 绿色层:4'b0101 蓝色层:4'b0001 | 在“显示控制”的时候,只要将相同的行,相同的列,不同的层信息发送到相关的输出信号。如:红色层 = Red Signal, 绿色层 = Green Signal, 蓝色层 = Blue Signal. |
实验九之四就是要建立如上图的组合模块。从实验九之三的基础上,添加了3个rom_module 亦即 red_rom_module.v,green_rom_module.v 和 blue_rom_module.v。每一个rom_module.v 如命名般分别储存“红色层”,“绿色层”,和“蓝色层”的点阵信息。
最后会经过vga_control_module.v 巧妙的“叠加”操作,就会显示一张带有颜色的图片。
大致上和实验九之三一样。
在第5行定义了 Red_Rom_Data, Green_Rom_Data, Blue_Rom_Data。这些分别是3个不rom的输入口,均为64位宽(第14~16行)。第26~46行,基本上是读取当前的 m 行(y地址-Row_Addr_Sig)和当前的 n列(x地址-Column_Addr_Sig)。
在47行定义了 Rom_Addr 输出值完全是基于 m(当前的y地址,当前显示的行)。
然而有一点不一样的就是发生在第50~53行,各个RGB Signal 都有各自的输出值。如:Red_Sig 它的输出完全是根据Red_Rom_Data的值,其余的也是类似。
至于n(当前的x地址,当前显示的列),是用于“列寻址”,然后判断该点阵是“点亮”还是“点灭”。
实验九之四的 vga_module.v 比较不同。在43~76行中实例了3个rom_module.v 分别是 red_rom_module.v(47~52行),green_rom_module.v(58~63行),blue_rom_module.v(69~74行)。然而各个rom_module.v 都有自己的输出连线Rom_Data(45行,56行,67行),这些连线的终点是vga_control_module.v(87~89行)。vga_control_module.v输出的 Rom_Addr(90行),3个rom_module.v同时公有的(72行,61行,50行)。
实验九之四主要是基于“图层概念”去实现颜色图像的显示。在试验中,创建了3个 64 x 64 的rom模块,每一个rom模块分别针对每一个层去储存信息。所谓的“叠加操作”也不过是微秒的对 Red_Sig , Green_Sig, Blue_Sig 信号发送本身“同颜色的层”而已。所以vga_control_module.v设计基本上是不难。
说白了 vga_control_module.v 和在实验九之三的操作一样,同样是处理点阵信息,不过不同的是,不是处理1副图片的点阵信息,而是3副图片的点阵信息。
完成后的扩展图:
关于“图层”的概念,在现实中如同不同颜色的半透明纸,叠加起来,呈现出不同颜色的效果。
关联到“帧”,大家都会想起什么?没错就是动画。但是笔者在认识众多的VGA实验当中,大家只是单纯的注重VGA驱动的方法,而忽略了帧。“帧”对于VGA来说是一个重要的参数之一。屏幕显示“逼不逼真?”,“有没有拖尾现象?”,这些都关系到“帧”。
在一般的VGA显示标准中 如:800 x 600 x 60Hz (一帧等于一副图像)。
最后一字的“60Hz”,表示该显示标准,在1秒内可以显示60帧图像。
在这里我们先玩一些计算游戏:
以800 x 600 x 60Hz 为例。我们知道1个行像素等于1056个列像素,而一个列像素需要 25ns。换一句话说,一“帧”的图像 需要 628 * 1056 * 25ns 的时间。那么一秒内到底能显示多少“帧”图片?
1帧图像所需要的时间 = 628 * 1056 * 25ns
= 0.0165792s
1秒内可以显示n帧图片 = 1 / 0.0165792
= 60.3
= 60
再尝试!以640 x 480 x 75Hz为例。1个列像素需要 31.74ns。
1帧图像所需要的时间 = 500 * 840 * ( 1 / 31.5Mhz )
= 0.0133333333333333
1秒内可以显示n帧图片 = 1 / 0.0133333333333333
= 75
再再尝试!以800 x 600 x 75Hz为例。1个列像素需要 20.2ns。
1帧图像所需要的时间 = 625 * 1056 * ( 1 / 49.5Mhz )
= 0.0133333333333333
1秒内可以显示n帧图片 = 1 / 0.0133333333333333
= 75
我们计算帧不是要研究什么“xx标准有多少xx帧”,我们的目的主要是实现“一副图像可以逗留帧数”,然而实现“动画效果”。
有一点请注意,这里所说的“动画”,不是读者在电视机上看的动画,那些动画有业界的标准(好像是1秒24帧图像)。我们说谈的是“动画效果”,因为“帧”可以影响“动画效果”的“切换速度”。
实验九之五要显示的“动画”很简单,就是总所皆知的交通灯“小绿人”。(偷偷告诉你噢,小绿人实验在我学习单片机期间,研究过很长一段时间)。完整的“小绿人”太多幅图像了(18副),这里我摘掉只剩前六副图像而已。
1 | 2 | 3
|
4
| 5 | 6
|
每一副小绿人的图像都是 16 x 16 x 1Bit,点阵信息如下:
行 | 第一副 | 第二幅 | 第三幅 | 第四幅 | 第五副 | 第六幅 |
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | 0000 1800 3C00 1800 0C00 0F00 0E80 1E40 2340 0300 0280 0440 0820 3010 0010 0000 | 0000 1800 3C00 1800 0C00 0F80 0EC0 1E60 3340 0300 0280 0460 0C20 3410 3830 0000 | 0000 1800 3C00 1800 0C00 0F80 0E40 1E20 0300 0300 0280 0460 0410 0410 3820 0000 | 0000 1800 3C00 1800 0C00 0F80 0C40 0E20 1320 0300 0280 04E0 0420 02C0 0E00 0000 | 0000 1800 3C00 1800 0C00 0F00 0D80 0EC0 17C0 0300 0280 04F0 0470 0670 0E10 0600 | 0000 1800 3C00 1800 0C00 0E00 0D00 0E80 0680 0300 0700 0900 08C0 0C20 0420 0C00 |
以上的点阵编码方式都是“高位在前”+“逐行扫描”,基本上和实验九之三一样。
现在的问题是:“如何将这些点阵信息安排储存入rom?”
很简单,我们只要建立 16 bit x 96 words 的rom就可以了。当然这样举动是有原因的。小绿人的一副图片是 16 x 16 的大小,一行有16个列像素,而且有16行。也就是说,如果建立16Bits的位宽,一副图片需要16 words ,而且这里有6 副图片, 6 x 16 = 96 需要 96 words。
每一副图像相差有 16个words,而这个“相差数目”我们称为“帧偏移量”。所以呀,每当想更换一副图像,都需要加上“帧偏移量”。
实验九之五的组合模块如上。在“同步模块”sync_module.v中多了一个输出 “Frame_Sig”信号,该信号主要是,每完成一帧图像的显示,就产生一个时间的高脉冲来通知 vga_control_module.v。然而,vga_control_module.v 会依 Frame_Sig 产生的高脉冲来计数“帧”。
注意:
要知道“一帧图像是否已经显示完?”,在 sync_module.v之中就要好好的利用“行像素”计数器 Count_V。以 800 x 600 x 60hz 为例,当行计数器 Count_V 计数到 628
的时候,亦即一帧图像已经显示完毕。
第4~13行定义了 Frame_Sig ,然而 Frame_Sig 会产生高脉冲当1帧图片显示结束(59行)。我们知道一个事实,以800 x 600 x 60Hz,当行像素计数器 Count_V 计数到628,的时候表示,最后一行已经扫描完毕。
第4~11行定义了 Frame_Sig 的入口。此外,为了能更好的显示“动画效果”,图像显,y地址从0(29行),x地址从2(第39行)开始。在46行,定义了 FRAME的常量。然而在50~58行,是FRAME的计数器。每当Frame_Sig 接收到一个高脉冲(57行),该计数器就递增,直到达到与常量FRAME相同的值(55行)就会赋值为0。
第60~104行是“切换图像”的操作,每当一副图像显示10帧(根据FRAME常量),就会跟换换下一副图像。而更换图像的操作就是为rAddr添加“帧偏移量”16。为了提升“执行效率”和“解读性”,人为的添加“图像”的起始地址。
根据green_rom_module.v 的配置,我们知道:
第一幅图像的起始地址是00,而行寻址是00~15。
第二幅图像的起始地址是16,而行寻址是16~31。
第三幅图像的起始地址是32,而行寻址是32~47。
第四幅图像的起始地址是48,而行寻址是48~63。
第五幅图像的起始地址是64,而行寻址是64~79。
第六幅图像的起始地址是80,而行寻址是80~95。
在第106行,Rom_Addr 输出的值是 当前“第n图像起始地址”+“m行寻址”。
假设一个情况:在模块开始运作的时候,Addr的值是0也就是说第一幅图片被显示。
第一幅图片会以点阵的方式显示。当第一章图片重复显示10次的时候(10帧),Addr的值会赋予16,也就是第二副图像被显示,而且同样以点阵的方式显示。直到显示10帧以后,下一张图像会被选中。
以上的顺序步骤会一直执行,直到显示完第6副图像,然后Addr会再一次赋予0值。
也就是说,“动画”会从新开始显示,“动画”会重复到永远 ...
最后的源码就是vga_module.v 这个组合模块。其实也没有什么特别的,只要好好的浏览“图形”,既然而然很容易明白。
实验九之五描述了“帧”对“动画效果”的影响。如果以 800 x 600 x 60Hz为例 , 我们可以知道一帧图像所需要的时间是 0.0167s ,在试验中一副图像重复了60次,也就是说一副图像逗留的时间是1s左右。结果有6副图像,“动画”时间需要6s。
完成后扩展图:
基本上笔者也不知道要说什么了,当你掌握5个实验的概念后,已经证明读者都明白笔者在写什么。
有VGA硬件接口的屏幕,看简单点即使一副大点阵屏幕,但是这个点阵屏幕可以支持多彩。但是为了很好与VGA沟通,我们必须了解“时序”的接口。当中 HSYNC Signal 等于 行控制信号,VSYNC Signal 等于 列控制信号。然和RGB Signal是 颜色控制信号。
这里的五个实验,都是描述VGA的基本时序了解和概念知识。而且在实验中,还强调了“功能模块” sync_module.v 控制着“显示标准”,然而“控制模块”vga_control_module.v 是执行着“图像控制”的操作。其中还有比较暧昧的“功能模块”rom_module.v,是用来储存图像信息。
实验九实验的设计完全基于“低级建模”的准则。尤其在实验九之四中,“低级建模”完全把该实验发挥到很高的级别。除此之外,实验九之三和五,也表示了“低级建模”在“代码整洁度”和“理解上”都有很好的效果。
还有一点就是“控制模块”的作用。在5个实验当中,唯有控制模块的修改最大,如:
“点阵操作”和“动画效果”等,都是控制模块在操作。
在HDL教科书中,才不会要你了解“模块的形状”或者“模块的性质”。这样的情况,常常使得设计者很容易“混乱”,混乱的受害者往往都是“新手们”。如果“模块含有性质和形状”的话,在设计和理解方面,思路会而外的清晰。
在以前还没有琢磨出这样的建模技巧之前,笔者真的无法完成上述的例程,那怕是简单的设计。即使笔者很侥幸的写出一两样东西,估计“结果”也不会好到那里去。
至于现在,笔者尝试使用“低级建模”去完成程式设计,感觉都是得心应手,而且设计越发的清晰。虽然“低级建模”的建模量确实很多,但是可以把它看成是一种修养“程序设计,哪里没有繁多?”如果没有耐性,就等于“有翼飞不上天”。
笔者也不再说什么了,再说下去笔者也会认为自己是老王卖瓜了。更多有趣,更多好玩的事情,还是需要读者自己在设计的过程中去体会吧。
这一章笔记就到此为止吧。
文章评论(0条评论)
登录后参与讨论