链接:I2C协议
主器件用于启动总线传送数据,并产生时钟以开放传送的器件,此时任何被寻址的器件均被认为是从器件.在总线上主和从、发和收的关系不是恒定的,而取决于此时数据传送方向。
I2C应答信号:
Master每发送完8bit数据后等待Slave的ACK。
即在第9个clock,若Slave发ACK,SDA会被拉低。
若没有ACK,SDA会被置高,这会引起Master发生RESTART或STOP流程,如下所示:
I2C位传输:
数据传输:SCL为高电平时,SDA线保持稳定,那个SDA上是在传输数据bit。
数据改变:SCL为低电平时,SDA线才能改变传输的bit
`timescale 1ns / 1ps
//////////////////////////////////////////////////////////////////////////////////
// Company:
// Engineer:
//
// Create Date: 2019/09/25 10:46:44
// Design Name:
// Module Name: I2C_WR
// Project Name:
// Target Devices:
// Tool Versions:
// Description:
//
// Dependencies:
//
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
//
//////////////////////////////////////////////////////////////////////////////////
//写 IIC2
module I2C_WR#(
parameter CLK_FREQ = 100_000_000, //100 MHz 时钟周期
parameter I2C_FREQ = 100_000 //10 KHz(< 400KHz) SCL周期
)
(
/*
设备地址,寄存器地址,输出都是8bit,都是先发送最高位,最低位自己改下
*/
input clk, //100M
input rst_n, //模块复位
output SCL, //时钟
inout SDA, //数据线
//控制信号
input [23:0] data_r, //{8'd设备地址,8'd寄存器地址,8'd数据地址}
input tx_start, //数据发送信号
output reg idle, //模块空闲信号
output reg tx_done, //发送完成信号
output reg error = 0, //发送报错
// 仿真使用
output next_state
);
//---------------------复位信号同步化----也可以使用异步复位同步释放-------------------------
reg [4:0] RESETn=5'd31;
always@(posedge clk)//
RESETn <={RESETn[3:0],rst_n};//最终使用的同步信号是RESETn[4]
//------- ----------------上电复位后延迟-----------------
//Delay xxus until i2c slave is steady
reg [16:0] delay_cnt;
localparam DELAY_TOP = CLK_FREQ/10000; //1ms Setting time after software/hardware reset
//localparam DELAY_TOP = 17'hff; //Just for test
always@(posedge clk)
begin
if(!RESETn[4])
delay_cnt <= 0;
else if(delay_cnt < DELAY_TOP)
delay_cnt <= delay_cnt + 1'b1;
else
delay_cnt <= delay_cnt;
end
wire delay_done = (delay_cnt == DELAY_TOP) ? 1'b1 : 1'b0; //1ms 延迟
//------------------接收开始信号,idle控制,tx_done控制---------------------
reg [1:0] tx_start_r = 0;
wire start_sig = ~tx_start_r[1]&tx_start_r[0];
reg i2c_transfer_en;
reg i2c_transfer_en_r = 0;
reg [23:0] data_r0 =0;
reg [23:0] data = 0; // 发送数据缓存
always @ (posedge clk)
begin
tx_start_r <= {tx_start_r[0],tx_start};
data_r0<= data_r;
i2c_transfer_en_r <= i2c_transfer_en;
if(start_sig)
begin
data <= data_r0;
end
end
reg [1:0] i = 0;
reg sig = 0; //发送过程信号
reg [4:0] current_state, next_state; //i2c write and read state
localparam I2C_WR_STOP = 5'd8;
always @ (posedge clk)
if(!RESETn[4])
begin
i<=0;
sig <=0;
idle <= 0;
end
else
begin
case(i)
2'd0:begin
sig <=0;
idle <=0;
tx_done <= 0;
if(delay_done)
begin
i<=2'd1;
end
end
2'd1:begin
sig <=0;
idle <=1;
tx_done <= 0;
if(start_sig)
i<=2'd2;
end
2'd2:begin
sig <=1;
idle <= 0;
if(current_state == I2C_WR_STOP && i2c_transfer_en == 1'b1 )
begin
i<=2'd1;
tx_done <= 1'b1;
end
end
default:begin
i<= 2'd1;
end
endcase
end
//------------------SCL时钟生成和SDA数据变换标志位信号-------------------
reg [15:0] clk_cnt; //时钟计数
reg i2c_ctrl_clk; //i2c control clock, H: valid; L: valid
//reg i2c_transfer_en; //send i2c data before, make sure that sdat is steady when i2c_sclk is High ,时钟的低电平中心
reg i2c_low_en; //时钟的高电平中心
always @ (posedge clk)
begin
if(!RESETn[4])
begin
clk_cnt <= 0;
i2c_ctrl_clk <= 0;
i2c_transfer_en <= 0;
i2c_low_en <= 0;
end
else
begin
if(delay_done)
begin
if(clk_cnt < (CLK_FREQ/I2C_FREQ) - 1'b1)
clk_cnt <= clk_cnt + 1'd1;
else
clk_cnt <= 0;
i2c_ctrl_clk <= ((clk_cnt >= (CLK_FREQ/I2C_FREQ)/4 + 1'b1) &&
(clk_cnt < (3*CLK_FREQ/I2C_FREQ)/4 + 1'b1)) ? 1'b1 : 1'b0;
i2c_transfer_en <= (clk_cnt == 16'd0) ? 1'b1 : 1'b0;
i2c_low_en <= (clk_cnt == (2*CLK_FREQ/I2C_FREQ)/4 - 1'b1) ? 1'b1 : 1'b0;
end
else
begin
clk_cnt <= 0;
i2c_ctrl_clk <= 0;
i2c_transfer_en <= 0;
i2c_low_en <= 0;
end
end
end
//I2C Timing state Parameter
localparam I2C_IDLE = 5'd0;
localparam I2C_WR_START = 5'd1;
localparam I2C_WR_IDADDR = 5'd2;
localparam I2C_WR_ACK1 = 5'd3;
localparam I2C_WR_REGADDR = 5'd4;
localparam I2C_WR_ACK2 = 5'd5;
localparam I2C_WR_REGDATA = 5'd6;
localparam I2C_WR_ACK3 = 5'd7;
//localparam I2C_WR_STOP = 5'd8;
//--------------------I2C contral-----------------------
// FSM: always1 时序逻辑
//reg [4:0] current_state, next_state; //i2c write and read state
always@(posedge clk)
begin
if(!RESETn[4])
current_state <= I2C_IDLE;
else if(i2c_transfer_en)
current_state <= next_state;
end
// FSM: always2 状态变换组合逻辑
reg [3:0] i2c_stream_cnt; //i2c data bit stream count
always@(*)
begin
next_state = I2C_IDLE; //state initialization
case(current_state)
I2C_IDLE: //5'd0
begin
if(delay_done) //1ms Setting time after software/hardware reset
begin
if(i2c_transfer_en &&sig)
begin
next_state = I2C_WR_START; //Write Data to I2C
end
else
next_state = next_state;
end
else
next_state = I2C_IDLE; //Wait I2C Bus is steady
end
//Write I2C: {ID_Address, REG_Address, W_REG_Data}
I2C_WR_START: //5'd1
begin
if(i2c_transfer_en) next_state = I2C_WR_IDADDR;
else next_state = I2C_WR_START;
end
I2C_WR_IDADDR: //5'd2
if(i2c_transfer_en == 1'b1 && i2c_stream_cnt == 4'd8)
next_state = I2C_WR_ACK1;
else next_state = I2C_WR_IDADDR;
I2C_WR_ACK1: //5'd3
if(i2c_transfer_en) next_state = I2C_WR_REGADDR;
else next_state = I2C_WR_ACK1;
I2C_WR_REGADDR: //5'd4
if(i2c_transfer_en == 1'b1 && i2c_stream_cnt == 4'd8)
next_state = I2C_WR_ACK2;
else next_state = I2C_WR_REGADDR;
I2C_WR_ACK2: //5'd5
if(i2c_transfer_en) next_state = I2C_WR_REGDATA;
else next_state = I2C_WR_ACK2;
I2C_WR_REGDATA: //5'd8
if(i2c_transfer_en == 1'b1 && i2c_stream_cnt == 4'd8)
next_state = I2C_WR_ACK3;
else next_state = I2C_WR_REGDATA;
I2C_WR_ACK3: //5'd9
if(i2c_transfer_en) next_state = I2C_WR_STOP;
else next_state = I2C_WR_ACK3;
I2C_WR_STOP: //5'd10
if(i2c_transfer_en) next_state = I2C_IDLE;
else next_state = I2C_WR_STOP;
default:; //default vaule
endcase
end
//-----------------------------------------
// FSM: always3 时序逻辑与current_state同时输出
reg i2c_sdat_out; //i2c data output
reg [7:0] i2c_wdata; //i2c data prepared to transfer
reg i2c_ack;
always@(posedge clk)
begin
if(!RESETn[4])
begin
i2c_sdat_out <= 1'b1;
i2c_stream_cnt <= 0;
i2c_wdata <= 0;
error <= 0;
end
else if(i2c_transfer_en)
begin
case(next_state)
I2C_IDLE: //5'd0
begin
i2c_sdat_out <= 1'b1; //idle state
i2c_stream_cnt <= 0;
i2c_wdata <= 0;
end
//Write I2C: {ID_Address, REG_Address, W_REG_Data}
I2C_WR_START: //5'd1
begin
i2c_sdat_out <= 1'b0;
i2c_stream_cnt <= 0;
i2c_wdata <= data[23:16]; //ID_Address
end
I2C_WR_IDADDR: //5'd2
begin
i2c_stream_cnt <= i2c_stream_cnt + 1'b1;
i2c_sdat_out <= i2c_wdata[3'd7 - i2c_stream_cnt]; //最高位先发
end
I2C_WR_ACK1: //5'd3
begin
i2c_stream_cnt <= 0;
i2c_wdata <= data[15:8]; //REG_Address
end
I2C_WR_REGADDR: //5'd4
begin
i2c_stream_cnt <= i2c_stream_cnt + 1'b1;
i2c_sdat_out <= i2c_wdata[3'd7 - i2c_stream_cnt]; //最高位先发
end
I2C_WR_ACK2: //5'd5
begin
i2c_stream_cnt <= 0;
i2c_wdata <= data[7:0]; //REG_Address
end
I2C_WR_REGDATA: //5'd6
begin
i2c_stream_cnt <= i2c_stream_cnt + 1'b1;
i2c_sdat_out <= i2c_wdata[3'd7 - i2c_stream_cnt];
end
I2C_WR_ACK3: //5'd7
i2c_stream_cnt <= 0;
I2C_WR_STOP:begin //5'd8
i2c_sdat_out <= 1'b0;
if(i2c_ack == 1&& i2c_transfer_en_r == 1'b1 ) //应答不成功, 报错
begin
error <= 1'b1;
end
end
default:
begin
i2c_sdat_out <= 1'b1;
i2c_stream_cnt <= 0;
i2c_wdata <= 0;
error <=0;
end
endcase
end
else
begin
i2c_stream_cnt <= i2c_stream_cnt;
i2c_sdat_out <= i2c_sdat_out;
end
end
//------------------------------接收应答信号-----------
//respone from slave for i2c data transfer
reg i2c_ack1, i2c_ack2, i2c_ack3,i2c_ack2a;
//reg i2c_ack;
//reg [7:0] i2c_rdata;
always@(posedge clk)
begin
if(!RESETn[4])
begin
{i2c_ack1, i2c_ack2, i2c_ack3,i2c_ack2a} <= 4'b1111;
i2c_ack <= 1'b1;
end
else if(i2c_low_en)
begin
case(next_state)
I2C_IDLE:
begin
{i2c_ack1, i2c_ack2, i2c_ack3} <= 3'b111;
i2c_ack <= 1'b1;
end
//Write I2C: {ID_Address, REG_Address, W_REG_Data}
I2C_WR_ACK1: i2c_ack1 <= SDA;
I2C_WR_ACK2: i2c_ack2 <= SDA;
I2C_WR_ACK3: i2c_ack3 <= SDA;
I2C_WR_STOP: i2c_ack <= (i2c_ack1 | i2c_ack2 | i2c_ack3); //只有三次完成全部成功,才为0;
endcase
end
else
begin
{i2c_ack1, i2c_ack2, i2c_ack3} <= {i2c_ack1, i2c_ack2, i2c_ack3};
i2c_ack <= i2c_ack;
end
end
wire bir_en =(current_state == I2C_WR_ACK1 || current_state == I2C_WR_ACK2 || current_state == I2C_WR_ACK3 ) ? 1'b1 : 1'b0;
//assign tx_done = (current_state == I2C_WR_STOP ) ? 1'b1 : 1'b0;
assign SCL = (current_state >= I2C_WR_IDADDR && current_state <= I2C_WR_ACK3)?i2c_ctrl_clk : 1'b1;
assign SDA = (~bir_en) ? i2c_sdat_out : 1'bz;
endmodule
注意:本次设计采用三段式状态机,分别是FSM1(时序),FSM2(current组合),FSM3(next时序),结果就是输出与current状态同步改变没有延时, 功能仿真等价于FSM1(时序),FSM2(current组合),FSM3(current组合),但是之中输出前端为组合逻辑所以会增大关键路径
`timescale 1ns / 1ps
//////////////////////////////////////////////////////////////////////////////////
// Company:
// Engineer:
//
// Create Date: 2019/09/25 14:22:14
// Design Name:
// Module Name: test_i2c
// Project Name:
// Target Devices:
// Tool Versions:
// Description:
//
// Dependencies:
//
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
//
//////////////////////////////////////////////////////////////////////////////////
module test_i2c(
);
reg clk;
reg rst_n;
parameter clk_period=10;
parameter clk_half_period=clk_period/2;
initial
begin
clk =0;
rst_n = 0;
#100 rst_n = 1;
end
always
# clk_half_period clk = ~clk;
reg [23:0] data;
reg tx_start;
wire idle;
wire tx_done;
wire [4:0] next_state;
wire SCL;
wire SDA;
wire error;
I2C_WR a1
(
.clk(clk), //100M
.rst_n(rst_n), //模块复位
.SCL(SCL),
.SDA(SDA),
//控制信号
.data_r(data), //{8'd设备地址,8'd寄存器地址,8'd数据地址}
.tx_start(tx_start), //数据发送信号
.idle(idle), //模块空闲信号
.tx_done(tx_done), //发送完成信号
.error(error),
.next_state(next_state)
);
reg [2:0] i = 0;
always @ (posedge clk)
begin
if(~rst_n)
begin
data <=0;
tx_start <=0;
end
else
begin
case(i)
0:begin
if(idle)
begin
tx_start <= 1;
data <= {8'b01100010,8'b11000100,8'b11011100};
i <= 1;
end
end
1:begin
tx_start <= 0;
data <= 0;
end
endcase
end
end
// SLAVE响应
reg ack = 1;
always @ (*)
begin
if(next_state == 5'd3 || next_state == 5'd5 ||next_state == 5'd7)
begin
ack <= 0;
end
else
begin
ack <= 1;
end
end
assign SDA = (next_state == 5'd3 || next_state == 5'd5 ||next_state == 5'd7)?ack:1'bz;
endmodule