I2C 协议基础
做嵌入式这些年,跟 I2C 打交道的次数多到数不清。从传感器读温度、写 EEPROM 配置、跟 ADC 通信,几乎每个板子都离不开它。想把 DMA 和 I2C 搭起来做高速采集,第一步就得把 I2C 的那些基础细节摸透。说白了,I2C 就两条线——一条时钟线 SCL,一条数据线 SDA,所有通信全靠这两根线折腾。
总线结构——两条线搞定一切
I2C 总线只有两根线:串行时钟线 SCL 和串行数据线 SDA。每个设备都挂在上面,主设备产生时钟,从设备响应数据。为什么能做到只用两根线?因为 I2C 是 开漏输出 + 上拉电阻 的结构。
- SDA:数据线,双向传输地址和数据。
- SCL:时钟线,由主设备驱动,决定传输速率。
- 上拉电阻:一般 4.7kΩ 或 10kΩ,保证总线空闲时被拉高到 VCC。
- 总线电容:线长、设备多会导致电容增大,影响上升沿时间。
我记得有个项目,板子上的 I2C 总线挂了 8 个从设备,线长接近 30cm。一开始上拉电阻用了 10kΩ,结果波形惨不忍睹,SCL 上升沿都快成三角波了。后来换成 2.2kΩ,才把通信稳定下来。嗯,这里要注意:上拉电阻不是随便选的,总线电容越大,电阻就得越小,否则信号爬不上去。
| 总线电容(pF) | 推荐电阻(kΩ) | 典型场景 |
|---|---|---|
| 50 – 100 | 4.7 – 10 | 短距离、少设备 |
| 100 – 200 | 2.2 – 4.7 | 中等长度、4-8 个设备 |
| 200 – 400 | 1.0 – 2.2 | 长线缆、多设备或高电容负载 |
主从模式——谁说了算
I2C 通信里,主设备(Master)负责产生时钟、发起通信、控制传输方向。从设备(Slave)只能被动响应,不能主动发起。一个总线上可以有多个主设备,但同一时刻只能有一个主设备控制总线。多主冲突时,靠仲裁机制决定谁赢。
我个人习惯把主设备想象成“项目经理”,从设备是“干活的人”。项目经理安排任务(发起起始条件)、说清楚找谁(发送从机地址)、告诉对方要读还是写(R/W 位),然后等着对方回答(应答位)。你觉得麻烦?其实习惯就好了,尤其是用 DMA 接管后,你连这些时序都不用操心。
我曾经踩过一个坑:某次用两个主设备共享一条 I2C 总线,一个主设备写 EEPROM,另一个主设备读温度传感器。逻辑上没问题,但时序没处理好多主仲裁,导致数据错乱。最后加了一个软件互斥锁,才解决冲突。所以,除非你有足够的经验,否则新手阶段建议让一个主设备独占总线。
起始与停止条件——通信的开端和结束
I2C 的数据传输以 起始条件 开始,以 停止条件 结束。这两个条件由主设备产生,所有从设备都能感知到。
- 起始条件 (S):SCL 高电平时,SDA 从高切换到低。
- 停止条件 (P):SCL 高电平时,SDA 从低切换到高。
- 总线空闲时,SCL 和 SDA 都是高电平。
你想想看,为什么起始条件要这样设计?因为 SCL 高电平期间 SDA 的变化被定义为“控制信号”,而不是数据。这样从设备就能区分是“开始通信”还是“传输数据”。
数据帧格式——地址、读写、应答、数据一气呵成
一个完整的 I2C 数据帧结构大致长这样:
- 起始条件
- 7 位从机地址(或 10 位地址模式)
- 读/写位(1 = 读,0 = 写)
- 从设备应答位(ACK / NACK)
- 8 位数据字节 + 应答位(重复 1~N 次)
- 停止条件
做数据采集时最常用的是 7 位地址模式,因为大多数传感器和 ADC 芯片都支持。记住:地址左移 1 位,最后 1 位是 R/W。比如地址 0x48,写操作时发送 0x90,读操作时发送 0x91。
核心要点:数据帧中,每个字节后的应答位是从设备给的。如果从设备忙或者没收到,就会拉低 SDA(ACK)或不拉低(NACK)。主机必须等待这个应答位,否则会丢数据。——我在搞 DMA 时,这部分最容易出问题,因为 DMA 不会自动重试,NACK 需要软件处理。
为什么 DMA 要和 I2C 配合?先看一个典型时序
下面这张图展示了 I2C 一次完整的数据传输流程。记住这个流程,后面讲 DMA 接管时你就知道哪些步骤可以被硬件直接完成。
小技巧:调试 I2C 时,用逻辑分析仪抓 SCL 和 SDA 波形,比看代码容易定位问题。我之前有个间歇性通信失败,看了半天代码没找到原因,一抓波形发现从设备有时会漏掉地址,原来是上拉电阻偏弱导致上升沿太慢。
关键参数:I2C 速率模式
I2C 有几种标准速率模式,做数据采集时最常用的是标准模式(100kHz)和快速模式(400kHz)。高速模式(3.4MHz)对硬件要求高,一般板级走线需要特别注意。DMA 配合 I2C 做高速采集,通常跑快速模式或者快速模式+(1MHz)。
| 模式 | 最大速率 | 典型应用 | 注意事项 |
|---|---|---|---|
| 标准模式 | 100 kHz | EEPROM、温度传感器 | 兼容性好,几乎所有 MCU 都支持 |
| 快速模式 | 400 kHz | 加速度计、ADC、部分显示设备 | 需注意上升时间 ≤ 300ns |
| 快速模式+ | 1 MHz | 高速 ADC、图像传感器 | 上拉电阻需更小(1kΩ~2kΩ) |
注意:I2C 的速率不是想跑多快就多快。总线电容、上拉电阻、从设备自身限制都会影响实际工作速率。我曾经在快速模式+下遇到从设备偶尔不响应,查完数据手册发现该芯片只支持到 400kHz,白忙活一场。所以开始前先确认所有设备的规格上限。
避坑指南:一个容易被忽略的细节——时钟拉伸
I2C 协议允许从设备在应答时将 SCL 拉低,迫使主设备等待,这叫时钟拉伸。有些从设备处理数据需要时间,比如 EEPROM 内部写操作,就会用时钟拉伸来告诉主机“等一下”。如果你的 DMA 控制器没有处理时钟拉伸的逻辑,就可能提前发出下一个字节,导致数据错位。
我之前做一个高速数据采集系统,用的 DMA + I2C 读 24 位 ADC。ADC 每次转换完成后需要读取结果,但它会拉低 SCL 约 10μs 来准备数据。我的 MCU 的 I2C 外设支持时钟拉伸,但 DMA 配置时忘了使能“NACK 自动重试”功能,结果丢了好几个采样点。后来在 DMA 中断里加了超时重试的代码才解决。所以,选 MCU 时建议优先选 I2C 外设硬件支持时钟拉伸的型号,省掉很多麻烦。
小结
I2C 看似简单,但真正做高速采集时,细节决定成败。总结一下本章的核心:
- 总线结构:两条开漏线 + 上拉电阻,多设备共享。
- 主从模式:主设备主导,从设备响应,多主需要仲裁。
- 起始/停止条件:SCL 高电平时 SDA 的下降沿/上升沿。
- 数据帧格式:起始 + 地址 + R/W + ACK + 数据 + ACK + 停止。
- 时钟拉伸:从设备主动拉低 SCL 要求等待,DMA 设计时必须考虑。
把这些基础吃透了,后面讲 DMA 配 I2C 的高速采集实战时,你才能一眼看出哪些步骤可以用 DMA 自动完成,哪些还得靠软件兜底。