3. I2C协议深度解析:7位与10位地址、标准/快速/高速模式、多主机仲裁
I2C协议,说白了就是两根线(SCL时钟、SDA数据)搞定一堆设备通信。很多初学者觉得它简单,不就是发个地址、读个数据嘛。但真到了项目里,时序对不上、地址冲突、仲裁失败……这些问题我几乎每个项目都遇到过。今天咱们就把I2C的底裤扒干净。
3.1 7位地址与10位地址:从机怎么被找到的?
I2C总线上每个从机都有一个唯一地址。主机发起通信时,先发一个地址帧,告诉总线“我要找谁”。
3.1.1 7位地址结构
标准I2C地址是7位的。发送时,主机把这7位左移1位,最低位填上R/W位(0=写,1=读)。所以你在逻辑分析仪上看到的地址字节,其实是 (addr << 1) | R/W。
举个例子: 一个温度传感器地址是0x48(7位)。主机要写它,发送的字节就是 0x48 << 1 | 0 = 0x90。要读它,发送 0x48 << 1 | 1 = 0x91。
7位地址范围是0x00~0x7F。但0x00是通用呼叫地址,0x01~0x07保留,0x78~0x7F是10位地址扩展前缀。实际可用的也就0x08~0x77,一共112个地址。够用吗?
我在一个车载项目里挂了8个同型号传感器,每个地址只能通过硬件引脚配置成4种之一。结果地址不够用,最后只能加I2C多路复用器。嗯,选型时一定要算好地址余量。
3.1.2 10位地址:什么时候用?
10位地址不是简单地把7位扩展成10位。它的发送流程分两阶段:
- 主机先发一个特殊头字节
1111 0XXx,其中XX是10位地址的高2位,x是R/W位。 - 然后发第二个字节,包含低8位地址。
| 阶段 | SDA内容 | 说明 |
|---|---|---|
| 起始条件后 | 1111 0XX0 | 高2位地址 + 写方向 |
| 从机ACK | SDA拉低 | 所有支持10位地址的从机响应 |
| 第二字节 | YYYY YYYY | 低8位地址 |
| 匹配的从机ACK | SDA拉低 | 只有地址完全匹配的从机响应 |
注意: 10位地址和7位地址可以混用在同一总线上。但7位地址0x78~0x7F被10位地址前缀占用了,不能分配给7位设备。我曾经在选型时没注意这个,结果一个7位地址0x78的传感器和10位设备冲突,排查了两天才发现。
3.2 标准、快速、高速模式:速度怎么选?
I2C的速度模式,说白了就是SCL时钟频率不同。但不同模式对IO口的上拉电阻、斜率、毛刺滤波都有要求。
| 模式 | 最大频率 | 上拉电阻建议 | 典型应用 |
|---|---|---|---|
| 标准模式(Sm) | 100 kHz | 4.7kΩ ~ 10kΩ | EEPROM、RTC、温湿度传感器 |
| 快速模式(Fm) | 400 kHz | 1.5kΩ ~ 4.7kΩ | 加速度计、陀螺仪、ADC |
| 高速模式(Hs) | 3.4 MHz | 0.5kΩ ~ 1.5kΩ | 图像传感器、高速数据采集 |
3.2.1 快速模式+(Fm+)
Fm+是快速模式的增强版,频率可达1 MHz。它主要靠更强的驱动能力(20 mA灌电流)和更小的上拉电阻实现。很多MCU的I2C外设都支持Fm+,但要注意PCB走线长度,超过20cm就容易出问题。
3.2.2 高速模式(Hs)的特殊之处
Hs模式不是简单地把时钟提上去。它有一个“握手”过程:
- 主机先以Fm模式发送一个特殊代码(0x08),告诉从机“我要切高速了”。
- 从机响应后,主机把SCL切换到Hs模式频率。
- 通信结束后,主机发停止条件,总线回到Fm模式。
我的经验: 除非你确定所有从机都支持Hs模式,否则别轻易用。混用设备时,Hs主机必须能降级到Fm。我见过一个项目,主机强制用Hs模式,结果一个老款温度传感器直接不响应,整条总线卡死。
3.3 多主机仲裁:谁抢到总线谁说话
I2C支持多个主机挂在同一总线上。但同一时刻只能有一个主机控制总线。如果两个主机同时发起通信,怎么办?
答案是:仲裁。
3.3.1 仲裁原理
I2C的SDA是开漏输出,多个设备可以同时拉低。仲裁发生在SDA上:
- 每个主机在发送数据位时,会同时监测SDA电平。
- 如果自己发送的是1(释放SDA),但检测到SDA是0(被其他主机拉低),说明仲裁失败。
- 失败的主机立即释放总线,切换到从机模式,等待下一次机会。
关键点: 仲裁不会丢失数据。因为失败的主机在发送地址或数据时,如果发现SDA被拉低,它知道自己输了,但总线上那个“赢家”的数据是完整的。这就是I2C的“无损仲裁”。
3.3.2 多主机系统的设计要点
- 时钟同步: 多个主机的SCL可能不同步。I2C用“线与”机制:谁先拉低SCL,总线就按谁的时钟走。所有主机都要监测SCL,等它释放。
- 总线空闲检测: 主机在发起通信前,必须检测SDA和SCL是否都为高(空闲状态)。如果总线忙,必须等待。
- 重试策略: 仲裁失败后,主机应该等待一段随机时间再重试,避免再次冲突。
我曾经踩过的坑: 在一个双主机系统里,两个MCU都通过I2C读写同一个EEPROM。其中一个MCU的仲裁失败重试间隔写死了1ms,结果两个主机几乎同时重试,连续冲突了十几次,导致EEPROM数据写错。后来改成随机延时+指数退避才解决。
3.4 知识结构总览
下面这张图把I2C协议的核心知识点串起来了。你可以把它当作调试时的检查清单。
3.5 实战中的几个关键检查点
每次调试I2C,我都会按这个顺序排查:
- 上拉电阻: 总线电容越大,上拉电阻要越小。100kHz用4.7kΩ,400kHz用2.2kΩ,1MHz以上用1kΩ甚至更小。
- 地址冲突: 用逻辑分析仪抓起始条件后的第一个字节,看是不是你预期的地址+R/W。
- 时钟拉伸: 有些从机(比如低速MCU)会拉低SCL来延长时钟周期。主机必须支持时钟拉伸,否则会超时。
- 毛刺滤波: 快速模式下,SCL/SDA上的毛刺可能导致误触发。硬件上可以加RC滤波,软件上可以多次采样。
我的习惯: 每个I2C设备驱动里,我都会加一个“地址扫描”函数。上电后先遍历所有可能的地址,看哪些设备在线。这样调试时一眼就能看出地址配置对不对,省得反复看数据手册。