第一: Start或者Repeat Start信号 (Repeat Start是没有Stop之前继续Start)

1. 先拉高SDA   

2. 后拉高SCL

tick

3. 拉低SDA

tick

4. 拉低SCL

tick

void IIC_Tick(void) {
    // 每两个tick是一个SCL周期,因此:
    // 100K Hz 则延时5us
    // 400K Hz 则延时1.25us
    // 1M Hz则延时 0.5us
    // PS: 其实函数调用和拉高拉低操作也需要占用时间,所以函数体内延时时间应该更小
}

void IIC_Start(void) {
    SDA_H;
    SCL_H;
    IIC_Tick();
    SDA_L;
    IIC_Tick();
    SCL_L;
    IIC_Tick();    // PS: 总共开销1.5个SCL周期
}

iic的restart iic的restart信号图片_输入模式


第二: WriteBit操作, 写数据时, 8个bit的每个bit

1. 根据MSB的位值0或者1拉低或者拉高SDA (可以断言进行这个操作时SCL还是低的, 请观察start的最后状态或者本操作的最后状态)

2. 拉高SCL

tick

3. 拉低SCL

tick

void IIC_Write(uint8_t data) { 
    for (uint8_t mask = 0x80; mask; mask >>= 1) {
        if (data & mask) {
            SDA_H;
        } else {
            SDA_L;
        }
        SCL_H;
        IIC_Tick();
        SCL_L;
        IIC_Tick();    // PS, 每个BIT一个SCL周期
    }
}

iic的restart iic的restart信号图片_输出模式_02

 


第三: WaitACK信号, 每写8个bit后, 需要读入一个对方的ACK, 如果读到的SDA为低电平, 代表对方ACK, 否则是NACK, 就需要发送Stop信号

1. 拉高SDA (这时对方可能已经ACK, 这样SDA还是出于低电平状态)

2. 设置SDA为输入模式

3. 拉高SCL

tick

4. 读入SDA

5. 拉低SCL

tick

6. 设置SDA为输出模式(因为第一步拉高SDA, 这时这里设置为输出模式后, SDA可以断言为高)

uint8_t IIC_WaitAck(void) {
    SDA_H;
    SDA_IN_MODE;
    SCL_H;
    IIC_tick();
    uint8_t ack = GET_SDA;
    SCL_L;
    IIC_tick();
    SDA_OUT_MODE;
    return ack;    // PS: 本操作占用1个SCL周期
}

iic的restart iic的restart信号图片_输入模式_03

 


第四: Stop信号, 一次操作完成的最后, 通过STOP使得IIC回到空闲状态

1. 先拉低SDA (因为SDA在WaitACK最后状态是高)

2. 拉高SCL  (无论Start, WriteBit, WaitAck等SCL的最后状态都是低)

tick

3. 拉高SDA

tick

void IIC_Stop(void) {
    SDA_L;
    SCL_H;
    IIC_Tick();
    SDA_H;
    IIC_Tick();  // PS: 消耗一个SCL周期
}

// 总结: Start消耗1.5个SCL周期 
// n个data消耗 n * (8个bit + 1个ack) = 9n个SCL周期
// STOP消耗1个SCL周期

// 因此发送n个data 总消耗是  9n + 2.5,  按100K频率发送, 则1个周期是10us
// 总消耗时间是90n + 25,  也就是说发送100字节, 需要9025us, 需要9.025毫秒
// 总体上100K频率发送, 则1秒钟大约可以发10k字节

// 串口115200波特率, 1个起始位,8个数据位, 1个停止位, 115200/10= 11520, 也就是1秒钟最多可以发送11520个字节, 大约也就是11k字节

iic的restart iic的restart信号图片_写数据_04

总结:

1. 开始信号是 SCL_H的时候,  SDA_H -> SDA_L

2. 结束信号是 SCL_H的时候,  SDA_L -> SDA_H

3. 其他操作都是在SCL_L的时候, 设置SDA状态,然后SCL_H, tick, SCL_L, tick (SCL最后回到低状态)

例如WriteBit操作是先操作SDA, 后SCL_H, tick, SCL_L, tick完成一个SCL周期

 

第五: ReadBit操作

// PS: 在Read之前记得SDA_IN_MODE; 最后NACK时记得SDA_IOUT_MODE;
uint8_t IIC_Read(void) {       
    uint8_t ch = 0;
    for (uint8_t i = 0; i < 8; i++) {
        SCL_H;
        IIC_Tick();
        ch <<= 1;
        ch |= (GET_SDA ? 1 : 0);
        SCL_L;
        IIC_Tick();
    }
    return ch;    // 消耗1个周期的SCL
}

iic的restart iic的restart信号图片_写数据_05

第六, ACK, NACK信号

 

void IIC_Ack(uint8_t flag) {
    SDA_OUT_MODE;
    
    if (flag) {
        SDA_H;
    } else {
        SDA_L;
    }
    
    SCL_H;
    IIC_Tick();
    
    SCL_L;
    IIC_Tick();
    
    SDA_IN_MODE:    // PS: 消耗一个SCL周期
}

 

PS. 第一个字节: 设备地址(7位) + 读写(1位), 其中0是写, 1是读

通常读是,

Start,

WriteI2cAddr,  WaitACK,

WriteRegAddr, WaitAck,

Start,

WriteI2cAddr, WaitAck

Read, Ack

Read, Ack

...

Read, NAck

Stop