08、安路SparkRoad国产FPGA测评【学习篇】VGA显示

1nnocent   2022-8-3 15:29 楼主

    该例程主要分为 3 个模块, 用于实现 VGA 的驱动显示功能。 其中顶层模块例化了 vga 时钟模块,VGA 驱动模块以及 VGA 显示测试模块。

    vga 时钟模块是通过调用 PLL IP 核产生 VGA 的驱动时钟。 需要注意的是选择不同的分辨率要生成不同的驱动时钟,这个可以在VESA官网上找到每种分辨率对应的驱动时钟,以及行场信号信息,这里直接贴出“VESA和计算机显示监视器计时(DMT)行业标准和指南”:

VESA-DMT-1.12.pdf (747.23 KB)
(下载次数: 3, 2022-7-29 11:31 上传)

    vga 驱动模块是用于驱动 VGA 显示。 用户可以通过改变 VGA 分辨率的时序参数(这里时序参数指的是行场信号,其中行信号主要包括:行显示同步信号、行显示前沿、行显示有效数据、行显示后沿,其中行显示有效数据是分辨率的具体数值比如1920;场信号包括:场显示同步信号、场显示前沿、场显示有效数据、场显示后沿,其中场显示有效数据是分辨率的具体数值比如1080;行同步信号以具体像素点为基本单位【比如行显示有效数据有1920个像素点】,场同步信号以场同步信号为基本单位【比如场显示有效数据有1080个信号,这里以行位基本单位,一个行信号不仅包含有效数据,还包含前沿、后沿等,是一个完整的行信号】)来自主选择实现不同的分辨率。 下图可以帮助理解(网上找的网图):

image.png  

image.png   image.png

之前也用示波器抓过行场信号,这里也贴出来,便于理解(图一为场信号【红色】、行信号【黄色】以及使能信号【蓝色】的总和;图二为行信号和使能信号,图二可以看出显示前沿、有效数据即蓝色使能信号、显示后沿,行同步信号):

SDS00003.png SDS00005.png
      vga 显示模块是用于测试 VGA 显示。

    现在分析代码:输入接口为:系统时钟clk_24m、外部按键复位rst_n;输出接口为:像素时钟vga_clk(用于驱动显示频)、行同步信号vga_hs、场同步信号vga_hs、输出数据使能vga_de、红色数据通道vga_r、绿色数据通道vga_g、蓝色数据通道vga_b。

    首先是PLL模块,PLL输入时钟为系统时钟24MHz,产生一个108MHz时钟用于驱动显示器,通过查找上述的“VESA和计算机显示监视器计时(DMT)行业标准和指南”可以知道该频率用于驱动1280x1024@60Hz分辨率。

    接下来是显示器驱动模块(Driver.v),该模块用于产生行场信号。1、模块定义hcnt对行同步信号进行计数,该分辨率计数1688次后清零hcnt重新计数;2、hcnt计数H_SYNC(112)次时产生行同步信号,拉高lcd_hs;3、定义vcnt对场同步信号进行计数,计数1066次后清零vcnt重新计数,vcnt自加时还须满足行信号计数完1688次;4、vcnt计数V_SYNC(3)次时产生场同步信号,拉高lcd_vs;5、当hcnt满足行有效数据区间(hcnt >= H_SYNC + H_BACK - H_AHEAD && hcnt < H_SYNC + H_BACK + H_DISP - H_AHEAD),并且vcnt满足场有效数据区间(vcnt >= V_SYNC + V_BACK && vcnt < V_SYNC + V_BACK + V_DISP)拉高数据使能信号lcd_en,此时向显示器发送像素数据。

