常用串行总线(三)——IIC协议(Verilog实现)-FPGA常见问题论坛-FPGA CPLD-ChipDebug

常用串行总线(三)——IIC协议(Verilog实现)

目录/contents

● IIC基础知识

● IIC传输协议

● IIC代码实现

● IIC的优缺点

01

IIC基础知识

集成电路总线(Inter-Intergrated Circuit),通常称作IICBUS,简称为IIC,是一种采用多主从结构的串行通信总线。IIC由PHILIPS公司于1980年推出,利用该总线可实现多主机系统所需的裁决和高低速设备同步等功能。

IIC串行总线一般有两根信号线,分别是时钟线SCL和数据线SDA,所有IIC总线各设备的串行数据SDA接到总线SDA上,时钟线SLC接到总线SCL上。

双向串行数据SDA:输出电路用于向总线发送数据,输入电路用于接收总线上的数据;

双向时钟数据SCL:输出电路用于向总线发送时钟信号,输入电路用于检测总线时钟电平以决定下次时钟脉冲电平时间。

各设备与总线相连的输出端采用高阻态以避免总线信号的混乱,通常采用漏极开路输出或集电极开路输出。总线空闲时,各设备都是开漏输出,通常可采用上拉电阻使总线保持高电平,而任一设备输出低电平都将拉低相应的总线信号。

图片[1]-常用串行总线(三)——IIC协议(Verilog实现)-FPGA常见问题论坛-FPGA CPLD-ChipDebug

IIC总线上的设备可分为主设备和从设备两种:主设备有权利主动发起/结束一次通信;从设备只能被动响应。所有连接在IIC总线上的设备都有各自唯一的地址(原地址位宽为7bits,改进后采用10bits位宽),每个设备都可以作为主设备或从设备,但是同一时刻只能有一个主设备控制。如果总线上有多个设备同时启用总线,IIC通过检测和仲裁机制解决传输冲突。IIC允许连接的设备传输速率不同,多台设备之间时钟信号的同步过程称为同步化。

02

IIC传输协议

2.1 IIC协议模式

IIC协议为半双工模式,同一条数据线上完成读/写操作,不同操作时的数据帧格式可分为写操作、读操作、读写操作。

2.1.1 写操作数据帧格式

主机向从机写数据的数据帧格式如下:

图片[2]-常用串行总线(三)——IIC协议(Verilog实现)-FPGA常见问题论坛-FPGA CPLD-ChipDebug
白色为主机发送数据,灰色为从机发送数据。

1. 主机发送开始信号S;

2. 主机发送地址数据ADDR;

3. 主机发送写信号0;

4. 从机响应主机写信号ACK;

5. 主机发送数据信息DATA;

6. 从机响应主机发送数据ACK;

7. 循环5 6步骤可完成主机向从机连续写数据过程;

8. 主机发送结束信号P.

 

2.1.2 读操作数据帧格式

主机从从机读数据的数据帧格式如下:

图片[3]-常用串行总线(三)——IIC协议(Verilog实现)-FPGA常见问题论坛-FPGA CPLD-ChipDebug
白色为主机发送数据,灰色为从机发送数据。

1. 主机发送开始信号S;

2. 主机发送地址数据ADDR;

3. 主机发送读信号1;

4. 从机响应主机读信号ACK;

5. 从机发送数据信息DATA;

6. 主机响应从机发送数据ACK;

7. 循环5 6步骤可完成主机向从机连续读数据过程;

8. 主机发送结束信号P.

 

2.1.3 读写同时操作数据帧格式

主机先向从机写数据后从从机读数据的数据帧格式如下:

图片[4]-常用串行总线(三)——IIC协议(Verilog实现)-FPGA常见问题论坛-FPGA CPLD-ChipDebug

白色为主机发送数据,灰色为从机发送数据。

主机可以在完成写操作后不发送结束信号P,直接进行读操作。该过程主机不可变,而从机可以通过发送不同地址选择不同的从机。

2.2 IIC写时序

IIC协议写时序可分为单字节写时序和连续写时序:

2.2.1 单字节写时序

单字节地址写时序过程:

图片[5]-常用串行总线(三)——IIC协议(Verilog实现)-FPGA常见问题论坛-FPGA CPLD-ChipDebug
1. 主机发送开始信号S;


2. 主机发送控制字节CONTROL BYTE(7bits设备地址dev addr和1bit读写信号),最低位为0表示主机向从机写数据;

3. 从机响应主机控制字节ACK;

4. 主机发送单字节寄存器地址信息WORD ADDRESS;

5. 从机响应主机发送寄存器地址ACK;

6. 主机发送单字节数据信息DATA;

7. 从机响应主机发送数据信息ACK;

8. 主机发送结束信号P.

 

双字节地址写时序过程:

图片[6]-常用串行总线(三)——IIC协议(Verilog实现)-FPGA常见问题论坛-FPGA CPLD-ChipDebug
1. 主机发送开始信号S;

2. 主机发送控制字节CONTROL BYTE(7bits设备地址dev addr和1bit读写信号),最低位为0表示主机向从机写数据;

3. 从机响应主机控制字节ACK;

4. 主机发送寄存器地址信息高字节ADDRESS HIGH BYTE;

5. 从机响应主机发送寄存器地址高字节ACK;

6. 主机发送寄存器地址信息低字节ADDRESS LOW BYTE;

7. 从机响应主机发送寄存器地址低字节ACK;

8. 主机发送单字节数据信息DATA;

9. 从机响应主机发送数据信息ACK;

10. 主机发送结束信号P.

 

2.2.2 连续写时序

单字节地址写时序过程:

图片[7]-常用串行总线(三)——IIC协议(Verilog实现)-FPGA常见问题论坛-FPGA CPLD-ChipDebug
1. 主机发送开始信号S;

2. 主机发送控制字节CONTROL BYTE(7bits设备地址dev addr和1bit读写信号),最低位为0表示主机向从机写数据;

3. 从机响应主机控制字节ACK;

4. 主机发送单字节寄存器地址信息WORD ADDRESS;

5. 从机响应主机发送寄存器地址ACK;

