目录
I2C通信介绍
I2C总线 的英文全称是:Inter IC BUS
他有两根通信线:SCL (Serial Clock)、SDA(Serial Data) 串行时钟线和串行数据线 支持总线挂载多设备。可以是一主多从和多主多从
- 一主多从的意思是单片机作为主机主导I2C总线的运行,其他挂载在I2C总线上的都是从机。从机只有被主机点名之后,才能控制I2C总线。就像老师点了你的名字之后你才能说话。
- 而多主多从模式是:每个模块都可以突然站出来,说接下来我是主机,你们听我的。 这时候如果有其他人说我也要当主机, I2C也会有一个仲裁器来仲裁, 优先级高的胜利。失败的自动变为从机。
I2C通信属于 半双工 同步 多设备 带数据应答的 通信
而USART是 全双工 异步 点对点的通信
- 半双工 是可以双向传输,但每次只能有一个传输方向
- 全双工 是既可以双向传输,它又可以同时有两个传输方向。
- 异步的好处是省下一根时钟线,节省资源。缺点是对时间要求严格,对硬件电路依赖严格。
- 同步时钟的好处是。对时间要求不严格,对硬件电路不依赖。稳定性也比异步的好,容易使用软件来模拟时序,缺点是多一根时钟线。
- 多设备则是在一根数据线上挂载很多的设备。在访问其中某个设备时。其他的设备对通信没有影响。
I2C通信可以理解外,通过一根时钟线和一根数据线。 来链接单片机外的外设。 并且可以读写其中指定的寄存器。实现这个外挂模块的完全控制
比如MPU6050模块,可以进行姿态测量。 它就使用了I2C通信协议 或者OLED模块,可以显示字符、图片等信息。也使用了I2C通信协议 以及AT24C02存储器模块。也是I2C的通信协议
或者DS3231 实时时钟模块。也是使用I2C通信。
因为使用通用的协议。对于开发者来说是很方便的
软件I2C和硬件I2C的区别
-
硬件电路件I2C的波形更加规整,时钟周期和占空比非常一致。
每个时钟周期后都有严格的延时,保证每个周期的时间相同。
硬件电路需要专门的某些GPIO口才能实现
-
软件I2C的波形较为不规整,每个时钟周期和空闲时间都不一致。
软件I2C时的引脚操作会有一定的延时,因此各个时钟周期的间隔和占空比都不均匀。
但是软件实现I2C更加的方便,随便那个GPIO口都可以实现。
I2C硬件电路规定
- 所有的I2C设备的SCL 均要连接在一起,SDA连接在一起
- 所有设备的SCL和SDA均要配置成开漏输出模式
- SCL和SDA各添加一个上拉电阻,阻值一般为4.7KΩ左右
为什么呢?
因为只有一根数据线,所以采用的是半双工的协议。
在一主多从的模式下,主机的SCL线可以是推挽输出状态。来拥有对SCL的绝对控制权。 (但是仍然采用的是开漏输出+上拉电阻的模式。因为在多主机模式下会用到这个特性) 所有从机的SCL线都配置为 浮空输入 或者 上拉输入 数据的流向为 主机发送,所有的从机只能被动接收, 这样是没问题的
**但SDA就不能这样了。**因为数据线是只有一条。 主机在接收的时候是输入,在发送的时候是输出。 同样,从机的SDA也会在输入和输出之间反复切换。 如果这时使用的是推挽输出模式下。 在总线没协调好两个设备的时序的情况下。就会出现两个设备都是输出的状态。如果此时一个输出高电平。另一个输出低电平。**就会出现电源对地短路的情况。**如图:
这是应该极力避免的。
所以,在I2C这里的解决办法是,禁止所有设备为强制上拉的高电平 采用了外置弱上拉电阻 加 开漏输出的电路结构。如图:
在开漏输出模式下,每个引脚的内部结构如图:
可以看到。不管是SCL 还是SDA
在输入时 都可以随便输入。对电路不会有影响
在输出高电平时 只需要打开自己所控制的mos管,外部的上拉电阻会拉为高电平。
在输出低电平时 只需要闭合自己所控制的mos管,外部的高电平会被强制拉低接地。
所以,开漏输出不同于推挽输出的强上拉强下拉。
开漏输出 配合 上拉电阻 是弱上拉、强下拉
所以,这个模式会有一个 “线与” 的现象。即只要有任意一个或多个设备输出了低电平,总线就处于低电平。只有所有的设备都输出高电平。总线才输出高电平
所以,挂载在I2C总线上的 所有设备。 所有的GPIO 引脚 都要设置为 开漏输出的模式。 并且在SCL和 SDA两条线 添加上拉电阻
I2C软件设计(时序基本单元)
起始条件与终止条件
起始和终止,都是由主机产生的,从机不允许产生起始和终止
在I2C总线处于空闲状态时。SCL和SDA都被上拉为高电平。
当主机需要进行数据收发时,就会产生一个起始条件。 起始条件就是,时钟线不动,拉低SDA数据线。 当从机捕获到SCL时钟线为高电平。SDA数据线产生了下降沿信号时,就会进行自身的复位,等待主机点名。 然后主机会拉下SCL时钟线的电平。准备开始下一步动作。
终止条件是,主机先对SCL时钟线放手,让上拉电阻把SCL拉高为高电平,再对SDA数据线放手,产生一个上升沿。这个上升沿就会触发终止条件
此时就会恢复到最初的两个高电平的平静状态
- 起始条件:SCL高电平期间,SDA从高电平切换到低电平
- 终止条件:SCL高电平期间,SDA从低电平切换到高电平
主机发送一个字节 的时序单元
时钟线SCL低电平期间,主机将数据位依次放到SDA数据线线上**(高位先行**),然后释放SCL, 从机将在SCL高电平期间读取数据位 所以SCL高电平期间SDA不允许有数据变化, 依次循环上述过程8次即可发送一个字节
因为SCL同步时钟是主机在控制。从机并不知道什么时候时钟会消失。所以从机要尽快在SCL高电平期间读取主机所发送的电平信号(一般在主机松手的一刹那,上升沿来临时就已经读取了)。
主机也应该在SCL时钟线下降沿上尽快把数据放到SDA数据线上。 但是主机有对时钟线的主导权。即使中途有什么事情打断了主机放数据的操作。主机也可以一直拉低时钟SCL。等到把数据放到SDA上之后再松开SCL 使他恢复高电平(这也是同步时序的好处)。
在主机的主导的SCL时钟下。 SCL低电平,主机发送; 高电平,从机接收; 如此循环8次 就发送了8位数据,也就是一个字节
在发送一个字节的时序中,主机完全掌控SCL和SDA,从机只能被动的读取。
主机接收一个字节 的时序单元
与发送一个字节同理。这里实线是主机控制,虚线是从机控制。 时钟线SCL低电平期间,从机将数据位依次放到SDA数据线线上**(高位先行**),然后释放SCL, 主机将在SCL高电平期间读取数据位 所以SCL高电平期间SDA不允许有数据变化, 依次循环上述过程8次即可读取一个字节
主机在接收之前,必须释放SDA(因为总线是线与的特征)
如图:
可以看到。主机在接收前,释放了SDA数据线。 这时数据线的主导权到了某个从机的手中。 但是SCL时钟线的主导权还是在主机这里。从机只能等待时钟为下降沿时被动发送。所以也是要尽快的发送,否则错过低电平的时期就不好啦(一般在下降沿来临后就已经发送了。贴着下降沿)。 主机则是在高电平任意时刻读取就行,反正主机能控制SCL时钟线
主机/从机 应答 基本单元
- 发送应答:主机在接收完一个字节之后,在下一个时钟发送一位数据,数据0表示应答,数据1表示非应答在下一个时钟接收一位数
- 接收应答:主机在发送完一个字节之后,判断从机是否应答,数据0表示应答,数据1表示非应答(主机在接收之前,需要释放SDA)
可以看出,接收方必须在接收到一个字节之后,在下一个时钟拉低SDA数据线,如果没有拉低,那么发送方就会认为 这次发送失败,没有人接收到。
如图:接收应答时,当主机释放SDA时,从机就应该立刻把SDA拉下来。在SCL高电平期间,主机读取应答位,如果应答位为0,就代表接收到。为1 代表没有接收到
同理,主机在接受到一个字节的时候,也要发送应答。拉低电平,然后使SCL为高电平,等待从机读取。在高电平期间,从机如果收到了主机的应答,他就可以继续执行操作。如果主机没有应答,那么 从机就会乖乖释放SDA的主导权,防止干扰主机之后的工作
I2C完整时序(拼接基本单元)
I2C的完整时序主要有3种
- 指定地址写
- 当前地址读
- 指定地址读
从机设备的地址
主机可以访问总线上的任何一个设备,要把每个从机都确定一个唯一的设备地址 从机设备地址,在I2C协议里分为:7位地址和 10位地址 (这里主要是讲7位地址) 在每一个I2C设备出厂时,厂商就会给他分配一个I2C 7 位地址(可以在芯片手册中查看)
比如MPU6050的7位地址是1101 000 AT24C02的7位地址是1010 000 一般不同芯片的地址是不一样的。 相同芯片的地址是一样的。
为了应对总线上挂载相同芯片的情况:一般器件地址的最后几位,是可以在电路中通过地址改变引脚的高低电平来改变的。(比如MPU6050的AD0引脚就可以决定地址。 低电平为1101 000 接高电平为 1101 001 ;AT24C02地址的最后三位,都可以由板子的A0 A1 A2来确定)
指定地址写
- 对于指定设备(Slave Address)在指定地址(Reg Address)**下写入指定数据(**Data)
对于指定设备(从机地址),在指定地址(寄存器地址)下写入数据。
空闲状态
- 两条总线(SCL和SDA)都是高电平。
产生起始条件
- 主机在SCL高电平期间拉低SDA,产生起始条件。
- 在起始条件之后,主机发送一个字节,该字节内容为从机地址和读写位(从机地址是7位,读写位是1位,总共8位)。
发送从机地址和读写位
- 发送从机地址:确定通信的对象。
- 发送读写位:确认接下来是写入还是读出(0表示写入,1表示读出)。
- 接收应答位
- 主机发送从机地址和读写位后,释放SDA。
- 从机响应后会拉低SDA,产生应答位。
- 应答位产生后,从机释放SDA的控制权。
发送数据
- 主机再次发送第二个字节,通常为寄存器地址或控制字。
- 接着发送第三个字节,表示要写入寄存器地址中的数据值。
产生停止条件
- 主机拉低SDA,为后续的上升沿做准备。
- 然后依次释放SCL和SDA,产生SC**L高电平期间的SDA上升沿,**表示停止条件。
示例
对于从机地址为1101000的设备,在其内部0x19地址的寄存器中写入0xAA这个数据,数据帧的过程如下:
- 起始条件:SCL高电平期间,SDA从高电平拉低。
- 发送从机地址和读写位(1101000+0)。
- 接收从机应答位。
- 发送寄存器地址(0x19)。
- 接收从机应答位。
- 发送数据值(0xAA)。
- 接收从机应答位。
- 停止条件:SCL高电平期间,SDA从低电平释放至高电平。
通过这些步骤,主机可以可靠地在从机的指定寄存器地址中写入数据
当前地址读
对于指定设备(Slave Address),在**当前地址指针指示的地址下,**读取从机数据(Data)
产生起始条件
- 主机在SCL高电平期间拉低SDA,产生起始条件。
寻址和读写操作
- 主机首先发送一个字节,用于从机的寻址和设置读写标志位。
- 在本次通信中,主机发送的目标地址是1101000,读写标志位为1,表示主机接下来要读取数据。
接收从机应答位
- 主机发送完寻址字节后,接收从机的应答位。
- 从机收到第一个字节后,拉低SDA,表示应答,并将SDA的控制权交回主机。
接收数据
- 主机调用接收数据的时序,准备接收从机发送的数据。
- 从机得到允许后,在SCL低电平期间将数据写入SDA。
- 主机在SCL高电平期间读取SDA上的数据。
- 主机在SCL高电平期间依次读取8位数据,从而接收到从机发送的一个字节数据(例如:0000 1111,即0x0F)。
指针自动递增
- I2C通信中没有指定寄存器地址的环节,默认情况下,从机中的所有寄存器被分配到一个线性区域中。
- 从机内部有一个指针变量,指向某个寄存器。上电时,这个指针通常默认指向0地址。
- 每次 写入 或 读出 一个字节后,这个指针会自动递增,指向下一个寄存器位置。
- 主机读取到的数据是当前指针指向的寄存器的值。
所以一般这个时序用的不是很多
指定地址读
对于指定设备(Slave Address),在指定地址(Reg Address)下,读取从机数据(Data)
对弈指定地址读,其实是先告诉设备我要写,当寄存器指向这个地址之后,我又不写了。我要读。
写入操作
- 指定从机地址和读写标志位: 从机地址为1101000,读写标志位为0,表示要进行写操作。
- 从机应答: 从机应答后,主机发送第二个字节(0001 1001),用于指定寄存器地址。 这个数据写入到从机的地址指针中,从机的寄存器指针指向0x19位置。
重复起始 开始读操作
- 产生一个新的起始条件: 主机再次产生一个起始条件。
- 重新寻址并指定读写标志位: 主机重新发送从机地址(1101000)和读写标志位(此时读写标志位为1,表示读操作)。
- 主机接收数据: 从机应答后,主机开始接收一个字节的数据。 这个字节的数据是从机0x19地址下的内容。
读写操作也不一定只能一次一个字节。 在主机读或写的时候,只要主机应答,从机就不会停。直到主机给非应答,从机才会停止,然后把SDA主导权交给主机。