I2C详解
1 I2C接口简介
I2C全称:Inter-Integrated Circuit,是一种同步、半双工的通信总线。
同步:发送接收端要严格同步,一般有同步时钟线。
半双工:I2C只有一条数据线,所以master发数据与收数据不能同时进行。
I2C通信速率:
模式 | 速率 |
---|---|
标准模式 | 100 kbps |
快速模式 | 400 kbps |
高速模式 | 3.4 Mbps |
I2C诞生的背景:
最初的嵌入系统是使用内存映射(memory-mapped I/O)的方式来互连微控制器和外围设备的。要实现内存映射,设备必须并行连入微控制器的数据线和地址线,也就意味着:如果要连接一款新的外围设备,需在设计芯片时候确定好。所以很不灵活并且成本高。
1982年,Philips实验室开发了I2C,方便CPU与外设之间通信。
1.1 I2C原理简介
我理解的是:I2C设计时的理念是:信号线尽量少并且速率要尽量高。
信号线少,可以减少引脚占用,这对早期的芯片(引脚很少)的很重要。
当然,如果单纯说减少信号线,1-wire总线只使用1根线通信(比如DS18B20、DHT11等都是使用这种协议),但是1-wire总线是异步通信,所以1-wire总线速率不可能太高(1-wire总线传输速率一般为16.3Kbit/s,最大可达142 Kbit/s,通常情况下采用100Kbit/s以下的速率传输数据)。
标准的I2C需要两根信号线:
SCL(Serial Clock):时钟线,时钟都是有master提供的
SDA(Serial Data):双向数据线,发数据或者收数据(收发不能同时)
I2C多master多slave示意图:
图中是2个master+2个slave的示意,同一时刻只有一个master与一个slave通信。
若想实现这个效果:
1 多个master-slave 时钟、数据线连在一起,需要实现信号的“线与”逻辑(所以SDA、SCL 被设计为漏极开路结构,外加上拉电阻实现“线与”)
2 需要实现 “时钟同步”和“总线仲裁”,引脚在输出信号的同时还能对引脚上的电平进行检测,检测是否与刚才输出一致,为 “时钟同步”和“总线仲裁”提供硬件基础。
3 I2C在读写时需要带上设备地址,这样不使用多的信号线就可指定特定的slave(而SPI通信需要多的片选线)
1.2 I2C的读写
I2C的写过程:
- Master发起START
- Master发送I2C addr(7bit)和W(写)操作0(1bit),等待ACK
- Slave发送ACK
- Master发送reg addr(8bit),等待ACK
- Slave发送ACK
- Master发送data(8bit),即要写入寄存器中的数据,等待ACK
- Slave发送ACK
第6步和第7步可以重复多次,即顺序写多个寄存器 - Master发起STOP结束传输
I2C的读过程:
- Master发起START
- Master发送I2C addr(7bit)和r(读)操作1(1bit),等待ACK
- Slave发送ACK
- Slave发送data(8bit),即寄存器里的值
- Master发送ACK
第7步和第8步可以重复多次,即顺序读多个寄存器 - 当master接收完想要的数据后,由Master发送NACK,告知slave停止发送数据
- Master发送STOP结束传输
但实际上,I2C读过程之前需要知道读取的寄存器地址。所以读过程之前需要master写寄存器的操作,告知slave后面读取寄存器的起始地址。
完整I2C读过程 = 写过程 + 读过程,其中涉及到读写方向的转变。
1.3 I2C的几个状态
上节几个图描述了读写数据流,图中的几个状态需要与实际信号一一对应起来。
1 总线空闲状态:
SDA和SCL同时处于高电平时,规定为总线的空闲状态。此时各个器件的输出级场效应管均处在截止状态,即释放总线,由两条信号线各自的上拉电阻把电平拉高。
2 总线START:
SCL为高电平时,SDA由高电平向低电平跳变,开始传送数据。
3 总线STOP:
SCL为高电平时,SDA由低电平向高电平跳变,结束传送数据。
4 总线Restart:
SCL为高电平时,SDA由高电平向低电平跳变,本质上也是START信号,用在完整I2C读过程中的读阶段,在首次发送停止信号之前,master通过发送Restart信号,可以转换与当前slave的通信模式(从写模式到读模式),或是切换到与另一个slave通信。
5 数据阶段:
在IIC总线上传送的每一位数据都有一个时钟脉冲相对应(或同步控制),即在SCL串行时钟的配合下,在SDA上逐位地串行传送每一位数据。进行数据传送时,在SCL呈现高电平期间,SDA上的电平必须保持稳定。只有在SCL为低电平期间,才允许SDA上的电平改变状态。简单的说就是,数据在SCL下降会被采样,所以SDA需要在SCL为高电平时保持稳定。
6 ACK与NACK信号:
IIC总线上的所有数据都是以8位字节传送的,发送器每发送一个字节,就在第9个时钟脉冲期间释放数据线,由接收器反馈一个应答信号。应答信号为低电平时,规定为有效应答位(ACK简称应答位),表示接收器已经成功地接收了该字节;应答信号为高电平时,规定为非应答位(NACK),一般表示接收器接收该字节没有成功。
这段话再说细一点:
在写阶段,master写了一字节数据,在第9个时钟脉冲期间释放数据线,由slave反馈应答信号,ACK(低)表示数据成功接收,NACK(高)表示该字节没有接收成功;
在读阶段,master向slave收数据,slave写了一字节数据,在第9个时钟脉冲期间释放数据线,由master反馈应答信号,ACK(低)表示数据成功接收,NACK(高)表示该字节没有接收成功。
还有一种特殊情况:当master决定不再接收数据时,应向slave发送NACK信号,高速slave不再发送。
以下情况会导致出现NACK位:
- 接收器没有发送机响应的地址,接收端没有任何ACK发送给发送器
- 由于接收器正在忙碌处理实时程序导致接无法接收或者发送
传输过程中,接收机器别不了发送机的数据或命令 - 接收器无法接收
- master接收完成读取数据后,要发送NACK结束告知slave。
当master接收到slave的NACK信号后,可以STOP这次传输,也可以重新START。
所以:NACK并不只是表示字节没有成功接收,也可以表示master告诉slave不再需要发送数据。
1.4 I2C通信中的几个问题
1 总线冲突和总线仲裁
I2C总线上的仲裁分两部分:SCL线的同步和SDA线的仲裁。
SDA仲裁:
这段话摘自《IIC总线解析》,见参考。
假如在某IIC总线系统中存在两个主器件节点,分别记为主器件1和主器件2,其输出数据分别为DATA1和DATA2,它们都有控制总线的能力,这就存在着发生总线冲突(即写冲突)的可能性。
假设在某一瞬间两者相继向总线发出了启动信号,鉴于:I2C总线的“线与”特性,使得在数据线SDA上得到的信号波形是DATA1和DATA2两者相与的结果
在总线被启动后,主器件1企图发送数据“101……”,主器件2企图发送数据“100……”。
两个主器件在每次发出一个数据位的同时都要对自己输出端的信号电平进行抽检,只要抽检的结果与它们自己预期的电平相符,就会继续占用总线,总线控制权也就得不到裁定结果。
主器件1的第3位期望发送“1”,也就是在第3个时钟周期内送出高电平。在该时钟周期的高电平期间,主器件1进行例行抽检时,结果检测到一个不相匹配的电平“0”,这时主器件1只好决定放弃总线控制权(主器件1切为slave模式,不再产生时钟,等主器件2完成传输,总线恢复空闲状态时,又重新启动主器件1的传输);因此,主器件2就成了总线的惟一主宰者,总线控制权也就最终得出了裁定结果,从而实现了总线仲裁的功能。
从以上总线仲裁的完成过程可以得出:仲裁过程主器件1和主器件2都不会丢失数据;各个主器件没有优先级别之分,总线控制权是随机裁定的。
系统实际上遵循的是“低电平优先”的仲裁原则,将总线判给在数据线上先发送低电平的主器件,而其他发送高电平的主器件将失去总线控制权。
SCL时钟同步:
如果在某一I2C总线系统中存在两个主器件节点,分别记为主器件1和主器件2,其时钟输出端分别为CLK1和CLK2,它们都有控制总线的能力。
假设在某一期间两者相继向SCL线发出了波形不同的时钟脉冲序列CLK1和CLK2,在总线控制权还没有裁定之前这种现象是可能出现的。
鉴于IIC总线的“线与”特性,使得时钟线SCL上得到的时钟信号波形,既不像主器件1所期望的CLK1,也不像主器件2所期望的CLK2,而是两者进行逻辑与的结果(注意数据在SCL为高电平时有效,所以为SCL为低电平并不影响SDA仲裁)。
CLKI和CLK2的合成波形作为共同的同步时钟信号,一旦总线控制权裁定给某一主器件,则总线时钟信号将会只由该主器件产生。
2 I2C总线死锁
正常I2C通信肯定不会出现死锁的,死锁即是I2C在某一特殊时刻出现了异常。
死锁的表现:SCL一直为高,SDA一直为低
场景1:在写阶段的ACK阶段,SDA被slave拉低作为ACK,master突然复位了,SCL也被复位为高电平,这样:master检查到SDA为低,会认为I2C总线被占用,会一直等待SCL和SDA信号变为高电平,而slave不知道master异常复位,还在傻傻等待SCL拉低,然后结束ACK信号,这样master与slave互相等待,造成死锁。
场景2:当I2C进行读操作时,slave应答后输出数据,如果在这个时刻master异常复位而此时slave输出的数据位正好为0,也会导致I2C总线进入死锁状态。
总的说来:SDA为低时,期待一个SCL的下降沿驱动,但是master由于异常复位导致SCL一直为高,这样就产生了死锁。
解决死锁的方法:
最好用模拟I2C实现,则不会死锁
将从机的电源设计为可控,当发生总线死锁的时将从机复位
可以在从机的程序中加入监测功能,如果总线长时间被拉低则释放对总线的控制
在主机中增加I2C总线恢复程序。每次主机复位后,如果检测到SDA被拉低,则控制SCL产生<=9个时钟脉冲(针对8位数据的情况),每发送一个时钟脉冲就检测SDA是否被释放,如果SDA已经被释放就再模拟产生一个停止信号,这样从机就可以完成被挂起的读写操作,从死锁状态中恢复过来。这种方法有一定的局限性,因为大部分主机的I2C模块由内置的硬件电路来实现,软件并不能够直接控制SCL信号模拟产生需要的时钟脉冲。
1.5 I2C与SPI对比
- I2C是单双工(只有SDA一根数据线),标准SPI是全双工(有MOSI和MISO两根数据线)。
- SPI没有指定的流控制,没有应答机制确认是否收到数据。
- I2C时序很多方面规定死了(如:只支持8bit数据,只支持优先发送MSB,在SCL为高电平数据有效等),而SPI则更“灵活”。
2 软件代码
2.1 GPIO模拟I2C
2.2 Linux I2C
3 I2C扩展
问题:两个嵌入式芯片怎么通过I2C通信?
如果被控器需要延迟下一个数据字节开始传送的时间,则可以通过把时钟线SCL电平拉低并且保持,使主控器进入等待状态。一旦被控器释放时钟线,数据传输就得以继续下去,这样就使得被控器得到足够时间转移已经收到的数据字节,或者准备好即将发送的数据字节。
带有CPU的被控器在对收到的地址字节做出应答之后,需要一定的时间去执行中断服务子程序,来分析或比较地址码,其间就把SCL线钳位在低电平上,直到处理妥当后才释放SCL线,进而使主控器继续后续数据字节的发送。(需要从机能够拉住SCL,用gpio模拟较方便)