1. I2C协议基础:总线结构、时序、寻址归纳总结
做嵌入式这些年,跟I2C打交道是最多的。老实说,它看起来简单,但坑是真不少。锁死故障就是最常见的一种——总线莫名其妙拉低,SCL还正常,SDA就是抬不起来。要排查这类问题,第一步得把协议底子扎牢。这章我们就聊聊I2C的总线结构、时序和寻址,把基础打结实。
1.1 总线结构:两根线,多个设备
I2C只有两条线:SCL(时钟线)和SDA(数据线)。所有设备挂在这两条线上,靠地址区分谁跟谁说话。主设备发起通信,从设备响应。标准模式下最高100kHz,快速模式400kHz,高速模式能到3.4MHz。
我个人习惯用上拉电阻把两根线拉到VDD。阻值常见4.7kΩ,但总线电容大了就得换小一点。我记得有一次项目里接了10个从设备,总线长度一米多,4.7kΩ死活拉不起来——换成2.2kΩ就好了。
| 参数 | 标准模式 | 快速模式 | 高速模式 |
|---|---|---|---|
| 最高时钟频率 | 100 kHz | 400 kHz | 3.4 MHz |
| 上拉电阻典型值 | 4.7 kΩ | 2.2 kΩ | 1 kΩ |
| 最大总线电容 | 400 pF | 400 pF | 100 pF |
关键点:两根线是开漏输出+上拉电阻。任何设备都能拉低总线,但没人能主动拉高。这个开漏结构既是I2C的优点(多机冲突检测),也是锁死故障的根源。
1.2 时序:起始、数据、应答、停止
I2C通信靠SCL的边沿对齐数据。时序上就那么几个动作:起始条件、数据位传输、应答位、停止条件。我们来一个个拆。
起始条件(START)
SCL高电平时,SDA从高跳低。主设备发出这个信号,告诉总线上所有设备:我要开始通信了。
数据位传输
每个数据字节8位,先传最高位(MSB)。SCL低电平时SDA可以变化,SCL高电平时采样SDA。这个我强调一下:SCL高电平期间SDA必须稳定。锁死故障很多时候就是SDA在SCL高时被拉死,主设备等不到它释放。
应答位(ACK/NACK)
第9个时钟周期,接收方把SDA拉低表示应答。如果不拉低(保持高),就是非应答(NACK)。主设备读数据时,每收到一个字节必须主动应答;如果不想读了,就发NACK然后发停止。
停止条件(STOP)
SCL高电平时,SDA从低跳高。一次传输结束。
// 伪代码示例:I2C 发送一个字节
void i2c_send_byte(uint8_t data) {
for (int i = 7; i >= 0; i--) {
// SCL 低
SCL_LOW();
// 设置SDA (MSB first)
if (data & (1 << i))
SDA_HIGH();
else
SDA_LOW();
// SCL 高,从设备采样
SCL_HIGH();
delay_half_period();
SCL_LOW();
}
// 释放SDA,等待应答
SDA_HIGH();
SCL_HIGH();
// 检查ACK
if (SDA_READ() == 0) {
// ACK received
} else {
// NACK: 处理错误
}
SCL_LOW();
}
我的小技巧:调试I2C锁死时,先用示波器看SCL和SDA的关系。如果SCL还在正常翻转,但SDA一直低,大概率是从设备把SDA锁死了。我曾经遇到过一颗温湿度传感器,上电后如果SCL线先拉高,它的内部状态机就卡死,必须重新上电才能释放。
1.3 寻址:7位和10位地址
主设备每次通信前先发一个地址字节。地址字节高7位是从设备地址,最低位是读/写位(0写,1读)。加上应答位,正好9位。
标准I2C用7位地址,理论上能挂128个设备,但保留地址占掉一些,实际可用100个左右。10位地址模式扩展了地址空间,实际项目里用得少。
| 地址模式 | 地址字节数 | 最大设备数 | 常见场景 |
|---|---|---|---|
| 7位地址 | 1字节(高7位 + R/W) | 112 | 大多数传感器、EEPROM |
| 10位地址 | 2字节(首字节11110xx + R/W,第二字节8位) | 1024 | 需要大量从设备的复杂系统 |
举个例子:AT24C02 EEPROM的7位地址是0x50,写操作发送0xA0,读操作发送0xA1。这个地址是硬件引脚决定的,A0/A1/A2三个引脚接VCC或GND来配置。
1.4 整个通信流程
一次完整的写操作大概长这样:
- 主设备发送起始条件
- 主设备发送从设备地址 + 写位(0)
- 从设备应答
- 主设备发送寄存器地址
- 从设备应答
- 主设备发送数据字节,每字节后从设备应答
- 主设备发送停止条件
读操作类似,只是中间会多一个重复起始条件(Restart)和地址+读位。
注意锁死风险点:如果在步骤3或步骤5,从设备没有应答(NACK),主设备应该及时发停止条件释放总线。有些主设备会傻等,导致SDA被从设备拉死。我现在设计代码时,每个字节传输后都设一个超时,超过一定时间没有应答就强制发停止并复位I2C外设。
1.5 本章知识结构图
下面这张图把I2C协议三要素的关系串起来,方便你快速回顾。
1.6 本章小结
基础不牢,地动山摇。I2C总线只有两根线,但锁死故障往往就出在时序细节或地址冲突上。记住三点:
- 开漏结构——任何设备拉低后只能靠上拉恢复,从设备卡住就不放
- 时序中的应答位——没等到ACK要果断收手,别死等
- 地址必须唯一——两条总线不能挂两个相同地址的从设备
我个人每次画板前都会用表格列一遍所有I2C器件的地址、速率和引脚映射,提前排除冲突。这活儿不复杂,但能省掉后期一堆debug时间。