IIC通信协议详解 & PCF8591应用(Verilog实现FPGA)

这篇具有很好参考价值的文章主要介绍了IIC通信协议详解 & PCF8591应用(Verilog实现FPGA)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

IIC通信协议详解 & PCF8591应用(Verilog实现/FPGA)

该文章结合PCF8591 8-bit AD/DA 模数/数模转换器来详细介绍IIC通信协议,尽量做到条理清晰,通俗易懂。该文图片均从PCF8591手册中截取,一定程度上引导读者学习阅读data sheet。

之后可能会更新如何将IIC的Verilog实现变为一个IP核,并在pynq-Z2板子上使用

1. PCF8591引脚

IIC通信协议详解 & PCF8591应用(Verilog实现FPGA)

2. 功能介绍

2.1 地址位

在I2C总线系统中,每个PCF8591设备都通过发送一个有效地址来激活。地址由固定部分可编程部分组成。可编程部分必须根据地址引脚A0、A1和A2进行设置。在I2C总线协议中,地址必须始终作为起始条件后的第一个字节发送。地址字节的最后一位是读/写位,它设置了后续数据传输的方向
IIC通信协议详解 & PCF8591应用(Verilog实现FPGA)
IIC通信协议详解 & PCF8591应用(Verilog实现FPGA)

在市面上我们所购买到的PCF8591 模数/数模转换器已经被集成到了PCB板上,根据博主的调研,其PCB的原理图绘制如上图所示。

其中A0,A1,A2均接GND,所以在进行DA转换时,发送的写地址位应为8‘h90;在进行AD转换时,先发送的写地址位应为8’h90,再发送读地址位8‘h91

2.2 控制位

发送到PCF8591设备的第二个字节将被存储在其控制寄存器中,并用于控制设备功能。

控制寄存器的高四位用于启用模拟输出,并将模拟输入编程为单端或差分输入低四位选择由高四位定义的一个模拟输入通道

如果设置了自动递增标志,每次A/D转换后通道号将自动递增。

  • 如下图所示,假设我们要进行D/A转换,即将数字信号输入转换为模拟信号输出

    1. 我们要允许模拟输出,即把control byte第6位(从0开始,从右往左数)设为1
    2. 此时不需要模拟信号输入,所以将第5位和第4位均设为0
    3. 第1位和第0位为选择的模拟输出通道,在这里我们选择channel 0通道输出,则第1位和第0位为00
    4. 不需要自动递增,则将第2位设为0

    综上所述,我们需要发送的control byte为8‘b0100_0000,即8’h40

  • 假设我们要进行A/D转换,即将模拟信号输入转换为数字信号输出

    1. 我们要关闭模拟输出,即把control byte第6位(从0开始,从右往左数)设为0
    2. 此时需要模拟信号输入选择每个信号均为单通道输入,所以将第5位和第4位均设为0
    3. 第1位和第0位为选择的模拟输入通道,在这里我们选择channel 0通道输出,则第1位和第0位为00
    4. 不需要自动递增,则将第2位设为0

    综上所述,我们需要发送的control byte为8‘b0000_0000,即8’h00

IIC通信协议详解 & PCF8591应用(Verilog实现FPGA)

2.3 D/A 转换

发送到PCF8591设备的第三个字节存储在DAC数据寄存器中,并使用芯片内的D/A转换器将其转换为相应的模拟电压

D/A转换序列的波形如下图所示。在PCF8591的D/A转换中,我们需要先发送写地址位8’h90,再发送控制位8‘h40,最后再发送想要转换的数字信号数据

IIC通信协议详解 & PCF8591应用(Verilog实现FPGA)

2.4 A/D 转换

A/D转换器采用逐次逼近转换技术。在发送有效的读取模式地址到PCF8591设备后,始终会启动A/D转换周期。 A/D转换周期在应答时钟脉冲的下降沿触发,并在传输上一次转换的结果时执行详情参见下图所示

一旦触发了转换周期,所选通道的输入电压样本将存储在芯片上,并转换为相应的8位二进制代码。

转换结果存储在ADC数据寄存器中,并等待传输。如果设置了自动递增标志,则选择下一个通道。

A/D转换序列的波形如下图所示。

IIC通信协议详解 & PCF8591应用(Verilog实现FPGA)

3. D/A转换及IIC通信协议波形详解

在有了上面的基本认识后,我们开始详细介绍IIC通信协议中SDA和SCL的波形。

开始D/A转换时,即IIC通信中主设备向从设备发送信号,我们需要模拟主设备的SDA和SCL信号

在该例子中主设备可以为包括FPGA在内的任意设备,而从设备为PCF8591,其他从设备也可以类比

