FPGA实现I2C时序详解

搞懂SPI时序后再来跟着小梅哥学习I2C时序,想要详细理解的可以看下http://training.eeworld.com.cn/video/22863。I2C时序也花了一两天的时间才从搞懂时序到看懂代码。这里记录一下学习过程中的迷惑。
I2C总线是由两根线组成:一根SCL,一根SDA,可以实现少量数据的读写,例如对某一外设进行配置,这种情况是对该外设某些寄存器进行配置。不宜进行大量数据的读写。
FPGA实现I2C时序详解_第1张图片
I2C时序的特点:在SCL位高电平时对数据采样,低电平时数据发生变化。
FPGA实现I2C时序详解_第2张图片
I2C的读写形式是多种多样的:

1)1个地址1字节的数据写时序;START可参考图1。控制位高四位位1010,第0位是控制I2C读或写的,为1时读,为0时写。ACK信号为应答位,可以理解为收到传输的8bit数据后从机反馈给主机的信号,告诉主机已收到主机发送的信号。1个字节的地址位,1个字节的数据位,在主机发送1个字节的数据后从机都会反馈给主机一个讯息。最后有一位停止位,时主机发给从机的,时序可参考图1.

FPGA实现I2C时序详解_第3张图片
I2C的难处在于它的灵活,当从机的地址位数超过8位时需要调整地址位的宽度,当数据位超过1个自己时也要调整数据位的宽度。如下图:从机的寄存器地址位宽大于256,需要更大的地址来寻址,所以调整为2字节的地址为。同意,在从机接收到信息后都会反馈给主机一个ACK信号,告诉主机信号接收成功。

FPGA实现I2C时序详解_第4张图片
同样还有多字节的数据,如下图所示:
FPGA实现I2C时序详解_第5张图片
以上是对从机进行写数据,下面来介绍从从机中读数据:
先来接收1字节地址和1字节数据的情况。同样具有开始位、结束位和应答位,此时的应答位是由主机向从机发出的。主机在发送控制位和要从什么寄存器中读取数据之后发出一个ACK,此时没有停止位,而是发送控制位,告诉从机此时要读该地址的数据了。读完从机的1字节数据后任然有一位空余,不过不在关心该空余位的状态,之后再产生停止位。在读的过程中需要两次发送控制位,第一次发送写信号,告诉从机,我要读这个地址的数据。在发送控制位,告诉从机可以开始读了。
FPGA实现I2C时序详解_第6张图片
当然I2C总线还可以读取地址大于1个字节的多字节数据。
FPGA实现I2C时序详解_第7张图片
经过以上分析,可以写代码了,首先我们可能想到的是序列机,当然序列机可以实现某一特定情况下的I2C功能,但是如使用序列机则不宜移植,功能单一。那么就需要总结以上都有什么样的共同点。
1、开始位
2、8位数据位(控制位(读或写)、数据为、地址位)
3、ACK(产生或检查,含NOACK)
4、停止位
这么多状态,应该没有漏掉的吧。
所以使用状态机的可移植性比序列及的可移植性要强。
下面给出小梅哥的代码:


module i2c_bit_shift(
	Clk,
	Rst_n,
	
	Cmd,
	Go,
	Rx_DATA,
	Tx_DATA,
	Trans_Done,
	ack_o,
	i2c_sclk,
	i2c_sdat
);
	input Clk;
	input Rst_n;
	
	input [5:0]Cmd;
	input Go;
	output reg[7:0]Rx_DATA;
	input [7:0]Tx_DATA;
	output reg Trans_Done;
	output reg ack_o;
	output reg i2c_sclk;
	inout i2c_sdat;
	
	reg i2c_sdat_o;

	//系统时钟采用50MHz
	parameter SYS_CLOCK = 50_000_000;
	//SCL总线时钟采用400kHz
	parameter SCL_CLOCK = 400_000;
	//产生时钟SCL计数器最大值
	localparam SCL_CNT_M = SYS_CLOCK/SCL_CLOCK/4 - 1;
	
	reg i2c_sdat_oe;
	
	localparam 
		WR =  6'b000001,	// 写请求
		STA = 6'b000010,	//起始位请求
		RD =  6'b000100,	//读请求
		STO = 6'b001000,	//停止位请求
		ACK = 6'b010000,	//应答位请求
		NACK = 6'b100000;	//无应答请求
		
	reg [19:0]div_cnt;
	reg en_div_cnt;
	//计数器
	always@(posedge Clk or negedge Rst_n)
	if(!Rst_n)
		div_cnt <= 20'd0;
	else if(en_div_cnt)begin
		if(div_cnt < SCL_CNT_M)
			div_cnt <= div_cnt + 1'b1;
		else
			div_cnt <= 0;
	end
	else
		div_cnt <= 0;

	wire sclk_plus = div_cnt == SCL_CNT_M;
	
	assign i2c_sdat = i2c_sdat_oe?i2c_sdat_o:1'bz;
		
	reg [7:0]state;
	
	localparam
		IDLE = 		8'b00000001,
		GEN_STA = 	8'b00000010,
		WR_DATA = 	8'b00000100,
		RD_DATA = 	8'b00001000,
		CHECK_ACK = 8'b00010000,
		GEN_ACK = 	8'b00100000,
		GEN_STO = 	8'b01000000;
		
	reg [4:0]cnt;
	//状态机
	always@(posedge Clk or negedge Rst_n)
	if(!Rst_n)begin
		Rx_DATA <= 0;
		i2c_sdat_oe <= 1'd0;
		en_div_cnt <= 1'b0;
		i2c_sdat_o <= 1'd1;
		Trans_Done <= 1'b0;
		ack_o <= 0;
		state <= IDLE;
		cnt <= 0;
	end
	else begin
		case(state)
			IDLE:
			begin
				Trans_Done <= 1'b0;
				i2c_sdat_oe <= 1'd1;
				if(Go)begin
					en_div_cnt <= 1'b1;
					if(Cmd & STA)
						state <= GEN_STA;
					else if(Cmd & WR)
						state <= WR_DATA;
					else if(Cmd & RD)
						state <= RD_DATA;
					else
						state <= IDLE;
				end
				else begin
					en_div_cnt <= 1'b0;
					state <= IDLE;
				end
			end
				
			GEN_STA:
				begin
					if(sclk_plus)begin
						if(cnt == 3)
							cnt <= 0;
						else
							cnt <= cnt + 1'b1;
						case(cnt)//序列机
							0:begin i2c_sdat_o <= 1; i2c_sdat_oe <= 1'd1;end
							1:begin i2c_sclk <= 1;end
							2:begin i2c_sdat_o <= 0; i2c_sclk <= 1;end
							3:begin i2c_sclk <= 0;end
							default:begin i2c_sdat_o <= 1; i2c_sclk <= 1;end
						endcase
						if(cnt == 3)begin
							if(Cmd & WR)
								state <= WR_DATA;
							else if(Cmd & RD)
								state <= RD_DATA;
						end
					end
				end
				
			WR_DATA:
				begin
					if(sclk_plus)begin
						if(cnt == 31)
							cnt <= 0;
						else
							cnt <= cnt + 1'b1;
						case(cnt)
							0,4,8,12,16,20,24,28:begin i2c_sdat_o <= Tx_DATA[7-cnt[4:2]]; i2c_sdat_oe <= 1'd1;end	//set data;
							1,5,9,13,17,21,25,29:begin i2c_sclk <= 1;end	//sclk posedge
							2,6,10,14,18,22,26,30:begin i2c_sclk <= 1;end	//sclk keep high
							3,7,11,15,19,23,27,31:begin i2c_sclk <= 0;end	//sclk negedge
				
							default:begin i2c_sdat_o <= 1; i2c_sclk <= 1;end
						endcase
						if(cnt == 31)begin
							state <= CHECK_ACK;
						end
					end
				end
				
			RD_DATA:
				begin
					if(sclk_plus)begin
						if(cnt == 31)
							cnt <= 0;
						else
							cnt <= cnt + 1'b1;
						case(cnt)
							0,4,8,12,16,20,24,28:begin i2c_sdat_oe <= 1'd0; i2c_sclk <= 0;end	//set data;
							1,5,9,13,17,21,25,29:begin i2c_sclk <= 1;end	//sclk posedge
							2,6,10,14,18,22,26,30:begin i2c_sclk <= 1; Rx_DATA <= {Rx_DATA[6:0],i2c_sdat};end	//sclk keep high
							3,7,11,15,19,23,27,31:begin i2c_sclk <= 0;end	//sclk negedge						
							default:begin i2c_sdat_o <= 1; i2c_sclk <= 1;end
						endcase
						if(cnt == 31)begin
							state <= GEN_ACK;
						end
					end
				end
			
			CHECK_ACK:
				begin
					if(sclk_plus)begin
						if(cnt == 3)
							cnt <= 0;
						else
							cnt <= cnt + 1'b1;
						case(cnt)
							0:begin i2c_sdat_oe <= 1'd0; i2c_sclk <= 0;end
							1:begin i2c_sclk <= 1;end
							2:begin ack_o <= i2c_sdat; i2c_sclk <= 1;end
							3:begin i2c_sclk <= 0;end
							default:begin i2c_sdat_o <= 1; i2c_sclk <= 1;end
						endcase
						if(cnt == 3)begin
							if(Cmd & STO)
								state <= GEN_STO;
							else begin
								state <= IDLE;
								Trans_Done <= 1'b1;
							end								
						end
					end
				end
			
			GEN_ACK:
				begin
					if(sclk_plus)begin
						if(cnt == 3)
							cnt <= 0;
						else
							cnt <= cnt + 1'b1;
						case(cnt)
							0:begin 
									i2c_sdat_oe <= 1'd1;
									i2c_sclk <= 0;
									if(Cmd & ACK)
										i2c_sdat_o <= 1'b0;
									else if(Cmd & NACK)
										i2c_sdat_o <= 1'b1;
								end
							1:begin i2c_sclk <= 1;end
							2:begin i2c_sclk <= 1;end
							3:begin i2c_sclk <= 0;end
							default:begin i2c_sdat_o <= 1; i2c_sclk <= 1;end
						endcase
						if(cnt == 3)begin
							if(Cmd & STO)
								state <= GEN_STO;
							else begin
								state <= IDLE;
								Trans_Done <= 1'b1;
							end
						end
					end
				end
			
			GEN_STO:
				begin
					if(sclk_plus)begin
						if(cnt == 3)
							cnt <= 0;
						else
							cnt <= cnt + 1'b1;
						case(cnt)
							0:begin i2c_sdat_o <= 0; i2c_sdat_oe <= 1'd1;end
							1:begin i2c_sclk <= 1;end
							2:begin i2c_sdat_o <= 1; i2c_sclk <= 1;end
							3:begin i2c_sclk <= 1;end
							default:begin i2c_sdat_o <= 1; i2c_sclk <= 1;end
						endcase
						if(cnt == 3)begin
							Trans_Done <= 1'b1;
							state <= IDLE;
						end
					end
				end
			default:state <= IDLE;
		endcase
	end
	