6. 主机发送单字节数据信息DATA;

7. 从机响应主机发送数据信息ACK;

8. 循环6 7步骤可以连续向从机写数据DATA(D+n);

9. 主机发送结束信号P.

 

双字节地址写时序过程:

图片[8]-常用串行总线(三)——IIC协议(Verilog实现)-FPGA常见问题论坛-FPGA CPLD-ChipDebug
1. 主机发送开始信号S;

2. 主机发送控制字节CONTROL BYTE(7bits设备地址dev addr和1bit读写信号),最低位为0表示主机向从机写数据;

3. 从机响应主机控制字节ACK;

4. 主机发送寄存器地址信息高字节ADDRESS HIGH BYTE;

5. 从机响应主机发送寄存器地址高字节ACK;

6. 主机发送寄存器地址信息低字节ADDRESS LOW BYTE;

7. 从机响应主机发送寄存器地址低字节ACK;

8. 主机发送单字节数据信息DATA;

9. 从机响应主机发送数据信息ACK;

10. 循环8 9步骤可以连续向从机写数据DATA(D+n);

11. 主机发送结束信号P.

 

2.3 IIC读时序

IIC协议读时序可分为单字节读时序和连续读时序:

2.3.1 单字节读时序

单字节地址读时序过程:

图片[9]-常用串行总线(三)——IIC协议(Verilog实现)-FPGA常见问题论坛-FPGA CPLD-ChipDebug
1. 主机发送开始信号S;


2. 主机发送控制字节CONTROL BYTE(7bits设备地址dev addr和1bit读写信号),最低位为0表示主机向从机写数据;

3. 从机响应主机控制字节ACK;

4. 主机发送单字节寄存器地址信息WORD ADDRESS;

5. 从机响应主机发送寄存器地址ACK;

6. 主机发送开始信号S;

7. 主机发送控制字节CONTROL BYTE(7bits设备地址dev addr和1bit读写信号),最低位为1表示主机从从机读数据;

8. 从机响应主机控制字节ACK;

9. 从机发送单字节数据信息DATA;

10. 主机响应从机发送数据信息NACK(非应答位: 接收器是主机时,在接收到最后一个字节后发送NACK已通知被控发送从机结束数据发送,并释放SDA数据线以便主机发送停止信号P);

11. 主机发送结束信号P.

 

双字节地址读时序过程:

图片[10]-常用串行总线(三)——IIC协议(Verilog实现)-FPGA常见问题论坛-FPGA CPLD-ChipDebug
1. 主机发送开始信号S;

2. 主机发送控制字节CONTROL BYTE(7bits设备地址dev addr和1bit读写信号),最低位为0表示主机向从机写数据;

3. 从机响应主机控制字节ACK;

4. 主机发送寄存器地址信息高字节ADDRESS HIGH BYTE;

5. 从机响应主机发送寄存器地址高字节ACK;

6. 主机发送寄存器地址信息低字节ADDRESS LOW BYTE;

7. 从机响应主机发送寄存器地址低字节ACK;

8. 主机发送开始信号S;

9. 主机发送控制字节CONTROL BYTE(7bits设备地址dev addr和1bit读写信号),最低位为1表示主机从从机读数据;

10. 从机响应主机控制字节ACK;

11. 从机发送单字节数据信息DATA;

12. 主机响应从机发送数据信息NACK(非应答位: 接收器是主机时,在接收到最后一个字节后发送NACK已通知被控发送从机结束数据发送,并释放SDA数据线以便主机发送停止信号P);

13. 主机发送结束信号P.

 

2.3.2 连续读时序

单字节地址读时序过程:

图片[11]-常用串行总线(三)——IIC协议(Verilog实现)-FPGA常见问题论坛-FPGA CPLD-ChipDebug
1. 主机发送开始信号S;


2. 主机发送控制字节CONTROL BYTE(7bits设备地址dev addr和1bit读写信号),最低位为0表示主机向从机写数据;

3. 从机响应主机控制字节ACK;

4. 主机发送单字节寄存器地址信息WORD ADDRESS;

5. 从机响应主机发送寄存器地址ACK;

6. 主机发送开始信号S;

7. 主机发送控制字节CONTROL BYTE(7bits设备地址dev addr和1bit读写信号),最低位为1表示主机从从机读数据;

8. 从机响应主机控制字节ACK;

9. 从机发送单字节数据信息DATA;

10. 主机响应从机发送数据信息ACK;

11. 循环9 10步骤可完成主机向从机连续读数据过程,读取最后一个字节数据时主机应响应NACK(非应答位: 接收器是主机时,在接收到最后一个字节后发送NACK已通知被控发送从机结束数据发送,并释放SDA数据线以便主机发送停止信号P);

12. 主机发送结束信号P.

 

双字节地址读时序过程:

图片[12]-常用串行总线(三)——IIC协议(Verilog实现)-FPGA常见问题论坛-FPGA CPLD-ChipDebug
1. 主机发送开始信号S;

2. 主机发送控制字节CONTROL BYTE(7bits设备地址dev addr和1bit读写信号),最低位为0表示主机向从机写数据;

3. 从机响应主机控制字节ACK;

4. 主机发送寄存器地址信息高字节ADDRESS HIGH BYTE;

5. 从机响应主机发送寄存器地址高字节ACK;

6. 主机发送寄存器地址信息低字节ADDRESS LOW BYTE;

7. 从机响应主机发送寄存器地址低字节ACK;

8. 主机发送开始信号S;

9. 主机发送控制字节CONTROL BYTE(7bits设备地址dev addr和1bit读写信号),最低位为1表示主机从从机读数据;

10. 从机响应主机控制字节ACK;

11. 从机发送单字节数据信息DATA;

12. 主机响应从机发送数据信息ACK;

13. 循环11 12步骤可完成主机向从机连续读数据过程,读取最后一个字节数据时主机应响应NACK(非应答位: 接收器是主机时,在接收到最后一个字节后发送NACK已通知被控发送从机结束数据发送,并释放SDA数据线以便主机发送停止信号P);

14. 主机发送结束信号P.

 

03

IIC代码实现

