3. I2C通信协议详解
各位同学好,今天我们来聊聊I2C。说实话,I2C是我在嵌入式开发中用得最多的总线之一。它简单、灵活,两根线就能搞定一堆传感器。但越是简单的东西,坑往往越多。我刚开始做驱动时,就在I2C上栽过跟头——传感器死活不响应,查了两天才发现是上拉电阻选错了。
好,咱们从头捋一遍I2C的核心知识点。
3.1 I2C总线物理层
I2C总线就两根线:SDA(数据线)和SCL(时钟线)。所有设备都挂在这两根线上,主设备发起通信,从设备响应。
这里有个关键点:SDA和SCL都是开漏输出。什么意思?说白了,设备只能把线拉低,不能主动拉高。拉高靠的是外部的上拉电阻。
为什么用开漏?因为多个设备可以共用一根线。任何一个设备拉低,总线就是低电平。这样就避免了多个设备同时输出高低电平导致的短路问题。
每个I2C设备都有一个7位或10位的地址。7位地址最常见,范围是0x00到0x7F。我习惯把地址表打印出来贴在调试台上,省得每次都要翻手册。
3.2 时序详解
I2C的时序其实不复杂,但几个关键动作必须记牢。
3.2.1 起始条件
SCL为高电平时,SDA从高电平切换到低电平。这就是起始信号。
// 起始条件伪代码
void i2c_start(void) {
SDA_HIGH();
SCL_HIGH();
delay(); // 保持一段时间
SDA_LOW(); // SDA先拉低
delay();
SCL_LOW(); // 然后SCL拉低
}
嗯,这里要注意:起始信号只能由主设备发出。从设备不能主动发起通信。
3.2.2 停止条件
SCL为高电平时,SDA从低电平切换到高电平。这就是停止信号。
void i2c_stop(void) {
SDA_LOW();
SCL_HIGH();
delay();
SDA_HIGH(); // SDA拉高,产生停止条件
delay();
}
我曾经遇到过一个奇怪的问题:停止信号发完后,总线一直忙。后来发现是停止条件没保持足够时间,从设备没识别到。加了个微秒级的延时就解决了。
3.2.3 应答机制
每传输完一个字节(8位),接收方必须发送一个应答位(ACK)。
- ACK(应答):接收方在第9个时钟周期拉低SDA,表示「收到,继续」
- NACK(非应答):接收方在第9个时钟周期保持SDA高电平,表示「别发了,我收够了」或「出错了」
个人经验:读数据时,主设备在最后一个字节要发送NACK,然后跟一个停止信号。这是告诉从设备:「别再发了,我要结束了。」很多新手在这里忘记发NACK,导致从设备一直发数据,总线卡死。
3.3 读写流程
I2C的读写流程其实就两个套路:写和读。咱们一个一个看。
3.3.1 写操作
写操作的流程是这样的:
- 主设备发送起始条件
- 主设备发送从设备地址 + 写位(0)
- 从设备返回ACK
- 主设备发送寄存器地址
- 从设备返回ACK
- 主设备发送数据字节
- 从设备返回ACK
- 重复步骤6-7,直到数据发完
- 主设备发送停止条件
// 写一个字节到指定寄存器
bool i2c_write_byte(uint8_t dev_addr, uint8_t reg_addr, uint8_t data) {
i2c_start();
if (!i2c_send_byte(dev_addr << 1 | 0)) { // 地址 + 写位
i2c_stop();
return false;
}
if (!i2c_send_byte(reg_addr)) { // 寄存器地址
i2c_stop();
return false;
}
if (!i2c_send_byte(data)) { // 数据
i2c_stop();
return false;
}
i2c_stop();
return true;
}
3.3.2 读操作
读操作稍微复杂一点,需要先写寄存器地址,再重新发起起始条件来读数据。
- 主设备发送起始条件
- 主设备发送从设备地址 + 写位(0)
- 从设备返回ACK
- 主设备发送寄存器地址
- 从设备返回ACK
- 主设备发送重复起始条件
- 主设备发送从设备地址 + 读位(1)
- 从设备返回ACK
- 主设备读取数据字节,发送ACK
- 重复步骤9,直到读完
- 主设备发送NACK + 停止条件
关键点:重复起始条件(Repeated Start)不是停止+起始,而是在不释放总线的情况下直接发新的起始信号。这样做的好处是:不会让其他设备抢占总线。
3.4 速率与上拉电阻选择
这部分是实战中容易出问题的地方。我来说说我的经验。
3.4.1 I2C速率
I2C有几种标准速率:
| 模式 | 最高速率 | 典型应用 |
|---|---|---|
| 标准模式 | 100 kbit/s | 大多数传感器 |
| 快速模式 | 400 kbit/s | EEPROM、ADC |
| 快速模式+ | 1 Mbit/s | 高速传感器 |
| 高速模式 | 3.4 Mbit/s | 特殊应用 |
我个人习惯:能用100k就别用400k。速率越高,对布线和上拉电阻的要求越苛刻。压传感器这种低速设备,100k完全够用。
3.4.2 上拉电阻选择
上拉电阻的取值直接影响信号质量。选小了,功耗大;选大了,上升沿变缓,高速通信会出问题。
计算公式:
R_min = Vcc / I_ol_max
R_max = t_r / (0.8473 × C_bus)
其中:
- Vcc:总线电压(通常是3.3V或5V)
- I_ol_max:设备最大灌电流(查手册,一般3-5mA)
- t_r:允许的上升时间(100k模式:1μs;400k模式:0.3μs)
- C_bus:总线电容(包括引脚电容和走线电容,一般10-50pF)
实际工程中,我一般这样选:
| 总线电压 | 100k模式 | 400k模式 |
|---|---|---|
| 3.3V | 4.7kΩ | 2.2kΩ |
| 5V | 4.7kΩ | 1.5kΩ |
避坑指南:我曾经在一个项目里用了10kΩ的上拉电阻,100k速率下没问题。后来换了个传感器,总线电容大了,波形上升沿变得很缓,通信时好时坏。示波器一看,上升沿都快1.5μs了。换成4.7kΩ后问题解决。
所以我的建议是:上拉电阻宁小勿大。只要不超过灌电流限制,小一点更可靠。
3.5 知识体系总览
下面这张图是我自己整理的I2C知识体系,方便大家对照学习:
这张图把I2C的核心知识点串起来了。物理层是基础,时序是规矩,读写流程是操作步骤,速率和上拉电阻是工程实践。四者缺一不可。
我的建议:学I2C最好的方法就是拿示波器看波形。把起始条件、停止条件、ACK、NACK一个个抓出来看。看懂了波形,I2C就没什么秘密了。