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 例子如下:
I2C的时序相对而言较复杂,因此实现方法自然是万能的三段式状态机(状态机大法好,状态机大法万岁!)
不同的 I2C 设备可能具有不同的读写序列,因此这里首先实现 Master 与 Slave 的状态机输出的子模块(即三段式状态机的第三段),分别为 I2C_Master_sub、I2C_Slave_sub,顶层模块只需要合理安排状态转移,即可实现各种 I2C 读写时序!
为了方便地控制 SDA 和 SCL ,Master 将一个 SCL 周期划分为 4 段;Slave 为了检测 SDA 和 SCL 的边沿并及时做出响应,须采用 8 倍以上的时钟。
/* * 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
/* * 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_sub 状态机输出控制子模块,分别搭建 Master 读/写控制子模块例程如下(这里实现的是比较常规的 I2C 读写时序,要实现更加具体的读写时序可参考该例程自行实现)
/* * 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
/* * 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_sub 设计 Slave 的读/写控制子模块例程如下
/* * 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
/* * 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
`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 时仍进行了数据请求(但没进行数据发送),在实际系统设计中读者应自行修正。
`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 确认信号,也会自行终止读取进程,结果如下