FPGA驱动VGA显示

驱动VGA显示

概述: VGA(Video Graphics Array),视频图形阵列,是一种视频传输标准,具有分辨率高、显示速度快、颜色丰富等优点,不支持音频传输。

说明: FPGA芯片采用了altera的Cyclon IV E系列的“EP4CE10F17C8”,软件环境-Quartus-Ⅱ。


1.VGA简介

1.1 VGA接口

●VGA接口:
在这里插入图片描述

VGA是一种D型接口,采用非对称分布连接方式,共有15针,分三排,每排5个孔。

●VGA接口管脚表:

管脚 定义 管脚 定义
1 红基色(Red) 在这里插入图片描述 9 保留(各家定义不同)
2 绿基色(Green)在这里插入图片描述 10 数字地
3 蓝基色(Blue) 在这里插入图片描述 11 地址码
4 地址码(ID Bit) 12 地址码
5 自测试(各家定义不同) 13 行同步
6 红地 14 场同步
7 绿地 15 地址码(各家定义不同)
8 蓝地

在15个管脚中,其中比较重要的是3根RGB彩色分量信号2根扫描同步信号HSYNC和VSYNC

●像素点构成: VGA显示器上每一个像素点可以有多种颜色,由三基色信号R、G、B组合构成。如果每个像素点采用3位二进制数表示(R、G、B信号各1位),则总共可以显示2×2×2=8种颜色,每个像素点采用8位二进制数表示(R、G、B信号分别为3、3、2),则总共可以显示8×8×4=256种颜色。

R(bit) G(bit) B(bit) 颜色个数
1 1 1 2×2×2=8
3 3 2 8×8×4=256

1.2 VGA是如何实现显示的

  要知道VGA显示器是不认识数字信号的,它只认识模拟信号。所谓它只认识模拟信号,即 在它的数据引脚1、2、3(RED、GREEN、BLUE)输入的不是简单的0、1数字信号,而是模拟电压(0V-0.714V)。1、2、3引脚具有不同的电压时,VGA显示器显示不同的颜色。

但是FPGA要想产生模拟信号就需要借助DA,利用DA产生模拟信号,输出至VGA的RED、GREEN、BLUE基色数据线。也有利用电阻网络分流模拟DA实现的。

●VGA各种颜色如何实现的
在VGA接口的1、2、3引脚分别接至以下电压:

RED GREEN BLUE 颜色
0.714V 0V 0V 红色 在这里插入图片描述
0V 0.714V 0V 绿色 在这里插入图片描述
0V 0V 0.714V 蓝色在这里插入图片描述
0V 0V 0.357V 半蓝色在这里插入图片描述
0V 0V 0V 黑色在这里插入图片描述

●利用电阻网络分流模拟DA
R-2R电阻网络分压原理:

(1)每个像素点采用3位二进制(R(1bit)、G(1bit)、B(1bit))
  例如VGA显示器要显示纯红色,则在RED引脚要输入0.714V电压,如果我们VGA_RED信号为1位电路如下图所示,那么(X+75)/3.3=75/0.714,计算得出X=271.6。
在这里插入图片描述
将R1阻值选择为271.6Ω,仿真得出VGA_RED引脚输入电压确实为0.714V。
在这里插入图片描述
(2)每个像素点采用8位二进制(R(3bit)、G(3bit)、B(2bit))
如果我们VGA_RED信号为3位电路如下图所示,那么(X+75)/3.3=75/0.714,计算得出X=271.6,X是通过三个电阻并联得到的。Rx||2Rx||4Rx=X,得出Rx=475.3Ω。
在这里插入图片描述
将R1、R2、R3阻值选择为475.3Ω、950.6Ω、1.9012KΩ,并且将R1、R2、R3都接通至3.3V,得出VGA_RED节点电压为0.714V。
在这里插入图片描述
上图的三个3.3V节点接至FPGA的三个输出引脚,就可以通过数字量去控制模拟量了。 例如FPGA三引脚输出数字量101,则对应以下电路(则会对应不同的颜色),VGA_RED节点电压为0.554V:
在这里插入图片描述
VGA_GREEN、VGA_BLUE同理,我们就会得到以下完整的电路:
在这里插入图片描述
原理很简单,之前说过,每个像素点采用8位二进制数表示(R、G、B信号分别为3、3、2)。观察上图得到以下对应关系:

RGB R(3bit) G(3bit) B(2bit)
8位二进制 D7、D6、D5 D4、D3、D2 D1、D0
对应硬件端口 VGA_R2、 VGA_R1、VGA_R0 VGA_G2、VGA_G1、VGA_G0 VGA_B1、VGA_B0

