I2C协议简介 Verilog实现
创始人
2025-06-01 07:47:14
0

I2C协议

  IIC 协议是三种最常用的串行通信协议(I2C,SPI,UART)之一,接口包含 SDA(串行数据线)和 SCL(串行时钟线),均为双向端口。I2C 仅使用两根信号线,极大地减少了连接线的数量,支持多主多从,且具有应答机制,因此在片间通信有较多的应用。

  I2C 主要包括四个状态:起始 START,数据传送 SEND,应答 ACK,停止 STOP。

在这里插入图片描述

  • 传输起始

  当 SCL 为高电平,SDA 出现下跳变时,标志着传输的起始。

  • 数据传输

  在传输数据位时,采用大端传输(即先传最高位 MSB),SDA 在SCL 低电平时改变,在 SCL=H 时,必须保持 SDA 稳定

  • 应答

  在传输完 8bit 数据后,Master 须释放 SDA ,Slave 接过 SDA 的控制权,给出应答信号 ACK,当 ACK=L 时,表示本字节数据传输有效。

  • 停止

  当 SCL 为高,SDA 出现上跳变时,标志着传输的结束。

  一次 I2C 传输可以传输多个字节,通常第一个字节为 I2C 设备地址 ADDR(7bit)和读写标志 R/W‾\rm{R/\overline W}R/W(1bit)。一个可能的 I2C 例子如下:

在这里插入图片描述

Verilog实现

  I2C的时序相对而言较复杂,因此实现方法自然是万能的三段式状态机(状态机大法好,状态机大法万岁!)

SCL/SDA 状态机输出控制

  不同的 I2C 设备可能具有不同的读写序列,因此这里首先实现 Master 与 Slave 的状态机输出的子模块(即三段式状态机的第三段),分别为 I2C_Master_sub、I2C_Slave_sub,顶层模块只需要合理安排状态转移,即可实现各种 I2C 读写时序!

  为了方便地控制 SDA 和 SCL ,Master 将一个 SCL 周期划分为 4 段;Slave 为了检测 SDA 和 SCL 的边沿并及时做出响应,须采用 8 倍以上的时钟。

  • I2C_Master_sub.v
