I2C 的通信原理
1. I2C 的 Clock
前面讲到,I2C 是同步通信,我们还记得 UART 是异步通信。这是因为,在 UART 中,数据的发送方只负责发送数据,接收方根据波特率进行接收数据。而 I2C 通过 Clock 同步双方的通信过程,只要 SCL 出现了上升沿,那么从机就会接收数据,这样可以有效避免双方数据采样出现误差的问题。
I2C 通常有四种时钟频率,也就是 SCL 的脉冲频率:
- 标准模式:小于 100KHz
- 快速模式:小于 400KHz
- 高速模式:小于 3.4MHz
- 超快速模式:小于 5MHz
我们可以拿 I2C 的快速模式 400KHz 和 UART 的 115200,也就是 115.2KHz 相比,可见 I2C 的通信速度要高于 UART。但是 I2C 的速度远远低于 SPI 的速度,这个到我们介绍 SPI 再做介绍。
值得注意的是,一般的设备都是支持标准模式和快速模式的,支持高速模式和超快速模式的设备比较少,也不建议 I2C 的通信速度高于 400KHz。
2. I2C 的通信过程
I2C 有严格的时序要求,当 SCL 处于低电平期间,SDA 可以改变其状态,而当 SCL 处于高电平期间,SDA 的电平必须稳定,因为这时候接收方要对数据进行采样。
标准的 I2C 通信由START+ADDR+R/W+ACK(+DATA+ACK+DATA+ACK+...)+STOP组成。
下面是标准的 I2C 通信时序图:
下面我们对这几个部分做一一介绍。
2.1 START
I2C 的开始信号是指 SCL 保持高电平的时候,SDA 由高电平变为低电平。
2.2 STOP
I2C 的结束信号正好和开始信号相反,在 SDA 低电平的时候,SCL 由低电平变为高电平。
2.3 ADDR
I2C 的地址位一般都是 7 位模式,也有 10 位模式,但是常用的还是 7 位模式。也就是说 I2C 可以由 128 个地址可以使用,但是0-7 是保留地址,因此有8-127这 120 个地址可以使用。日常项目中几乎不会用这么多,所以完全不必担心地址不够用的情况。
2.4 R/W
一般情况下,继地址位之后就是读/写位,这一位决定了主机是需要向从机写数据还是读数据,0 是写数据,1 是读数据。
2.5 ACK
无论是主机还是从机,在接收完数据后都需要发送一个应答位,应答位可以告诉对方成功接收到数据。
2.6 DATA
在发送完地址,收到从机的应答后,主机就可以接收或者发送数据了。每次只能发送一个 8 位的数据,和 UART 不同,数据由高位向低位进行发送。
3. Arduino 的 Wire 库
上面是 I2C 的通信原理,但是通常情况下,任何一款单片机都是有封装好的硬件驱动,Arduino 的 I2C 驱动是 Wire 库。同时,Arduino Uno 只有一个 I2C 接口,即SCL—A5,SDA—A4。
下面我们对 Arduino 的 Wire 库中几个重要的函数做简单介绍。
3.1 begin()
语法:
Wire.begin();
Wire.begin(address);
用于初始化 Arduino 的 I2C,如果你想让 Arduino 作为主机,参数为空,如果想让 Arduino 作为从机,则可以填入想要的从机地址。正常情况都是作为主机,参数可以为空。
3.2 beginTransmission()
语法:
Wire.beginTransmission(address);
向从机发出开始信号,参数为从机地址。
3.3 write()
语法:
Wire.write(value);
Wire.write(string);
Wire.write(data, length);
向从机发送数据,可以是一个字节的数据,也可以是一个字符串,或者是一个数组。
3.4 requestFrom()
语法:
Wire.requestFrom(address, quantity);
Wire.requestFrom(address, quantity, stop);
向从机发送读数据的请求,读取的数据会存储在缓冲中,可以通过read()
函数读取。参数 address 是从机地址,quantity 是请求的字节数。可选参数 stop 是一个布尔值,如果是 0,接受完数据后会发送一个重新开始的信号;如果是 1,接受完数据后会发送结束通信的信号,结束通信。
Arduino 为了方便初学者使用,使用了requestFrom()
这个函数,方便向从机读数据,虽然这样就完全掩盖了 I2C 的通信原理。
3.5 read()
语法:
Wire.read();
向从机读取一个字节的数据。
3.6 endTransmission()
语法:
Wire.endTransmission();
Wire.endTransmission(stop);
向从机发送一个结束通信的信号。在Wire.endTransmission()
中,你还可以填入一个布尔值,如果是 0,Arduino 会发送一个重新开始的信号;如果是 1,Arduino 会直接发送停止信号,结束通信。
可以发现,在 Wire 库中没有提到 ACK,这是因为 Wire 库自动为我们验证和发送了 ACK。同时,由于使用了requestFrom()
函数,导致 I2C 的通信原理也被封装了,后面的例子中我将尽力解释清楚其中的原理。
下面是一个 Arduino 官网提供的 I2C 读取数据的例子:
#include <Wire.h>
void setup() {
Wire.begin(); // join i2c bus (address optional for master)
Serial.begin(9600); // start serial for output
}
void loop() {
Wire.requestFrom(2, 6); // request 6 bytes from slave device #2
while (Wire.available()) // slave may send less than requested
{
char c = Wire.read(); // receive a byte as character
Serial.print(c); // print the character
}
delay(500);
}
4. 适用的设备
常见的使用 I2C 的设备有以下几个:
DS3231 | MPU6050 | OLED |
---|---|---|
下个的章节我们将学习如何通过 I2C 向 DS3231 读写数据。