3.1 IIC目标实现功能

设计一个IIC模块,具体要求如下:

设计一个IIC协议提供给主设备,通过查找表实现对从设备寄存器进行配置。按查找表顺序lut_index依次对设备地址为lut_dev_addr的从设备寄存器进行配置,为lut_reg_addr配置寄存器数据lut_reg_data,同时将传输异常和传输结束信号引出以对传输过程进行监控。模块的定义如下:

module i2c(

input              rst,                     //复位信号

input              clk,                     //时钟信号

input[15:0]        clk_div_cnt,     //时钟计数器

input              i2c_addr_2byte, //双字节地址

output reg[9:0]    lut_index,      //查找表顺序号

input[7:0]         lut_dev_addr,   //从设备地址

input[15:0]        lut_reg_addr,   //寄存器地址

input[7:0]         lut_reg_data,    //寄存器数据

output reg         error,               //传输异常信号

output             done,                 //传输结束信号

inout              i2c_scl,                //IIC时钟信号

inout              i2c_sda               //IIC数据信号

);

 

3.2 Verilog代码

1. 顶层模块 (i2c):

module i2c(

input              rst,

input              clk,

input[15:0]        clk_div_cnt,

input              i2c_addr_2byte,

output reg[9:0]    lut_index,

input[7:0]         lut_dev_addr,

input[15:0]        lut_reg_addr,

input[7:0]         lut_reg_data,

output reg         error,

output             done,

inout              i2c_scl,

inout              i2c_sda

);

wire scl_pad_i;

wire scl_pad_o;

wire scl_padoen_o;

wire sda_pad_i;

wire sda_pad_o;

wire sda_padoen_o;

assign sda_pad_i = i2c_sda;

assign i2c_sda = ~sda_padoen_o ? sda_pad_o : 1'bz;

assign scl_pad_i = i2c_scl;

assign i2c_scl = ~scl_padoen_o ? scl_pad_o : 1'bz;

reg i2c_read_req;

wire i2c_read_req_ack;

reg i2c_write_req;

wire i2c_write_req_ack;

wire[7:0] i2c_slave_dev_addr;

wire[15:0] i2c_slave_reg_addr;

wire[7:0] i2c_write_data;

wire[7:0] i2c_read_data;

wire err;

reg[2:0] state;

localparam S_IDLE                =  0;

localparam S_WR_I2C_CHECK        =  1;

localparam S_WR_I2C              =  2;

localparam S_WR_I2C_DONE         =  3;

assign done = (state == S_WR_I2C_DONE);

assign i2c_slave_dev_addr  = lut_dev_addr;

assign i2c_slave_reg_addr = lut_reg_addr;

assign i2c_write_data  = lut_reg_data;

//cascatrix carson

always@(posedge clk or posedge rst)

begin

if(rst)

begin

    state <= S_IDLE;

    error <= 1'b0;

    lut_index <= 8'd0;

end