/* * file			: I2C_Master_sub.v* author		: 今朝无言* Lab			: WHU-EIS-LMSWE* date			: 2023-03-19* version		: v1.0* description	: I2C master 的 SDA/SCL 控制模块(通过 state)*/
module I2C_Master_sub(
input				clk,			//4倍SCLinput		[7:0]	wrdat_buf,
output	reg	[7:0]	rddat_tmp,
output	reg			check_ack,		//检查Slave给出的ACK信号,若为NACK,输出一个高电平脉冲inout				SCL,
inout				SDA,output	reg			change_state,	//上升沿时 top 模块应执行 state <- next_state
input		[7:0]	state
);localparam	IDLE		= 8'h01;	//空闲,释放SCL/SDA
localparam	START		= 8'h02;	//起始,SCL=H,SDA=D
localparam	SEND_DATA	= 8'h04;	//发送数据
localparam	GET_DATA	= 8'h08;	//读取数据,释放SDA
localparam	CHECK_ACK	= 8'h10;	//检查SDA的ACK/NACK,释放SDA
localparam	ACK			= 8'h20;	//发出ACK,SDA=L
localparam	NACK		= 8'h40;	//发出NACK,SDA=H
localparam	STOP		= 8'h80;	//停止,SCL=H,SDA=Rreg				SCL_link	= 1'b0;
reg				SDA_link	= 1'b0;reg				SCL_buf		= 1'b1;	//o_buf
reg				SDA_buf		= 1'b1;wire			SCL_ibuf;			//i_buf
wire			SDA_ibuf;reg		[3:0]	bit_cnt		= 4'd15;//----------------------IO_BUF-----------------------------
//IOBUF fo SCL
IOBUF IOBUF_SCL(.O		(SCL_ibuf),		// Buffer output   Buffer的输出,接采集信号.IO		(SCL),			// Buffer inout port (connect directly to top-level port).I		(SCL_buf),		// Buffer input   Buffer的输入,接要输出到FPGA外的信号.T		(~SCL_link)		// 3-state enable input, high=input, low=output    =1时,O <- IO;=0时,IO <- I
);//IOBUF fo SDA
IOBUF IOBUF_SDA(.O		(SDA_ibuf),.IO		(SDA),.I		(SDA_buf),.T		(~SDA_link)
);//---------------------clk div-----------------------------
//将一个SCL周期划分为4份,便于逻辑实现
reg 	[1:0]	clk_cnt	= 2'd0;always @(posedge clk) beginclk_cnt		<= clk_cnt + 1'b1;
end//---------------------SCL_link-----------------------------
always @(posedge clk) begincase(state)IDLE: beginSCL_link	<= 1'b0;endSTART, SEND_DATA, GET_DATA, CHECK_ACK, ACK, NACK, STOP: beginSCL_link	<= 1'b1;enddefault: beginSCL_link	<= 1'b0;endendcase
end//---------------------SDA_link-----------------------------
always @(posedge clk) begincase(state)IDLE, GET_DATA, CHECK_ACK: beginSDA_link	<= 1'b0;endSTART, SEND_DATA, ACK, NACK, STOP: beginSDA_link	<= 1'b1;enddefault: beginSDA_link	<= 1'b0;endendcase
end//---------------------SCL_buf-----------------------------
always @(posedge clk) begincase(state)IDLE: begin											//1111SCL_buf		<= 1'b1;endSTART: begin										//1110case(clk_cnt)2'd0, 2'd1, 2'd2: beginSCL_buf		<= 1'b1;end2'd3: beginSCL_buf		<= 1'b0;enddefault: ;endcaseendSTOP: begin											//0111case(clk_cnt)2'd1, 2'd2, 2'd3: beginSCL_buf		<= 1'b1;end2'd0: beginSCL_buf		<= 1'b0;enddefault: ;endcaseendSEND_DATA, GET_DATA, CHECK_ACK, ACK, NACK: begin	//0110case(clk_cnt)2'd1, 2'd2: beginSCL_buf		<= 1'b1;end2'd0, 2'd3: beginSCL_buf		<= 1'b0;enddefault: ;endcaseenddefault: begin										//1111SCL_buf		<= 1'b1;endendcase
end//---------------------bit_cnt-----------------------------
always @(posedge clk) begincase(state)SEND_DATA, GET_DATA: begincase(clk_cnt)2'd2: beginbit_cnt		<= bit_cnt - 1'b1;enddefault: ;endcaseendSTART, ACK, NACK, CHECK_ACK: beginbit_cnt		<= 4'd7;enddefault: beginbit_cnt		<= 4'd15;endendcase
end//--------------------rddat_tmp----------------------------
always @(posedge clk) begincase(state)GET_DATA: begincase(clk_cnt)2'd1: beginrddat_tmp[bit_cnt]	<= SDA_ibuf;enddefault: ;endcaseenddefault: beginrddat_tmp	<= rddat_tmp;endendcase
end//--------------------check_ack----------------------------
always @(posedge clk) begincase(state)CHECK_ACK: begincase(clk_cnt)2'd1: begincheck_ack	<= SDA_ibuf;enddefault: begincheck_ack	<= check_ack;endendcaseenddefault: begincheck_ack	<= 0;endendcase
end//---------------------SDA_buf-----------------------------
always @(posedge clk) begincase(state)IDLE: beginSDA_buf		<= 1'b1;endSTART: begin										//1100,从而在SCL=H时,产生SDA=Dcase(clk_cnt)2'd0, 2'd1: beginSDA_buf		<= 1'b1;end2'd2, 2'd3: beginSDA_buf		<= 1'b0;enddefault: ;endcaseendSEND_DATA: begin									//在clk_cnt=0给出数据,从而在clk_cnt=1,2时(SCL=H)保持SDA的稳定case(clk_cnt)2'd0: beginSDA_buf		<= wrdat_buf[bit_cnt];enddefault: ;endcaseendGET_DATA: beginSDA_buf		<= 1'b1;endCHECK_ACK: beginSDA_buf		<= 1'b0;endACK: beginSDA_buf		<= 1'b0;endNACK: beginSDA_buf		<= 1'b1;endSTOP: begin											//0011,从而在SCL=H时,产生SDA=Rcase(clk_cnt)2'd0, 2'd1: beginSDA_buf		<= 1'b0;end2'd2, 2'd3: beginSDA_buf		<= 1'b1;enddefault: ;endcaseenddefault: beginSDA_buf		<= 1'b1;endendcase
end//-------------------change_state---------------------------
always @(posedge clk) begincase(state)IDLE, ACK, NACK, CHECK_ACK, STOP: begincase(clk_cnt)2'd3: beginchange_state	<= 1'b1;enddefault: beginchange_state	<= 1'b0;endendcaseendSEND_DATA, GET_DATA: begincase(bit_cnt)4'd15: begincase(clk_cnt)2'd3: beginchange_state	<= 1'b1;enddefault: beginchange_state	<= 1'b0;endendcaseenddefault: beginchange_state	<= 1'b0;endendcaseenddefault: begincase(clk_cnt)2'd3: beginchange_state	<= 1'b1;enddefault: beginchange_state	<= 1'b0;endendcaseendendcase
endendmodule
  • I2C_Slave_sub.v
