I²C 是嵌入式开发中最常见的板级通信总线之一。只用 SDA 和 SCL 两根信号线,一个控制器就能连接传感器、EEPROM、RTC、OLED 等多个器件。国内开发者也常把它写成 IIC;本文统一使用规范写法 I²C。
它的引脚少、寻址清晰、外设生态成熟,但“能看到时钟”并不等于“通信一定正确”。地址写法、上拉电阻、总线电容、ACK、重复起始和时钟拉伸,都是实际调试中最容易踩坑的地方。下面从电气结构开始,完整走一遍 I²C。
一、I²C 总线由什么组成

I²C 是同步串行总线。SCL 提供时钟,SDA 传输数据。总线通常由一个控制器发起事务,目标器件根据地址响应。NXP 最新规范使用 Controller/Target 描述这两个角色,很多旧资料中仍会看到 Master/Slave,它们指的是同一组角色。
同一总线上的目标器件必须使用不冲突的地址。图中的温湿度传感器、EEPROM 和 OLED 虽然共用两根线,但控制器能够通过地址选择其中一个器件。
二、为什么 SDA 和 SCL 都要上拉
I²C 引脚通常采用开漏(Open-drain)结构:器件可以主动把总线拉低,却不会主动输出高电平。没有器件拉低时,外部上拉电阻把总线恢复为高电平。因此,总线上的高电平实际表示“所有器件都释放了这根线”。
这种结构带来两个关键能力:
- 线与逻辑:任何一个器件拉低,总线读到的就是低电平,避免多个器件直接推挽对打。
- 多器件协作:ACK、时钟拉伸和多控制器仲裁,都建立在“低电平优先”的电气特性上。
常见的 4.7 kΩ 只是经验起点,并不是所有 I²C 电路的固定答案。速度越快、走线越长、器件越多,总线电容越大,对上拉电阻的要求就越严格。
三、START、STOP 与数据采样
总线空闲时,SDA 和 SCL 都保持高电平。一次事务由控制器产生 START 条件开始,并由 STOP 条件结束:
- START:SCL 为高电平时,SDA 从高变低。
- STOP:SCL 为高电平时,SDA 从低变高。
- 普通数据位:SDA 应在 SCL 低电平期间改变,在 SCL 高电平期间保持稳定。
- 发送顺序:每个字节都从最高位 MSB 开始传输。