D7-D0分别映射到FPGA的8个IO口,就可以通过这8个IO口的数字量去控制VGA的颜色显示了。


2. VGA通信协议

2.1 VGA通信时序

在这里插入图片描述
   从上图中看出,帧时序和行时序都有四部分:
●帧时序:

   帧时序的四个部分别是:同步脉冲(Sync o)、显示后沿(Back porch p)、显示时序段(Display interval q)和显示前沿(Front porchr)。其中同步脉冲(Sync o)、显示后沿(Back porch p)和显示前沿(Front porch r)是消隐区,RGB信号无效,屏幕不显示数据。显示时序段(Display interval q)是有效数据区。.

o p q r
同步脉冲(Sync) 显示后沿(Back porch) 显示时序段(Display interval) 显示前沿(Front porch)
RGB信号无效在这里插入图片描述 RGB信号无效 在这里插入图片描述 有效数据区 在这里插入图片描述 RGB信号无效在这里插入图片描述

●行时序:
   行时序的四个部分分别是:同步脉冲(Sync a)、显示后沿(Back porch b)、显示时序(Display interval c)和显示前沿(Front porchd)。其中同步脉冲(Sync a)、显示后沿(Back porch b)和显示前沿(Front porch d)是消隐区,RGB信号无效,屏幕不显示数据。显示时序段(Display interval c)是有效数据区。

a b c d
同步脉冲(Sync) 显示后沿(Back porch) 显示时序段(Display interval) 显示前沿(Front porch)
RGB信号无效在这里插入图片描述 RGB信号无效 在这里插入图片描述 有效数据区 在这里插入图片描述 RGB信号无效在这里插入图片描述

2.2 VGA时序解析

在这里插入图片描述
   不同的分辨率,它的时序是不一样的。例如800*600@60Hz的VGA时序
在这里插入图片描述
在这里插入图片描述

行时序(HSYNC数据线):

a b c d e
拉低的128个列像素 拉高的88个列像素 拉高的800个列像素 拉高的40个列像素 总共1056个列像素

帧时序(VSYNC数据线):

o p q r s
拉低的4个行像素 拉高的23个行像素 拉高的600个行像素 拉高的1个行像素 总共628个行像素

时钟频率:
628×1056×60约为40MHz。


3. FPGA驱动VGA显示彩条

电路图:
在这里插入图片描述

重点都在代码里:

//行时序宏定义
`define HSYNC_A	16'd128
`define HSYNC_B	16'd216
`define HSYNC_C	16'd1016
`define HSYNC_D	16'd1056

//列时序宏定义
`define VSYNC_O	16'd4
`define VSYNC_P	16'd27
`define VSYNC_Q	16'd627
`define VSYNC_R	16'd628

//颜色定义
`define RED			8'hE0		//1110_0000(参照上面电路图)
`define GREEN		8'h1C		//0001_1100(参照上面电路图)
`define BLUE		8'h03		//0000_0011(参照上面电路图)
`define YELLOW		8'hFC
`define BLACK		8'h00
module VGA
(
	//输入
	input 						CLK_50M,
	input						RST_N,
	//输出
	output  reg 				VSYNC,			//垂直同步端口
	output  reg 				HSYNC,			//水平同步端口
	output  reg[7:0] 			VGA_DATA			//数据端口

);

reg[15:0] hsync_cnt;		//水平扫描计数器
reg[15:0] vsync_cnt;		//垂直扫描计数器

reg vga_data_valid;			//RGB数据信号有效区使能信号 


//水平扫描(扫描1056个点)
always@(posedge CLK_40M or negedge RST_N)
begin
	if(!RST_N)
		hsync_cnt <= 16'd0;
	else if(hsync_cnt == `HSYNC_D)
		hsync_cnt <= 16'd0;
	else
		hsync_cnt <= hsync_cnt + 16'd1;
end


//垂直扫描(扫描628个点)
always@(posedge CLK_40M or negedge RST_N)
begin
	if(!RST_N)
		vsync_cnt <= 16'd0;
	else if((vsync_cnt == `VSYNC_R) && (hsync_cnt == `HSYNC_D))
		vsync_cnt <= 16'd0;
	else if(hsync_cnt == `HSYNC_D)
		vsync_cnt <= vsync_cnt + 16'd1;
	else 
		vsync_cnt <= vsync_cnt;
end

//行时序
always@(posedge CLK_40M or negedge RST_N)
begin
	if(!RST_N)
		HSYNC <= 1'b0;
	else if(hsync_cnt < `HSYNC_A)	//a域为0
		HSYNC <= 1'b0;
	else
		HSYNC <= 1'b1;				//其他域为1
end

