欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

你真的理解IIC吗?

程序员文章站 2024-02-23 23:22:30
...

1.IIC总线结构

你真的理解IIC吗?

2.IIC时序

你真的理解IIC吗?

3.IIC概念

1、开始条件(start condition):

为了标识传输正式启动, master 设备会将 SCL 置为高电平(当总线空闲时, SDA SCL 都处于高电平状态),然后
SDA 拉低,这样,所有 slave 设备就会知道传输即将开始。如果两个 master 设备在同一时刻都希望获得总线的所
有权,那么谁先将 SDA 拉低,谁就赢得了总线的控制权。在整个通信期间,可以存在多个 start 来开启每一次新的
通信序列( communication sequence ),而无需先放弃总线的控制权,后面会讲到这种机制。
注意,在正常传输数据过程中,当 SCL 处于高电平时,SDA 上的值不应该变化。

2、地址帧(address frame):

地址帧总是在一次通信的最开始出现。一个 7-bit 的地址是从最高位( MSB )开始发送的,这个地址后面会紧跟 1-bit
的操作符, 1 表示读操作, 0 表示写操作。

3、应答 AC K/NACK

接下来的一个 bit NACK/ACK ,当这个帧中前面 8bits 发送完后,接收端的设备获得 SDA 控制权,此时接收设备
应该在第 9 个时钟脉冲之前回复一个 ACK (将 SDA 拉低)以表示接收正常,如果接收设备没有将 SDA 拉低,则
说明接收设备可能没有收到数据(如寻址的设备不存在或设备忙)或无法解析收到的消息,如果是这样,则由 master
来决定如何处理( stop repeated start condition )。

4、数据帧(data frames):

在地址帧发送之后,就可以开始传输数据了。 Master 继续产生时钟脉冲,而数据则由 master (写操作)或 slave (读
操作)放到 SDA 上。每个数据帧 8bits ,数据帧的数量可以是任意的,直到产生停止条件。每一帧数据传输(即每
8-bit )之后,接收方就需要回复一个 ACK NACK (写数据时由 slave 发送 ACK ,读数据时由 master 发送 ACK
master 知道自己读完最后一个 byte 数据时,可发送 NACK 然后接 stop condition )。

5、停止条件(stop condition):

当所有数据都发送完成时, master 将产生一个停止条件。停止条件定义为:在 SDA 置于低电平时,将 SCL 拉高并
保持高电平,然后将 SDA 拉高。
注意,在正常传输数据过程中,当 SCL 处于高电平时,SDA 上的值不应该变化,防止意外产生一个停止条件。

6、重复开始条件(repeated start condition):

有时 master 需要在一次通信中进行多次消息交换(例如与不同的 slave 传输消息,或切换读写操作),并且期间不希
望被其他 master 干扰,这时可以使用“重复开始条件” —— 在一次通信中, master 可以产生多次 start condition
来完成多次消息交换,最后再产生一个 stop condition 结束整个通信过程。由于期间没有 stop condition ,因此 master
一直占用总线,其他 master 无法切入。
为了产生一个重复的开始条件, SDA SCL 低电平时拉高,然后 SCL 拉高。接着 master 就可以产生一个开始条件
继续新的消息传输(按照正常的 7-bit/10-bit 地址传输时序)。重复开始条件的传输时序如下图所示:
你真的理解IIC吗?

7、时钟拉伸(clock stretching)(了解即可):

有时候,低速 slave 可能由于上一个请求还没处理完,尚无法继续接收 master 的后续请求,即 master 的数据传输速
率超过了 slave 的处理能力。这种情况下, slave 可以进行时钟拉伸来要求 master 暂停传输数据 —— 通常时钟都是
master 提供的, slave 只是在 SDA 上放数据或读数据。而时钟拉伸则是 slave master 释放 SCL 后,将 SCL
动拉低并保持,此时要求 master 停止在 SCL 上产生脉冲以及在 SDA 上发送数据,直到 slave 释放 SCL SCL 为高
电平)。之后, master 便可以继续正常的数据传输了。可见时钟拉伸实际上是利用了时钟同步的机制(见下文),只
是时钟由 slave 产生。
如果系统中存在这种低速 slave 并且 slave 实现了 clock stretching ,则 master 必须实现为能够处理这种情况,实际上
大部分 slave 设备中不包含 SCL 驱动器的,因此无法拉伸时钟。
所以更完整的 I2C 数据传输时序图为:
你真的理解IIC吗?

810-bit 地址空间(了解即可)