四、地址、方向位与 ACK
最常见的 I²C 地址长度是 7 位。START 之后的第一个字节由“7 位地址 + 1 位读写方向”组成:方向位为 0 表示写,为 1 表示读。紧接着的第 9 个时钟用于应答。
- ACK:接收方在第 9 个时钟把 SDA 拉低,表示已接收。
- NACK:SDA 保持高电平,可能表示地址无人响应、目标器件尚未准备好,或者读取方不再需要更多字节。
最常见的地址混淆
假设器件的 7 位地址是 0x3C,放入总线首字节后,写地址字节是 0x78,读地址字节是 0x79。有些数据手册给 7 位地址,有些旧代码直接给左移后的 8 位地址。调用驱动前一定要确认 API 需要哪一种;多数现代 HAL 接口要求传入 7 位地址,但也有接口要求调用者自行左移。
五、典型的寄存器读写流程
写一个寄存器
START
-> 地址 + W -> ACK
-> 寄存器地址 -> ACK
-> 数据 -> ACK
-> STOP
读取一个寄存器
START
-> 地址 + W -> ACK
-> 寄存器地址 -> ACK
-> REPEATED START
-> 地址 + R -> ACK
-> 数据 -> NACK
-> STOP
读取寄存器时,前半段写入的是“接下来要读哪个寄存器”。随后控制器发出重复起始(Repeated START),不释放总线便切换到读方向。读取最后一个字节后,控制器发送 NACK,告诉目标器件读取结束,再产生 STOP。
六、常见速率模式
| 模式 | 最高速率 | 常见用途 |
|---|---|---|
| Standard-mode | 100 kbit/s | 兼容性优先、低速传感器 |
| Fast-mode | 400 kbit/s | 常见 MCU 与显示、传感器 |
| Fast-mode Plus | 1 Mbit/s | 支持 Fm+ 的高速器件 |
| High-speed mode | 3.4 Mbit/s | 对器件和总线设计要求较高 |
| Ultra Fast-mode | 5 Mbit/s | 单向传输,使用场景较少 |
实际速率不能只看 MCU 能跑多快,还要确认总线上每一个器件、双向电平转换器、上拉网络和走线电容都支持该模式。对常见开发板来说,100 kHz 和 400 kHz 通常最实用。
七、时钟拉伸与多控制器仲裁
目标器件如果暂时来不及处理数据,可以把 SCL 保持在低电平,这叫时钟拉伸(Clock Stretching)。控制器释放 SCL 后,应读取引脚的真实电平,等它确实变高再继续。使用硬件 I²C 外设时,需要确认控制器及其驱动是否支持目标器件要求的拉伸行为。
在多控制器系统中,多个控制器可能同时发起通信。仲裁同样利用线与逻辑:一个控制器释放 SDA、准备发送 1,却读到低电平,说明另一个控制器正在发送 0,此时前者失去仲裁并停止驱动。普通单控制器项目通常遇不到这一过程,但理解它有助于解释 I²C 为什么必须使用开漏结构。
八、上拉电阻怎么选
电阻太小,会让器件拉低时承受过大的灌电流;电阻太大,又会使 RC 上升沿过慢。TI 的应用说明给出了实用边界:
Rₚ(min) = (VCC - VOL(max)) / IOL
Rₚ(max) = tr / (0.8473 × Cb)
其中,tr 是规范允许的最大上升时间,Cb 是总线总电容。举例:3.3 V 总线、VOL(max)=0.4 V、允许灌电流 3 mA,则最小值约为 967 Ω;若 Fast-mode 的上升时间按 300 ns、总线电容约 100 pF 估算,最大值约为 3.54 kΩ。这种情况下可从 2.2 kΩ 或 3.3 kΩ 开始验证。
还要注意,开发板和模块上可能已经焊有上拉电阻。多个模块的上拉会并联,例如两组 4.7 kΩ 并联后等效约 2.35 kΩ。最终应结合器件手册,并用示波器观察 SDA、SCL 的低电平和上升沿。
九、一个简化的写寄存器示例
bool i2c_write_register(uint8_t address7,
uint8_t reg,
uint8_t value)
{
if (!i2c_start()) return false;
if (!i2c_write_byte((address7 << 1) | 0u)) goto fail;
if (!i2c_write_byte(reg)) goto fail;
if (!i2c_write_byte(value)) goto fail;
i2c_stop();
return true;
fail:
i2c_stop();
return false;
}
这段伪代码展示的是总线事务,不对应某个具体芯片。实际工程优先使用 MCU 的硬件 I²C 外设和经过验证的 HAL/驱动,并补充超时、错误码、总线恢复和并发保护。
十、常见故障与排查顺序
| 现象 | 优先检查 |
|---|---|
| 地址后没有 ACK | 7 位/8 位地址是否混用;供电、共地、引脚复用和上拉是否正确 |
| SDA 一直为低 | 目标器件是否在传输中复位;先复位器件,必要时输出 9 个 SCL 脉冲后再产生 STOP |
| 上升沿缓慢 | 减小上拉阻值、缩短走线、减少器件和连接线带来的电容 |
| 100 kHz 正常,400 kHz 失败 | 上升时间、布线、电平转换器、目标器件速率上限 |
| 偶发错码或读数跳变 | 逻辑电平阈值、供电噪声、信号完整性、时钟拉伸和软件超时 |
推荐使用逻辑分析仪或示波器按以下顺序观察:先确认空闲电平,再找 START,然后检查地址与方向位,最后看第 9 个时钟是否出现 ACK。把问题定位到“电气层、地址层还是数据层”,通常比反复修改驱动代码更快。
十一、连接前的快速检查表
- SDA、SCL 是否接对,所有器件是否共地。
- 总线电压是否满足每个器件的输入阈值,跨电压域时是否使用合适的双向转换器。
- SDA、SCL 是否都有上拉,等效阻值是否合理。
- 地址是 7 位还是已经左移的 8 位表示。
- 目标器件的地址选择脚、复位脚和启动延时是否符合手册。
- 速率、时钟拉伸、重复起始是否被控制器驱动正确支持。
结语
I²C 的协议并不复杂:两根开漏信号线、START/STOP、按字节传输以及每字节后的 ACK。但真正稳定的系统,还依赖正确的电气设计和清晰的错误处理。理解“谁在拉低总线、什么时候采样、为什么没有 ACK”,就能把大多数 I²C 故障从猜测变成可测量、可定位的问题。


打卡打卡!