`timescale 1ns/1ns
/* VGA参数配置表
************	clk		 	H_SYNC 		H_BACK 		H_DISP 		H_FRONT 	H_TOTAL 		V_SYNC 		V_BACK 		V_DISP 		V_FRONT 	V_TOTAL		*
640x480@60Hz	25.2MHz		96			48 			640 		16 			800 			2			33			480 		10			525		*
800x600@60Hz	40MHz		128			88 			800 		40 			1056			4			23			600 		1			628		*
1024x768@60Hz	65MHz		136			160 		1024 		24 			1344			6			29			768 		3			806		*
1280x720@60Hz	74.25MHz	40			220 		1280 		110			1650			5			20			720 		5			750		*
1280x1024@60Hz	108MHz		112			248 		1280 		48 			1688			3			38			1024		1			1066	*
1920x1080@60Hz	148.5MHz	44			148 		1920 		88 			2200			5			36			1080		4			1125	*
*/	
module Driver
#(
	parameter H_SYNC = 112	, 		// 行同步信号时间
	parameter H_BACK = 248	, 		// 行消隐后肩时间
	parameter H_DISP = 1280	, 		// 行数据有效时间
	parameter H_FRONT = 48	, 		// 行消隐前肩时间
	parameter H_TOTAL = 1688, 		// 行扫描总时间
			
	parameter V_SYNC = 3	, 		// 列同步信号时间
	parameter V_BACK = 38	, 		// 列消隐后肩时间
	parameter V_DISP = 1024	, 		// 列数据有效时间
	parameter V_FRONT = 1	, 		// 列消隐前肩时间
	parameter V_TOTAL = 1066  		// 列扫描总时间
)
(
	input  wire			clk,			//VGA clock
	input  wire			rst_n,     		//sync reset
	input  wire	[23:0]	lcd_data,		//lcd data
	
	//lcd interface
	output wire			lcd_dclk,   	//lcd pixel clock
	output wire			lcd_hs,	    	//lcd horizontal sync
	output wire			lcd_vs,	    	//lcd vertical sync
	output wire			lcd_en,			//lcd display enable
	output wire	[23:0]	lcd_rgb,		//lcd display data

	//user interface
	output wire	[11:0]	lcd_xpos,		//lcd horizontal coordinate
	output wire	[11:0]	lcd_ypos		//lcd vertical coordinate
);	
 
localparam	H_AHEAD = 	12'd1;

reg [11:0] hcnt; 
reg [11:0] vcnt;
wire lcd_request;

/*******************************************
		SYNC--BACK--DISP--FRONT
*******************************************/ 
//h_sync counter & generator
always @ (posedge clk or negedge rst_n)
begin
	if (!rst_n)
		hcnt <= 12'd0;
	else
	begin
        if(hcnt < H_TOTAL - 1'b1)		//line over			
            hcnt <= hcnt + 1'b1;
        else
            hcnt <= 12'd0;
	end
end 

assign	lcd_hs = (hcnt <= H_SYNC - 1'b1) ? 1'b0 : 1'b1; // line over flag

//v_sync counter & generator
always@(posedge clk or negedge rst_n)
begin
	if (!rst_n)
		vcnt <= 12'b0;
	else if(hcnt == H_TOTAL - 1'b1)	//line over
		begin
		if(vcnt == V_TOTAL - 1'b1)		//frame over
			vcnt <= 12'd0;
		else
			vcnt <= vcnt + 1'b1;
		end
end

assign	lcd_vs = (vcnt <= V_SYNC - 1'b1) ? 1'b0 : 1'b1; // frame over flag

// LED clock
assign	lcd_dclk = ~clk;

// Control Display
assign	lcd_en		=	(hcnt >= H_SYNC + H_BACK  && hcnt < H_SYNC + H_BACK + H_DISP) &&
						(vcnt >= V_SYNC + V_BACK  && vcnt < V_SYNC + V_BACK + V_DISP) 
						? 1'b1 : 1'b0;                   // Display Enable Signal
						
assign	lcd_rgb 	= 	lcd_en ? lcd_data : 24'h000000;	

//ahead x clock
assign	lcd_request	=	(hcnt >= H_SYNC + H_BACK - H_AHEAD && hcnt < H_SYNC + H_BACK + H_DISP - H_AHEAD) &&
						(vcnt >= V_SYNC + V_BACK && vcnt < V_SYNC + V_BACK + V_DISP) 
						? 1'b1 : 1'b0;
//lcd xpos & ypos
assign	lcd_xpos	= 	lcd_request ? (hcnt - (H_SYNC + H_BACK - H_AHEAD)) : 12'd0;
assign	lcd_ypos	= 	lcd_request ? (vcnt - (V_SYNC + V_BACK)) : 12'd0;

endmodule

 

    最后是显示模块(Display.v):该模块在数据有效期间将像素数据按坐标规律输出给显示器,这里一共定义四种播放模式。

 

`timescale 1ns/1ns

// Define colors RGB--8|8|8
`define RED		24'hFF0000 
`define GREEN	24'h00FF00 
`define BLUE  	24'h0000FF 
`define WHITE 	24'hFFFFFF 
`define BLACK 	24'h000000 
`define YELLOW	24'hFFFF00 
`define CYAN  	24'hFF00FF 
`define ROYAL 	24'h00FFFF 