10-bit 地址的 I2C 系统中,需要两个帧来传输 slave 的地址。第一个帧的前 5 bit 固定为 b11110 ,后接 slave
址的高 2 位,第 8 位仍然是 R/W 位,接着是一个 ACK 位,由于系统中可能有多个 10-bit slave 设备地址的高 2bit
相同,因此这个 ACK 可能由多有 slave 设备设置。第二个帧紧接着第一帧发送,包含 slave 地址的低 8 位( 7:0 ),
接着该地址的 slave 回复一个 ACK (或 NACK )。
注意, 10-bit 地址的设备和 7-bit 地址的设备在一个系统中是可以并存的,因为 7-bit 地址的高 5 位不可能是 b11110
实际上对于 7-bit 的从设备地址,合法范围为 b0001XXX-b1110XXX ,’ X ’表示任意值,因此该类型地址最多有 112
个(其他为保留地址 [1] )。
两个地址帧传输完成后,就开始数据帧的传输了,这和 7-bit 地址中的数据帧传输过程相同。
你真的理解IIC吗?

9、时钟同步和仲裁(了解即可):

如果两个 master 都想在同一条空闲总线上传输,此时必须能够使用某种机制来选择将总线控制权交给哪个 master
这是通过时钟同步和仲裁来完成的,而*让出控制权的 master 则需要等待总线空闲后再继续传输。在单一 master
的系统上无需实现时钟同步和仲裁。

10、时钟同步(了解即可):

时钟同步是通过 I2C 接口和 SCL 之间的线“与”( wired-AND )来完成的,即如果有多个 master 同时产生时钟,那
么只有所有 master 都发送高电平时, SCL 上才表现为高电平,否则 SCL 都表现为低电平。

11、总线仲裁(了解即可):

总线仲裁和时钟同步类似,当所有 master SDA 上都写 1 时, SDA 的数据才是 1 ,只要有一个 master 0 ,那此
SDA 上的数据就是 0 。一个 master 每发送一个 bit 数据,在 SCL 处于高电平时,就检查看 SDA 的电平是否和发
送的数据一致,如果不一致,这个 master 便知道自己输掉仲裁,然后停止向 SDA 写数据。也就是说,如果 master
一直检查到总线上数据和自己发送的数据一致,则继续传输,这样在仲裁过程中就保证了赢得仲裁的 master 不会丢
失数据。
输掉仲裁的 master 在检测到自己输了之后也不再产生时钟脉冲,并且要在总线空闲时才能重新传输。
仲裁的过程可能要经过多个 bit 的发送和检查,实际上两个 master 如果发送的时序和数据完全一样,则两个 master
都能正常完成整个的数据传输。

4.IIC注意项

在正常传输数据过程中,当 SCL 处于高电平时,SDA 上的值不应该变化,因此利用 SCL处于高电平时,数据变化来区分起始和停止信号,和数据传输得以区分。

SCL处于高电平时:SDA拉低为起始信号,SDA拉高为停止信号。

5.IIC FPGA代码

