VGA
1、VGA接口
VGA(Video Graphics Array)即视频图形阵列,是IBM在1987年推出的使用模拟信号的一种视频传输标准,在当时具有分辨率高、显示速率快、颜色丰富等优点,在彩色显示器领域得到了广泛的应用。这个标准对于现今的个人电脑市场已经十分过时。VGA可分为,VGA硬件接口,和VGA协议。VGA硬件接口没有什么好学的,VGA时序才是正点。
上图左边带针的叫VGA公头,右边带孔的叫VGA母头。VGA接口不支持热插拔,不支持音频传输。


VGA并没有特殊的外部芯片,我们需要关注的其实只有5个信号:HS行同步信号,VS场同步信号,R红基色,G绿基色,B蓝基色(13、14、1、2、3引脚)
1. RGB_8, R:G:B = 3:3:2,即RGB332
2. RGB_16,R:G:B = 5:6:5,即RGB565,2^16=65536色
3. RGB_24,R:G:B = 8:8:8,即RGB888

2、VGA协议
VGA 显示器扫描方式从屏幕左上角一点开始,从左向右逐点扫描,每扫描完一行,电子束回到屏幕的左边下一行的起始位置,在这期间,CRT 对电子束进行消隐,每行结束时,用行同步信号进行同步;当扫描完所有的行,形成一帧,用场同步信号进行场同步,并使扫描回到屏幕左上方,同时进行场消隐,开始下一帧。完成一行扫描的时间称为水平扫描时间,其倒数称为行频率;完成一帧(整屏)扫描的时间称为垂直扫描时间,其倒数称为场频率,即屏幕的刷新频率,常见的有 60Hz,75Hz 等等。

行消隐(HBlank)在将光信号转换为电信号的扫描过程中,扫描总是从图像的左上角开始,水平向前行进,同时扫描点也以较慢的速率向下移动。当扫描点到达图像右侧边缘时,扫描点快速返回左侧,重新开始进行第2行扫描,行与行之间的返回过程称为水平消隐。在移动期间就必须有一个信号加到电路上,使得电子束不能发出。不然这个回扫线会破坏屏幕图像的。
扫描点扫描完一帧后,要从图像的右下角返回到图像的左上角,开始新一帧的扫描,这一时间间隔,叫做垂直消隐,也称场消隐(VBlank)。
3、行-场信号
行场信号共有 4 种模式,即 hsync 和 vsync 的高低状态不同,可以理解为00、01、10、11 。这里直接给出几种信号的组合
( 来源于“VESA and Industry Standards and Guidelines for Computer Display Monitor Timing (DMT)” )分辨率VESA标准。链接:https://pan.baidu.com/s/1F_6FR3lMtfmGjFORhJHohQ 提取码:1234




上图看起来较为难理解,其实在实际应用中,应该是如下图所示,若干个HSync信号才会有一个VSync信号。

总帧时序如下: Blanking为消隐部分,Border为边界,最中央为实际显示区域。

行时序是以”像素”(Pixels)为单位的, 场时序是以”行”(Lines)为单位的。参考以640x480@60Hz这种分辨率格式来说,像素时钟频率为:25.175Mhz ≈ 60*(640+16+96+48)*(480+10+2+33)=60*800*525
分辨率VESA标准中有如下参数设置:

一般情况下,可以用a、b、c、d、e简化这个表达:以行同步为例,a=96,b=40+8=48,c=640,d=8+8=16 。
4、实例练习
① VGA单色显示

通过查阅标准手册可知,800*600@60Hz的VGA时序参数如上所示。以下设计可以实现由开发板按钮控制VGA单色显示,一秒更换一种颜色。(参考B站)

