MCU通信协议(二):I2C协议
I2C概述
I2C(Inter IC BUS)是一种半双工的通信协议,具有以下特点:
同步半双工:一根线发送和接收数据,最大化利用线资源;时钟线保证时序一致性,去除对硬件异步电路依赖;
两根数据线:即时钟线SCL(Serial Clock)与数据线SDA(Serial Data)
一主多从/多主多从:允许多个从机挂载在I2C总线上,一个系统的从机也可以通过多主多从成为主机;
具有数据应答功能:发送、接收一个字节都需要进行数据确定;
通信速率较低:100kbps、400kbps、3.4Mbps;
I2C硬件设计
硬件上,I2C只需要两根线就可以将多个从机链接在一起,节省了数据线的资源。另一个特点是,I2C的数据线均采用开漏输出的模式。
首先SDA必须采取开漏输出:在GPIO模式讨论过开漏输出的特点,也即低电平仍然是mos管导通接地,是一种强下拉。而漏极是开路的,说明无法直接输出高电平,只能通过外接电阻(I2C中一般采用4.7k限流电阻)输出高电平,mos管阻值很大,因此上拉电压接近外部电压,是一种弱上拉;这种模式在主机、从机SDA或者实现从机线与时,不会导致输出灌流到mos管,损坏器件。
SCL采取开漏输出:SCL作为时钟线,在一主多从只要配合好电平控制时序,这种情况下主机SCL可以设置为推挽输出,从机SCL设置为输入模式;但是I2C还支持多主多从协议,开漏输出上是线与逻辑,意味着只要一个从机拉低了总线电平,SCL总线就处于低电平,通过这种方式其他从机能够发出中断信号或者仲裁控制信号,转换成主机角色。因此习惯上,SCL仍然采用开漏输出的模式。
I2C通信时序(一主多从)
本文仅讨论一主多从下的I2C时序,暂时不涉及多主仲裁的I2C协议。
一主多从模式下SCL、SDA的控制权掌握在主机手中,只有从机发送数据、主机接收时从机能获得SDA短暂控制权,而SCL控制权一直由主机控制。
主机召唤(起始条件)和劝退(终止条件)从机的时序
主机召唤从机时序:SCL、SDA初始为高电平,主机首先将SDA拉低,所有从机接收该数据会将自身复位并且等待主机召唤,然后主机再将SCL拉低,说明数据的发送/接收时序即将开始。
数据传输完成,主机劝退从机时序如下:SCL、SDA发送/接收数据结束都是处于低电平,主机需要将SCL拉高(释放),代表时钟占用完成,再将SDA拉高回复到初始状态。
I2C协议没有片选线来选择从机设备,而是通过SDA发送从机ID来选中设备。
发送/接收一个字节时序
SCL在召唤从机将SCL拉低后,就可以将数据放到SDA总线上,高位先行(MSB),然后主机拉高SCL,从机会在SCL高电平期间读取SDA总线的高低电平(SCL高电平期间不允许SDA电平变化),然后主机继续拉低SCL继续放第二位数据,如此循环8次,即完成一个字节的发送时序;
主机接收时,需要交出SDA控制权,SCL仍然由主机控制;SCL低电平时,从机将数据放在SDA总线,主机拉高SCL时,读取SDA总线的电平作为一位数据,高位在前(MSB),然后拉低SCL,从机再次放第二位数据,如此循环8次,主机即接收了一个字节数据。
数据应答时序
主机和从机作为接受方接收一个字节数据时都会使用应答机制:
接收应答:主机发送一个字节后,从机应该发送一个位的数据应答主机(此时SDA控制权转交从机),发0代表应答,发1代表非应答;
发送应答:主机接收一个字节数据后,应该发送一位数据应答从机,发0代表应答,1代表非应答;
特殊的I2C完整时序
以上六个时序作为I2C时序的基本单元,拼接这些基本单元就组成了I2C发送/接收数据的方法。以下介绍了一些I2C习惯用例,包括写地址、指定地址写、读地址、指定地址读等简述,更详细应该参考对应外设的数据手册。
写地址时序
I2C主机通过从机地址(Slave Address)来选择要读写的从机对象,许多外设习惯采用7位地址。其中高几位为厂商约定,低几位可以由指定引脚电平确定,这样允许了同一芯片也具有不同的从机设备地址。
地址之后的0和1通常用于表示主机要进行读操作还是写操作,其中0代表主机要写入数据,1表示主机要读取数据;
指定地址写数据
发完以上8位数据,从机SDA发送0代表ACK应答,主机可以继续写入数据,首先发送8位的数据,通常是需要写入数据寄存器的地址(Reg Address),从机发送ACK应答,主机继续写入8位数据,代表写入的数据,从机发送ACK应答......直到主机数据发送完毕,发出终止条件(SCL先拉高,再拉高SDA)。
读数据/指定地址读
I2C主机读数据时序:发完以上8位数据(第8位为1代表读),从机发送ACK应答,然后从机开始向SDA总线发数据,这个数据就是数据,而不是寄存器地址;从机内部往往会维护一个计数器,称地址指针(CADDR),每次写入操作时,地址指针就指向写入地址的寄存器,写入数据完成,指针自动+1,指向下一个寄存器,此时进行读数据,读取的就是这个寄存器的数据。
然后继续主机接收数据,发送ACK应答,如果为0代表主机接收,并且期望接收数据,从机应该继续向SDA发送信息;如果ACK发送的是1,代表主机期望停止数据接收,从机抛出控制权,主机发出终止条件停止接收。
如果需要指定地址读,就需要拼接时序: 首先主机发送起始条件、7位设备地址,发送1位读写标志位0(代表写),然后从机ACK应答,然后发送8位寄存器地址(更新地址指针),从机应答ACK,此时不应该写入数据,而是读取地址指针的数据,所以主机应该再发起一次起始条件(SDA先拉低、SDL再拉低),发送7位设备地址+1位读写标志位1(代表读),然后从机向SDA放数据,此时的数据就是指定寄存器的数据。
总结,一些要点:
主机写操作发送地址后会指定地址,而读操作不会;
起始条件后发送一个字节的最后一位才会被认为是读写标记的转换,读写操作转换都应该重复发送起始条件;
无论是指定位置读还是写,指定位置一次即可,后面发送/接收数据,每发送/接收一字节数据,下一个字节的数据就来自下一个寄存器的内容,这个过程由地址指针完成;
主机读数据结束时,一定要发送ACK=1的非应答,代表获取SDA控制权,才能正常发送终止条件结束接收数据;