协议篇(二)I2Cverilog实现

零.基本协议

链接: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

仿真

在这里插入图片描述

接收模块(略),把最后的状态改为接收,三态门在这两天个状态打开并接收数据就可以了

评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值