嵌入式芯片工程师之家 – 基础研究系列
1. 概述
I2C(Inter-Integrated Circuit)是由 Philips 公司(现 NXP)于 1982 年开发的一种串行通信总线协议。它最初设计用于连接微控制器和外围设备,现已成为嵌入式系统中最常用的通信协议之一。
1.1 主要特点
- 两线制:仅需 SDA(数据线)和 SCL(时钟线)两根信号线
- 多主多从:支持多个主设备和多个从设备在同一总线上
- 同步通信:由主设备提供时钟信号
- 地址寻址:每个从设备有唯一的地址(7 位或 10 位)
- 开漏输出:需要外部上拉电阻
- 标准速率:
- 标准模式(Standard-mode):100 kbps
- 快速模式(Fast-mode):400 kbps
- 快速模式+(Fast-mode Plus):1 Mbps
- 高速模式(High-speed mode):3.4 Mbps
2. 物理层
2.1 总线结构
VCC
|
┌┴┐
│ │ Rpull-up (典型 4.7kΩ)
└┬┘
├────────── SDA
│
┌────┴────┐
│ Master │
│ Slave │
└─────────┘
2.2 电气特性
| 参数 | 标准模式 | 快速模式 | 快速模式+ |
|---|---|---|---|
| 电源电压 | 2.5V – 5.5V | 2.5V – 5.5V | 1.65V – 3.6V |
| 上拉电阻 | 1kΩ – 10kΩ | 1kΩ – 4.7kΩ | 500Ω – 1kΩ |
| 上升时间 | ≤ 1000ns | ≤ 300ns | ≤ 120ns |
| 下降时间 | ≤ 300ns | ≤ 300ns | ≤ 120ns |
2.3 开漏输出原理
I2C 总线采用开漏(Open-Drain)或开集(Open-Collector)输出结构:
- 低电平:MOSFET 导通,将总线拉低到 GND
- 高电平:MOSFET 截止,由上拉电阻将总线拉高到 VCC
- 优点:
- 支持多设备共享总线(线与逻辑)
- 支持不同电压等级的设备(通过上拉到不同电压)
- 简化总线仲裁
3. 协议时序
3.1 基本信号定义
SDA: ────┐ ┌───────┐ ┌────
│ │ │ │
└───┘ └───┘
↑ ↑ ↑ ↑
S P Data S P
tart top tart top
SCL: ─────┐ ┌─────┐ ┌─────
│ │ │ │
└─┘ └─┘
3.2 起始条件(START)
- SCL 为高电平时,SDA 从高电平跳变到低电平
- 标志一次传输的开始
- 只有主设备可以产生起始条件
3.3 停止条件(STOP)
- SCL 为高电平时,SDA 从低电平跳变到高电平
- 标志一次传输的结束
- 释放总线
3.4 重复起始条件(Repeated START)
- 在不产生 STOP 的情况下再次产生 START
- 用于连续访问同一从设备或切换读写方向
- 保持对总线的控制
3.5 数据有效性
┌─────────────┐
SDA: │ 数据位 │
│ 稳定有效 │
└─────────────┘
↑ ↑
SCL 上升 SCL 下降
采样点 变化允许
- 数据在 SCL 高电平期间必须保持稳定
- 数据变化只能在 SCL 低电平期间进行
- 在 SCL 上升沿采样数据
4. 数据帧格式
4.1 字节格式
MSB LSB
┌───┬───┬───┬───┬───┬───┬───┬───┬───┐
│D7 │D6 │D5 │D4 │D3 │D2 │D1 │D0 │ACK│
└───┴───┴───┴───┴───┴───┴───┴───┴───┘
│←─────── 8 位数据 ───────→│← ACK →│
- 每个字节 8 位,MSB 先发
- 每个字节后跟随 1 位应答(ACK/NACK)
4.2 应答机制
| 类型 | 产生者 | SDA 电平 | 含义 |
|---|---|---|---|
| ACK | 接收方 | 低电平 | 成功接收 |
| NACK | 接收方 | 高电平 | 未成功接收/结束 |
- 发送方在第 9 个时钟周期释放 SDA
- 接收方拉低 SDA 表示 ACK
- 接收方保持 SDA 高电平表示 NACK
4.3 完整传输序列
START → [地址 + R/W] → ACK → [数据] → ACK → ... → STOP
5. 寻址方式
5.1 7 位地址
┌───┬───┬───┬───┬───┬───┬───┬───┐
│A6 │A5 │A4 │A3 │A2 │A1 │A0 │R/W│
└───┴───┴───┴───┴───┴───┴───┴───┘
│←──── 7 位地址 ────→│← 方向 →│
- 地址范围:0x00 – 0x7F(0x00-0x07 和 0x78-0x7F 保留)
- 可用地址:112 个(0x08-0x77)
- R/W 位:0=写,1=读
5.2 10 位地址
第一次传输:
┌───┬───┬───┬───┬───┬───┬───┬───┐
│ 1 │ 1 │ 1 │ 1 │ 0 │A9 │A8 │R/W│
└───┴───┴───┴───┴───┴───┴───┴───┘
固定前缀 │← 高 2 位 →│
第二次传输:
┌───┬───┬───┬───┬───┬───┬───┬───┐
│A7 │A6 │A5 │A4 │A3 │A2 │A1 │A0 │
└───┴───┴───┴───┴───┴───┴───┴───┘
│←─────── 低 8 位 ───────→│
- 地址范围:0x000 – 0x3FF
- 需要两次传输完成寻址
- 兼容性较差,使用较少
5.3 保留地址
| 地址 | 用途 |
|---|---|
| 0x00 | 通用呼叫地址 |
| 0x01-0x03 | 保留(10 位地址) |
| 0x78-0x7B | 保留(10 位地址) |
| 0x7C-0x7F | 保留 |
6. 传输模式
6.1 主设备写从设备
START → [地址 + 0] → ACK → [寄存器地址] → ACK → [数据] → ACK → STOP
6.2 主设备读从设备(单字节)
START → [地址 + 0] → ACK → [寄存器地址] → ACK →
RESTART → [地址 + 1] → ACK → [数据] → NACK → STOP
6.3 主设备读从设备(多字节)
START → [地址 + 0] → ACK → [寄存器地址] → ACK →
RESTART → [地址 + 1] → ACK → [数据 1] → ACK → [数据 2] → ACK → ... → [数据 N] → NACK → STOP
6.4 组合传输
START → [地址 + 0] → ACK → [命令] → ACK →
RESTART → [地址 + 1] → ACK → [数据] → NACK → STOP
7. 时钟延展(Clock Stretching)
从设备可以通过拉低 SCL 来延缓通信:
SCL: ────┐ ┌───────┐ ┌────
│ │ │ │
└───┘ └───┘
↑
从设备拉低
(处理数据中)
- 从设备需要更多时间处理数据时使用
- 主设备必须等待 SCL 释放
- 某些主设备不支持时钟延展
8. 总线仲裁
8.1 仲裁原理
当多个主设备同时尝试控制总线时:
- 每个主设备在发送数据时同时监测 SDA
- 如果发送高电平但检测到低电平,说明有其他主设备在发送
- 该主设备立即停止发送,释放总线
- 发送低电平的主设备赢得仲裁
8.2 仲裁时序
Master A: 发送 1 → 检测到 0 → 失去仲裁 → 释放总线
Master B: 发送 0 → 检测到 0 → 继续发送 → 赢得仲裁
- 仲裁只发生在地址和数据传输阶段
- 仲裁失败的主设备可以在总线空闲时重试
9. 常见问题与调试
9.1 硬件问题
| 问题 | 可能原因 | 解决方案 |
|---|---|---|
| 无 ACK | 从设备未连接/地址错误 | 检查接线和地址 |
| 通信不稳定 | 上拉电阻过大/过小 | 调整上拉电阻值 |
| 波形畸变 | 总线电容过大 | 减少总线长度/设备数量 |
| 电平不匹配 | 电压等级不一致 | 使用电平转换器 |
9.2 软件问题
| 问题 | 可能原因 | 解决方案 |
|---|---|---|
| 超时 | 从设备无响应 | 添加超时处理 |
| 数据错误 | 时序不匹配 | 调整 I2C 时钟频率 |
| 死锁 | 总线被占用 | 实现总线恢复机制 |
9.3 调试技巧
- 使用逻辑分析仪:捕获完整时序
- 检查上拉电阻:确保阻值合适
- 验证地址:确认从设备地址正确
- 测量波形:检查上升/下降时间
- 简化测试:先单设备通信,再增加设备
10. 代码示例(STM32 HAL)
10.1 初始化
// I2C 初始化配置
I2C_HandleTypeDef hi2c1;
hi2c1.Instance = I2C1;
hi2c1.Init.Timing = 0x00702681; // 根据时钟配置
hi2c1.Init.OwnAddress1 = 0x00;
hi2c1.Init.AddressingMode = I2C_ADDRESSINGMODE_7BIT;
hi2c1.Init.DualAddressMode = I2C_DUALADDRESS_DISABLE;
hi2c1.Init.GeneralCallMode = I2C_GENERALCALL_DISABLE;
hi2c1.Init.NoStretchMode = I2C_NOSTRETCH_DISABLE;
HAL_I2C_Init(&hi2c1);
10.2 写入寄存器
uint8_t dev_addr = 0x50; // 设备地址
uint8_t reg_addr = 0x00; // 寄存器地址
uint8_t data = 0xAA; // 写入数据
HAL_I2C_Mem_Write(&hi2c1, dev_addr, reg_addr,
I2C_MEMADD_SIZE_8BIT, &data, 1, 100);
10.3 读取寄存器
uint8_t dev_addr = 0x50;
uint8_t reg_addr = 0x00;
uint8_t data;
HAL_I2C_Mem_Read(&hi2c1, dev_addr, reg_addr,
I2C_MEMADD_SIZE_8BIT, &data, 1, 100);
11. 参考资料
- NXP I2C-Bus Specification and User Manual
- Texas Instruments I2C Application Report
- STMicroelectronics I2C Application Note
文档版本:1.0 创建日期:2026-04-07 作者:狗剩 🌱
欢迎关注公众号,第一时间收到更新
本文首发于 monykate.com · 全文与代码格式以博客为准