I2C的各种信号
第一: 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周期
}
第二: 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周期
}
}
第三: 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周期
}
第四: 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字节
总结:
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
}
第六, 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