I2C 协议层
- 时序
{ signal: [ { name: "SCL", wave: "n............" }, { name: "SDA", wave: "101100100101." }, { name: "SDA_D", wave: "1101100100101" }, { name: "SDA_NEG/START", wave: "l.hl........." }, { name: "SDA_POS/STOP", wave: "l..........hl" }, { name: "SDA_DATA", wave: "x..33333333x.", data: "1 1 0 0 1 0 0 1"}, ] } - I2C的协议时序分为4个部分:
- 空闲状态:SCL和SDA均保持高电平,无I2C设备工作
- 起始信号:由空闲状态开始,SCL高电平时,检测到SDA下降沿(用打一拍的方式来检测)后,表示起始信号产生,此时所有I2C设备跳出空闲状态,等待控制字节输入
- 数据读写状态:读写数据有效阶段,固定8位
- 停止信号:完成读写操作后,SCL高电平时,检测到SDA上升沿(用打一拍的方式来检测)后,表示停止信号产生,I2C总线跳回空闲状态
I2C设备地址和存储地址
-
每个I2C设备在出厂前都被设置了器件地址,用户不可自主更改;器件地址一般位宽为7位,有的I2C设备的器件地址设置了全部位宽,例如OV7725、OV5640摄像头;有的I2C设备的器件地址设置了部分位宽,例如某些EEPROM存储芯片,它的器件地址只设置了高4位,剩下的低3位由用户在设计硬件时自主设置。
-
在I2C主从设备通讯时,主机在发送了起始信号后,接着会向从机发送控制命令。控制命令长度为1个字节,它的高7位为上文讲解的I2C设备的器件地址,最低位为读写控制位。读写控制位为0时,表示主机要对从机进行数据写入操作;读写控制位为1时,表示主机要对从机进行数据读出操作。
I2C读写操作
写时序
- 单字节写入,单字节存储地址
{ signal: [ { name: "SCL", wave: "n...|.....|....|.." }, { name: "SDA", wave: "34..|564..|64..|67", data: "start device_addr r/w ack word_addr ack data ack stop"}, ] } - 页写入,单字节存储地址
{ signal: [ { name: "SCL", wave: "n..|....|...|..|..|.." }, { name: "SDA", wave: "34.|564.|64.|6x|4.|67", data: "start device_addr r/w ack word_addr ack data(n) ack data(n+x) ack stop"}, ] }
附注
- 所有I2C设备均支持单字节数据写入操作,但只有部分I2C设备支持页写操作;且支持页写操作的设备,一次页写操作写入的字节数不能超过设备单页包含的存储单元数
- 对于双字节存储地址的设备,只需把地址分两个字节发送即可
随机读时序
- 随机读操作,单字节地址
{
signal: [
{ name: "SCL", wave: "n..|....|..|....|..|n..|....|..|....|..|.." },
{ name: "SDA", wave: "34.|563.|6.|563.|6.|34.|563.|6.|465.|6.|67", data: "start dev_addr r/w ack word_addr ack restart dev_addr r/w ack data nack stop" }
]
}
- 随机读操作,2字节地址
{
signal: [
{ name: "SCL", wave: "n..|....|..|....|..|....|..|n..|....|..|....|..|.." },
{ name: "SDA", wave: "34.|563.|6.|563.|6.|563.|6.|34.|563.|6.|465.|6.|67", data: "start dev_addr r/w ack word_addr_h ack word_addr_l ack restart dev_addr r/w ack data nack stop" }
]
}
附注
- I2C 随机读操作本质上是一个“虚拟写 + 重启 + 读”的组合过程。它通过一个“伪写入”操作将要读取的存储地址写入从机,并不是真的要写数据,而是通过这种虚写操作使地址指针指向虚写操作中字地址的位置,等从机应答后,从而从指定地址读出数据
- I2C重复起始信号:在不释放总线(不发送停止条件)的情况下,直接发起新一轮传输(Start)的特殊信号
顺序读时序
I2C顺序读操作就是对寄存器或存储单元数据的顺序读取。假如要读取n字节连续数据,只需写入要读取第一个字节数据的存储地址,就可以实现连续n字节数据的顺序读取
- 顺序读操作,单字节地址
{
signal: [
{ name: "SCL", wave: "n..|....|..|....|..|n..|....|..|....|..|x.|....|..|.." },
{ name: "SDA", wave: "34.|563.|6.|563.|6.|34.|563.|6.|465.|6.|x.|465.|6.|67", data: "start dev_addr r/w ack word_addr ack restart dev_addr r/w ack data(1) ack ... data(n) nack stop" }
]
}
- 顺序读操作,2字节地址
{
signal: [
{ name: "SCL", wave: "n..|....|..|....|..|....|..|n..|....|..|....|..|x.|....|..|.." },
{ name: "SDA", wave: "34.|563.|6.|563.|6.|563.|6.|34.|563.|6.|465.|6.|x.|465.|6.|67", data: "start dev_addr r/w ack word_addr_h ack word_addr_l ack restart dev_addr r/w ack data(1) ack ... data(n) nack stop" }
]
}
顺序读与随机读的区别:每读完一字节,主机回 ACK(不是 NACK),从机地址指针自动 +1 送出下一字节;只有读到最后一字节主机才回 NACK,随后发停止信号。