endmodule

module i2c_control(
	Clk,
	Rst_n,
	
	wrreg_req,
	rdreg_req,
	addr,
	addr_mode,
	wrdata,
	rddata,
	device_id,
	RW_Done,
	
	ack,
	
	i2c_sclk,
	i2c_sdat
);

	input Clk;
	input Rst_n;
	
	input wrreg_req;//可写
	input rdreg_req;//可读
	input [15:0]addr;
	input addr_mode;//地址发送模式,先发高位或先发低位
	input [7:0]wrdata;
	output reg[7:0]rddata;
	input [7:0]device_id;
	output reg RW_Done;
	
	output reg ack;

	output i2c_sclk;
	inout i2c_sdat;
	
	reg [5:0]Cmd;
	reg [7:0]Tx_DATA;
	wire Trans_Done;
	wire ack_o;
	reg Go;
	wire [15:0] reg_addr;
	
	assign reg_addr = addr_mode?addr:{addr[7:0],addr[15:8]};
	
	
	wire [7:0]Rx_DATA;
	
	localparam 
		WR =  6'b000001,	// 写请求
		STA = 6'b000010,	//起始位请求
		RD =  6'b000100,	//读请求
		STO = 6'b001000,	//停止位请求
		ACK = 6'b010000,	//应答位请求
		NACK = 6'b100000;	//无应答请求
	
	i2c_bit_shift i2c_bit_shift(
		.Clk(Clk),
		.Rst_n(Rst_n),
		.Cmd(Cmd),
		.Go(Go),
		.Rx_DATA(Rx_DATA),
		.Tx_DATA(Tx_DATA),
		.Trans_Done(Trans_Done),
		.ack_o(ack_o),
		.i2c_sclk(i2c_sclk),
		.i2c_sdat(i2c_sdat)
	);
	
	reg [6:0]state;
	reg [7:0]cnt;
	
	localparam
		IDLE = 7'b0000001,
		WR_REG = 7'b0000010,
		WAIT_WR_DONE = 7'b0000100,
		WR_REG_DONE = 7'b0001000,
		RD_REG = 7'b0010000,
		WAIT_RD_DONE = 7'b0100000,
		RD_REG_DONE = 7'b1000000;
	
	always@(posedge Clk or negedge Rst_n)
	if(!Rst_n)begin
		Cmd <= 6'd0;
		Tx_DATA <= 8'd0;
		Go <= 1'b0;
		rddata <= 0;
		state <= IDLE;
		ack <= 0;
	end
	else begin
		case(state)
			IDLE:
				begin
					cnt <= 0;
					ack <= 0;
					RW_Done <= 1'b0;					
					if(wrreg_req)
						state <= WR_REG;
					else if(rdreg_req)
						state <= RD_REG;
					else
						state <= IDLE;
				end
			
			WR_REG:
				begin
					state <= WAIT_WR_DONE;
					case(cnt)
						0:write_byte(WR | STA, device_id);//cmd=000001|000010=000011
						1:write_byte(WR, reg_addr[15:8]);//cmd=000001
						2:write_byte(WR, reg_addr[7:0]);//cmd=000001
						3:write_byte(WR | STO, wrdata);//cmd=000001|001000=001001
						default:;
					endcase
				end
						
			WAIT_WR_DONE:
				begin
					Go <= 1'b0; 
					if(Trans_Done)begin
						ack <= ack | ack_o;
						case(cnt)
							0: begin cnt <= 1; state <= WR_REG;end
							1: 
								begin 
									state <= WR_REG;
									if(addr_mode)
										cnt <= 2; 
									else
										cnt <= 3;
								end
									
							2: begin
									cnt <= 3;
									state <= WR_REG;
								end
							3:state <= WR_REG_DONE;
							default:state <= IDLE;
						endcase
					end
				end
								
			WR_REG_DONE:
				begin
					RW_Done <= 1'b1;
					state <= IDLE;
				end
				
			RD_REG:
				begin
					state <= WAIT_RD_DONE;
					case(cnt)
						0:write_byte(WR | STA, device_id);
						1:if(addr_mode)
								write_byte(WR, reg_addr[15:8]);
							else
								write_byte(WR | STO, reg_addr[15:8]);
						2:write_byte(WR | STO, reg_addr[7:0]);
						3:write_byte(WR | STA, device_id | 8'd1);
						4:read_byte(RD | ACK | STO);
						default:;
					endcase
				end
				
			WAIT_RD_DONE:
				begin
					Go <= 1'b0; 
					if(Trans_Done)begin
						if(cnt <= 3)
							ack <= ack | ack_o;
						case(cnt)
							0: begin cnt <= 1; state <= RD_REG;end
							1: 
								begin 
									state <= RD_REG;
									if(addr_mode)
										cnt <= 2; 
									else
										cnt <= 3;
								end
									
							2: begin
									cnt <= 3;
									state <= RD_REG;
								end
							3:begin
									cnt <= 4;
									state <= RD_REG;
								end
							4:state <= RD_REG_DONE;
							default:state <= IDLE;
						endcase
					end
				end
				
			RD_REG_DONE:
				begin
					RW_Done <= 1'b1;
					rddata <= Rx_DATA;
					state <= IDLE;				
				end
			default:state <= IDLE;
		endcase
	end
	
	task read_byte;
		input [5:0]Ctrl_Cmd;
		begin
			Cmd <= Ctrl_Cmd;
			Go <= 1'b1; 
		end
	endtask
	
	task write_byte;
		input [5:0]Ctrl_Cmd;
		input [7:0]Wr_Byte_Data;
		begin
			Cmd <= Ctrl_Cmd;
			Tx_DATA <= Wr_Byte_Data;
			Go <= 1'b1; 
		end
	endtask

endmodule

FPGA实现I2C时序详解_第8张图片
该段代码有两个状态机实现,一个状态机控制状态变化,另一个状态机控制I2C的读或写。

你可能感兴趣的:(FPGA实现I2C时序详解)