**这是本文档旧的修订版!**

基于STEP FPGA的8色VGA功能驱动

本节将和大家一起使用FPGA驱动底板上的8色VGA接口实现8色彩条显示功能。

====硬件说明====

VGA(video graphics array)即视频图形阵列,是IBM在1987年随PS/2一起推出的使用模拟信号的一种视频传输标准。VGA接口分公口和母口,如下图:

VGA接口引脚定义如下:

一个标准的VGA接口应该有以下端口:

  • 红绿蓝三色信号(R\G\B)
  • 行场同步信号(HS\VS)
  • 以及很多的地屏蔽;

三色信号都是模拟信号,行场同步信号都是数字信号;
对于VGA的接口模拟电压,为0~0.714V,0代表无色,0.714代表满色,FPGA输出3.3V,所以还必须要经过DAC的转换。现今有两种比较成熟的方法:电阻分压方式和DAC转换方式。
我们的底板上就是采用的电阻分压的方式,因VGA显示器端有75欧的下拉电阻,为了得到0.714V的电压我们给RGB信号线上串入270欧的电阻,3.3V*75/(270+75)=0.717V。如下

VGA驱动显示器用的是扫描的方式,逐行扫描the HS (Horizontal Synchronization)逐行扫描是扫描从屏幕的左上角一点开始,由左向右逐点扫描,每扫描完一行,电子束回到屏幕的左边下一行的起始位置,在这期间CRT(阴极射线显像管)对电子束进行消隐,每行结束时,用行同步信号进行同步;当扫描完所有行之后形成一帧,用场同步信号进行同步,并使扫描回到屏幕左上方,同时进行场消隐,开始下一帧。VGA一直在扫描,每一场的扫描包括了若干行扫描,依次循环;
VGA显示时序如下:
VGA显示区域和消隐区域:

====Verilog代码====