module vga( input clk, //以800*600@60hz为例,即40Mhz时钟 input rst_n, output reg vga_hs, //行同步 output reg vga_vs, //列同步 output en //output en_rgb ); parameter H_total = 1056, //行总和 H_a = 128, //行同步sync H_b = 88, //行back_porch + left_border H_c = 800, //行实际像素 H_d = 40, //行front_porch + right_border V_total = 628, //列总和 V_a = 4, //列同步sync V_b = 23, //列back_porch + top_border V_c = 600, //列实际行 V_d = 1; //列front_porch + bottom_border reg [12:0]cnt_h; reg [12:0]cnt_v; //***************行列计数***********************// always @(posedge clk or negedge rst_n)begin //行计数 if(!rst_n) cnt_h <= 11'b0; else if(cnt_h == H_total-1'b1) //计数一直加,直到加到H_total就置零 cnt_h <= 11'b0; else cnt_h <= cnt_h + 1'b1; end always @(posedge clk or negedge rst_n)begin if(!rst_n) cnt_v <= 11'b0; else if(cnt_v == V_total-1'b1) cnt_v <= 11'b0; else if(cnt_h == H_total-1'b1) cnt_v <= cnt_v + 1'b1; else cnt_v <= cnt_v; end //************行列同步**********************// always @(posedge clk or negedge rst_n)begin if(!rst_n) vga_hs <= 1'b0; else if(cnt_h == H_total-1'b1) vga_hs <= 1'b1; else if(cnt_h == H_a) vga_hs <= 1'b0; else vga_hs <= vga_hs; end always @(posedge clk or negedge rst_n)begin if(!rst_n) vga_vs <= 1'b0; else if(cnt_v == V_total-1'b1) vga_vs <= 1'b1; else if(cnt_v == V_a) vga_vs <= 1'b0; else vga_vs <= vga_vs; end //*************限定有效显示区域***************// wire [12:0]en1; wire [12:0]en2; assign en1 = (cnt_h >= (H_a+H_b) && cnt_h <= (H_a+H_b+H_c))? (cnt_h - H_a - H_b) : 12'b0; assign en2 = (cnt_v >= (V_a+V_b) && cnt_v <= (V_a+V_b+V_c))? //当计数到C段就把C的值赋给en (cnt_v - V_a - V_b) : 12'b0; //比如,当前cnt_h计到145,处于C段,就把超过的1赋值给en1, //表示显示区域的第一位,一直叠加到640个像素,一行结束 //即cnt_h=96+48+640,超过C段后en1置零 assign en = (en1 > 0 && en2 > 0)? 1'b1:1'b0; //行列同时有效 /* wire en_r,en_g,en_b; assign en_r = (en && ( cnt_v >= (V_a+V_b) && cnt_v <= (V_a+V_b+10'd160) ) )? 1'b1:1'b0; //红色有效 assign en_g = (en && ( cnt_v >= (V_a+V_b+10'd160) && cnt_v <= (V_a+V_b+10'd320) ) )? 1'b1:1'b0;//绿色有效 assign en_b = (en && ( cnt_v >= (V_a+V_b+10'd320) && cnt_v <= (V_a+V_b+10'd480) ) )? 1'b1:1'b0;//蓝色有效 assign en_rgb = {en_r,en_g,en_b}; */ endmodule
module rgb( input en, input rst_n, input clk, output reg[4:0] vga_r, output reg[5:0] vga_g, output reg[4:0] vga_b, input key ); reg [2:0]i=0; reg [25:0]cnt; reg clk1; always @(posedge clk or negedge rst_n)begin if(!rst_n)begin cnt <= 'b0; clk1 <= 1'b0; end else if(cnt==24_999_999) begin cnt <= 'b0; clk1 <= ~clk1; end else cnt <= cnt +1'b1; end always @(posedge clk1)begin if(!key) i <= i + 1'b1; else if(i==7) i <= 3'b0; end always @(*)begin if(en) begin case(i) 0: vga_r = 5'b1_1111; 1: vga_r = 5'b1_1111; 2: vga_r = 5'b0_0000; 3: vga_r = 5'b0_0000; 4: vga_r = 5'b1_1111; 5: vga_r = 5'b0_0000; 6: vga_r = 5'b1_1111; default: vga_r = vga_r; endcase end else vga_r = 5'b0_0000; end always @(*)begin if(en) begin case(i) 0: vga_g = 6'b00_0000; 1: vga_g = 6'b00_0000; 2: vga_g = 6'b11_1111; 3: vga_g = 6'b00_0000; 4: vga_g = 6'b11_1111; 5: vga_g = 6'b11_1111; 6: vga_g = 6'b11_1111; default: vga_g = vga_g; endcase end else vga_g = 6'b00_0000; end always @(*)begin if(en) begin case(i) 0: vga_b = 5'b1_1111; 1: vga_b = 5'b0_0000; 2: vga_b = 5'b0_0000; 3: vga_b = 5'b1_1111; 4: vga_b = 5'b0_0000; 5: vga_b = 5'b1_1111; 6: vga_b = 5'b1_1111; default: vga_b = vga_b; endcase end else vga_b = 5'b0_0000; end endmodule
module top( input clk, input rst_n, output vga_hs, //行同步 output vga_vs, //列同步 output [4:0] vga_r, output [5:0] vga_g, output [4:0] vga_b, input key ); wire en; wire c0; vga vga_inst( .clk(c0), .rst_n(rst_n), .vga_hs(vga_hs), //行同步 .vga_vs(vga_vs), //列同步 .en(en) ); rgb rgb_inst( .en(en), .clk(clk), .vga_r(vga_r), .vga_g(vga_g), .vga_b(vga_b), .key(key), .rst_n(rst_n) ); pll pll_inst ( .areset (~rst_n), .inclk0 (clk), .c0 (c0) ); endmodule
该设计还存在缺点,但功能能实现,考虑后期优化。
② VGA彩条显示

使用VGA实现1920*1080@60Hz参数下的彩条显示,代码如下:
//*****vga控制模块,以1920*1080@60Hz参数为例********// module vga_driver( input clk_in, input rst_n, input [15:0]data_in, //输入的需要显示的RGB565数据 output [4:0]vga_r, //rgb三色输出,以rgb565为例 output [5:0]vga_g, output [4:0]vga_b, output [11:0]vga_x, //横纵坐标显示 output [11:0]vga_y, output vga_hs, //行场同步信号 output vga_vs, output vga_req, output vga_de, output vga_clk, output vga_blank, output vga_sync ); parameter ha = 12'd44, hb = 12'd148, hc = 12'd1920, hd = 12'd88, h_total = 12'd2200; parameter va = 12'd5, vb = 12'd36, vc = 12'd1080, vd = 12'd4, v_total = 12'd1125; reg [11:0]cnt_h; //行场计数 reg [11:0]cnt_v; //**************行场计数*******************************// always @(posedge clk_in or negedge rst_n)begin if(!rst_n) cnt_h <= 12'b0; else if(cnt_h == h_total-1'b1) cnt_h <= 12'b0; else cnt_h <= cnt_h +1'b1; end always @(posedge clk_in or negedge rst_n)begin if(!rst_n) cnt_v <= 12'b0; else if(cnt_h == h_total-1'b1)begin if(cnt_v == v_total-1'b1) cnt_v <= 12'b0; else cnt_v <= cnt_v +1'b1; end else cnt_v <= cnt_v; end //****************行场同步信号********************// //vesa标准中指出**Hor Sync Polarity = POSITIVE***// //***************Ver Sync Polarity = POSITIVE***// assign vga_hs = (cnt_h < ha)? 1'b1:1'b0; assign vga_vs = (cnt_v < va)? 1'b1:1'b0; //****************vga有效显示数据区域************************// assign vga_de = ((cnt_h >= ha+hb) && (cnt_h <= ha+hb+hc) && (cnt_v >= va+vb) && (cnt_v <= va+vb+vc)) ? 1'b1:1'b0; //****************rgb数据分量******************************// assign vga_r = vga_de? data_in[15:11] : 5'b0; assign vga_g = vga_de? data_in[10: 5] : 6'b0; assign vga_b = vga_de? data_in[ 4: 0] : 5'b0; //****************vga请求,比数据data_in提前一拍***********************// assign vga_req = ((cnt_h >= ha+hb-1'b1) && (cnt_h <= ha+hb+hc-1'b1) && (cnt_v >= va+vb) && (cnt_v <= va+vb+vc)) ? 1'b1:1'b0; //****************xy坐标,比数据data_in提前一拍********************************// assign vga_x = vga_req? (cnt_h-(ha+hb-1'b1)) : 12'd0; assign vga_y = vga_req? (cnt_v-(va+vb)) : 12'd0; //**********************其他信号*********************// assign vga_clk = clk_in; assign vga_blank = vga_de; assign vga_sync = 1'b0; endmodule
//********rgb显示模块*****************// module rgb( input clk_in, input rst_n, input [11:0]vga_x, //横纵坐标显示 input [11:0]vga_y, output reg [15:0]data_out //输出rgb组合数据 ); parameter hc = 12'd1920; //这里使用竖向显示彩条,将行像素分为8份 //parameter vc = 12'd1080; parameter RED = 16'b11111_000000_00000, GREEN = 16'b00000_111111_00000, BLUE = 16'b00000_000000_11111, YELLOW = 16'b11111_111111_00000, PURPLE = 16'b11111_000000_11111, CYAN = 16'b00000_111111_11111, WHITE = 16'b11111_111111_11111, BLACK = 16'b00000_000000_00000; //*******************把vga显示分为8份,显示8种颜色************************// always @(posedge clk_in or negedge rst_n)begin if(!rst_n) data_out <= 16'b0; else if(vga_x < (hc/8)) //显示红色 data_out <= RED; else if((vga_x >= (hc/8)*1) && (vga_x <= (hc/8)*2)) //显示绿色 data_out <= GREEN; else if((vga_x >= (hc/8)*2) && (vga_x <= (hc/8)*3)) //显示蓝色 data_out <= BLUE; else if((vga_x >= (hc/8)*3) && (vga_x <= (hc/8)*4)) //显示黄色 data_out <= YELLOW; else if((vga_x >= (hc/8)*4) && (vga_x <= (hc/8)*5)) //显示紫色 data_out <= PURPLE; else if((vga_x >= (hc/8)*5) && (vga_x <= (hc/8)*6)) //显示青色 data_out<=CYAN; else if((vga_x >= (hc/8)*6) && (vga_x <= (hc/8)*7)) //显示黑色 data_out <= BLACK; else if((vga_x>= (hc/8)*7) && (vga_x <= hc)) //显示白色 data_out <= WHITE; end endmodule
//*****作者:DOVI666************************// //*****功能:实现vga的彩条显示***************// //*****顶层模块****************************// module vga_top( input clk_sys, input reset_n, output [4:0]vga_r, //rgb三色输出,以rgb565为例 output [5:0]vga_g, output [4:0]vga_b, output vga_hs, //行场同步信号 output vga_vs ); wire c0; wire rst_n; wire locked; wire [15:0]data; wire [11:0]vga_x; wire [11:0]vga_y; assign rst_n = locked & reset_n; //***********************************// pll pll_inst ( .areset ( ~reset_n ), .inclk0 ( clk_sys ), .c0 ( c0 ), .locked ( locked ) ); //**********************************// rgb rgb_inst( .clk_in ( c0 ), .rst_n ( rst_n ), .vga_x ( vga_x ), //横纵坐标显示 .vga_y ( vga_y ), .data_out ( data ) //输出rgb组合数据 ); //**********************************// vga_driver vga_driver_inst( .clk_in ( c0 ), .rst_n ( rst_n ), .data_in ( data ), .vga_r ( vga_r ), //rgb三色输出,以rgb565为例 .vga_g ( vga_g ), .vga_b ( vga_b ), .vga_x ( vga_x ), //横纵坐标显示 .vga_y ( vga_y ), .vga_hs ( vga_hs ), //行场同步信号 .vga_vs ( vga_vs ), .vga_req ( ), //没有用到的端口,不接 .vga_de ( ), .vga_clk ( ), .vga_blank( ), .vga_sync ( ) ); //**********************************// endmodule
RTL视图如下

最终效果如下图所示


③VGA图片显示
图片的尺寸是指一幅图片长度和宽度各占多少像素,我们平常说的一张640×480的图片指的就是这张图片的长度有640个像素点,宽度有480个像素点。
位深度是指图片的每个像素是用多少位(bit)来表示的。比如黑白二色的图像是数字图像中最简单的一种,它只有黑、白两种颜色,也就是说它的每个像素只有1位颜色,位深度是1,用2的零次幂来表示;
8位颜色的图,位深度就是8,用2的8次幂表示,它含有256种颜色 ( 或256种灰度等级 )。
24位颜色可称之为真彩色,位深度是24,它能组合成2的24次幂种颜色,即:16777216种颜色 ( 或称千万种颜色 )。当我们用24位来记录颜色时,实际上是以2^(8×3),即红、绿、蓝 ( RGB ) 三基色各以2的8次幂,256种颜色而存在的,三色组合就形成一千六百万种颜色。
除了上面这几种情况以外,有的图片的位深度是16位,其中红基色占5位,绿基色占6位,蓝基色占5位,他们一共可以组成2^16中颜色。
在电脑上,通过查看图片属性可以观察到这几种参数:

VGA显示图片可以先将图片数据存储在FPGA中,在彩条显示代码的基础上,我添加了一个rom ip核存储待显示的图片数据。这里存储的数据是一种可以存储图片信息的.mif文件(quartus软件中使用.mif文件,Xilinx软件中使用.coe文件)。通过查阅FPGA板载存储大小,将图片调整至合适的分辨率(可通过电脑自带软件 “ 画图 ” 调整),使用软件或者matlab即可生成需要格式的.mif文件。为了简便,这里使用了正点原子“PicToMif_V1.0.exe”直接生成mif文件。
vga控制驱动部分直接使用彩条显示中给出的即可,顶层top做些许修改即可实现。
//********rgb显示模块*****************// module rgb( input clk_in, input rst_n, input [11:0]vga_x, //横纵坐标显示 input [11:0]vga_y, input key, output reg[15:0]data_out //输出rgb组合数据 ); parameter hc = 12'd1920; //这里使用竖向显示彩条,将行像素分为8份 //parameter vc = 12'd1080; parameter RED = 16'b11111_000000_00000, GREEN = 16'b00000_111111_00000, BLUE = 16'b00000_000000_11111, YELLOW = 16'b11111_111111_00000, PURPLE = 16'b11111_000000_11111, CYAN = 16'b00000_111111_11111, WHITE = 16'b11111_111111_11111, BLACK = 16'b00000_000000_00000; parameter IMG_WIDTH = 12'd80 ; //图片宽度 parameter IMG_HEIGHT = 12'd80 ; //图片高度 //*******************把vga显示分为8份,显示8种颜色************************// /* always @(posedge clk_in or negedge rst_n)begin if(!rst_n) data_out <= 16'b0; else if(vga_x < (hc/8)) //显示红色 data_out <= RED; else if((vga_x >= (hc/8)*1) && (vga_x <= (hc/8)*2)) //显示绿色 data_out <= GREEN; else if((vga_x >= (hc/8)*2) && (vga_x <= (hc/8)*3)) //显示蓝色 data_out <= BLUE; else if((vga_x >= (hc/8)*3) && (vga_x <= (hc/8)*4)) //显示黄色 data_out <= YELLOW; else if((vga_x >= (hc/8)*4) && (vga_x <= (hc/8)*5)) //显示紫色 data_out <= PURPLE; else if((vga_x >= (hc/8)*5) && (vga_x <= (hc/8)*6)) //显示青色 data_out<=CYAN; else if((vga_x >= (hc/8)*6) && (vga_x <= (hc/8)*7)) //显示黑色 data_out <= BLACK; else if((vga_x>= (hc/8)*7) && (vga_x <= hc)) //显示白色 data_out <= WHITE; end */ wire [15:0]data; reg [15:0]addr; wire rd_en; reg rd_en_r; //将图片显示在显示器中央 assign rd_en = (vga_x >= 920) && (vga_x < 920 + IMG_WIDTH ) && (vga_y >= 500) && (vga_y < 500 + IMG_HEIGHT) ? 1'b1 : 1'b0; always @(posedge clk_in or negedge rst_n)begin if(!rst_n) addr <= 16'b0; else if(rd_en)begin if(addr == IMG_WIDTH*IMG_HEIGHT-1'b1) addr <= 16'b0; else addr <= addr + 1'b1; end end always @(posedge clk_in or negedge rst_n)begin if(!rst_n) rd_en_r <= 16'b0; else rd_en_r <= rd_en; end always @(posedge clk_in or negedge rst_n)begin if(!rst_n) data_out <= 16'b0; else if(rd_en_r && key=='b0) data_out <= data; else data_out <= WHITE; end rom rom_inst ( .address ( addr ), .clock ( clk_in ), .q ( data ) ); endmodule
效果如下: 通过按下fpga开发板上的按钮,即可实现图片的显示,松开按钮即恢复白色底板界面。

资料来源:
ninghechuan、咸鱼IC、黑金动力社区、jgliu【接口时序】7、VGA接口原理与Verilog实现 - jgliu - 博客园 (cnblogs.com)

浙公网安备 33010602011771号