I2C协议简介 Verilog实现
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 确认信号,也会自行终止读取进程,结果如下

相关文章:
I2C协议简介 Verilog实现
I2C协议 IIC 协议是三种最常用的串行通信协议(I2C,SPI,UART)之一,接口包含 SDA(串行数据线)和 SCL(串行时钟线),均为双向端口。I2C 仅使用两根信号线…...
服务器被DDoS攻击,怎么破?
文章目录前言网站受到DDoS的症状判断是否被攻击查看网络带宽占用查看网络连接TCP连接攻击SYN洪水攻击防御措施TCP/IP内核参数优化iptables 防火墙预防防止同步包洪水(Sync Flood)Ping洪水攻击(Ping of Death)控制单个IP的最大并发…...
实现完全二叉树
文章目录1、树概念及结构2、孩子兄弟表示法3、二叉树3.1、二叉树的概念3.2、特殊的二叉树3.3、二叉树的存储4、堆的性质5、数组结构实现完全二叉树1、结构体的定义2、初始化堆3、销毁堆4、交换函数5、向上调整函数6、插入数据7、向下调整函数8、删除堆顶数据函数9、判断是否空堆…...
【独家】华为OD机试 - 矩阵最值(C 语言解题)
最近更新的博客 华为od 2023 | 什么是华为od,od 薪资待遇,od机试题清单华为OD机试真题大全,用 Python 解华为机试题 | 机试宝典【华为OD机试】全流程解析+经验分享,题型分享,防作弊指南华为od机试,独家整理 已参加机试人员的实战技巧本期题目:矩阵最值 题目 给定一个仅包…...
C++模板(进阶)
文章目录非类型模板参数类模板的特化类模板的概念函数模板特化类模板的特化全特化偏特化参数的进一步限制模板的分离编译模板的优缺点非类型模板参数 模板参数分类型形参与非类型形参. 类型形参: 出现在模板参数列表中,跟在class,typename之类的参数类型名称. 非类型形参: 就是…...
【数据分析之道(二)】列表
文章目录专栏导读1、列表介绍2、访问列表中的值3、列表增加和修改4、删除元素5、列表函数6、列表方法专栏导读 ✍ 作者简介:i阿极,CSDN Python领域新星创作者,专注于分享python领域知识。 ✍ 本文录入于《数据分析之道》,本专栏针…...
架构师必须要掌握的大小端问题
一、什么是大端和小端 所谓的大端模式,就是高位字节排放在内存的低地址端,低位字节排放在内存的高地址端。 所谓的小端模式,就是低位字节排放在内存的低地址端,高位字节排放在内存的高地址端。 简单来说:大端——高尾端,小端——低尾端 举个例子,比如数字 0x12 34 56 78…...
2023年ACM竞赛班 2023.3.20题解
目录 瞎编乱造第一题 瞎编乱造第二题 瞎编乱造第三题 瞎编乱造第四题 瞎编乱造第五题 不是很想编了但还是得编的第六题 不是很想编了但还是得编的第七题 还差三道题就编完了的第八题 还差两道题就编完了的第九题 太好啦终于编完了 为啥一周六天早八阿 瞎编乱造第一题…...
什么是语法糖?Java中有哪些语法糖?
本文从 Java 编译原理角度,深入字节码及 class 文件,抽丝剥茧,了解 Java 中的语法糖原理及用法,帮助大家在学会如何使用 Java 语法糖的同时,了解这些语法糖背后的原理1 语法糖语法糖(Syntactic Sugar&#…...
STM32学习(五)
GPIO General Purpose Input Output,通用输入输出端口,简称GPIO。 作用: 采集外部器件的信息(输入)控制外部器件的工作(输出) GPIO特点 1,不同型号,IO口数量可能不一样…...
STM32的CAN总线调试经验分享
相关文章 CAN总线简易入门教程 CAN总线显性电平和隐性电平详解 STM32的CAN总线调试经验分享 文章目录相关文章背景CAN总线CAN控制器CAN收发器调试过程硬件排查CAN分析仪芯片CAN控制器调试总结背景 最近负责的一个项目用的主控芯片是STM32F407IGT6,需要和几个电机控…...
深度剖析自定义类型(结构体、枚举、联合)——“C”
各位CSDN的uu们你们好呀,今天,小雅兰的内容是心心念念的结构体啦,其实在此之前,我也写过结构体的知识点,只是并没有很深入,那么,今天我会仔细来学习自定义类型的知识点,下面…...
《水经注地图服务》发布的全球影像数据在水经微图中调用
(本文首发于“水经注GIS”公号,订阅“水经注GIS”公号,为你分享更多GIS技术 )1、引言古人云:“工欲善其事,必先利其器。”意思是说:工匠想要使他的工作做好,一定要先让工具锋利&…...
MyBatis --- 缓存、逆向工程、分页插件
一、MyBatis的缓存 1.1、MyBatis的一级缓存 一级缓存是SqlSession级别的,通过同一个SqlSession查询的数据会被缓存,下次查询相同的数据,就会从缓存中直接获取,不会从数据库重新访问 使一级缓存失效的四种情况: 1、…...
vue3自定义svg图标组件
可参考: 未来必热:SVG Sprites技术介绍 懒人神器:svg-sprite-loader实现自己的Icon组件 在Vue3项目中使用svg-sprite-loader 前置知识 在页面中,虽然可以通过如下的方式使用img标签,来引入svg图标。但是,…...
智能火焰与烟雾检测系统(Python+YOLOv5深度学习模型+清新界面)
摘要:智能火焰与烟雾检测系统用于智能日常火灾检测报警,利用摄像头画面实时识别火焰与烟雾,另外支持图片、视频火焰检测并进行结果可视化。本文详细介绍基于智能火焰与烟雾检测系统,在介绍算法原理的同时,给出Python的…...
Java实习生------JUC并发编程(多线程)10道面试题打卡⭐⭐⭐
目录 并行和并发有什么区别? 线程和进程有什么区别? 创建线程有哪几种方式? runnable和callable有什么区别? 线程的状态及转换? sleep()和wait()的区别? run()和start()有什么区别? 在…...
ChatGPT和百度文心一言写用例,谁更强?
文心一言发布的第一时间,就排队申请了邀请码,昨晚看了下,邀请码已经到手,索性就拿一个例子试了一下,看看哪个能够真正意义上的提高生产力,最简单的录制了个GIF动画如下:问题:你是一个…...
设计模式总结
设计模式的六大原则 开放-封闭原则(OCP) (总原则) Open-Close Principle:该对扩展开放,对修改关闭。 目的就是保证程序的扩展性好,易于维护和升级。 开放-封闭原则是面向对象设计的核心所在, 开闭原则是Java世界里最基础的设计原则。 开闭…...
【K8S系列】深入解析Pod对象(一)
目录 序言 1.问题引入 1.1 问题描述 2 问题解答 2.1 pod 属性 2.1.1 NodeSelector 2.1.2 HostAliases 2.1.3 shareProcessNamespace 2.1.4 NodeName 2.1.5 其他pod属性 2.2 容器属性 2.2.1 ImagePullPolicy 2.2.2 Lifecycle 3 总结 4. 投票 序言 任何一件事情&am…...
springboot+vue基于web的药店管理系统 药品商城在线购药系统
目录同行可拿货,招校园代理 ,本人源头供货商功能模块分析技术实现要点扩展功能建议项目技术支持源码获取详细视频演示 :文章底部获取博主联系方式!同行可合作同行可拿货,招校园代理 ,本人源头供货商 功能模块分析 后台管理系统(SpringBoot&…...
Kettle转换里‘阻塞数据’控件为啥不灵?我用这个真实ETL案例给你讲透
Kettle转换中‘阻塞数据’控件的实战解析:从失效到精准控制 在ETL工具Kettle的实际应用中,数据流的精确控制往往是决定任务成败的关键。许多中高级用户在使用"阻塞数据直到步骤都完成"控件时,都曾遇到过看似配置正确却无法生效的困…...
PyTorch 3.0静训性能断崖预警:当AllReduce延迟>8.3ms或图编译耗时>117s时,你的训练任务已在 silently fail——附实时诊断CLI工具
第一章:PyTorch 3.0静态图分布式训练的静默失效危机全景PyTorch 3.0 引入的 TorchScript 静态图编译机制与 torch.distributed 的深度耦合,在多节点多卡场景下暴露出一类高危静默失效现象:训练进程持续运行、梯度同步无报错、loss 曲线看似收…...
从拒稿到录用:我的TOMM投稿实战复盘与经验分享
1. 从TMM拒稿到TOMM录用的心路历程 第一次收到TMM的拒稿邮件时,我正在实验室熬夜改代码。邮件弹出来的那一刻,整个人就像被泼了一盆冷水。那篇论文已经经历了三轮大修,每次都是几十条审稿意见,我们团队前前后后修改了上百个细节。…...
ENet核心架构深度解析:从主机管理到对等通信
ENet核心架构深度解析:从主机管理到对等通信 【免费下载链接】enet ENet reliable UDP networking library 项目地址: https://gitcode.com/gh_mirrors/en/enet ENet是一款高性能的可靠UDP网络库,专为实时多人游戏和低延迟应用设计。它通过创新的…...
AMD Ryzen硬件调试终极指南:3大突破性能优化秘籍揭秘
AMD Ryzen硬件调试终极指南:3大突破性能优化秘籍揭秘 【免费下载链接】SMUDebugTool A dedicated tool to help write/read various parameters of Ryzen-based systems, such as manual overclock, SMU, PCI, CPUID, MSR and Power Table. 项目地址: https://git…...
告别CTex!TeX Live+Texstudio组合安装避坑指南(Windows/Mac双平台)
告别CTex!TeX LiveTexstudio组合安装避坑指南(Windows/Mac双平台) 如果你曾经使用过CTex套装,可能会被其"开箱即用"的便利性所吸引。但当你需要跨平台协作或追求更灵活的定制时,TeX LiveTexstudio的组合无疑…...
AI 使用过程中遇到的问题及解决方案
////////////////////////////////////////////////////////////////////////////////////////////////////////context_management: Extra inputs are not permitted Received Model Groupclaude-sonnet-4-6错误原因这是 Claude API 的 context management(上下文管…...
千问3.5-2B一文详解:4.3GB权重免下载、24GB显存优化、温度参数调优手册
千问3.5-2B一文详解:4.3GB权重免下载、24GB显存优化、温度参数调优手册 1. 千问3.5-2B模型概述 千问3.5-2B是Qwen系列中的小型视觉语言模型,专为图片理解与文本生成任务设计。这个模型最吸引人的特点是它能同时处理视觉和语言信息,让你通过…...
Simulink仿真速度太慢?试试用C Mex S函数给模型“提提速”
Simulink性能优化实战:用C Mex S函数突破仿真速度瓶颈 当Simulink模型运行缓慢时,工程师们常常陷入漫长的等待。本文将揭示如何通过C Mex S函数这一利器,将仿真速度提升10倍以上,特别适合处理复杂算法、图像处理和大规模系统仿真等…...