// --------------------------------------------------------------------
// >>>>>>>>>>>>>>>>>>>>>>>>> COPYRIGHT NOTICE <<<<<<<<<<<<<<<<<<<<<<<<<
// --------------------------------------------------------------------
// Module: ADC_I2C
// 
// Author: Step
// 
// Description: ADC_I2C
// 
// Web: www.stepfpga.com
//
// --------------------------------------------------------------------
// Code Revision History :
// --------------------------------------------------------------------
// Version: |Mod. Date:   |Changes Made:
// V1.1     |2016/10/30   |Initial ver
// --------------------------------------------------------------------
module ADC_I2C
(
	input				clk_in,		//系统时钟
	input				rst_n_in,	//系统复位,低有效
	output				scl_out,	//I2C总线SCL
	inout				sda_out,	//I2C总线SDA
	output	reg			adc_done,	//ADC采样完成标志
	output	reg	[7:0]	adc_data	//ADC采样数据
);
 
	parameter	CNT_NUM	=	15;
 
	localparam	IDLE	=	3'd0;
	localparam	MAIN	=	3'd1;
	localparam	START	=	3'd2;
	localparam	WRITE	=	3'd3;
	localparam	READ	=	3'd4;
	localparam	STOP	=	3'd5;
 
	//根据PCF8591的datasheet,I2C的频率最高为100KHz,
	//我们准备使用4个节拍完成1bit数据的传输,所以需要400KHz的时钟触发完成该设计
	//使用计数器分频产生400KHz时钟信号clk_400khz
	reg					clk_400khz;
	reg		[9:0]		cnt_400khz;
	always@(posedge clk_in or negedge rst_n_in) begin
		if(!rst_n_in) begin
			cnt_400khz <= 10'd0;
			clk_400khz <= 1'b0;
		end else if(cnt_400khz >= CNT_NUM-1) begin
			cnt_400khz <= 10'd0;
			clk_400khz <= ~clk_400khz;
		end else begin
			cnt_400khz <= cnt_400khz + 1'b1;
		end
	end
 
	reg		[7:0]		adc_data_r;
	reg					scl_out_r;
	reg					sda_out_r;
	reg		[2:0]		cnt;
	reg		[3:0]		cnt_main;
	reg		[7:0]		data_wr;
	reg		[2:0]		cnt_start;
	reg		[2:0]		cnt_write;
	reg		[4:0]		cnt_read;
	reg		[2:0]		cnt_stop;
	reg		[2:0] 		state;
 
	always@(posedge clk_400khz or negedge rst_n_in) begin
		if(!rst_n_in) begin	//如果按键复位,将相关数据初始化
			scl_out_r <= 1'd1;
			sda_out_r <= 1'd1;
			cnt <= 1'b0;
			cnt_main <= 4'd0;
			cnt_start <= 3'd0;
			cnt_write <= 3'd0;
			cnt_read <= 5'd0;
			cnt_stop <= 1'd0;
			adc_done <= 1'b0;
			adc_data <= 1'b0;
			state <= IDLE;
		end else begin
			case(state)
				IDLE:begin	//软件自复位,主要用于程序跑飞后的处理
						scl_out_r <= 1'd1;
						sda_out_r <= 1'd1;
						cnt <= 1'b0;
						cnt_main <= 4'd0;
						cnt_start <= 3'd0;
						cnt_write <= 3'd0;
						cnt_read <= 5'd0;
						cnt_stop <= 1'd0;
						adc_done <= 1'b0;
						state <= MAIN;
					end
				MAIN:begin
						if(cnt_main >= 4'd6) cnt_main <= 4'd6;  //对MAIN中的子状态执行控制cnt_main
						else cnt_main <= cnt_main + 1'b1;
						case(cnt_main)
							4'd0:	begin state <= START; end	//I2C通信时序中的START
							4'd1:	begin data_wr <= 8'h90; state <= WRITE; end	//A0,A1,A2都接了GND,写地址为8'h90
							4'd2:	begin data_wr <= 8'h00; state <= WRITE; end	//control byte为8'h00,采用4通道ADC中的通道0
							4'd3:	begin state <= STOP; end	//I2C通信时序中的START
							4'd4:	begin state <= START; end	//I2C通信时序中的STOP
							4'd5:	begin data_wr <= 8'h91; state <= WRITE; end	//A0 A1 A2都接了GND,读地址为8'h91
							4'd6:	begin state <= READ; adc_done <= 1'b0; end	//读取ADC的采样数据
							4'd7:	begin state <= STOP; adc_done <= 1'b1; end	//I2C通信时序中的STOP,读取完成标志
							4'd8:	begin state <= MAIN; end	//预留状态,不执行
							default: state <= IDLE;	//如果程序失控,进入IDLE自复位状态
						endcase
					end
				START:begin	//I2C通信时序中的起始START
						if(cnt_start >= 3'd5) cnt_start <= 1'b0;	//对START中的子状态执行控制cnt_start
						else cnt_start <= cnt_start + 1'b1;
						case(cnt_start)
							3'd0:	begin sda_out_r <= 1'b1; scl_out_r <= 1'b1; end	//将SCL和SDA拉高,保持4.7us以上
							3'd1:	begin sda_out_r <= 1'b1; scl_out_r <= 1'b1; end	//clk_400khz每个周期2.5us,需要两个周期
							3'd2:	begin sda_out_r <= 1'b0; end	//SDA拉低到SCL拉低,保持4.0us以上
							3'd3:	begin sda_out_r <= 1'b0; end	//clk_400khz每个周期2.5us,需要两个周期
							3'd4:	begin scl_out_r <= 1'b0; end	//SCL拉低,保持4.7us以上
							3'd5:	begin scl_out_r <= 1'b0; state <= MAIN; end	//clk_400khz每个周期2.5us,需要两个周期,返回MAIN
							default: state <= IDLE;	//如果程序失控,进入IDLE自复位状态
						endcase
					end
				WRITE:begin	//I2C通信时序中的写操作WRITE和相应判断操作ACK
						if(cnt <= 3'd6) begin	//共需要发送8bit的数据,这里控制循环的次数
							if(cnt_write >= 3'd3) begin cnt_write <= 1'b0; cnt <= cnt + 1'b1; end
							else begin cnt_write <= cnt_write + 1'b1; cnt <= cnt; end
						end else begin
							if(cnt_write >= 3'd7) begin cnt_write <= 1'b0; cnt <= 1'b0; end	//两个变量都恢复初值
							else begin cnt_write <= cnt_write + 1'b1; cnt <= cnt; end
						end
						case(cnt_write)
							//按照I2C的时序传输数据
							3'd0:	begin scl_out_r <= 1'b0; sda_out_r <= data_wr[7-cnt]; end	//SCL拉低,并控制SDA输出对应的位
							3'd1:	begin scl_out_r <= 1'b1; end	//SCL拉高,保持4.0us以上
							3'd2:	begin scl_out_r <= 1'b1; end	//clk_400khz每个周期2.5us,需要两个周期
							3'd3:	begin scl_out_r <= 1'b0; end	//SCL拉低,准备发送下1bit的数据
							//获取从设备的响应信号并判断
							3'd4:	begin sda_out_r <= 1'bz; end	//释放SDA线,准备接收从设备的响应信号
							3'd5:	begin scl_out_r <= 1'b1; end	//SCL拉高,保持4.0us以上
							3'd6:	begin if(sda_out) state <= IDLE; else state <= state; end	//获取从设备的响应信号并判断
							3'd7:	begin scl_out_r <= 1'b0; state <= MAIN; end	//SCL拉低,返回MAIN状态
							default: state <= IDLE;	//如果程序失控,进入IDLE自复位状态
						endcase
					end
				READ:begin	//I2C通信时序中的读操作READ和返回ACK的操作
						if(cnt <= 3'd6) begin	//共需要接收8bit的数据,这里控制循环的次数
							if(cnt_read >= 3'd3) begin cnt_read <= 1'b0; cnt <= cnt + 1'b1; end
							else begin cnt_read <= cnt_read + 1'b1; cnt <= cnt; end
						end else begin
							if(cnt_read >= 3'd7) begin cnt_read <= 1'b0; cnt <= 1'b0; end	//两个变量都恢复初值
							else begin cnt_read <= cnt_read + 1'b1; cnt <= cnt; end
						end
						case(cnt_read)
							//按照I2C的时序接收数据
							3'd0:	begin scl_out_r <= 1'b0; sda_out_r <= 1'bz; end	//SCL拉低,释放SDA线,准备接收从设备数据
							3'd1:	begin scl_out_r <= 1'b1; end	//SCL拉高,保持4.0us以上
							3'd2:	begin adc_data_r[7-cnt] <= sda_out; end	//读取从设备返回的数据
							3'd3:	begin scl_out_r <= 1'b0; end	//SCL拉低,准备接收下1bit的数据
							//向从设备发送响应信号
							3'd4:	begin sda_out_r <= 1'b0; adc_done <= 1'b1; adc_data <= adc_data_r; end	//发送响应信号,将前面接收的数据锁存
							3'd5:	begin scl_out_r <= 1'b1; end	//SCL拉高,保持4.0us以上
							3'd6:	begin scl_out_r <= 1'b1; adc_done <= 1'b0; end	//SCL拉高,保持4.0us以上
							3'd7:	begin scl_out_r <= 1'b0; state <= MAIN; end	//SCL拉低,返回MAIN状态
							default: state <= IDLE;	//如果程序失控,进入IDLE自复位状态
						endcase
					end
				STOP:begin	//I2C通信时序中的结束STOP
						if(cnt_stop >= 3'd5) cnt_stop <= 1'b0;	//对STOP中的子状态执行控制cnt_stop
						else cnt_stop <= cnt_stop + 1'b1;
						case(cnt_stop)
							3'd0:	begin sda_out_r <= 1'b0; end	//SDA拉低,准备STOP
							3'd1:	begin sda_out_r <= 1'b0; end	//SDA拉低,准备STOP
							3'd2:	begin scl_out_r <= 1'b1; end	//SCL提前SDA拉高4.0us
							3'd3:	begin scl_out_r <= 1'b1; end	//SCL提前SDA拉高4.0us
							3'd4:	begin sda_out_r <= 1'b1; end	//SDA拉高
							3'd5:	begin sda_out_r <= 1'b1; state <= MAIN; end	//完成STOP操作,返回MAIN状态
							default: state <= IDLE;	//如果程序失控,进入IDLE自复位状态
						endcase
					end
				default:;
			endcase
		end
	end
 
	assign	scl_out = scl_out_r;	//对SCL端口赋值
	assign	sda_out = sda_out_r;	//对SDA端口赋值
 
endmodule

====小结====

本节主要为大家讲解了使用I2C驱动PCF8591的ADC功能的原理及软件设计,需要大家掌握的同时自己创建工程,通过整个设计流程,生成FPGA配置文件加载测试。
如果你对Diamond软件的使用不了解,请参考这里:Diamond的使用

====相关资料====


使用STEP-MXO2第二代的PCF8591的ADC驱动程序: 后续会有下载连接 待更新
使用STEP-MAX10的PCF8591的ADC驱动程序: 后续会有下载连接 待更新