//列时序
always@(posedge CLK_40M or negedge RST_N)
begin
	if(!RST_N)
		VSYNC <= 1'b0;
	else if(vsync_cnt < `VSYNC_O)	//o域为0
		VSYNC <= 1'b0;
	else
		VSYNC <= 1'b1;				//其他域为1
end

//提取显示有效区(q域+c域)
always@(posedge CLK_40M or negedge RST_N)
begin
	if(!RST_N)
		vga_data_valid <= 1'b0;
	else if((hsync_cnt > `HSYNC_B && hsync_cnt < `HSYNC_C) && (vsync_cnt >  `VSYNC_P && vsync_cnt < `VSYNC_Q))	//数据有效区
		vga_data_valid <= 1'b1;
	else
		vga_data_valid <= 1'b0;
end


//在数据有效区,在将数据送至VGA_RED、VGA_GREEN、VGA_BLUE数据引脚
always@(*)
begin
	if(vga_data_valid)
	begin
		if(vsync_cnt >`VSYNC_P)//显示区
		begin
			if((hsync_cnt > `HSYNC_B) && (hsync_cnt < `HSYNC_B+10'd300))
				VGA_DATA <= `RED;			//红色		1110_0000
			else if((hsync_cnt > `HSYNC_B+10'd300) && (hsync_cnt < `HSYNC_B+10'd400))
				VGA_DATA <= `BLUE;		//蓝色		0000_0111
			else if((hsync_cnt > `HSYNC_B+10'd400) && (hsync_cnt < `HSYNC_B+10'd500))
				VGA_DATA <= `YELLOW;		//黄色		1111_1100
			else if((hsync_cnt > `HSYNC_B+10'd500) && (hsync_cnt < `HSYNC_B+10'd800))
				VGA_DATA <= `GREEN;		//绿色		0001_1100
			else
				VGA_DATA <= `BLACK;		//黑色		0000_0000
		end
		else
			VGA_DATA <= `BLACK;			//黑色
	end
	else
		VGA_DATA <= `BLACK;				//黑色
end

//PLL_IP获取40M时钟
wire CLK_40M;	
PLL	PLL_inst (
	.inclk0 ( CLK_50M ),
	.c0 ( CLK_40M )
	);

显示效果:
在这里插入图片描述


4. FPGA驱动VGA显示文字

4.1 产生字库

   玩过LCD、OLED屏幕的都知道显示字符、数字、汉字,首先要对其取模。我们借助的工具为PCtoLCD2002软件进行字模的提取。
取模格式设置:
在这里插入图片描述

比如对“5V”取模,字模数组生成:
在这里插入图片描述

生成的数组为:

{0x00,0x00,0x1F,0x98,0x10,0x84,0x11,0x04,0x11,0x04,0x10,0x88,0x10,0x70,0x00,0x00},/*"5",0*/
{0x10,0x00,0x1E,0x00,0x11,0xE0,0x00,0x1C,0x00,0x70,0x13,0x80,0x1C,0x00,0x10,0x00},/*"V",1*/

字模是如何对应字符的:
例如:数字‘5’的字模数组为:

{0x00,0x00,0x1F,0x98,0x10,0x84,0x11,0x04,0x11,0x04,0x10,0x88,0x10,0x70,0x00,0x00},/*"5",0*/

我们字符的大小为8*16:
在这里插入图片描述
取模方式为:逐列式
则数组:

//第一列   第二例	第三列	   第四列     第五列   第六列     第七列    第8列	
{0x00,0x00,0x1F,0x98,0x10,0x84,0x11,0x04,0x11,0x04,0x10,0x88,0x10,0x70,0x00,0x00},/*"5",0*/
//每列两个字节(16位),构成8*16大小的字符

将十六进制转换为二进制,并按行列录入至excel观察:
在这里插入图片描述
第二例:0x1F98转换为二进制=1111110011000,对应excel_B列,以此类推。

4.2 字库数据存入Memory-ROM_IP

   生成的字库放置在存储器里面才能被我们调用,在这里选择了将数据放置置ROM_IP核内。

4.2.1 生成.mif文件

  .mif是配置存储器专用的文件格式,利用QuartusⅡ生成.mif文件:
在这里插入图片描述
并将数据录入至.mif文件中:
在这里插入图片描述
并将生成的.mif文件关联至创建的ROM IP核中。

4.2.2 ROM IP核

  创建的ROM IP核如下所示,输入为地址和时钟信号,输出为存储的数据,我们在使用数据时,在ROM的时钟沿给予ROM相应的地址即可在输出端口得到数据。
在这里插入图片描述

4.3 verilog代码

时序:
在这里插入图片描述
代码:

//行时序宏定义
`define HSYNC_A	16'd128
`define HSYNC_B	16'd216
`define HSYNC_C	16'd1016
`define HSYNC_D	16'd1056

//列时序宏定义
`define VSYNC_O	16'd4
`define VSYNC_P	16'd27
`define VSYNC_Q	16'd627
`define VSYNC_R	16'd628

//颜色定义
`define RED			8'hE0		//1110_0000(参照上面电路图)
`define GREEN		8'h1C		//0001_1100(参照上面电路图)
`define BLUE		8'h03		//0000_0011(参照上面电路图)
`define YELLOW		8'hFC
`define BLACK		8'h00
module VGA
(
	//输入
	input 					CLK_50M,
	input					RST_N,
	//输出
	output  reg 			VSYNC,				//垂直同步端口
	output  reg 			HSYNC,				//水平同步端口
	output  reg[7:0] 		VGA_DATA			//数据端口

);


reg[15:0] hsync_cnt;		//水平扫描计数器
reg[15:0] vsync_cnt;		//垂直扫描计数器

reg vga_data_valid;			//RGB数据信号有效区使能信号 


//水平扫描
always@(posedge CLK_40M or negedge RST_N)
begin
	if(!RST_N)
		hsync_cnt <= 16'd0;
	else if(hsync_cnt == `HSYNC_D)
		hsync_cnt <= 16'd0;
	else
		hsync_cnt <= hsync_cnt + 16'd1;
end


//垂直扫描
always@(posedge CLK_40M or negedge RST_N)
begin
	if(!RST_N)
		vsync_cnt <= 16'd0;
	else if((vsync_cnt == `VSYNC_R) && (hsync_cnt == `HSYNC_D))
		vsync_cnt <= 16'd0;
	else if(hsync_cnt == `HSYNC_D)
		vsync_cnt <= vsync_cnt + 16'd1;
	else 
		vsync_cnt <= vsync_cnt;
end

//行时序
always@(posedge CLK_40M or negedge RST_N)
begin
	if(!RST_N)
		HSYNC <= 1'b0;
	else if(hsync_cnt < `HSYNC_A)
		HSYNC <= 1'b0;
	else
		HSYNC <= 1'b1;
end

//列时序
always@(posedge CLK_40M or negedge RST_N)
begin
	if(!RST_N)
		VSYNC <= 1'b0;
	else if(vsync_cnt < `VSYNC_O)
		VSYNC <= 1'b0;
	else
		VSYNC <= 1'b1;
end

//提取显示有效区
always@(posedge CLK_40M or negedge RST_N)
begin
	if(!RST_N)
		vga_data_valid <= 1'b0;
	else if((hsync_cnt > `HSYNC_B && hsync_cnt < `HSYNC_C) && (vsync_cnt >  `VSYNC_P && vsync_cnt < `VSYNC_Q))	//数据有效区
		vga_data_valid <= 1'b1;
	else
		vga_data_valid <= 1'b0;
end


//显示字符
reg  [7:0] 	rom_add;
wire [15:0] rom_data;
wire [15:0] vga_x;
wire [15:0] vga_y;
assign vga_x = hsync_cnt - `HSYNC_B;
assign vga_y = vsync_cnt - `VSYNC_P;

assign display_en = (vga_x >= 10'd98) && (vga_x <= 10'd116) && (vga_y >= 10'd90) && (vga_y <= 10'd106);//开窗

//读取字库
always@(posedge CLK_40M or negedge RST_N)
begin
	if(!RST_N)
		rom_add <= 8'h0;
	else if(display_en)	
	begin
		if(vga_x == 10'd98)					//5  x坐标
			rom_add <= 8'h0;				//看.mif文件中的地址
		else if(vga_x == 10'd106)			//v  x坐标
			rom_add <= 8'h08;				//看.mif文件中的地址
		else 
			rom_add <= rom_add + 1'b1;			
	end
	else
		rom_add <= 8'h0;	
end

//显示
always@(*)
begin
	if(display_en)
	begin
	if(rom_data[10'd106 - vga_y])		//从下往上刷(rom_data元素为16的列向量)
		VGA_DATA <= `RED;					//红色
	else
		VGA_DATA <= `YELLOW;				//黑色
	end
	else
		VGA_DATA <= `BLACK;					//黑色		
end

//ROM_IP 存放字库数据
ROM	ROM_inst (
	.address ( rom_add ),
	.clock ( CLK_40M ),
	.q ( rom_data )
	);
	
//PLL_IP生成VGA需要的40M时钟
wire CLK_40M;	
PLL	PLL_inst (
	.inclk0 ( CLK_50M ),
	.c0 ( CLK_40M )
	);

endmodule

显示效果:
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/qq_40147893/article/details/108342484