企业网站要求,网站建设成立领导小组,惠州高端模板建站,主视觉设计网站I2C协议 IIC 协议是三种最常用的串行通信协议#xff08;I2C#xff0c;SPI#xff0c;UART#xff09;之一#xff0c;接口包含 SDA#xff08;串行数据线#xff09;和 SCL#xff08;串行时钟线#xff09;#xff0c;均为双向端口。I2C 仅使用两根信号线#xf…I2C协议 IIC 协议是三种最常用的串行通信协议I2CSPIUART之一接口包含 SDA串行数据线和 SCL串行时钟线均为双向端口。I2C 仅使用两根信号线极大地减少了连接线的数量支持多主多从且具有应答机制因此在片间通信有较多的应用。 I2C 主要包括四个状态起始 START数据传送 SEND应答 ACK停止 STOP。 传输起始 当 SCL 为高电平SDA 出现下跳变时标志着传输的起始。
数据传输 在传输数据位时采用大端传输即先传最高位 MSBSDA 在SCL 低电平时改变在 SCLH 时必须保持 SDA 稳定。
应答 在传输完 8bit 数据后Master 须释放 SDA Slave 接过 SDA 的控制权给出应答信号 ACK当 ACKL 时表示本字节数据传输有效。
停止 当 SCL 为高SDA 出现上跳变时标志着传输的结束。 一次 I2C 传输可以传输多个字节通常第一个字节为 I2C 设备地址 ADDR7bit和读写标志 R/W‾\rm{R/\overline W}R/W1bit。一个可能的 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 8h01; //空闲释放SCL/SDA
localparam START 8h02; //起始SCLHSDAD
localparam SEND_DATA 8h04; //发送数据
localparam GET_DATA 8h08; //读取数据释放SDA
localparam CHECK_ACK 8h10; //检查SDA的ACK/NACK释放SDA
localparam ACK 8h20; //发出ACKSDAL
localparam NACK 8h40; //发出NACKSDAH
localparam STOP 8h80; //停止SCLHSDARreg SCL_link 1b0;
reg SDA_link 1b0;reg SCL_buf 1b1; //o_buf
reg SDA_buf 1b1;wire SCL_ibuf; //i_buf
wire SDA_ibuf;reg [3:0] bit_cnt 4d15;//----------------------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, highinput, lowoutput 1时O - IO0时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 2d0;always (posedge clk) beginclk_cnt clk_cnt 1b1;
end//---------------------SCL_link-----------------------------
always (posedge clk) begincase(state)IDLE: beginSCL_link 1b0;endSTART, SEND_DATA, GET_DATA, CHECK_ACK, ACK, NACK, STOP: beginSCL_link 1b1;enddefault: beginSCL_link 1b0;endendcase
end//---------------------SDA_link-----------------------------
always (posedge clk) begincase(state)IDLE, GET_DATA, CHECK_ACK: beginSDA_link 1b0;endSTART, SEND_DATA, ACK, NACK, STOP: beginSDA_link 1b1;enddefault: beginSDA_link 1b0;endendcase
end//---------------------SCL_buf-----------------------------
always (posedge clk) begincase(state)IDLE: begin //1111SCL_buf 1b1;endSTART: begin //1110case(clk_cnt)2d0, 2d1, 2d2: beginSCL_buf 1b1;end2d3: beginSCL_buf 1b0;enddefault: ;endcaseendSTOP: begin //0111case(clk_cnt)2d1, 2d2, 2d3: beginSCL_buf 1b1;end2d0: beginSCL_buf 1b0;enddefault: ;endcaseendSEND_DATA, GET_DATA, CHECK_ACK, ACK, NACK: begin //0110case(clk_cnt)2d1, 2d2: beginSCL_buf 1b1;end2d0, 2d3: beginSCL_buf 1b0;enddefault: ;endcaseenddefault: begin //1111SCL_buf 1b1;endendcase
end//---------------------bit_cnt-----------------------------
always (posedge clk) begincase(state)SEND_DATA, GET_DATA: begincase(clk_cnt)2d2: beginbit_cnt bit_cnt - 1b1;enddefault: ;endcaseendSTART, ACK, NACK, CHECK_ACK: beginbit_cnt 4d7;enddefault: beginbit_cnt 4d15;endendcase
end//--------------------rddat_tmp----------------------------
always (posedge clk) begincase(state)GET_DATA: begincase(clk_cnt)2d1: 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)2d1: 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 1b1;endSTART: begin //1100从而在SCLH时产生SDADcase(clk_cnt)2d0, 2d1: beginSDA_buf 1b1;end2d2, 2d3: beginSDA_buf 1b0;enddefault: ;endcaseendSEND_DATA: begin //在clk_cnt0给出数据从而在clk_cnt1,2时(SCLH)保持SDA的稳定case(clk_cnt)2d0: beginSDA_buf wrdat_buf[bit_cnt];enddefault: ;endcaseendGET_DATA: beginSDA_buf 1b1;endCHECK_ACK: beginSDA_buf 1b0;endACK: beginSDA_buf 1b0;endNACK: beginSDA_buf 1b1;endSTOP: begin //0011从而在SCLH时产生SDARcase(clk_cnt)2d0, 2d1: beginSDA_buf 1b0;end2d2, 2d3: beginSDA_buf 1b1;enddefault: ;endcaseenddefault: beginSDA_buf 1b1;endendcase
end//-------------------change_state---------------------------
always (posedge clk) begincase(state)IDLE, ACK, NACK, CHECK_ACK, STOP: begincase(clk_cnt)2d3: beginchange_state 1b1;enddefault: beginchange_state 1b0;endendcaseendSEND_DATA, GET_DATA: begincase(bit_cnt)4d15: begincase(clk_cnt)2d3: beginchange_state 1b1;enddefault: beginchange_state 1b0;endendcaseenddefault: beginchange_state 1b0;endendcaseenddefault: begincase(clk_cnt)2d3: beginchange_state 1b1;enddefault: beginchange_state 1b0;endendcaseendendcase
endendmoduleI2C_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 8h01; //空闲
localparam START 8h02; //起始检测到SCLHSDAD
localparam SEND_DATA 8h04; //Slave发送数据接管SDA控制权
localparam GET_DATA 8h08; //Slave读取数据
localparam CHECK_ACK 8h10; //检查SDA的ACK/NACK
localparam ACK 8h20; //发出ACKSDAL接管SDA控制权
localparam NACK 8h40; //发出NACKSDAH接管SDA控制权
localparam STOP 8h80; //停止检测到SCLHSDAR
//不实现Clock Stretching功能因此Slave从不试图接管SCL
//除注释注明的状态外不获取SDA控制权wire SCL_link;
reg SDA_link 1b0;reg SCL_buf 1b1; //o_buf
reg SDA_buf 1b1;wire SCL_ibuf; //i_buf
wire SDA_ibuf;assign SCL_link 1b0;//----------------------IO_BUF-----------------------------
//IOBUF fo SCL
IOBUF IOBUF_SCL(.O (SCL_ibuf), //Buffer的输出接采集信号.IO (SCL),.I (SCL_buf), //Buffer的输入接要输出到FPGA外的信号.T (~SCL_link) //1时O - IO0时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 // SCLHSDAD接收起始busy 1b1;endelse if(SDA_ibuf SCL_ibuf) begin // SCLHSDAR接收结束busy 1b0;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 4d0;endelse if(SCL_ne SCL_cnt4d9) beginSCL_cnt 4d0;endelse if(SCL_pe) beginSCL_cnt SCL_cnt 1b1;endelse beginSCL_cnt SCL_cnt;end
end//---------------------change_state--------------------------
always (posedge clk) begincase(state)IDLE: beginif(busy_pe) beginchange_state 1b1;endelse beginchange_state 1b0;endendSTART: beginif(SCL_ne) beginchange_state 1b1;endelse beginchange_state 1b0;endendSEND_DATA, GET_DATA: beginif(SCL_ne SCL_cnt4d8) beginchange_state 1b1;endelse beginchange_state 1b0;endendACK, NACK, CHECK_ACK: beginif(SCL_ne) beginchange_state 1b1;endelse beginchange_state 1b0;endendSTOP: beginif(busy_ne) beginchange_state 1b1;endelse beginchange_state 1b0;endenddefault: beginchange_state 1b0;endendcase
end//-----------------------SDA_link----------------------------
always (posedge clk) begincase(state)SEND_DATA, ACK, NACK: beginSDA_link 1b1;enddefault: beginSDA_link 1b0;endendcase
end//----------------------check_ack----------------------------
always (posedge clk) begincase(state)CHECK_ACK: beginif(SCL_pe) begincheck_ack SDA_ibuf;endelse begincheck_ack 1b0;endenddefault: begincheck_ack 1b0;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 1b0;endNACK: beginSDA_buf 1b1;enddefault: beginSDA_buf 1b1;endendcase
endendmoduleMaster 读/写子模块 基于 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 7h11; //I2C设备地址
parameter WR_DATA_LEN 16d1; //写的数据个数localparam RW_W 1b0;
localparam RW_R 1b1;//---------------------I2C Master State Define----------------------
localparam IDLE 8h01; //空闲释放SCL/SDA
localparam START 8h02; //起始SCLHSDAD
localparam SEND_DATA 8h04; //发送数据
localparam GET_DATA 8h08; //读取数据释放SDA
localparam CHECK_ACK 8h10; //检查SDA的ACK/NACK释放SDA
localparam ACK 8h20; //发出ACKSDAL
localparam NACK 8h40; //发出NACKSDAH
localparam STOP 8h80; //停止SCLHSDAR//------------------------------------------------------------------
reg [7:0] state IDLE;
reg [7:0] next_state IDLE;reg start_flag 1b0;wire change_state;reg [7:0] wrdat_buf 8d0;
reg [15:0] data_cnt 16d0;//------------------------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)? 1b0 : 1b1;always (posedge clk) beginif(wr_en_pe ~busy) beginstart_flag 1b1;endelse if(state START) beginstart_flag 1b0;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检测到NACKSTOPnext_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 1b1;endelse beginwrdat_req 1b0;endenddefault: beginwrdat_req 1b0;endendcase
end// -----data_cnt-----
always (posedge change_state) begincase(state)IDLE: begindata_cnt 16d0;endSEND_DATA: begindata_cnt data_cnt 1b1;enddefault: begindata_cnt data_cnt;endendcase
end// -----wrdat_buf-----
always (posedge change_state) begincase(state)IDLE: beginwrdat_buf 8d0;endSTART: beginwrdat_buf {ADDR, RW_W};endCHECK_ACK: beginwrdat_buf wrdat;enddefault: beginwrdat_buf wrdat_buf;endendcase
endendmoduleI2C_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 7h11; //I2C设备地址
parameter RD_DATA_LEN 16d1; //读的数据个数localparam RW_W 1b0;
localparam RW_R 1b1;//---------------------I2C Master State Define----------------------
localparam IDLE 8h01; //空闲释放SCL/SDA
localparam START 8h02; //起始SCLHSDAD
localparam SEND_DATA 8h04; //发送数据
localparam GET_DATA 8h08; //读取数据释放SDA
localparam CHECK_ACK 8h10; //检查SDA的ACK/NACK释放SDA
localparam ACK 8h20; //发出ACKSDAL
localparam NACK 8h40; //发出NACKSDAH
localparam STOP 8h80; //停止SCLHSDAR//------------------------------------------------------------------
reg [7:0] state IDLE;
reg [7:0] next_state IDLE;reg start_flag 1b0;wire change_state;reg [7:0] wrdat_buf 8d0;
wire [7:0] rddat_tmp;
reg [15:0] data_cnt 16d0;//------------------------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)? 1b0 : 1b1;always (posedge clk) beginif(rd_en_pe ~busy) beginstart_flag 1b1;endelse if(state START) beginstart_flag 1b0;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 //检测到NACKSTOPnext_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 1b1;enddefault: beginrddat_vaild 1b0;endendcase
end// -----data_cnt-----
always (posedge change_state) begincase(state)IDLE: begindata_cnt 16d0;endGET_DATA: begindata_cnt data_cnt 1b1;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 8d0;endSTART: beginwrdat_buf {ADDR, RW_R};endCHECK_ACK: beginwrdat_buf 8d0;enddefault: beginwrdat_buf wrdat_buf;endendcase
endendmoduleSlave 读/写子模块 同样基于 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 7h11; //I2C设备地址
parameter RECEIVE_DATA_LEN 16d1; //接收的数据个数localparam RW_W 1b0;
localparam RW_R 1b1;//---------------------I2C Slave State Define----------------------
localparam IDLE 8h01; //空闲
localparam START 8h02; //起始检测到SCLHSDAD
localparam SEND_DATA 8h04; //Slave发送数据接管SDA控制权
localparam GET_DATA 8h08; //Slave读取数据
localparam CHECK_ACK 8h10; //检查SDA的ACK/NACK
localparam ACK 8h20; //发出ACKSDAL接管SDA控制权
localparam NACK 8h40; //发出NACKSDAH接管SDA控制权
localparam STOP 8h80; //停止检测到SCLHSDAR//------------------------------------------------------------------
reg [7:0] state IDLE;
reg [7:0] next_state IDLE;wire change_state;wire [7:0] rddat_tmp;
reg [15:0] data_cnt 16d0;reg isMe 1b0; //是否为本机地址//-------------------------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 1b0;endACK: beginif(data_cnt1) beginrddat_vaild 1b1;endelse beginrddat_vaild 1b0;endenddefault: beginrddat_vaild 1b0;endendcase
end// ---rddat---
always (posedge change_state) begincase(state)GET_DATA: beginif(data_cnt0) beginrddat rddat_tmp;endelse beginrddat 8d0;endenddefault: beginrddat rddat;endendcase
end// ---data_cnt---
always (posedge change_state) begincase(state)IDLE: begindata_cnt 16d0;endGET_DATA: begindata_cnt data_cnt 1b1;enddefault: begindata_cnt data_cnt;endendcase
end// ---isMe---
always (*) begincase(state)IDLE: beginisMe 1b0;endGET_DATA: beginif(data_cnt0) beginif(rddat_tmp{ADDR, RW_W}) begin //地址本机且RWW启动Slave接收进程isMe 1b1;endelse beginisMe isMe;endendelse beginisMe isMe;endenddefault: beginisMe isMe;endendcase
endendmoduleI2C_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 7h11; //I2C设备地址
parameter SEND_DATA_LEN 16d1; //发送的数据个数localparam RW_W 1b0;
localparam RW_R 1b1;//---------------------I2C Slave State Define----------------------
localparam IDLE 8h01; //空闲
localparam START 8h02; //起始检测到SCLHSDAD
localparam SEND_DATA 8h04; //Slave发送数据接管SDA控制权
localparam GET_DATA 8h08; //Slave读取数据
localparam CHECK_ACK 8h10; //检查SDA的ACK/NACK
localparam ACK 8h20; //发出ACKSDAL接管SDA控制权
localparam NACK 8h40; //发出NACKSDAH接管SDA控制权
localparam STOP 8h80; //停止检测到SCLHSDAR//------------------------------------------------------------------
reg [7:0] state IDLE;
reg [7:0] next_state IDLE;wire change_state;wire [7:0] rddat_tmp;reg [15:0] data_cnt 16d0;reg isMe 1b0; //是否为本机地址//-------------------------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 1b1;endelse beginwrdat_req 1b0;endenddefault: beginwrdat_req 1b0;endendcase
end// ---data_cnt---
always (posedge change_state) begincase(state)IDLE: begindata_cnt 16d0;endGET_DATA, SEND_DATA: begindata_cnt data_cnt 1b1;enddefault: begindata_cnt data_cnt;endendcase
end// ---isMe---
always (*) begincase(state)IDLE: beginisMe 1b0;endGET_DATA: beginif(data_cnt0) beginif(rddat_tmp{ADDR, RW_R}) begin //地址本机且RWR启动Slave发送进程isMe 1b1;endelse beginisMe isMe;endendelse beginisMe isMe;endenddefault: beginisMe isMe;endendcase
endendmoduleTest Bench 测试结果
Master写 Slave接收
I2C_Master_w_Slave_r_tb.v
timescale 1ns/100psmodule I2C_Master_w_Slave_r_tb();
//测试Master写、Slave接收reg clk_100M 1b1;
always #5 beginclk_100M ~clk_100M;
endreg clk_50M 1b1;
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 8d0;wire busy;
wire check_ack;I2C_Master_Write #(.ADDR (7h44),.WR_DATA_LEN (16d4))
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 1b1;
end//-------------------Slave-----------------------
wire rddat_vaild;
wire [7:0] rddat;
wire S_busy;I2C_Slave_Receive #(.ADDR (7h44),.RECEIVE_DATA_LEN (16d4))
I2C_Slave_Receive_inst(.clk (clk_100M),.rddat_vaild (rddat_vaild),.rddat (rddat),.busy (S_busy),.SCL (SCL),.SDA (SDA)
);//---------------------test-------------------------
initial beginwr_en 1b0;#100;wr_en 1b1;#100;wr_en 1b0;wait(busy);wait(~busy);#100;wr_en 1b1;#100;wr_en 1b0;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 1b1;
always #5 beginclk_100M ~clk_100M;
endreg clk_50M 1b1;
always #10 beginclk_50M ~clk_50M;
endwire SCL;
wire SDA;pullup(SCL);
pullup(SDA);//-------------------Master-----------------------
reg rd_en 1b0;
wire rddat_vaild;
wire [7:0] rddat;wire busy;
wire check_ack;I2C_Master_Read #(.ADDR (7h44),.RD_DATA_LEN (16d4))
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 8d0;wire S_busy;I2C_Slave_Send #(.ADDR (7h44),.SEND_DATA_LEN (16d4))
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 1b1;
end//---------------------test-------------------------
initial beginrd_en 1b0;#100;rd_en 1b1;#100;rd_en 1b0;wait(busy);wait(~busy);#100;rd_en 1b1;#100;rd_en 1b0;wait(busy);wait(~busy);#200;$stop;
endendmodule设置 Master 读设备地址与 Slave 设备地址相同单次 I2C 通信读取 4 个数据结果如下 若两者地址不同Slave 会自行挂起直到 I2C 总线释放后自动回到 IDLE 状态而 Master 由于没有收到指定设备的 ACK 确认信号也会自行终止读取进程结果如下