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

基于STEP FPGA的PCF8591的ADC(I2C)功能驱动

本节将和大家一起使用FPGA驱动底板上的PCF8591的ADC采样(I2C)功能。

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

PCF8591是集成了4路ADC和1路DAC的芯片,使用I2C总线通信。
I2C总线是由Philips公司开发的一种简单、双向二线制同步串行总线。它只需要两根线即可在连接于总线上的器件之间传送信息。主器件用于启动总线传送数据,并产生时钟以开放传送的器件,此时任何被寻址的器件均被认为是从器件。如果主机要发送数据给从器件,则主机首先寻址从器件,然后主动发送数据至从器件,最后由主机终止数据传送;如果主机要接收从器件的数据,首先由主器件寻址从器件,然后主机接收从器件发送的数据,最后由主机终止接收过程。这里不做过多的讲解,硬件连接如下:
本设计的硬件连接如下
本设计中FPGA作为I2C主设备,PCF8591作为I2C从设备,从设备的地址由固定地址和可编程地址组成,我们的外设底板已将可编程地址A0、A1、A2接地,所以7位地址为7'h48,加上最低位的读写控制,所以给PCF8591写数据时的寻址地址为8'h90,对PCF8591读数据时的寻址地址为8'h91。如下
PCF8591集成了很多功能,当需要不同的功能时要对PCF8591做相应的配置,配置数据存储在名为CONTROL BYTE的寄存器中,下图展示了寄存器中部分bit的功能,详细请参考PCF8591的datasheet,本设计中我们只使用通道1的ADC功能,配置数据为8'h01。
本设计中我们需要两次通信,

  • 第一次为配置数据,具体为:开始–写寻址–读响应–写配置数据–读响应–结束
  • 第二次为读ADC数据,具体为:开始–读寻址–读响应–[读ADC数据–写响应–]循环读

第二次的时序如下图:
通过上面的介绍大家应该对如何驱动PCF8591进行ADC采样有了整体的概念,还有一些细节就是I2C通信的时序明细,如下图

====Verilog代码====

<code verilog> ——————————————————————–

>>>>>>>>>>>>>>>>>>>>>>>>> COPYRIGHT NOTICE <<<<<<<<<<<<<<<<<<<<<<<<<

/

/ ——————————————————————– Module: ADCI2C Author: Step Description: ADCI2C Web: www.stepfpga.com ——————————————————————– Code Revision History : ——————————————————————– Version: |Mod. Date: |Changes Made: V1.1 |2016/10/30 |Initial ver ——————————————————————– module ADCI2C ( input clkin, 系统时钟 input rstnin, 系统复位,低有效

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 <end code>

====小结====

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

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


使用STEP-MXO2第二代的UART通信程序: 后续会有下载连接 待更新
使用STEP-MAX10的UART通信程序: 后续会有下载连接 待更新