else

    case(state)

    S_IDLE:

    begin

        state <= S_WR_I2C_CHECK;

        error <= 1'b0;

        lut_index <= 8'd0;

    end

    S_WR_I2C_CHECK:

    begin

        if(i2c_slave_dev_addr != 8'hff)

        begin

            i2c_write_req <= 1'b1;

            state <= S_WR_I2C;

        end

        else

        begin

            state <= S_WR_I2C_DONE;

        end

    end

    S_WR_I2C:

    begin

        if(i2c_write_req_ack)

        begin

            error <= err ? 1'b1 : error;

            lut_index <= lut_index + 8'd1;

            i2c_write_req <= 1'b0;

            state <= S_WR_I2C_CHECK;

        end

    end

    S_WR_I2C_DONE:

    begin

        state <= S_WR_I2C_DONE;

    end

    default:

        state <= S_IDLE;

endcase

end

i2c_ctrl i2c_ctrl

(

.rst(rst),

.clk(clk),

.clk_div_cnt(clk_div_cnt),

// I2C signals

// i2c clock line

.scl_pad_i(scl_pad_i),       // SCL-line input

.scl_pad_o(scl_pad_o),       // SCL-line output (always 1'b0)

.scl_padoen_o(scl_padoen_o),    // SCL-line output enable (active low)

// i2c data line

.sda_pad_i(sda_pad_i),       // SDA-line input

.sda_pad_o(sda_pad_o),       // SDA-line output (always 1'b0)

.sda_padoen_o(sda_padoen_o),    // SDA-line output enable (active low)

.i2c_read_req(i2c_read_req),

.i2c_addr_2byte(i2c_addr_2byte),

.i2c_read_req_ack(i2c_read_req_ack),

.i2c_write_req(i2c_write_req),

.i2c_write_req_ack(i2c_write_req_ack),

.i2c_slave_dev_addr(i2c_slave_dev_addr),

.i2c_slave_reg_addr(i2c_slave_reg_addr),

.i2c_write_data(i2c_write_data),

.i2c_read_data(i2c_read_data),

.error(err)

);

endmodule

 

2. 组帧模块 (i2c_ctrl):

module i2c_ctrl

(

input rst,

input clk,

input[15:0] clk_div_cnt,

// I2C signals

// i2c clock line

input  scl_pad_i,         //SCL-line input

output scl_pad_o,       //SCL-line output (always 1'b0)

output scl_padoen_o,   //SCL-line output enable (active low)

// i2c data line

input  sda_pad_i,       //SDA-line input

output sda_pad_o,    //SDA-line output (always 1'b0)

output sda_padoen_o,    //SDA-line output enable (active low)

input i2c_addr_2byte,  //register address 16bit or 8bit

input i2c_read_req,              //Read register request

output i2c_read_req_ack,  //Read register request response

input i2c_write_req,             //Write register request

output i2c_write_req_ack,   //Write register request response

input[7:0] i2c_slave_dev_addr,   //I2c device address

input[15:0] i2c_slave_reg_addr, //I2c register address

input[7:0] i2c_write_data,        //I2c write register data

output reg[7:0] i2c_read_data,//I2c read register data

output reg error      //The error indication, generally there is no response

);

//State machine definition cascatrix carson

localparam S_IDLE= 0;

localparam S_WR_DEV_ADDR=1;

localparam S_WR_REG_ADDR=2;

localparam S_WR_DATA=3;

localparam S_WR_ACK=4;

localparam S_WR_ERR_NACK=5;

localparam S_RD_DEV_ADDR0=6;

localparam S_RD_REG_ADDR=7;

localparam S_RD_DEV_ADDR1=8;

localparam S_RD_DATA=9;

localparam S_RD_STOP=10;

localparam S_WR_STOP=11;

localparam S_WAIT=12;

localparam S_WR_REG_ADDR1=13;

localparam S_RD_REG_ADDR1=14;

localparam S_RD_ACK=15;

reg start;

reg stop;

reg read;

reg write;

reg ack_in;

reg[7:0] txr;

wire[7:0] rxr;

wire i2c_busy;

wire i2c_al;

wire done;

wire irxack;

reg[3:0] state, next_state;

assign i2c_read_req_ack = (state == S_RD_ACK);

assign i2c_write_req_ack = (state == S_WR_ACK);

always@(posedge clk or posedge rst)

begin

    if(rst)

        state <= S_IDLE;

    else

        state <= next_state;

end

always@(*)

begin

    case(state)

    S_IDLE:

    //Waiting for read and write requests

        if(i2c_write_req)

            next_state <= S_WR_DEV_ADDR;

        else if(i2c_read_req)

            next_state <= S_RD_DEV_ADDR0;

        else

            next_state <= S_IDLE;

    //Write I2C device address

    S_WR_DEV_ADDR:

        if(done && irxack)

            next_state <= S_WR_ERR_NACK;

        else if(done)

            next_state <= S_WR_REG_ADDR;

        else

            next_state <= S_WR_DEV_ADDR;

    //Write the address of the I2C register

    S_WR_REG_ADDR:

        if(done)

    //If it is the 8bit register address, it enters the write data state

        next_state<=i2c_addr_2byte? S_WR_REG_AD DR1  : S_WR_DATA;

        else

            next_state <= S_WR_REG_ADDR;

    S_WR_REG_ADDR1:

        if(done)

            next_state <= S_WR_DATA;

        else

            next_state <= S_WR_REG_ADDR1;

    //Write data

    S_WR_DATA:

        if(done)

            next_state <= S_WR_STOP;

        else

            next_state <= S_WR_DATA;

    S_WR_ERR_NACK:

        next_state <= S_WR_STOP;

    S_RD_ACK,S_WR_ACK:

        next_state <= S_WAIT;

    S_WAIT:

        next_state <= S_IDLE;

    S_RD_DEV_ADDR0:

        if(done && irxack)

            next_state <= S_WR_ERR_NACK;

        else if(done)

            next_state <= S_RD_REG_ADDR;

        else

            next_state <= S_RD_DEV_ADDR0;

    S_RD_REG_ADDR:

        if(done)

        next_state<=i2c_addr_2byte?S_RD_REG_ADDR1 : S_RD_DEV_ADDR1;

        else

            next_state <= S_RD_REG_ADDR;

    S_RD_REG_ADDR1:

        if(done)

            next_state <= S_RD_DEV_ADDR1;

        else

            next_state <= S_RD_REG_ADDR1;

    S_RD_DEV_ADDR1:

        if(done)

            next_state <= S_RD_DATA;

        else

            next_state <= S_RD_DEV_ADDR1;

    S_RD_DATA:

        if(done)

            next_state <= S_RD_STOP;

        else

            next_state <= S_RD_DATA;

    S_RD_STOP:

        if(done)

            next_state <= S_RD_ACK;

        else

            next_state <= S_RD_STOP;

    S_WR_STOP:

        if(done)

            next_state <= S_WR_ACK;

        else

            next_state <= S_WR_STOP;

    default:

        next_state <= S_IDLE;

    endcase

end

always@(posedge clk or posedge rst)

begin

    if(rst)

        error <= 1'b0;

    else if(state == S_IDLE)

        error <= 1'b0;

    else if(state == S_WR_ERR_NACK)

        error <= 1'b1;

end

always@(posedge clk or posedge rst)

begin

    if(rst)

        start <= 1'b0;

    else if(done)

        start <= 1'b0;

  else if(state == S_WR_DEV_ADDR || state == S_RD_DEV_ADDR0 || state == S_RD_DEV_ADDR1)

        start <= 1'b1;

end

always@(posedge clk or posedge rst)

begin

    if(rst)

        stop <= 1'b0;

    else if(done)

        stop <= 1'b0;

    else if(state == S_WR_STOP || state == S_RD_STOP)

        stop <= 1'b1;

end

always@(posedge clk or posedge rst)

begin

    if(rst)

        ack_in <= 1'b0;

    else

        ack_in <= 1'b1;

end

always@(posedge clk or posedge rst)

begin

    if(rst)

        write <= 1'b0;

    else if(done)

        write <= 1'b0;

  else if(state == S_WR_DEV_ADDR || state == S_WR_REG_ADDR || state == S_WR_REG_ADDR1|| state == S_WR_DATA || state == S_RD_DEV_ADDR0 || state == S_RD_DEV_ADDR1 || state == S_RD_REG_ADDR || state == S_RD_REG_ADDR1)

        write <= 1'b1;

end

always@(posedge clk or posedge rst)

begin

    if(rst)

        read <= 1'b0;

    else if(done)

        read <= 1'b0;

    else if(state == S_RD_DATA)

        read <= 1'b1;

end

always@(posedge clk or posedge rst)

begin

    if(rst)

        i2c_read_data <= 8'h00;

    else if(state == S_RD_DATA && done)

        i2c_read_data <= rxr;

end

always@(posedge clk or posedge rst)

begin

    if(rst)

        txr <= 8'd0;

    else

    case(state)

    S_WR_DEV_ADDR,S_RD_DEV_ADDR0:

        txr <= {i2c_slave_dev_addr[7:1],1'b0};

    S_RD_DEV_ADDR1:

        txr <= {i2c_slave_dev_addr[7:1],1'b1};

    S_WR_REG_ADDR,S_RD_REG_ADDR:

    txr<=(i2c_addr_2byte==1'b1)?i2c_slave_reg_addr[15 :8] : i2c_slave_reg_addr[7:0];

    S_WR_REG_ADDR1,S_RD_REG_ADDR1:

        txr <= i2c_slave_reg_addr[7:0];

    S_WR_DATA:

        txr <= i2c_write_data;

    default:

        txr <= 8'hff;

    endcase

end

i2c_byte_ctrl byte_controller (

.clk      ( clk          ),

.rst      ( rst          ),

.nReset   ( 1'b1         ),

.ena      ( 1'b1         ),

.clk_cnt  ( clk_div_cnt  ),

.start    ( start        ),

.stop     ( stop         ),

.read     ( read         ),

.write    ( write        ),

.ack_in   ( ack_in       ),

.din      ( txr          ),

.cmd_ack  ( done         ),

.ack_out  ( irxack       ),

.dout     ( rxr          ),

.i2c_busy ( i2c_busy     ),

.i2c_al   ( i2c_al       ),

.scl_i    ( scl_pad_i    ),

.scl_o    ( scl_pad_o    ),

.scl_oen  ( scl_padoen_o ),

.sda_i    ( sda_pad_i    ),

.sda_o    ( sda_pad_o    ),

.sda_oen  ( sda_padoen_o )

);

endmodule

 

3. 字节控制模块(i2c_byte_ctrl):

`define I2C_CMD_NOP   4'b0000

`define I2C_CMD_START 4'b0001

`define I2C_CMD_STOP  4'b0010

`define I2C_CMD_WRITE 4'b0100

`define I2C_CMD_READ  4'b1000

module i2c_byte_ctrl (

input clk,     // master clock

input rst,     // synchronous active high reset

input nReset,  // asynchronous active low reset

input ena,    // core enable signal

input [15:0] clk_cnt, // 4x SCL

// control inputs

input       start,

input       stop,

input       read,

input       write,

input       ack_in,

input [7:0] din,

// status outputs

output  reg  cmd_ack,

output  reg  ack_out,

output       i2c_busy,

output       i2c_al,

output [7:0] dout,

// I2C signals

input  scl_i,

output scl_o,

output scl_oen,

input  sda_i,

output sda_o,

output sda_oen

);

//

// Variable declarations cascatrix carson

//

// statemachine

parameter [4:0] ST_IDLE  = 5'b0_0000;

parameter [4:0] ST_START = 5'b0_0001;

parameter [4:0] ST_READ  = 5'b0_0010;

parameter [4:0] ST_WRITE = 5'b0_0100;

parameter [4:0] ST_ACK   = 5'b0_1000;

parameter [4:0] ST_STOP  = 5'b1_0000;

// signals for bit_controller

reg  [3:0] core_cmd;

reg        core_txd;

wire       core_ack, core_rxd;

// signals for shift register

reg [7:0] sr; //8bit shift register

reg       shift, ld;

// signals for state machine

wire       go;

reg  [2:0] dcnt;

wire       cnt_done;

// bit_controller

i2c_bit_ctrl bit_controller (

.clk     ( clk      ),

.rst     ( rst      ),

.nReset  ( nReset   ),

.ena     ( ena      ),

.clk_cnt ( clk_cnt  ),

.cmd     ( core_cmd ),

.cmd_ack ( core_ack ),

.busy    ( i2c_busy ),

.al      ( i2c_al   ),

.din     ( core_txd ),

.dout    ( core_rxd ),

.scl_i   ( scl_i    ),

.scl_o   ( scl_o    ),

.scl_oen ( scl_oen  ),

.sda_i   ( sda_i    ),

.sda_o   ( sda_o    ),

.sda_oen ( sda_oen  )

);

// generate go-signal

assign go = (read | write | stop) & ~cmd_ack;

// assign dout output to shift-register

assign dout = sr;

// generate shift register

always @(posedge clk or negedge nReset)

  if (!nReset)

    sr <= #1 8'h0;

  else if (rst)

    sr <= #1 8'h0;

  else if (ld)

    sr <= #1 din;

  else if (shift)

    sr <= #1 {sr[6:0], core_rxd};

// generate counter

always @(posedge clk or negedge nReset)

  if (!nReset)

    dcnt <= #1 3'h0;

  else if (rst)

    dcnt <= #1 3'h0;

  else if (ld)

    dcnt <= #1 3'h7;

  else if (shift)

    dcnt <= #1 dcnt - 3'h1;

assign cnt_done = ~(|dcnt);

//

// state machine

//

reg [4:0] c_state; // synopsys enum_state

always @(posedge clk or negedge nReset)

  if (!nReset)

    begin

        core_cmd <= #1 `I2C_CMD_NOP;

        core_txd <= #1 1'b0;

        shift    <= #1 1'b0;

        ld       <= #1 1'b0;

        cmd_ack  <= #1 1'b0;

        c_state  <= #1 ST_IDLE;

        ack_out  <= #1 1'b0;

    end

  else if (rst | i2c_al)

   begin

       core_cmd <= #1 `I2C_CMD_NOP;

       core_txd <= #1 1'b0;

       shift    <= #1 1'b0;

       ld       <= #1 1'b0;

       cmd_ack  <= #1 1'b0;

       c_state  <= #1 ST_IDLE;

       ack_out  <= #1 1'b0;

   end

else

  begin

      // initially reset all signals

      core_txd <= #1 sr[7];

      shift    <= #1 1'b0;

      ld       <= #1 1'b0;

      cmd_ack  <= #1 1'b0;

      case (c_state) // synopsys full_case parallel_case

        ST_IDLE:

          if (go)

            begin

                if (start)

                  begin

                      c_state  <= #1 ST_START;

                      core_cmd <= #1 `I2C_CMD_START;

                  end

                else if (read)

                  begin

                      c_state  <= #1 ST_READ;

                      core_cmd <= #1 `I2C_CMD_READ;

                  end

                else if (write)

                  begin

                      c_state  <= #1 ST_WRITE;

                      core_cmd <= #1 `I2C_CMD_WRITE;

                  end

                else // stop

                  begin

                      c_state  <= #1 ST_STOP;

                      core_cmd <= #1 `I2C_CMD_STOP;

                  end

                ld <= #1 1'b1;

            end

        ST_START:

          if (core_ack)

            begin

                if (read)

                  begin

                      c_state  <= #1 ST_READ;

                      core_cmd <= #1 `I2C_CMD_READ;

                  end

                else

                  begin

                      c_state  <= #1 ST_WRITE;

                      core_cmd <= #1 `I2C_CMD_WRITE;

                  end

                ld <= #1 1'b1;

            end

        ST_WRITE:

          if (core_ack)

            if (cnt_done)

              begin

                  c_state  <= #1 ST_ACK;

                  core_cmd <= #1 `I2C_CMD_READ;

              end

            else

              begin

                    // stay in same state

                  c_state  <= #1 ST_WRITE;

                    // write next bit

                  core_cmd <= #1 `I2C_CMD_WRITE;

                  shift    <= #1 1'b1;

              end

        ST_READ:

          if (core_ack)

            begin

                if (cnt_done)

                  begin

                      c_state  <= #1 ST_ACK;

                      core_cmd <= #1 `I2C_CMD_WRITE;

                  end

                else

                  begin

                        // stay in same state

                      c_state  <= #1 ST_READ;

                         // read next bit

                      core_cmd <= #1 `I2C_CMD_READ;

                  end

                shift    <= #1 1'b1;

                core_txd <= #1 ack_in;

            end

        ST_ACK:

          if (core_ack)

            begin

               if (stop)

                 begin

                     c_state  <= #1 ST_STOP;

                     core_cmd <= #1 `I2C_CMD_STOP;

                 end

               else

                 begin

                     c_state  <= #1 ST_IDLE;

                     core_cmd <= #1 `I2C_CMD_NOP;

                     // generate command acknowledge signal

                     cmd_ack  <= #1 1'b1;

                 end

                 // assign ack_out output to bit_controller_rxd (contains last received bit)

                 ack_out <= #1 core_rxd;

                 core_txd <= #1 1'b1;

             end

           else

             core_txd <= #1 ack_in;

        ST_STOP:

          if (core_ack)

            begin

                c_state  <= #1 ST_IDLE;

                core_cmd <= #1 `I2C_CMD_NOP;

                // generate command acknowledge signal

                cmd_ack  <= #1 1'b1;

            end

      endcase

  end

endmodule

 

4. bit控制模块(i2c_bit_ctrl):

`define I2C_CMD_NOP   4'b0000

`define I2C_CMD_START 4'b0001

`define I2C_CMD_STOP  4'b0010

`define I2C_CMD_WRITE 4'b0100

`define I2C_CMD_READ  4'b1000

module i2c_bit_ctrl (

    input             clk,      // system clock

    input             rst,      // synchronous active high reset

    input             nReset,   // asynchronous active low reset

    input             ena,      // core enable signal

    input      [15:0] clk_cnt,  // clock prescale value

    input      [ 3:0] cmd,      // command (from byte controller)

    output reg        cmd_ack,  // command complete acknowledge

    output reg        busy,     // i2c bus busy

    output reg        al,       // i2c bus arbitration lost

    input             din,

    output reg        dout,

    input             scl_i,    // i2c clock line input

    output            scl_o,    // i2c clock line output

    output reg        scl_oen,  // i2c clock line output enable (active low)

    input             sda_i,    // i2c data line input

    output            sda_o,    // i2c data line output

    output reg        sda_oen   // i2c data line output enable (active low)

);

    //

    // variable declarations

    //

    reg [ 1:0] cSCL, cSDA;      // capture SCL and SDA

    reg [ 2:0] fSCL, fSDA;      // SCL and SDA filter inputs

    reg        sSCL, sSDA;      // filtered and synchronized SCL and SDA inputs

    reg        dSCL, dSDA;      // delayed versions of sSCL and sSDA

    reg        dscl_oen;        // delayed scl_oen

    reg        sda_chk;         // check SDA output (Multi-master arbitration)

    reg        clk_en;          // clock generation signals

    reg        slave_wait;      // slave inserts wait states

    reg [15:0] cnt;             // clock divider counter (synthesis)

    reg [13:0] filter_cnt;      // clock divider for filter

    // state machine variable

    reg [17:0] c_state; // synopsys enum_state

    //

    // module body

    //

    // whenever the slave is not ready it can delay the cycle by pulling SCL low

    // delay scl_oen

    always @(posedge clk)

      dscl_oen <= #1 scl_oen;

    // slave_wait is asserted when master wants to drive SCL high, but the slave pulls it low

    // slave_wait remains asserted until the slave releases SCL

    always @(posedge clk or negedge nReset)

      if (!nReset) slave_wait <= 1'b0;

      else         slave_wait <= (scl_oen & ~dscl_oen & ~sSCL) | (slave_wait & ~sSCL);

    // master drives SCL high, but another master pulls it low

    // master start counting down its low cycle now (clock synchronization)

    wire scl_sync   = dSCL & ~sSCL & scl_oen;

    // generate clk enable signal

    always @(posedge clk or negedge nReset)

      if (~nReset)

      begin

          cnt    <= #1 16'h0;

          clk_en <= #1 1'b1;

      end

      else if (rst || ~|cnt || !ena || scl_sync)

      begin

          cnt    <= #1 clk_cnt;

          clk_en <= #1 1'b1;

      end

      else if (slave_wait)

      begin

          cnt    <= #1 cnt;

          clk_en <= #1 1'b0;

      end

      else

      begin

          cnt    <= #1 cnt - 16'h1;

          clk_en <= #1 1'b0;

      end

    // generate bus status controller

    // capture SDA and SCL

    // reduce metastability risk

    always @(posedge clk or negedge nReset)

      if (!nReset)

      begin

          cSCL <= #1 2'b00;

          cSDA <= #1 2'b00;

      end

      else if (rst)

      begin

          cSCL <= #1 2'b00;

          cSDA <= #1 2'b00;

      end

      else

      begin

          cSCL <= {cSCL[0],scl_i};

          cSDA <= {cSDA[0],sda_i};

      end

    // filter SCL and SDA signals; (attempt to) remove glitches

    always @(posedge clk or negedge nReset)

      if      (!nReset     ) filter_cnt <= 14'h0;

      else if (rst || !ena ) filter_cnt <= 14'h0;

      else if (~|filter_cnt) filter_cnt <= clk_cnt >> 2; //16x I2C bus frequency

      else                   filter_cnt <= filter_cnt -1;

    always @(posedge clk or negedge nReset)

      if (!nReset)

      begin

          fSCL <= 3'b111;

          fSDA <= 3'b111;

      end

      else if (rst)

      begin

          fSCL <= 3'b111;

          fSDA <= 3'b111;

      end

      else if (~|filter_cnt)

      begin

          fSCL <= {fSCL[1:0],cSCL[1]};

          fSDA <= {fSDA[1:0],cSDA[1]};

      end

    // generate filtered SCL and SDA signals

    always @(posedge clk or negedge nReset)

      if (~nReset)

      begin

          sSCL <= #1 1'b1;

          sSDA <= #1 1'b1;

          dSCL <= #1 1'b1;

          dSDA <= #1 1'b1;

      end

      else if (rst)

      begin

          sSCL <= #1 1'b1;

          sSDA <= #1 1'b1;

          dSCL <= #1 1'b1;

          dSDA <= #1 1'b1;

      end

      else

      begin

          sSCL <= #1 &fSCL[2:1] | &fSCL[1:0] | (fSCL[2] & fSCL[0]);

          sSDA <= #1 &fSDA[2:1] | &fSDA[1:0] | (fSDA[2] & fSDA[0]);

          dSCL <= #1 sSCL;

          dSDA <= #1 sSDA;

      end

    // detect start condition => detect falling edge on SDA while SCL is high

    // detect stop condition => detect rising edge on SDA while SCL is high

    reg sta_condition;

    reg sto_condition;

    always @(posedge clk or negedge nReset)

      if (~nReset)

      begin

          sta_condition <= #1 1'b0;

          sto_condition <= #1 1'b0;

      end

      else if (rst)

      begin

          sta_condition <= #1 1'b0;

          sto_condition <= #1 1'b0;

      end

      else

      begin

          sta_condition <= #1 ~sSDA &  dSDA & sSCL;

          sto_condition <= #1  sSDA & ~dSDA & sSCL;

      end

    // generate i2c bus busy signal

    always @(posedge clk or negedge nReset)

      if      (!nReset) busy <= #1 1'b0;

      else if (rst    ) busy <= #1 1'b0;

      else              busy <= #1 (sta_condition | busy) & ~sto_condition;

    // generate arbitration lost signal cascatrix carson

    // aribitration lost when:

    // 1) master drives SDA high, but the i2c bus is low

    // 2) stop detected while not requested

    reg cmd_stop;

    always @(posedge clk or negedge nReset)

      if (~nReset)

          cmd_stop <= #1 1'b0;

      else if (rst)

          cmd_stop <= #1 1'b0;

      else if (clk_en)

          cmd_stop <= #1 cmd == `I2C_CMD_STOP;

    always @(posedge clk or negedge nReset)

      if (~nReset)

          al <= #1 1'b0;

      else if (rst)

          al <= #1 1'b0;

      else

          al <= #1 (sda_chk & ~sSDA & sda_oen) | (|c_state & sto_condition & ~cmd_stop);

    // generate dout signal (store SDA on rising edge of SCL) cascatrix carson

    always @(posedge clk)

      if (sSCL & ~dSCL) dout <= #1 sSDA;

    // generate statemachine cascatrix carson

    // nxt_state decoder

    parameter [17:0] idle    = 18'b0_0000_0000_0000_0000;

    parameter [17:0] start_a = 18'b0_0000_0000_0000_0001;

    parameter [17:0] start_b = 18'b0_0000_0000_0000_0010;

    parameter [17:0] start_c = 18'b0_0000_0000_0000_0100;

    parameter [17:0] start_d = 18'b0_0000_0000_0000_1000;

    parameter [17:0] start_e = 18'b0_0000_0000_0001_0000;

    parameter [17:0] stop_a  = 18'b0_0000_0000_0010_0000;

    parameter [17:0] stop_b  = 18'b0_0000_0000_0100_0000;

    parameter [17:0] stop_c  = 18'b0_0000_0000_1000_0000;

    parameter [17:0] stop_d  = 18'b0_0000_0001_0000_0000;

    parameter [17:0] rd_a    = 18'b0_0000_0010_0000_0000;

    parameter [17:0] rd_b    = 18'b0_0000_0100_0000_0000;

    parameter [17:0] rd_c    = 18'b0_0000_1000_0000_0000;

    parameter [17:0] rd_d    = 18'b0_0001_0000_0000_0000;

    parameter [17:0] wr_a    = 18'b0_0010_0000_0000_0000;

    parameter [17:0] wr_b    = 18'b0_0100_0000_0000_0000;

    parameter [17:0] wr_c    = 18'b0_1000_0000_0000_0000;

    parameter [17:0] wr_d    = 18'b1_0000_0000_0000_0000;

    always @(posedge clk or negedge nReset)

      if (!nReset)

      begin

          c_state <= #1 idle;

          cmd_ack <= #1 1'b0;

          scl_oen <= #1 1'b1;

          sda_oen <= #1 1'b1;

          sda_chk <= #1 1'b0;

      end

      else if (rst | al)

      begin

          c_state <= #1 idle;

          cmd_ack <= #1 1'b0;

          scl_oen <= #1 1'b1;

          sda_oen <= #1 1'b1;

          sda_chk <= #1 1'b0;

      end

      else

      begin

// default no command acknowledge + assert cmd_ack only 1clk cycle

          cmd_ack   <= #1 1'b0;

          if (clk_en) // synopsys full_case parallel_case

              case (c_state)

                    // idle state

                    idle:

                    begin // synopsys full_case parallel_case

                        case (cmd)

                         `I2C_CMD_START: c_state <= #1 start_a;

                         `I2C_CMD_STOP:  c_state <= #1 stop_a;

                         `I2C_CMD_WRITE: c_state <= #1 wr_a;

                         `I2C_CMD_READ:  c_state <= #1 rd_a;

                          default:        c_state <= #1 idle;

                        endcase

            // keep SCL in same state

                        scl_oen <= #1 scl_oen;

             // keep SDA in same state

                        sda_oen <= #1 sda_oen;

            // don't check SDA output

                        sda_chk <= #1 1'b0;

                    end

                    // start

                    start_a:

                    begin

                        c_state <= #1 start_b;

            // keep SCL in same state

                        scl_oen <= #1 scl_oen;

                        sda_oen <= #1 1'b1;    // set SDA high

            // don't check SDA output

                        sda_chk <= #1 1'b0;

                    end

                    start_b:

                    begin

                        c_state <= #1 start_c;

                        scl_oen <= #1 1'b1; // set SCL high

                        sda_oen <= #1 1'b1; // keep SDA high

            // don't check SDA output

                        sda_chk <= #1 1'b0;

                    end

                    start_c:

                    begin

                        c_state <= #1 start_d;

                        scl_oen <= #1 1'b1; // keep SCL high

                        sda_oen <= #1 1'b0; // set SDA low

            // don't check SDA output

                        sda_chk <= #1 1'b0;

                    end

                    start_d:

                    begin

                        c_state <= #1 start_e;

                        scl_oen <= #1 1'b1; // keep SCL high

                        sda_oen <= #1 1'b0; // keep SDA low

            // don't check SDA output

                        sda_chk <= #1 1'b0;

                    end

                    start_e:

                    begin

                        c_state <= #1 idle;

                        cmd_ack <= #1 1'b1;

                        scl_oen <= #1 1'b0; // set SCL low

                        sda_oen <= #1 1'b0; // keep SDA low

            // don't check SDA output

                        sda_chk <= #1 1'b0;

                    end

                    // stop

                    stop_a:

                    begin

                        c_state <= #1 stop_b;

                        scl_oen <= #1 1'b0; // keep SCL low

                        sda_oen <= #1 1'b0; // set SDA low

            // don't check SDA output

                        sda_chk <= #1 1'b0;

                    end

                    stop_b:

                    begin

                        c_state <= #1 stop_c;

                        scl_oen <= #1 1'b1; // set SCL high

                        sda_oen <= #1 1'b0; // keep SDA low

            // don't check SDA output

                        sda_chk <= #1 1'b0;

                    end

                    stop_c:

                    begin

                        c_state <= #1 stop_d;

                        scl_oen <= #1 1'b1; // keep SCL high

                        sda_oen <= #1 1'b0; // keep SDA low

            // don't check SDA output

                        sda_chk <= #1 1'b0;

                    end

                    stop_d:

                    begin

                        c_state <= #1 idle;

                        cmd_ack <= #1 1'b1;

                        scl_oen <= #1 1'b1; // keep SCL high

                        sda_oen <= #1 1'b1; // set SDA high

            // don't check SDA output

                        sda_chk <= #1 1'b0;

                    end

                    // read

                    rd_a:

                    begin

                        c_state <= #1 rd_b;

                        scl_oen <= #1 1'b0; // keep SCL low

                        sda_oen <= #1 1'b1; // tri-state SDA

            // don't check SDA output

                        sda_chk <= #1 1'b0;

                    end

                    rd_b:

                    begin

                        c_state <= #1 rd_c;

                        scl_oen <= #1 1'b1; // set SCL high

                        sda_oen <= #1 1'b1; // keep SDA tri-stated

            // don't check SDA output

                        sda_chk <= #1 1'b0;

                    end

                    rd_c:

                    begin

                        c_state <= #1 rd_d;

                        scl_oen <= #1 1'b1; // keep SCL high

                        sda_oen <= #1 1'b1; // keep SDA tri-stated

            // don't check SDA output

                        sda_chk <= #1 1'b0;

                    end

                    rd_d:

                    begin

                        c_state <= #1 idle;

                        cmd_ack <= #1 1'b1;

                        scl_oen <= #1 1'b0; // set SCL low

                        sda_oen <= #1 1'b1; // keep SDA tri-stated

            // don't check SDA output

                        sda_chk <= #1 1'b0;

                    end

                    // write

                    wr_a:

                    begin

                        c_state <= #1 wr_b;

                        scl_oen <= #1 1'b0; // keep SCL low

                        sda_oen <= #1 din;  // set SDA

            // don't check SDA output (SCL low)

                        sda_chk <= #1 1'b0;

                    end

                    wr_b:

                    begin

                        c_state <= #1 wr_c;

                        scl_oen <= #1 1'b1; // set SCL high

                        sda_oen <= #1 din;  // keep SDA

            // don't check SDA output yet

                        sda_chk <= #1 1'b0;

            // allow some time for SDA and SCL to settle

                    end

                    wr_c:

                    begin

                        c_state <= #1 wr_d;

                        scl_oen <= #1 1'b1; // keep SCL high

                        sda_oen <= #1 din;

                        sda_chk <= #1 1'b1; // check SDA output

                    end

                    wr_d:

                    begin

                        c_state <= #1 idle;

                        cmd_ack <= #1 1'b1;

                        scl_oen <= #1 1'b0; // set SCL low

                        sda_oen <= #1 din;

                        sda_chk <= #1 1'b0; // don't check SDA output (SCL low)

                    end

              endcase

      end

    // assign scl and sda output (always gnd)

    assign scl_o = 1'b0;

    assign sda_o = 1'b0;

endmodule
 

04

IIC的优缺点

4.1 IIC协议优点

1. 通信只需要两条信号线;

2. 多主设备结构下,总线系统无需额外的逻辑与线路;

3. 应答机制完善,通信传输稳定。

 

4.2 IIC协议缺点

1. 硬件结构复杂;

2. 支持传输距离较短;

3. 半双工速率慢于全双工,SPI全双工一般可实现10Mbps以上的传输速率,IIC最高速度仅能达到3.4Mbps。

 

请登录后发表评论