/* * file			: I2C_Slave_sub.v* author		: 今朝无言* Lab			: WHU-EIS-LMSWE* date			: 2023-03-19* version		: v1.0* description	: I2C Slave 的 SDA/SCL 控制模块(通过 state)*/
module I2C_Slave_sub(
input				clk,			//SCL的8倍以上input		[7:0]	wrdat_buf,
output	reg	[7:0]	rddat_tmp,
output	reg			check_ack,		//检查Master给出的ACK信号,若为NACK,输出一个高电平脉冲inout				SCL,
inout				SDA,output	reg			change_state,	//上升沿时 top 模块应执行 state <- next_state
input		[7:0]	state,output	reg			busy
);localparam	IDLE		= 8'h01;	//空闲
localparam	START		= 8'h02;	//起始,检测到SCL=H,SDA=D,
localparam	SEND_DATA	= 8'h04;	//Slave发送数据,接管SDA控制权
localparam	GET_DATA	= 8'h08;	//Slave读取数据
localparam	CHECK_ACK	= 8'h10;	//检查SDA的ACK/NACK
localparam	ACK			= 8'h20;	//发出ACK,SDA=L,接管SDA控制权
localparam	NACK		= 8'h40;	//发出NACK,SDA=H,接管SDA控制权
localparam	STOP		= 8'h80;	//停止,检测到SCL=H,SDA=R
//不实现Clock Stretching功能,因此Slave从不试图接管SCL
//除注释注明的状态外,不获取SDA控制权wire			SCL_link;
reg				SDA_link	= 1'b0;reg				SCL_buf		= 1'b1;	//o_buf
reg				SDA_buf		= 1'b1;wire			SCL_ibuf;			//i_buf
wire			SDA_ibuf;assign	SCL_link	= 1'b0;//----------------------IO_BUF-----------------------------
//IOBUF fo SCL
IOBUF IOBUF_SCL(.O		(SCL_ibuf),		//Buffer的输出,接采集信号.IO		(SCL),.I		(SCL_buf),		//Buffer的输入,接要输出到FPGA外的信号.T		(~SCL_link)		//=1时,O <- IO;=0时,IO <- I
);//IOBUF fo SDA
IOBUF IOBUF_SDA(.O		(SDA_ibuf),.IO		(SDA),.I		(SDA_buf),.T		(~SDA_link)
);//--------------------------busy----------------------------
reg				busy_d0;
reg				busy_d1;
wire			busy_pe;
wire			busy_ne;always @(SDA_ibuf) beginif(~SDA_ibuf & SCL_ibuf) begin			// SCL=H,SDA=D,接收起始busy	<= 1'b1;endelse if(SDA_ibuf & SCL_ibuf) begin		// SCL=H,SDA=R,接收结束busy	<= 1'b0;endelse beginbusy	<= busy;end
endalways @(posedge clk) beginbusy_d0		<= busy;busy_d1		<= busy_d0;
endassign busy_pe	= busy_d0 & (~busy_d1);
assign busy_ne	= (~busy_d0) & busy_d1;//--------------------------edge----------------------------
reg				SDA_d0;
reg				SDA_d1;
wire			SDA_pe;
wire			SDA_ne;reg				SCL_d0;
reg				SCL_d1;
wire			SCL_pe;
wire			SCL_ne;always @(posedge clk) beginSDA_d0		<= SDA_ibuf;SDA_d1		<= SDA_d0;SCL_d0		<= SCL_ibuf;SCL_d1		<= SCL_d0;
endassign SDA_pe	= SDA_d0 & (~SDA_d1);
assign SDA_ne	= (~SDA_d0) & SDA_d1;assign SCL_pe	= SCL_d0 & (~SCL_d1);
assign SCL_ne	= (~SCL_d0) & SCL_d1;//-----------------------SCL_cnt----------------------------
reg		[3:0]	SCL_cnt;	//计算当前是第几个SCL_pealways @(posedge clk) beginif(busy_pe) beginSCL_cnt		<= 4'd0;endelse if(SCL_ne && SCL_cnt==4'd9) beginSCL_cnt		<= 4'd0;endelse if(SCL_pe) beginSCL_cnt		<= SCL_cnt + 1'b1;endelse beginSCL_cnt		<= SCL_cnt;end
end//---------------------change_state--------------------------
always @(posedge clk) begincase(state)IDLE: beginif(busy_pe) beginchange_state	<= 1'b1;endelse beginchange_state	<= 1'b0;endendSTART: beginif(SCL_ne) beginchange_state	<= 1'b1;endelse beginchange_state	<= 1'b0;endendSEND_DATA, GET_DATA: beginif(SCL_ne && SCL_cnt==4'd8) beginchange_state	<= 1'b1;endelse beginchange_state	<= 1'b0;endendACK, NACK, CHECK_ACK: beginif(SCL_ne) beginchange_state	<= 1'b1;endelse beginchange_state	<= 1'b0;endendSTOP: beginif(busy_ne) beginchange_state	<= 1'b1;endelse beginchange_state	<= 1'b0;endenddefault: beginchange_state	<= 1'b0;endendcase
end//-----------------------SDA_link----------------------------
always @(posedge clk) begincase(state)SEND_DATA, ACK, NACK: beginSDA_link	<= 1'b1;enddefault: beginSDA_link	<= 1'b0;endendcase
end//----------------------check_ack----------------------------
always @(posedge clk) begincase(state)CHECK_ACK: beginif(SCL_pe) begincheck_ack	<= SDA_ibuf;endelse begincheck_ack	<= 1'b0;endenddefault: begincheck_ack	<= 1'b0;endendcase
end//----------------------rddat_tmp----------------------------
always @(posedge clk) begincase(state)GET_DATA: beginif(SCL_pe) beginrddat_tmp[7 - SCL_cnt]	<= SDA_ibuf;endelse ;enddefault: ;endcase
end//-----------------------SDA_buf-----------------------------
always @(posedge clk) begincase(state)SEND_DATA: beginif(SCL_ne || change_state) beginSDA_buf		<= wrdat_buf[7 - SCL_cnt];endelse beginSDA_buf		<= SDA_buf;endendACK: beginSDA_buf		<= 1'b0;endNACK: beginSDA_buf		<= 1'b1;enddefault: beginSDA_buf		<= 1'b1;endendcase
endendmodule

Master 读/写子模块

  基于 Master_sub 状态机输出控制子模块,分别搭建 Master 读/写控制子模块例程如下(这里实现的是比较常规的 I2C 读写时序,要实现更加具体的读写时序可参考该例程自行实现

  • I2C_Master_Write.v
/* * file			: I2C_Master_Write.v* author		: 今朝无言* Lab			: WHU-EIS-LMSWE* date			: 2023-03-20* version		: v1.0* description	: I2C写功能* Copyright © 2023 WHU-EIS-LMSWE, All Rights Reserved.*/
module I2C_Master_Write(
input				clk,			//4倍SCLinput				wr_en,			//上升沿有效
output	reg			wrdat_req,		//上升沿驱动上层模块给出wrdat
input		[7:0]	wrdat,output				busy,
output				check_ack,		//检查Slave给出的ACK信号,若为NACK,输出一个高电平脉冲inout				SCL,
inout				SDA
);
// S {ADDR,RW_W} A DATA A ... DATA A Pparameter	ADDR		= 7'h11;    //I2C设备地址
parameter	WR_DATA_LEN	= 16'd1;    //写的数据个数localparam	RW_W		= 1'b0;
localparam	RW_R		= 1'b1;//---------------------I2C Master State Define----------------------
localparam	IDLE		= 8'h01;	//空闲,释放SCL/SDA
localparam	START		= 8'h02;	//起始,SCL=H,SDA=D
localparam	SEND_DATA	= 8'h04;	//发送数据
localparam	GET_DATA	= 8'h08;	//读取数据,释放SDA
localparam	CHECK_ACK	= 8'h10;	//检查SDA的ACK/NACK,释放SDA
localparam	ACK			= 8'h20;	//发出ACK,SDA=L
localparam	NACK		= 8'h40;	//发出NACK,SDA=H
localparam	STOP		= 8'h80;	//停止,SCL=H,SDA=R//------------------------------------------------------------------
reg		[7:0]	state		= IDLE;
reg		[7:0]	next_state	= IDLE;reg				start_flag	= 1'b0;wire			change_state;reg		[7:0]	wrdat_buf	= 8'd0;
reg		[15:0]	data_cnt	= 16'd0;//------------------------start_flag--------------------------------
reg		wr_en_d0;
reg		wr_en_d1;
wire	wr_en_pe;always @(posedge clk) beginwr_en_d0	<= wr_en;wr_en_d1	<= wr_en_d0;
endassign	wr_en_pe	= wr_en_d0 & (~wr_en_d1);
assign	busy 		= (state == IDLE)? 1'b0 : 1'b1;always @(posedge clk) beginif(wr_en_pe && ~busy) beginstart_flag	<= 1'b1;endelse if(state == START) beginstart_flag	<= 1'b0;endelse beginstart_flag	<= start_flag;end
end//-------------------------State Machine----------------------------
always @(posedge change_state) beginstate		<= next_state;
end//状态转移
always @(*) begincase(state)IDLE: beginif(start_flag) beginnext_state	<= START;endelse beginnext_state	<= IDLE;endendSTART: beginnext_state	<= SEND_DATA;endSEND_DATA: beginnext_state	<= CHECK_ACK;endCHECK_ACK: beginif(data_cnt == 1 && check_ack) begin		//第一个CHECK检测到NACK,STOPnext_state	<= STOP;endelse beginif(data_cnt > WR_DATA_LEN) beginnext_state	<= STOP;endelse beginnext_state	<= SEND_DATA;endendendSTOP: beginnext_state	<= IDLE;enddefault: beginnext_state	<= IDLE;endendcase
end//三段式状态机第三段,I2C Master sub
I2C_Master_sub I2C_Master_sub_inst(.clk			(clk),.wrdat_buf		(wrdat_buf),.rddat_tmp		(),.check_ack		(check_ack),.SCL			(SCL),.SDA			(SDA),.change_state	(change_state),.state			(state)
);// -----data_req-----
always @(*) begincase(state)CHECK_ACK: beginif(data_cnt <= WR_DATA_LEN) beginwrdat_req	<= 1'b1;endelse beginwrdat_req	<= 1'b0;endenddefault: beginwrdat_req	<= 1'b0;endendcase
end// -----data_cnt-----
always @(posedge change_state) begincase(state)IDLE: begindata_cnt	<= 16'd0;endSEND_DATA: begindata_cnt	<= data_cnt + 1'b1;enddefault: begindata_cnt	<= data_cnt;endendcase
end// -----wrdat_buf-----
always @(posedge change_state) begincase(state)IDLE: beginwrdat_buf	<= 8'd0;endSTART: beginwrdat_buf	<= {ADDR, RW_W};endCHECK_ACK: beginwrdat_buf	<= wrdat;enddefault: beginwrdat_buf	<= wrdat_buf;endendcase
endendmodule
  • I2C_Master_Read.v
/* * file			: I2C_Master_Read.v* author		: 今朝无言* Lab			: WHU-EIS-LMSWE* date			: 2023-03-20* version		: v1.0* description	: I2C读功能* Copyright © 2023 WHU-EIS-LMSWE, All Rights Reserved.*/
module I2C_Master_Read(
input				clk,			//4倍SCLinput				rd_en,			//上升沿有效
output	reg			rddat_vaild,
output	reg	[7:0]	rddat,output				busy,
output              check_ack,inout				SCL,
inout				SDA
);
// S {ADDR,RW_R} A DATA A ... DATA A Pparameter	ADDR		= 7'h11;    //I2C设备地址
parameter	RD_DATA_LEN	= 16'd1;    //读的数据个数localparam	RW_W		= 1'b0;
localparam	RW_R		= 1'b1;//---------------------I2C Master State Define----------------------
localparam	IDLE		= 8'h01;	//空闲,释放SCL/SDA
localparam	START		= 8'h02;	//起始,SCL=H,SDA=D
localparam	SEND_DATA	= 8'h04;	//发送数据
localparam	GET_DATA	= 8'h08;	//读取数据,释放SDA
localparam	CHECK_ACK	= 8'h10;	//检查SDA的ACK/NACK,释放SDA
localparam	ACK			= 8'h20;	//发出ACK,SDA=L
localparam	NACK		= 8'h40;	//发出NACK,SDA=H
localparam	STOP		= 8'h80;	//停止,SCL=H,SDA=R//------------------------------------------------------------------
reg		[7:0]	state		= IDLE;
reg		[7:0]	next_state	= IDLE;reg				start_flag	= 1'b0;wire			change_state;reg		[7:0]	wrdat_buf	= 8'd0;
wire	[7:0]	rddat_tmp;
reg		[15:0]	data_cnt	= 16'd0;//------------------------start_flag--------------------------------
reg		rd_en_d0;
reg		rd_en_d1;
wire	rd_en_pe;always @(posedge clk) beginrd_en_d0	<= rd_en;rd_en_d1	<= rd_en_d0;
endassign	rd_en_pe	= rd_en_d0 & (~rd_en_d1);
assign	busy 		= (state == IDLE)? 1'b0 : 1'b1;always @(posedge clk) beginif(rd_en_pe && ~busy) beginstart_flag	<= 1'b1;endelse if(state == START) beginstart_flag	<= 1'b0;endelse beginstart_flag	<= start_flag;end
end//-------------------------State Machine----------------------------
always @(posedge change_state) beginstate		<= next_state;
end//状态转移
always @(*) begincase(state)IDLE: beginif(start_flag) beginnext_state	<= START;endelse beginnext_state	<= IDLE;endendSTART: beginnext_state	<= SEND_DATA;endSEND_DATA: beginnext_state	<= CHECK_ACK;endCHECK_ACK: beginif(check_ack) begin		//检测到NACK,STOPnext_state	<= STOP;endelse beginnext_state	<= GET_DATA;endendGET_DATA: beginnext_state	<= ACK;endACK: beginif(data_cnt >= RD_DATA_LEN) beginnext_state	<= STOP;endelse beginnext_state	<= GET_DATA;endendSTOP: beginnext_state	<= IDLE;enddefault: beginnext_state	<= IDLE;endendcase
end//三段式状态机第三段,I2C Master sub
I2C_Master_sub I2C_Master_sub_inst(.clk			(clk),.wrdat_buf		(wrdat_buf),.rddat_tmp		(rddat_tmp),.check_ack		(check_ack),.SCL			(SCL),.SDA			(SDA),.change_state	(change_state),.state			(state)
);// -----data_valid-----
always @(*) begincase(state)ACK: beginrddat_vaild	<= 1'b1;enddefault: beginrddat_vaild	<= 1'b0;endendcase
end// -----data_cnt-----
always @(posedge change_state) begincase(state)IDLE: begindata_cnt	<= 16'd0;endGET_DATA: begindata_cnt	<= data_cnt + 1'b1;enddefault: begindata_cnt	<= data_cnt;endendcase
end// -----rddat-----
always @(posedge change_state) begincase(state)IDLE: beginrddat	<= rddat;endGET_DATA: beginrddat	<= rddat_tmp;enddefault: beginrddat	<= rddat;endendcase
end// ---wrdat_buf---
always @(posedge change_state) begincase(state)IDLE: beginwrdat_buf	<= 8'd0;endSTART: beginwrdat_buf	<= {ADDR, RW_R};endCHECK_ACK: beginwrdat_buf	<= 8'd0;enddefault: beginwrdat_buf	<= wrdat_buf;endendcase
endendmodule

Slave 读/写子模块

  同样,基于 Slave_sub 设计 Slave 的读/写控制子模块例程如下

  • I2C_Slave_Receive.v
/* * file			: I2C_Slave_Receive.v* author		: 今朝无言* Lab			: WHU-EIS-LMSWE* date			: 2023-03-21* version		: v1.0* description	: 作为Slave<接收>数据* Copyright © 2023 WHU-EIS-LMSWE, All Rights Reserved.*/
module I2C_Slave_Receive(
input				clk,			//SCL的8倍以上output	reg			rddat_vaild,	//下降沿有效
output	reg	[7:0]	rddat,output				busy,inout				SCL,
inout				SDA
);
// S {ADDR,RW_W} A DATA A ... DATA A P		-- 本机地址
// S {ADDR,RW_W} NA                  P		-- 非本机地址parameter	ADDR				= 7'h11;    //I2C设备地址
parameter	RECEIVE_DATA_LEN	= 16'd1;    //接收的数据个数localparam	RW_W		= 1'b0;
localparam	RW_R		= 1'b1;//---------------------I2C Slave State Define----------------------
localparam	IDLE		= 8'h01;	//空闲
localparam	START		= 8'h02;	//起始,检测到SCL=H,SDA=D,
localparam	SEND_DATA	= 8'h04;	//Slave发送数据,接管SDA控制权
localparam	GET_DATA	= 8'h08;	//Slave读取数据
localparam	CHECK_ACK	= 8'h10;	//检查SDA的ACK/NACK
localparam	ACK			= 8'h20;	//发出ACK,SDA=L,接管SDA控制权
localparam	NACK		= 8'h40;	//发出NACK,SDA=H,接管SDA控制权
localparam	STOP		= 8'h80;	//停止,检测到SCL=H,SDA=R//------------------------------------------------------------------
reg		[7:0]	state		= IDLE;
reg		[7:0]	next_state	= IDLE;wire			change_state;wire	[7:0]	rddat_tmp;
reg		[15:0]	data_cnt	= 16'd0;reg				isMe		= 1'b0;	//是否为本机地址//-------------------------State Machine----------------------------
always @(posedge change_state or negedge busy) beginif(~busy) beginstate		<= IDLE;endelse beginstate		<= next_state;end
end//状态转移
always @(*) beginif(busy) begincase(state)IDLE: beginnext_state	<= START;endSTART: beginnext_state	<= GET_DATA;endGET_DATA: beginif(isMe) beginnext_state	<= ACK;endelse beginnext_state	<= NACK;endendACK: beginif(data_cnt > RECEIVE_DATA_LEN) beginnext_state	<= STOP;endelse beginnext_state	<= GET_DATA;endendNACK: beginnext_state		<= STOP;endSTOP: beginnext_state		<= IDLE;enddefault: beginnext_state		<= IDLE;endendcaseendelse beginnext_state		<= START;end
end//三段式状态机第三段,I2C Slave sub
I2C_Slave_sub I2C_Slave_sub_inst(.clk			(clk),.wrdat_buf		(),.rddat_tmp		(rddat_tmp),.check_ack		(),.SCL			(SCL),.SDA			(SDA),.change_state	(change_state),.state			(state),.busy			(busy)
);// ---rddat_vaild---
always @(*) begincase(state)IDLE: beginrddat_vaild		<= 1'b0;endACK: beginif(data_cnt>1) beginrddat_vaild		<= 1'b1;endelse beginrddat_vaild		<= 1'b0;endenddefault: beginrddat_vaild		<= 1'b0;endendcase
end// ---rddat---
always @(posedge change_state) begincase(state)GET_DATA: beginif(data_cnt>0) beginrddat		<= rddat_tmp;endelse beginrddat		<= 8'd0;endenddefault: beginrddat		<= rddat;endendcase
end// ---data_cnt---
always @(posedge change_state) begincase(state)IDLE: begindata_cnt	<= 16'd0;endGET_DATA: begindata_cnt	<= data_cnt + 1'b1;enddefault: begindata_cnt	<= data_cnt;endendcase
end// ---isMe---
always @(*) begincase(state)IDLE: beginisMe	<= 1'b0;endGET_DATA: beginif(data_cnt==0) beginif(rddat_tmp=={ADDR, RW_W}) begin	//地址=本机,且RW=W,启动Slave接收进程isMe	<= 1'b1;endelse beginisMe	<= isMe;endendelse beginisMe	<= isMe;endenddefault: beginisMe	<= isMe;endendcase
endendmodule
  • I2C_Slave_Send.v
/* * file			: I2C_Slave_Send.v* author		: 今朝无言* Lab			: WHU-EIS-LMSWE* date			: 2023-03-21* version		: v1.0* description	: 作为Slave<发送>数据* Copyright © 2023 WHU-EIS-LMSWE, All Rights Reserved.*/
module I2C_Slave_Send(
input				clk,			//SCL的8倍以上output	reg			wrdat_req,
input		[7:0]	wrdat,output				busy,inout				SCL,
inout				SDA
);
// S {ADDR,RW_R} A DATA A ... DATA A P		-- 本机地址
// S {ADDR,RW_R} NA                  P		-- 非本机地址parameter	ADDR			= 7'h11;    //I2C设备地址
parameter	SEND_DATA_LEN	= 16'd1;    //发送的数据个数localparam	RW_W		= 1'b0;
localparam	RW_R		= 1'b1;//---------------------I2C Slave State Define----------------------
localparam	IDLE		= 8'h01;	//空闲
localparam	START		= 8'h02;	//起始,检测到SCL=H,SDA=D,
localparam	SEND_DATA	= 8'h04;	//Slave发送数据,接管SDA控制权
localparam	GET_DATA	= 8'h08;	//Slave读取数据
localparam	CHECK_ACK	= 8'h10;	//检查SDA的ACK/NACK
localparam	ACK			= 8'h20;	//发出ACK,SDA=L,接管SDA控制权
localparam	NACK		= 8'h40;	//发出NACK,SDA=H,接管SDA控制权
localparam	STOP		= 8'h80;	//停止,检测到SCL=H,SDA=R//------------------------------------------------------------------
reg		[7:0]	state		= IDLE;
reg		[7:0]	next_state	= IDLE;wire			change_state;wire	[7:0]	rddat_tmp;reg		[15:0]	data_cnt	= 16'd0;reg				isMe		= 1'b0;	//是否为本机地址//-------------------------State Machine----------------------------
always @(posedge change_state or negedge busy) beginif(~busy) beginstate		<= IDLE;endelse beginstate		<= next_state;end
end//状态转移
always @(*) beginif(busy) begincase(state)IDLE: beginnext_state		<= START;endSTART: beginnext_state		<= GET_DATA;endGET_DATA: beginif(isMe) beginnext_state	<= ACK;endelse beginnext_state	<= NACK;endendACK: beginnext_state		<= SEND_DATA;endNACK: beginnext_state		<= STOP;endSEND_DATA: beginnext_state		<= CHECK_ACK;endCHECK_ACK: beginif(data_cnt > SEND_DATA_LEN) beginnext_state	<= STOP;endelse beginnext_state	<= SEND_DATA;endendSTOP: beginnext_state		<= IDLE;enddefault: beginnext_state		<= IDLE;endendcaseendelse beginnext_state			<= START;end
end//三段式状态机第三段,I2C Slave sub
I2C_Slave_sub I2C_Slave_sub_inst(.clk			(clk),.wrdat_buf		(wrdat),.rddat_tmp		(rddat_tmp),.check_ack		(check_ack),.SCL			(SCL),.SDA			(SDA),.change_state	(change_state),.state			(state),.busy			(busy)
);// ---wrdat_req---
always @(*) begincase(state)ACK, CHECK_ACK: beginif(data_cnt <= SEND_DATA_LEN) beginwrdat_req	<= 1'b1;endelse beginwrdat_req	<= 1'b0;endenddefault: beginwrdat_req	<= 1'b0;endendcase
end// ---data_cnt---
always @(posedge change_state) begincase(state)IDLE: begindata_cnt	<= 16'd0;endGET_DATA, SEND_DATA: begindata_cnt	<= data_cnt + 1'b1;enddefault: begindata_cnt	<= data_cnt;endendcase
end// ---isMe---
always @(*) begincase(state)IDLE: beginisMe	<= 1'b0;endGET_DATA: beginif(data_cnt==0) beginif(rddat_tmp=={ADDR, RW_R}) begin	//地址=本机,且RW=R,启动Slave发送进程isMe	<= 1'b1;endelse beginisMe	<= isMe;endendelse beginisMe	<= isMe;endenddefault: beginisMe	<= isMe;endendcase
endendmodule

Test Bench & 测试结果

Master写 & Slave接收

  • I2C_Master_w_Slave_r_tb.v
`timescale 1ns/100psmodule I2C_Master_w_Slave_r_tb();
//测试Master写、Slave接收reg		clk_100M	= 1'b1;
always #5 beginclk_100M	<= ~clk_100M;
endreg		clk_50M	= 1'b1;
always #10 beginclk_50M	<= ~clk_50M;
endwire			SCL;
wire			SDA;pullup(SCL);
pullup(SDA);//-------------------Master-----------------------
reg				wr_en;
wire			wrdat_req;
reg		[7:0]	wrdat	= 8'd0;wire			busy;
wire			check_ack;I2C_Master_Write #(.ADDR			(7'h44),.WR_DATA_LEN	(16'd4))
I2C_Master_Write_inst(.clk			(clk_50M),.wr_en			(wr_en),.wrdat_req		(wrdat_req),.wrdat			(wrdat),.busy			(busy),.check_ack		(check_ack),.SCL			(SCL),.SDA			(SDA)
);always @(posedge wrdat_req) beginwrdat	<= wrdat + 1'b1;
end//-------------------Slave-----------------------
wire			rddat_vaild;
wire	[7:0]	rddat;
wire			S_busy;I2C_Slave_Receive #(.ADDR				(7'h44),.RECEIVE_DATA_LEN	(16'd4))
I2C_Slave_Receive_inst(.clk			(clk_100M),.rddat_vaild	(rddat_vaild),.rddat			(rddat),.busy			(S_busy),.SCL			(SCL),.SDA			(SDA)
);//---------------------test-------------------------
initial beginwr_en		<= 1'b0;#100;wr_en		<= 1'b1;#100;wr_en		<= 1'b0;wait(busy);wait(~busy);#100;wr_en		<= 1'b1;#100;wr_en		<= 1'b0;wait(busy);wait(~busy);#200;$stop;
endendmodule

  设置 Master 写设备地址与 Slave 设备地址相同,单次 I2C 通信发送/接收 4 个数据,结果如下

在这里插入图片描述

若设置两者地址不同,Master 会检测到 NACK 信号,从而直接终止通信,结果如下

在这里插入图片描述

由于例程编写考虑并不全面,因此这里检查到 NACK 时仍进行了数据请求(但没进行数据发送),在实际系统设计中读者应自行修正

Master读 & Slave发送

  • I2C_Master_r_Slave_s_tb.v
`timescale 1ns/100psmodule I2C_Master_r_Slave_s_tb();
//测试Maste读、Slave发送reg		clk_100M	= 1'b1;
always #5 beginclk_100M	<= ~clk_100M;
endreg		clk_50M	= 1'b1;
always #10 beginclk_50M	<= ~clk_50M;
endwire			SCL;
wire			SDA;pullup(SCL);
pullup(SDA);//-------------------Master-----------------------
reg				rd_en		= 1'b0;
wire			rddat_vaild;
wire	[7:0]	rddat;wire			busy;
wire			check_ack;I2C_Master_Read #(.ADDR			(7'h44),.RD_DATA_LEN	(16'd4))
I2C_Master_Read_inst(.clk			(clk_50M),.rd_en			(rd_en),.rddat_vaild	(rddat_vaild),.rddat			(rddat),.busy			(busy),.check_ack		(check_ack),.SCL			(SCL),.SDA			(SDA)
);//-------------------Slave-----------------------
wire			wrdat_req;
reg		[7:0]	wrdat		= 8'd0;wire			S_busy;I2C_Slave_Send #(.ADDR			(7'h44),.SEND_DATA_LEN	(16'd4))
I2C_Slave_Send_inst(.clk			(clk_100M),.wrdat_req		(wrdat_req),.wrdat			(wrdat),.busy			(S_busy),.SCL			(SCL),.SDA			(SDA)
);always @(posedge wrdat_req) beginwrdat	<= wrdat + 1'b1;
end//---------------------test-------------------------
initial beginrd_en		<= 1'b0;#100;rd_en		<= 1'b1;#100;rd_en		<= 1'b0;wait(busy);wait(~busy);#100;rd_en		<= 1'b1;#100;rd_en		<= 1'b0;wait(busy);wait(~busy);#200;$stop;
endendmodule

 设置 Master 读设备地址与 Slave 设备地址相同,单次 I2C 通信读取 4 个数据,结果如下

在这里插入图片描述

若两者地址不同,Slave 会自行挂起,直到 I2C 总线释放后自动回到 IDLE 状态,而 Master 由于没有收到指定设备的 ACK 确认信号,也会自行终止读取进程,结果如下

在这里插入图片描述

相关内容

热门资讯

【MySQL】锁 锁 文章目录锁全局锁表级锁表锁元数据锁(MDL)意向锁AUTO-INC锁...
【内网安全】 隧道搭建穿透上线... 文章目录内网穿透-Ngrok-入门-上线1、服务端配置:2、客户端连接服务端ÿ...
GCN的几种模型复现笔记 引言 本篇笔记紧接上文,主要是上一篇看写了快2w字,再去接入代码感觉有点...
数据分页展示逻辑 import java.util.Arrays;import java.util.List;impo...
Redis为什么选择单线程?R... 目录专栏导读一、Redis版本迭代二、Redis4.0之前为什么一直采用单线程?三、R...
【已解决】ERROR: Cou... 正确指令: pip install pyyaml
关于测试,我发现了哪些新大陆 关于测试 平常也只是听说过一些关于测试的术语,但并没有使用过测试工具。偶然看到编程老师...
Lock 接口解读 前置知识点Synchronized synchronized 是 Java 中的关键字,...
Win7 专业版安装中文包、汉... 参考资料:http://www.metsky.com/archives/350.htm...
3 ROS1通讯编程提高(1) 3 ROS1通讯编程提高3.1 使用VS Code编译ROS13.1.1 VS Code的安装和配置...
大模型未来趋势 大模型是人工智能领域的重要发展趋势之一,未来有着广阔的应用前景和发展空间。以下是大模型未来的趋势和展...
python实战应用讲解-【n... 目录 如何在Python中计算残余的平方和 方法1:使用其Base公式 方法2:使用statsmod...
学习u-boot 需要了解的m... 一、常用函数 1. origin 函数 origin 函数的返回值就是变量来源。使用格式如下...
常用python爬虫库介绍与简... 通用 urllib -网络库(stdlib)。 requests -网络库。 grab – 网络库&...
药品批准文号查询|药融云-中国... 药品批文是国家食品药品监督管理局(NMPA)对药品的审评和批准的证明文件...
【2023-03-22】SRS... 【2023-03-22】SRS推流搭配FFmpeg实现目标检测 说明: 外侧测试使用SRS播放器测...
有限元三角形单元的等效节点力 文章目录前言一、重新复习一下有限元三角形单元的理论1、三角形单元的形函数(Nÿ...
初级算法-哈希表 主要记录算法和数据结构学习笔记,新的一年更上一层楼! 初级算法-哈希表...
进程间通信【Linux】 1. 进程间通信 1.1 什么是进程间通信 在 Linux 系统中,进程间通信...
【Docker】P3 Dock... Docker数据卷、宿主机与挂载数据卷的概念及作用挂载宿主机配置数据卷挂载操作示例一个容器挂载多个目...