3.1 空闲状态

如下图所示,在总线空闲时,数据线(SDA)时钟线(SCL)都保持高电平

3.2 开始状态(START condition)

如下图所示,当D/A转换开始时,时钟线(SCL)仍为高电平数据线(SDA)从高电平跳变到低电平,这被定义为起始条件

从设备在检测到起始条件后,会等待主设备发送地址和读/写位,以确定是否需要参与到通信过程中。

IIC通信协议详解 & PCF8591应用(Verilog实现FPGA)

3.3 写状态

2.1部分及2.2部分所说,在PCF8591的D/A转换中,我们需要先发送写地址位8’h90,再发送控制位8‘h40,最后再发送想要转换的数字信号数据

归根结底,我们向从机发送地址位、控制位和数字信号数据其实都是在向从机写入数据,并且每个数据都是8-bit,所以在这三个阶段时钟线(SCL)数据线(SDA)所遵循的规则是一样的

IIC通信协议详解 & PCF8591应用(Verilog实现FPGA)
IIC通信协议详解 & PCF8591应用(Verilog实现FPGA)
现在详细解释一下,在一个发送8-bit数据时,SDA和SCL的信号变化(数据由高位到低位传输)。

在IIC通信协议中,发送数据过程中,SCL信号为低电平时SDA信号可以发生变化,而在SCL为高电平时,SDA信号有效,应保持不变,所以SDA信号1bit、1bit地传输数据时,SCL信号也对应地由低变高再由低边高。

!!!!!!!!!!!!!!!!!!!以下开始为重点!!!!!!!!!!!!!!!!!!!!

如上图所示,承接开始状态时SDA从高电平跳转为低电平,向从机发送数据,写状态开始。

  1. 在开始状态SDA变为低电平后,SCL需要至少经过 t H D ;   S T A t_{HD;\ STA} tHD; STA μ s \mu s μs,即4.7 μ s \mu s μs后才能下拉为低电平
  2. 并且SCL低电平的时间至少要持续 t L O W t_{LOW} tLOW μ s \mu s μs,即4.7 μ s \mu s μs才能再度变为高电平,在这期间,SDA开始传输数据,即这段时间内SDA信号可以发生变化。
  3. t L o w t_{Low} tLow μ s \mu s μs过去后,SCL变为高电平,此时SDA信号有效(即SDA所表示的0、1数据被写入从机对应的寄存器中),SCL高电平的时间至少维持 t H I G H t_{HIGH} tHIGH μ s \mu s μs,即4.0 μ s \mu s μs。在此期间SDA信号不能发生改变,否则会导致信号传输错误
  4. t H I G H t_{HIGH} tHIGH μ s \mu s μs过去后,SCL又变为低电平,此时进入下 1 bit的传输,重复1~4步骤的内容,循环8次,直到传输完8-bit的数据为止(每循环一次代表传输1bit)
  5. 传输完8-bit的数据后从机会返回一个Acknowledge信号(ACK信号,即应答信号),此时主机应该释放SDA信号线特别注意,SDA信号线是inout类型,可以由外部传输数据进来,也可以由内部传输信号出去),以便从机控制SDA信号线传输ACK信号(应答信号),即该阶段的SDA信号线表示的是ACK信号。如果ACK信号为低电平时(因为SDA信号线在默认状态下会被拉为高电平,所以将下拉为低电平作为有效信号),说明传输成功,可以继续进行下一个8-bit数据的传输或是结束传输,转为终止状态(STOP condition);如果ACK信号为高电平时,说明传输失败,转为空闲状态。
  6. 接收ACK信号时,SCL信号和在发送1 bit的数据时一样,先经过 t L O W t_{LOW} tLOW μ s \mu s μs变为高电平,再经过 t H I G H t_{HIGH} tHIGH μ s \mu s μs后变为低电平,此时ACK信号接收完成。

3.4 终止状态(STOP)

在终止状态时SCL信号先变成高电平,在至少经过 t S U ;   S T O t_{SU;\ STO} tSU; STO μ s \mu s μs,即4.0 μ s \mu s μs后SDA才能变为高电平,至此D/A转换结束,IIC通信协议结束。

3.5 D/A转换代码(Verilog实现)

