前言
FPGA主要运用于芯片验证、通信、图像处理。显示VGA接口的显示器是最基本的要求了。
原理
首先需要了解 :
(1)VGA接口协议:VGA端子_维基百科 、VGA视频传输标准_百度
引脚1 | RED | 红色视频 | |
---|---|---|---|
引脚2 | GREEN | 绿色视频 | |
引脚3 | BLUE | 蓝色视频 | |
引脚4 | ID2/RES | 过去为屏幕ID比特2;自DDC2起保留 | |
引脚5 | GND | 接地(水平同步) | |
引脚6 | RED_RTN | 红色接回 | |
引脚7 | GREEN_RTN | 绿色接回 | |
引脚8 | BLUE_RTN | 蓝色接回 | |
引脚9 | KEY/PWR | 过去为key;现为 +5V DC | |
引脚10 | GND | 接地(垂直同步,DDC) | |
引脚11 | ID0/RES | 过去为屏幕ID比特0;自E-DDC起保留 | |
引脚12 | ID1/SDA | 过去为屏幕ID比特1;自DDC2起为I²C数据 | |
引脚13 | HSync | 水平同步 | |
引脚14 | VSync | 垂直同步 | |
引脚15 | ID3/SCL | 过去为屏幕ID比特3;自DDC2起为I²C时钟 | |
图表中详叙了较新的15针VESA DDC2连接头。图标中的针脚编号是显卡常见的母接头;在公接头上的针脚相当于图例的左右镜像。 |
最主要的几根线:
(2)VGA时序
各种分辨率的显示器时序参数:http://tinyvga.com/vga-timing
a、扫描轨迹
扫描从屏幕左上方开始,从左至右,从上到下,进行扫描。
每扫描完一行,电子束(CRT)回到屏幕的左边下一行起始位置,在此期间对电子束进行消隐,每行结束时,用行同步信号进行同步。
扫描完所有行,用场同步信号进行场同步,并使扫描回到屏幕的左上方,同时进行消隐,预备下一行的扫描。
b、行场扫描(工业标准为负,大部分显示器HS、VS可正可负,显示器自动调整,此处为正极性)
c、HS时序深入分析
可见时序的循环,可被划分为a,b,c,d4个时期。这四个时期定义如下:
A~B:行消隐期 即同步,相当于还原扫描坐标吧
B~C:行消隐后肩 相当于准备开始扫描吧
C~D:行显示期 扫描中,数据有效区域
D~E:行消隐前肩 完成扫描,相当于准备同步
d、VS时序深入分析
可见时序的循环,可被划分为a,b,c,d4个时期。这四个时期定义如下:
A~B:场消隐期 即同步,相当于还原扫描坐标吧
B~C:场消隐后肩 相当于准备开始扫描吧
C~D:场显示期 扫描中,数据有效区域
D~E:场消隐前肩 完成扫描,相当于准备同步
综上描述,我们只要知道每个时期的时间,便可以表示出VGA的时序。而FPGA的工作是由固定频率的时钟触发的,因此某固定时间可以用n次触发来表示。因此我们很容易就想到了FPGA常用的计数方法:比如说行扫描,我们计数0~H_total-1。用另一个进程将其划分为4个时期,按标注分配。其实这相当于状态机。
以下是固定分辨率1280*1024 60fps下HS,VS的标准:
有图可得:
①各个时期像素数:
// Horizontal Parameter( Pixel )
H_DISP = 11'd1280,
H_FRONT = 11'd48,
H_SYNC = 11'd112,
H_BACK = 11'd248,
H_TOTAL = 11'd1688,
// Virtical Parameter( Line )
V_DISP = 11'd1024,
V_FRONT = 11'd1,
V_SYNC = 11'd3,
V_BACK = 11'd38,
V_TOTAL = 11'd1066;
②像素时钟频率(即所需的VGA时钟频率)
(H_DISP + H_BACK + H_SYNC + H_FRONT)×(V_DISP + V_BACK + V_SYNC + V_FRONT)× REFRESH_RATE
在此为(1280 + 248 + 112 + 48)×(1024 + 38 + 3 + 1)× 60 = 1688 × 1066 × 60 = 107.964480 MHz (工业标准为 108.000 MHz ,当然都是能正常显示的,推荐用工业标准。)
③一帧图像的数据量(以RGB565 格式为例)
H_DISP × V_DISP × (5 + 6 + 5)bit = [H_DISP × V_DISP × (5 + 6 + 5)bit ] ÷ 8 B = [H_DISP × V_DISP × (5 + 6 + 5)bit ] ÷ 8 ÷ 1024 K
在此为 1280 × 1024 × 16 bit = 20971520 ÷ 8 B = 2621440 ÷ 1024 K = 2560 ÷ 1024 M = 2.5 M
(3)三基色原理
常见的彩色显示器一般都是由CRT (阴极射线管) 构成,每一个像素的色彩由R ( 红, Red) 、G( 绿,Green) 、B(蓝,Blue) 三基色构成。显示时采用的是逐行扫描的方式。由VGA 显示模块产生的水平同步信号和垂直同步信号控制阴极射线管中的电子枪产生电子束,轰击涂有荧光粉的屏幕,产生RGB 三基色,于显示屏上合成一个彩色像素点。
像素是产生各种颜色的基本单元。根据物理学中的混色原理,三色发光的亮度比例适当,可呈现白色。适当的调整发光比例可以出现不同的颜色。
程序设计
显示模块(顶层):
1 /************************************************************************ 2 * Author : yllinux 博客:http://www.cnblogs.com/yllinux/ 3 * Module Name : vga_color_line.v 4 * Tool versions : Quartus II 12.1;Cyclone IV E(EP4CE6F17C8) 5 * Create Date : 2017-7-21 6 * Revision : v1.0 7 * Description : 使1280*1024@60HZ的VGA显示器显示竖彩条 8 ************************************************************************/ 9 10 module VGA_color_line (clk, rst_n, hs_vga, vs_vga, r_vga, g_vga, b_vga); 11 12 input clk, rst_n; //系统时钟和低电平复位 13 output hs_vga, vs_vga; //行同步hs_vga,场同步vs_vga 14 output r_vga, g_vga , b_vga; //像素三基色输出R、G、B 15 reg [2:0] rgb_vga; //相当于输入信号了 16 wire clk_vga; //像素时钟 1688 * 1066 * 60 MZ (工业标准 108 MHZ) 17 18 // VGA_1280_1024_60fps_50MHz 19 // Horizontal Parameter( Pixel ) 20 parameter 21 H_DISP = 11'd1280, 22 H_FRONT = 11'd48, 23 H_SYNC = 11'd112, 24 H_BACK = 11'd248, 25 H_TOTAL = 11'd1688, 26 // Virtical Parameter( Line ) 27 V_DISP = 11'd1024, 28 V_FRONT = 11'd1, 29 V_SYNC = 11'd3, 30 V_BACK = 11'd38, 31 V_TOTAL = 11'd1066; 32 33 //调用IP核ALTPLL,像素频率108 MHZ 34 pll_pixel_clock pll_pixel_clock_inst ( 35 .inclk0 ( clk ), 36 .c0 ( clk_vga ) //例化端口必须是网线型 37 ); 38 39 //行同步计数器(信号发生器) 40 reg [10:0] hcnt; 41 reg hs_vga; 42 always @ (posedge clk_vga or negedge rst_n) 43 begin 44 if (!rst_n) 45 hcnt <= 0; //复位后计数清零 46 else 47 begin 48 if (hcnt < H_TOTAL - 1'b1) //判断扫描完一行,像素区间为[0, H_TOTAL - 1'b1] 49 hcnt <= hcnt + 1'b1; 50 else 51 hcnt <= 0; 52 end 53 end 54 //行同步 55 always @ (posedge clk_vga or negedge rst_n) 56 begin 57 if (!rst_n) 58 hs_vga <= 0; //因为复位后计数器置零,而0 ~ (H_DISP - 1)区间为显示区,此处必为 0 59 else 60 begin 61 //像素(H_DISP + H_FRONT - 1, H_DISP + H_FRONT + H_SYNC - 1]区间同步 62 if (hcnt >= (H_DISP + H_FRONT - 1'b1) && hcnt < (H_DISP + H_FRONT + H_SYNC - 1'b1)) 63 hs_vga <= 1; //在同步区置1,行同步 64 else 65 hs_vga <= 0; 66 end 67 end 68 69 //场同步计数器(信号发生器) 70 reg [10:0] vcnt; 71 reg vs_vga; 72 always @ (posedge clk_vga or negedge rst_n) //异步复位 73 begin 74 if (!rst_n) 75 vcnt <= 0; //复位后计数清零 76 else 77 begin 78 if (hcnt == H_DISP - 1'b1) //判断显示区扫面完一行,此处也可判断整个一行扫描完 79 begin 80 if (vcnt < V_TOTAL - 1'b1) //判断扫面完一场 81 vcnt <= vcnt + 1'b1; 82 else 83 vcnt <= 0; 84 end 85 else 86 vcnt <= vcnt; 87 end 88 end 89 //场同步 90 always @ (posedge clk_vga or negedge rst_n) 91 begin 92 if (!rst_n) 93 vs_vga <= 0; 94 else 95 begin 96 if (vcnt >= (V_DISP + V_FRONT - 1'b1) && vcnt < (V_DISP + V_FRONT + V_SYNC - 1'b1)) 97 vs_vga <= 1; 98 else 99 vs_vga <= 0; 100 end 101 end 102 103 //在显示期坐标根据显示的扫描而改变,在非显示期,坐标置零 104 wire [10:0] xpos_vga, ypos_vga; 105 assign xpos_vga = (hcnt < H_DISP) ? (hcnt + 1'b1) : 11'd0; //在显示区横坐标 + 1(即1~H_DISP) 106 assign ypos_vga = (vcnt < V_DISP) ? (vcnt + 1'b1) : 11'd0; //在显示区竖坐标 + 1(即1~V_DISP) 107 108 //竖彩条显示 109 always @ (posedge clk_vga or negedge rst_n) 110 if (!rst_n) 111 rgb_vga <= 3'b000; 112 else 113 begin 114 /*if (xpos_vga > 0 && xpos_vga < 80) rgb_vga <= 3'b010; 115 else if (xpos_vga < 160) rgb_vga <= 3'b011; 116 else if (xpos_vga < 240) rgb_vga <= 3'b100; 117 else if (xpos_vga < 320) rgb_vga <= 3'b101; 118 else if (xpos_vga < 400) rgb_vga <= 3'b110; 119 else if (xpos_vga < 480) rgb_vga <= 3'b111; 120 else if (xpos_vga < 560) rgb_vga <= 3'b001; 121 else if (xpos_vga < 640) rgb_vga <= 3'b010; 122 else if (xpos_vga < 720) rgb_vga <= 3'b011; 123 else if (xpos_vga < 800) rgb_vga <= 3'b100; 124 else if (xpos_vga < 880) rgb_vga <= 3'b101; 125 else if (xpos_vga < 960) rgb_vga <= 3'b110; 126 else if (xpos_vga < 1040) rgb_vga <= 3'b111; 127 else if (xpos_vga < 1120) rgb_vga <= 3'b001; 128 else if (xpos_vga < 1200) rgb_vga <= 3'b010; 129 else if (xpos_vga <= 1280) rgb_vga <= 3'b011; 130 else rgb_vga <= 3'b000;*/ 131 132 if (xpos_vga > 0 && xpos_vga <= 80) rgb_vga <= 3'b111;//white 133 else if (xpos_vga > 80 && xpos_vga <= 160) rgb_vga <= 3'b100;//red 134 else if (xpos_vga > 160 && xpos_vga <= 240) rgb_vga <= 3'b101;//mangenta 135 else if (xpos_vga > 240 && xpos_vga <= 320) rgb_vga <= 3'b110;//yellow 136 else if (xpos_vga > 320 && xpos_vga <= 400) rgb_vga <= 3'b010;//green 137 else if (xpos_vga > 400 && xpos_vga <= 480) rgb_vga <= 3'b001;//blue 138 else if (xpos_vga > 480 && xpos_vga <= 560) rgb_vga <= 3'b011;//cyan 139 else if (xpos_vga > 560 && xpos_vga <= 640) rgb_vga <= 3'b000; 140 141 else if (xpos_vga > 640 && xpos_vga <= 720) rgb_vga <= 3'b111;//white 142 else if (xpos_vga > 720 && xpos_vga <= 800) rgb_vga <= 3'b100; 143 else if (xpos_vga > 800 && xpos_vga <= 880) rgb_vga <= 3'b101; 144 else if (xpos_vga > 880 && xpos_vga <= 960) rgb_vga <= 3'b110; 145 else if (xpos_vga > 960 && xpos_vga <= 1040) rgb_vga <= 3'b010; 146 else if (xpos_vga > 1040 && xpos_vga <= 1120) rgb_vga <= 3'b001; 147 else if (xpos_vga > 1120 && xpos_vga <= 1200) rgb_vga <= 3'b011; 148 else if (xpos_vga > 1200 && xpos_vga <= 1280) rgb_vga <= 3'b111; 149 else rgb_vga <= 3'b000;//black,这个很重要,不然颜色不怎么正常 150 151 end 152 153 //三基色分离(可省,直接用 rgb_vga) 154 assign r_vga = rgb_vga[2]; 155 assign g_vga = rgb_vga[1]; 156 assign b_vga = rgb_vga[0]; 157 158 endmodule
调用的锁相环 IP 核 ALTPLL:
View Code
【注】:
①对时序部分代码的编写不论从哪个时期作为判断依据都无所谓,这是一个周期性循环的。后面附另一种判断的写法,其实都一样。
②尽管使用的是LCD而不是CRT,消隐前后期必须保留,若令 H_FRONT、H_BACK、V_FRONT、V_BACK 为零,显示器将不能显示,亲测。
③尽量按照工业标准来写,这样兼容性更强。
RTL:
Technology Map:
Modelsim 仿真
写一个简单的 testbench:
1 `timescale 10 ns/ 1 ps 2 module VGA_color_line_vlg_tst(); 3 reg clk; 4 reg rst_n; 5 // wires 6 wire b_vga; 7 wire g_vga; 8 wire hs_vga; 9 wire r_vga; 10 wire vs_vga; 11 12 VGA_color_line i1 ( 13 .b_vga(b_vga), 14 .clk(clk), 15 .g_vga(g_vga), 16 .hs_vga(hs_vga), 17 .r_vga(r_vga), 18 .rst_n(rst_n), 19 .vs_vga(vs_vga) 20 ); 21 initial 22 begin 23 clk = 0; 24 end 25 always 26 begin 27 #1 clk = ~clk; 28 end 29 endmodule
仿真波形:
可以看出波形正常。
2017-07-22--------------------------------------------------------------------------------------------------------------↓↓↓
对于VGA时序里 hcnt、xpos_vga、hs_vga 等随时钟变化存疑,故手动仿真如下:
我们假设 H_DISP = 4, H_BACK = 1, H_SYNC = 1, H_FRONT = 1, H_TOTAL = 7。
由此用 quartus ii 9.0 自带仿真工具测试一下:
1 module VGA_color_line_test (clk, rst, xpos_vga, hcnt, hs_vga); 2 input clk, rst; 3 output hcnt, hs_vga; 4 output [2:0] xpos_vga; 5 6 reg [2:0] hcnt; 7 reg hs_vga; 8 wire [2:0] xpos_vga; 9 10 parameter 11 H_DISP = 3'd4, 12 H_FRONT = 3'd1, 13 H_SYNC = 3'd1, 14 H_BACK = 3'd1, 15 H_TOTAL = 3'd7; 16 17 always @(posedge clk or negedge rst) begin 18 if (!rst) 19 hs_vga <= 0; 20 else 21 if (hcnt >= H_DISP + H_FRONT - 1 && hcnt < H_DISP + H_FRONT + H_SYNC - 1) 22 hs_vga <= 1; 23 else 24 hs_vga <= 0; 25 end 26 27 always @(posedge clk or negedge rst) begin 28 if (!rst) 29 hcnt <= 0; 30 else 31 if (hcnt < H_TOTAL - 1) 32 hcnt <= hcnt + 1; 33 else 34 hcnt <= 0; 35 end 36 37 assign xpos_vga = (hcnt < H_DISP) ? hcnt : 0; 38 39 endmodule
从仿真波形可以看出手动分析完全正确。则原代码中 105 和 106 行 assign语句中不应该加 1。
(assign xpos_vga = (hcnt < H_DISP) ? hcnt : 11'd0; 即可,ypos_vga同理)
由此能得出在非显示区坐标为 0,那 0 坐标点在非显示期会不会出现异常呢,我把非显示期改为 11'dz 也能正常显示,但是这样好吗??? (希望大神们给点提示^_^)
改为11'dz 仿真波形如下:
2017-07-22--------------------------------------------------------------------------------------------------------------↑↑↑
硬件实测
硬件采用:Cyclone IV E(EP4CE6F17C8)
引脚分配:
实际效果:
上面的是比较完善的,下面粘贴第一个版本代码,能显示,稍有瑕疵,程序中有些细节可能有问题,懒得改了,直接粘贴
顶层文件:
View Code
pll :
View Code
testbench:
View Code
未完待续。。。
参考:
Crazy Bingo:http://www.cnblogs.com/crazybingo/archive/2011/02/24/1963652.html
《EDA技术实用教程--Verilog HDL版(第四版)》---- 潘松、黄继业、潘明 编著
声明:此篇文章是转载一位大佬的,为了方便自己学习,特意保留下来了 ,如有侵权还望谅解!