// Define Display Mode
// `define	VGA_HORIZONTAL_COLOR	// 八种颜色横彩条
// `define	VGA_VERTICAL_COLOR		// 八种颜色竖彩条
// `define	VGA_GRAY_GRAPH			// 红色彩条2x5
 `define	VGA_GRAFTAL_GRAPH		// lcd_data <= lcd_xpos * lcd_ypos;

module Display
#(
	parameter H_DISP = 1280,
	parameter V_DISP = 1024
)
( 
	input  wire	 		clk,	
	input  wire			rst_n,	
	input  wire	[11:0]	lcd_xpos,	//lcd horizontal coordinate
	input  wire	[11:0]	lcd_ypos,	//lcd vertical coordinate
	
	output reg  [23:0]	lcd_data	//lcd data
);

`ifdef VGA_HORIZONTAL_COLOR
always@(posedge clk or negedge rst_n)
begin
	if(!rst_n)
		lcd_data <= 24'h0;
	else
		begin
		if	(lcd_ypos >= 0 && lcd_ypos < (V_DISP/8)*1)
			lcd_data <= `RED;
		else if(lcd_ypos >= (V_DISP/8)*1 && lcd_ypos < (V_DISP/8)*2)
			lcd_data <= `GREEN;
		else if(lcd_ypos >= (V_DISP/8)*2 && lcd_ypos < (V_DISP/8)*3)
			lcd_data <= `BLUE;
		else if(lcd_ypos >= (V_DISP/8)*3 && lcd_ypos < (V_DISP/8)*4)
			lcd_data <= `WHITE;
		else if(lcd_ypos >= (V_DISP/8)*4 && lcd_ypos < (V_DISP/8)*5)
			lcd_data <= `BLACK;
		else if(lcd_ypos >= (V_DISP/8)*5 && lcd_ypos < (V_DISP/8)*6)
			lcd_data <= `YELLOW;
		else if(lcd_ypos >= (V_DISP/8)*6 && lcd_ypos < (V_DISP/8)*7)
			lcd_data <= `CYAN;
		else
			lcd_data <= `ROYAL;
		end
end
`endif

`ifdef VGA_VERTICAL_COLOR
always@(posedge clk or negedge rst_n)
begin
	if(!rst_n)
		lcd_data <= 24'h0;
	else
		begin
		if	(lcd_xpos >= 0 && lcd_xpos < (H_DISP/8)*1)
			lcd_data <= `RED;
		else if(lcd_xpos >= (H_DISP/8)*1 && lcd_xpos < (H_DISP/8)*2)
			lcd_data <= `GREEN;
		else if(lcd_xpos >= (H_DISP/8)*2 && lcd_xpos < (H_DISP/8)*3)
			lcd_data <= `BLUE;
		else if(lcd_xpos >= (H_DISP/8)*3 && lcd_xpos < (H_DISP/8)*4)
			lcd_data <= `WHITE;
		else if(lcd_xpos >= (H_DISP/8)*4 && lcd_xpos < (H_DISP/8)*5)
			lcd_data <= `BLACK;
		else if(lcd_xpos >= (H_DISP/8)*5 && lcd_xpos < (H_DISP/8)*6)
			lcd_data <= `YELLOW;
		else if(lcd_xpos >= (H_DISP/8)*6 && lcd_xpos < (H_DISP/8)*7)
			lcd_data <= `CYAN;
		else
			lcd_data <= `ROYAL;
		end
end
`endif

`ifdef VGA_GRAFTAL_GRAPH
always@(posedge clk or negedge rst_n)
begin
	if(!rst_n)
		lcd_data <= 24'h0;
	else
		lcd_data <= lcd_xpos * lcd_ypos;
end
`endif


`ifdef VGA_GRAY_GRAPH
always@(posedge clk or negedge rst_n)
begin
	if(!rst_n)
		lcd_data <= 24'h0;
	else
		begin
		if(lcd_ypos < V_DISP/2)
			lcd_data <= {lcd_ypos[7:0], lcd_ypos[7:0], lcd_ypos[7:0]};
		else
			lcd_data <= {lcd_xpos[7:0], lcd_xpos[7:0], lcd_xpos[7:0]};
		end
end
`endif

endmodule

    以下为四种显示效果,后续在发个VGA显示的【实战篇】,通过按键控制VGA输出显示的模式(下图四种),另一个按键控制VGA输出的分辨率:

image.png  

本帖最后由 1nnocent 于 2022-7-30 09:19 编辑

回复评论 (4)

四种显示效果可以,期待后续的VGA显示测试

点赞  2022-8-4 07:35
引用: Jacktang 发表于 2022-8-4 07:35 四种显示效果可以,期待后续的VGA显示测试

安路这款FPGA的PLL IP核产生的频率不是很准,目前只能做两种分辨率的切换,本来想做个四五种分辨率按键控制切换的,产生的五个时钟,只有两个符合要求

点赞  2022-8-4 08:55
引用: 1nnocent 发表于 2022-8-4 08:55 安路这款FPGA的PLL IP核产生的频率不是很准,目前只能做两种分辨率的切换,本来想做个四五种分辨率按键控 ...

PLL IP不能生成任意频率,有些频率会存在误差,具体可以从IP配置界面里看到。

点赞  2022-8-4 09:40
引用: littleshrimp 发表于 2022-8-4 09:40 PLL IP不能生成任意频率,有些频率会存在误差,具体可以从IP配置界面里看到。

好的

 

 

了解

 

点赞  2022-8-4 11:09
电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 京公网安备 11010802033920号
    写回复