module DAC_I2C
(
	input				clk_in,		//系统时钟
	input				rst_n_in,	//系统复位,低有效
 
	output	reg			dac_done,	//DAC采样完成标志
	input		[7:0]	dac_data,	//DAC采样数据
 
	output				scl_out,	//I2C总线SCL
	inout				sda_out		//I2C总线SDA
);
 
	parameter	CNT_NUM	=	15;
 
	localparam	IDLE	=	3'd0;
	localparam	MAIN	=	3'd1;
	localparam	START	=	3'd2;
	localparam	WRITE	=	3'd3;
	localparam	STOP	=	3'd4;
 
	//根据PCF8591的datasheet,I2C的频率最高为100KHz,
	//我们准备使用4个节拍完成1bit数据的传输,所以需要400KHz的时钟触发完成该设计
	//使用计数器分频产生400KHz时钟信号clk_400khz
	//其中CNT_NUM控制分配器的分频,例如如果FPGA的时钟为50MHz,则CNT_NUM = 125,因为400K*125 = 50MHz
	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		[2:0]		cnt_main;
	reg		[7:0]		data_wr;
	reg		[2:0]		cnt_start;
	reg		[2:0]		cnt_write;
	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 <= 1'b0;
			cnt_start <= 1'b0;
			cnt_write <= 3'd0;
			cnt_stop <= 1'd0;
			dac_done <= 1'b1;
			state <= IDLE;
		end else begin
			case(state)
				IDLE:begin	//软件自复位,主要用于程序跑飞后的处理
						scl_out_r <= 1'd1;
						sda_out_r <= 1'd1;
						cnt <= 1'b0;
						cnt_main <= 1'b0;
						cnt_start <= 1'b0;
						cnt_write <= 3'd0;
						cnt_stop <= 1'd0;
						dac_done <= 1'b1;
						state <= MAIN;
					end
				MAIN:begin
						if(cnt_main >= 3'd3) cnt_main <= 3'd3;  //对MAIN中的子状态执行控制cnt_main
						else cnt_main <= cnt_main + 1'b1;
						case(cnt_main)
							3'd0:	begin state <= START; end	//I2C通信时序中的START
							3'd1:	begin data_wr <= 8'h90; state <= WRITE; end	//A0,A1,A2都接了GND,写地址为8'h90
							3'd2:	begin data_wr <= 8'h40; state <= WRITE; end	//control byte为8'h40,打开DAC功能
							3'd3:	begin data_wr <= dac_data; state <= WRITE; dac_done <= 1'b0; end	//需要进行DAC转换的数据
							3'd4:	begin state <= STOP; end	//I2C通信时序中的结束STOP
							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; dac_done <= 1'b1; 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
				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

4. A/D转换及IIC通信协议波形详解

4.1 A/D转换过程

A/D转换过程与D/A转换类似,只不过A/D转换比D/A多了一个读状态,用来读取由模拟信号转化为数字信号的数据

  1. 设置从机工作状态:A/D转换先从空闲状态开始,再进入开始状态,再进入写状态,如2.1部分及2.2部分所说,我们先要发送写地址位8’h90,再发送控制位8‘h00,此时从机(PCF8591)被设置成A/D转换模式,此时模拟输入从channel 0通道进入,再进入终止状态结束此次通信。
  2. 读取从机返回数据:IIC通信再进入开始状态,再进入写状态,在写状态中如2.1部分所说,将读地址位8’h91写入从机对应的寄存器中,此时SDA信号线的数据即从机信号返回的数字信号的数据,然后IIC通信再进入读状态

如果读者看到这里觉得很绕,没关系,文章最后的代码将状态转换写得十分清楚,下面我们先来讲解读状态。

4.2 读状态

读状态和写状态类似,一次也是读8-bit的数据,其中SCL信号的变化和写状态一致SDA信号则要在读状态开始时设为1’bz,即主机释放SDA信号线,使得从机能够控制SDA信号线返回数字信号的数据(返回数据也由高到低返回),并且在每读完一个8-bit的数据后,主机应向从机发送ACK信号,即把SDA信号线拉低,表示传输成功,在这期间需要把SCL信号拉高并且维持 4.7 μ s 4.7 \mu s 4.7μs

4.3 A/D转换代码(Verilog实现)

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
  	//其中CNT_NUM控制分配器的分频,例如如果FPGA的时钟为50MHz,则CNT_NUM = 125,因为400K*125 = 50MHz
	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

5.总结

如果读者有何疑问欢迎在评论区下面评论,或者博主有哪些写错的地方也欢迎指正。文章来源地址https://www.toymoban.com/news/detail-441611.html

到了这里,关于IIC通信协议详解 & PCF8591应用(Verilog实现FPGA)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 蓝桥杯单片机—— PCF8591的基本原理及A/D转换应用(14)

    一、原理分析 1、基本概念 PCF8591是一个单片集成、单独供电、低功耗、 8-bit CMOS数据获取器件 。PCF8591具有 4个模拟输入、1个模拟输出和1个串行I²C总线接口 。PCF8591的3个地址引脚 A0, A1和A2可用于硬件地址编程 ,允许在同个I2C总线上接入8个PCF8591器件,而无需额外的硬件。在

    2023年04月14日
    浏览(42)
  • 详解通信协议之IIC通信协议

    本文结合AT24C02对IIC通信协议原理进行了描述。 IIC通讯协议(Inter-Integrated Circuit)是由 Philips 公司开发双向同步半双工串行总线,只需要两根线(SDA、SCL)即可在连接于总线上的器件之间传送信息。IIC总线是一种共享的串行总线,是用于两个设备之间的短距离低速速率(250K左右)通

    2024年02月08日
    浏览(24)
  • 【FPGA协议篇】UART通信及其verilog实现(代码采用传参实现模块通用性,适用于快速开发)

    ​ 即通用异步收发器(Universal Asynchronous Receiver/Transmitter),是一种 串行、异步、全双工 的通信协议。特点是通信线路简单,适用于远距离通信,但传输速度慢。 数据传输速率:波特率(单位:baud,波特) 常见波特率有:1200、2400、4800、19200、38400、57600等,最常用的是9600和11520

    2024年02月05日
    浏览(33)
  • 详解UART通信协议以及FPGA实现

      从《浅谈UART,TTL,RS-232,RS-485的区别》这篇文章,我们知道了UART是一种串行、异步、全双工的通信协议,属于协议层;传输过程一般采用RS-232,RS-485电平标准,将所需传输的数据一位接一位地传输;整体传输框架如下:   串口通信由发送端和接收端构成,两根信号线

    2024年04月28日
    浏览(27)
  • FPGA协议篇:UART通信及Verilog最易懂实现方式/通用于任何工程/带握手信号 ----UART_TX

            UART(Universal Asynchronous Receiver/Transmitter)是一种通用的 异步收发传输协议 ,用于在计算机系统和外部设备之间进行串行数据传输。UART 协议定义了数据的传输格式和通信规则,使得不同设备之间能够进行可靠的数据交换。 首先先把设计代码放到这里: UART_TX完整代

    2024年03月27日
    浏览(37)
  • AD(DA)芯片PCF8591使用介绍

    AD (DA)芯片PCF8591简介       PCF8591是具有I2C 总线接口的8 位A/D 及D/A 转换器。PCF8591有4个模拟输入、1个模拟输出和1个I²C 总线接口 。PCF8591有3个地址 引脚 A0, A1和A2,用于硬件地址设定,这允许在一条I2C总线上接入8个PCF8591器件。       PCF8591 实物如下图所示: AD (DA)芯片P

    2024年02月01日
    浏览(28)
  • 蓝桥杯单片机(十一)PCF8591(A/D转换)

    PCF8591是一个单片集成、单独供电、低功耗、8-bit CMOS数据获取器件。PCF8591具有4个模拟输入、1个模拟输出和1个串行IIC总线接口。 首先蓝桥杯单片机开发板上的原理图部分 其中A0-A2为地址线,主要功能是当有多个PCF8591时,可将A0-A2按不同的最小项来区分不同PCF8591,比如第一个

    2023年04月08日
    浏览(25)
  • 蓝桥杯单片机(十二)PCF8591(D/A转换)

    接上一篇 蓝桥杯单片机(十一)PCF8591(A/D转换) 首先看蓝桥杯单片机开发板的原理图 AOUT是模拟输出端,AGND为模拟信号地。 模拟参考电压为Vref两端的电压。 由于Vref接到了VCC(5V),所以模拟输出就是以5V为参考电压。 由于PCF8591是8位数模转换,所以 比如0000 00001就是5/255伏。 由于进

    2023年04月09日
    浏览(27)
  • 【接口协议】FPGA实现IIC协议对EEPROM进行数据存储与读取(AT24C64)

    使用vivado实现IIC协议对EEPROM进行数据存储与读取。本文是基于正点原子的“达芬奇”开发板资料进行学习的笔记,对部分地方进行了修改,并进行了详细的讲解。 IIC(Inter-Integrated Circuit),即集成电路总线,是一种同步半双工串行总线,用于连接微控制器及外围设备,是用于数据

    2024年02月04日
    浏览(62)
  • PCF8591芯片的AD/DA转换(适用于蓝桥杯单片机)

    目录 1、PCF8591的引脚介绍  2、用IIC发送或接收字节              发送的第一个字节         发送的第二个字节   发送第三个字节     3.发送和接收的字节转换  4.全部代码 IIC部分 接收电压数据 发送电压数据                      在和单片机实际应用中它的

    2024年02月05日
    浏览(28)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包