简介:《I2C总线协议详解(中文)》是一份面向嵌入式开发初学者与硬件工程师的协议入门文档,围绕Philips提出的两线制串行总线标准展开,帮助读者理解SDA、SCL信号如何配合、主从设备如何寻址以及数据如何传输,可用于传感器、EEPROM、实时时钟等低速外设的接口设计与调试参考。文档章节覆盖协议版本演进、对设计人员与厂商的收益、总线概念与总体特征、位传输的数据有效性、起始与停止条件、字节格式与应答位、仲裁与时钟同步、7位与10位寻址等内容,目录层次完整,便于按模块查阅,也适合作为培训讲义或课题参考资料。资源包共1个PDF文件,约872KB,单文件即下即读。目前已有842人学习下载,可作为快速建立I2C知识框架、对照手册排查通信时序问题的实用材料。
1. 两根线的控制论:I2C 为什么敢让多主机在同一对线上抢总线
I2C 最反直觉的地方在于:它只有两根线——SDA 和 SCL——却要同时承载多主仲裁、从机应答、时钟拉伸、7 位和 10 位寻址、三档速率,还要在 400pF 的电容负载上保证边沿不失真。它没有片选线,所有器件挂在同一对线上,寻址靠地址字节,冲突靠回读电平,这意味着总线本身既是数据通道,又是竞争检测器。很多人第一次调 I2C,都会经历“逻辑分析仪上数据看着没问题,读回来全是 0xFF”的阶段。这篇从起始条件的电平跳变一路拆到上拉电阻怎么算、仲裁为什么不会丢数据,把规范里那些看起来像约定的条目还原成被物理逼出来的设计。读懂之后,再看任何一颗传感器的 I2C 时序图,都能一眼看出它在哪一步做了妥协。
2. 起始与停止条件:为什么电平跳变能当控制符用
2.1 开漏输出与上拉电阻:线与的物理基础
先做个思想实验。如果 I2C 的两根线用普通推挽输出:A 器件想把 SDA 拉高,B 器件想把 SDA 拉低,两个 CMOS 管同时导通,电源到地之间就形成一条低阻通路,瞬间电流足以把其中一颗芯片打出内伤。多器件共线,推挽输出是绝对不能碰的。
所以规范规定:所有连接到总线的输出级必须是漏极开路(OD)或集电极开路(OC)。这类输出只有两种状态——主动拉低,或者高阻释放。释放时总线电平由外部上拉电阻 Rp 决定。这个“只能拉低、不能主动拉高”的约束,换来的是一个非常强的性质:线与。只要有一个器件把线拉低,整条线就是低;只有当所有器件都释放,上拉电阻才把线抬到高。逻辑上等价于对所有器件输出做与运算。
SDA 和 SCL 都这么设计,于是 I2C 就有了两件事的物理基础:应答(任何一个接收方拉低 SDA 就是 ACK),以及仲裁(多个主机同时驱动,电平由“最强势”的那个 0 决定)。
代价也很直接:高电平完全靠 Rp 给总线电容充电,上升沿是一条 RC 充电曲线,不是理想方波。总线上挂的器件越多、走线越长,等效电容 Cb 越大,上升沿越缓。规范把这条线的负载上限写死成 400pF,本质上是“在这个电容下,用合理的 Rp 还能把边沿控制进时序窗口”的经验值,而不是协议层规定。
| 速率档位 | 最高位速率 | 上升沿上限 tr | 允许的总线电容 Cb |
|---|---|---|---|
| 标准模式 | 100 kbit/s | 1000 ns | 400 pF |
| 快速模式 | 400 kbit/s | 300 ns | 400 pF(工程常用 200 pF 以内) |
| 高速模式 | 3.4 Mbit/s | 80 ns(Cb 约 100 pF) | 100 pF 量级,需主动电流源上拉 |
2.2 SCL 高电平期间的电平跳变为什么能编码控制信息
数据有效性规则是 I2C 全部时序的地基:SDA 上的数据在 SCL 高电平期间必须稳定,SDA 只允许在 SCL 低电平期间改变。这条规则一立,SCL 高电平期间 SDA 的任何跳变就被“空出来”了——它不可能是数据,只能是控制。
规范正是用这个空档定义了两个特殊条件:
- 起始条件 S:SCL 为高时,SDA 从高变低。
- 停止条件 P:SCL 为高时,SDA 从低变高。
这就是典型的带内信令:不需要额外的控制线,起始和停止直接编码在数据线的电平跳变里,而且因为数据规则排除了歧义,任何从机器件只要盯着 SDA 在 SCL 高电平期间的边沿,就能识别这两个事件。
/* 软件模拟 I2C 的起始与停止;SDA/SCL 均为开漏引脚,宏里只操作输出级 */ void i2c_start(void) { SDA_RELEASE(); /* 先释放 SDA,确保起始前总线处于空闲高电平 */ SCL_RELEASE(); i2c_delay_half(); /* 满足 tSU;STA,标准模式不小于 4.7us */ SDA_DRIVE_LOW(); /* SCL 高电平期间 SDA 由高变低:起始条件 S */ i2c_delay_half(); SCL_DRIVE_LOW(); /* 拉低 SCL,进入数据传输阶段 */ } void i2c_stop(void) { SCL_DRIVE_LOW(); SDA_DRIVE_LOW(); /* 先把 SDA 拉到低,保证随后释放时产生上升沿 */ i2c_delay_half(); SCL_RELEASE(); /* 释放 SCL,等待上拉电阻把它抬到高 */ i2c_delay_half(); /* 满足 tSU;STO,标准模式不小于 4.0us */ SDA_RELEASE(); /* SCL 高电平期间 SDA 由低变高:停止条件 P */ i2c_delay_half(); }这两个函数里最容易翻车的是顺序和方向。起始条件要求 SDA 的下降沿落在 SCL 高电平期间,所以必须先释放 SCL 让它变高,再动 SDA;停止条件反过来,先在 SCL 低时把 SDA 拉低,再释放 SCL,最后释放 SDA。参数 tSU;STA 和 tSU;STO 都要去规范的时序表里按速率档位查,不能拍脑袋定一个延时。
注意:宏里必须是“释放”而不是“输出高”。如果总线上有从机正在拉低 SDA,你强行把引脚驱动成高电平,就是两个输出级对打,轻则波形畸变,重则烧引脚。
2.3 重复起始条件与总线占用状态
重复起始条件 Sr 的语义是:不产生停止条件 P,直接再产生一个起始条件 S。总线上看,SDA 在 SCL 高电平期间又出现一次高到低的跳变,但中间没有 P。总线一直保持忙,不释放给其他主机。
这个机制最典型的用途是读操作。7 位寻址的读,标准流程是先写地址字节(R/W 位为 0,表示要写),从机应答,再发寄存器地址,从机应答,然后 Sr,再发一次地址字节(R/W 位为 1),从机开始吐数据。为什么不一次搞定?因为地址字节里的 R/W 只有一位,而“先写寄存器地址再读数据”这种复合操作需要两次方向切换,Sr 让这个切换不释放总线。
如果没有 Sr,就必须 P 之后再 S,中间总线空闲,其他主机可能抢走总线,你的“写地址—读数据”就被拆成两个独立事务,对需要原子性的寄存器读取是灾难。规范里把 S 和 Sr 在功能上等同看待,除非特别声明,否则符号 S 既表示起始也表示重复起始,原因就在这里。
从总线状态机看,空闲 → 起始 → 忙 → 停止 → 空闲。Sr 只是在“忙”状态内部重新触发一次起始,不改变忙的状态。软件上区分这两者的意义在于:产生 Sr 之前不能释放总线控制权,也不能插入其他事务。
3. 应答位与时钟拉伸:从机如何用一根 SCL 反向控制主机
3.1 第 9 个时钟脉冲与 ACK/NACK 的电平语义
I2C 的数据单位是字节,但每个字节后面必须跟一个应答位,所以一次完整传输是 9 个时钟脉冲。前 8 个时钟传数据,最高位先出;第 9 个时钟传应答。
关键在谁驱动 SDA。前 8 个时钟由发送方驱动,接收方只是采样。到第 9 个时钟前,发送方必须释放 SDA,把线的控制权交给接收方。接收方如果拉低 SDA,就是 ACK;如果保持释放,就是 NACK。因为线与,ACK 必须是低电平——任何一方拉低都能被发送方读到。
这个“低有效应答”不是随便定的。开漏总线里,“主动”动作只有拉低,“不动作”就是释放。所以协议把“确认”定义成主动拉低,“否认”定义成不动作。这样即使某个器件根本没接上,线也会被上拉电阻拉高,发送方读到 NACK——失败默认可见,不会静默丢包。
/* 写一个字节,返回 0 表示收到 ACK,1 表示 NACK。data 按 MSB 先发 */ uint8_t i2c_write_byte(uint8_t data) { for (uint8_t mask = 0x80; mask; mask >>= 1) { <p> <a href="https://download.csdn.net/download/weixin_45659878/88857641" style="color:#ec7500;font-size:14px;"> 本文还有配套的精品资源,点击获取 </a> <img alt="menu-r.4af5f7ec.gif" src="https://csdnimg.cn/release/wenkucmsfe/public/img/menu-r.4af5f7ec.gif" style="width:16px;margin-left:4px;vertical-align:text-bottom;cursor:text;"> </p>