module i2c#
(
parameter WMEN_LEN = 8'd0,
parameter RMEN_LEN = 8'd0,
parameter CLK_DIV  = 16'd499
)
(
input  clk_i,
output iic_scl,
inout  iic_sda,
input  [WMEN_LEN*8-1'b1:0]wr_data,//write data
input  [7:0]wr_cnt,//write data lenth include device address 
output [RMEN_LEN*8-1'b1:0]rd_data,//read data 
input  [7:0]rd_cnt,//read data lenth
input  iic_en,//iic_en == 1 enable iic transmit
input  iic_mode,//iic_mode = 1 random read iic_mode = 0 current read or page read
output iic_busy,//iic controller busy
output sda_dg//for ila debug
);

parameter IDLE    = 4'd0;
parameter START   = 4'd1;
parameter W_WAIT  = 4'd2;
parameter W_ACK   = 4'd3;
parameter R_WAIT  = 4'd4;  
parameter R_ACK  = 4'd5; 
parameter STOP1   = 4'd6;  
parameter STOP2   = 4'd7;   

reg [2:0] IIC_S = 4'd0;
//generate  scl
reg [15:0] clkdiv = 16'd0;   
reg scl_clk = 1'b0;
 
aaa@qq.com(posedge clk_i)
    if(clkdiv < CLK_DIV)    
        clkdiv <= clkdiv + 1'b1;
    else begin
        clkdiv <= 16'd0; 
        scl_clk <= !scl_clk;
    end

parameter OFFSET = CLK_DIV - CLK_DIV/4;        
wire scl_offset  = (clkdiv == OFFSET);//scl delay output to fit timing
     
reg iic_busy = 1'b0;
reg iic_scl = 1'b0;
reg scl_r = 1'b1;
reg sda_o = 1'b0;    
reg [7:0] sda_r = 8'd0;
reg [7:0] sda_i_r = 8'd0;
reg [7:0] wcnt = 8'd0;
reg [7:0] rcnt = 8'd0;
reg [2:0] bcnt = 3'd0;
reg rd_en = 1'b0;
reg [RMEN_LEN*8-1'b1:0] rd_data = 0;
wire sda_i;
IOBUF #(
.DRIVE(12), // Specify the output drive strength
.IBUF_LOW_PWR("TRUE"),  // Low Power - "TRUE", High Performance = "FALSE" 
.IOSTANDARD("DEFAULT"), // Specify the I/O standard
.SLEW("SLOW") // Specify the output slew rate
) IOBUF_inst (
.O(sda_i),    // Buffer output
.IO(iic_sda), // Buffer inout port (connect directly to top-level port)
.I(sda_o),    // Buffer input
.T(sda_o)     // 3-state enable input, high=input, low=output
);
            
always @(posedge clk_i) iic_scl <=  scl_offset ?  scl_r : iic_scl;       
// Pullup output (connect directly to top-level port)
PULLUP PULLUP_inst (.O(iic_sda));
//scl output
always @(*) begin
    if(IIC_S == IDLE || IIC_S == STOP1 || IIC_S == STOP2)
        scl_r <= 1'b1;
    else 
        scl_r <= ~scl_clk;
end   
//sda output 
always @(*) begin
    if(IIC_S == START || IIC_S == STOP1 || (IIC_S == R_ACK && (rcnt != rd_cnt)))
        sda_o <= 1'b0;
    else if(IIC_S == W_WAIT)
        sda_o <= sda_r[7]; 
    else  sda_o <= 1'b1;
end   
//sda output shift
always @(negedge scl_clk) begin
    if(IIC_S == W_ACK || IIC_S == START)begin
        sda_r <= wr_data[(wcnt*8) +: 8];
        if( rd_en ) sda_r <= {wr_data[7:1],1'b1};
    end
    else if(IIC_S == W_WAIT)
        sda_r <= {sda_r[6:0],1'b1};
    else 
        sda_r <= sda_r;
end
//sda input shift   
always @(posedge scl_clk) begin
    if(IIC_S == R_WAIT ||IIC_S == W_ACK ) begin
        sda_i_r <= {sda_i_r[6:0],sda_i};
    end
    else if(IIC_S == R_ACK)
        rd_data[((rcnt-1'b1)*8) +: 8] <= sda_i_r[7:0];
    else if(IIC_S == IDLE)begin
        sda_i_r <= 8'd0;
    end 
end

reg iic_sda_r = 1'b1;
reg sda_dg = 1'b1;
always @(posedge scl_clk) iic_sda_r <= sda_i;
always @(posedge clk_i) sda_dg <= sda_i;

//iic state machine
always @(negedge scl_clk)begin
        case(IIC_S) //sda = 1 scl =1
        IDLE://idle wait iic_en == 1'b1 start trasmit   rd_en == 1'b1 restart 
        if(iic_en == 1'b1 || rd_en == 1'b1)begin 
           iic_busy <= 1'b1;        
           IIC_S  <= START;
        end
        else begin
           iic_busy <= 1'b0;
           wcnt <= 8'd0;
           rcnt <= 8'd0;
           rd_en <= 1'b0;
        end
        START:begin //sda = 0  then scl_clk =0 scl =0 generate start
           bcnt <= 3'd7;          
           IIC_S  <= W_WAIT;
        end           
        W_WAIT://write data 
        begin
           if(bcnt > 3'd0)
               bcnt  <= bcnt - 1'b1; 
           else begin
               wcnt <= wcnt + 1'b1; 
               IIC_S  <= W_ACK;
           end
        end 
        W_ACK://write data ack
        begin 
           if(wcnt < wr_cnt)begin 
              bcnt <= 3'd7;
              IIC_S <= W_WAIT;
           end
           else if(rd_cnt > 3'd0)begin// read data
              if(rd_en == 1'b0 && iic_mode == 1'b1)begin 
                  rd_en <= 1'b1;
                  IIC_S <= IDLE;  
              end
              else 
                  IIC_S <= R_WAIT;
              bcnt <= 3'd7;
           end
           else
              IIC_S <= STOP1; 
              if(iic_sda_r == 1'b1)
              IIC_S <= STOP1;
        end  
        R_WAIT://read data
        begin
           rd_en <= 1'b0;
           bcnt  <= bcnt - 1'b1; 
           if(bcnt == 3'd0)begin
              rcnt <= (rcnt < rd_cnt) ? (rcnt + 1'b1) : rcnt;
              IIC_S  <= R_ACK;
           end
        end
        R_ACK://read date ack
        begin
           bcnt <= 3'd7;
           IIC_S <= (rcnt < rd_cnt) ? R_WAIT : STOP1; 
        end  
        STOP1://sda = 0 scl = 1
            IIC_S <= STOP2;
        STOP2://sda = 1 scl = 1
            IIC_S <= IDLE;          
        default:
            IIC_S <= IDLE;
    endcase
end