I2C 协议研究

嵌入式芯片工程师之家 – 基础研究系列


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 仲裁原理

当多个主设备同时尝试控制总线时:

  1. 每个主设备在发送数据时同时监测 SDA
  2. 如果发送高电平但检测到低电平,说明有其他主设备在发送
  3. 该主设备立即停止发送,释放总线
  4. 发送低电平的主设备赢得仲裁

8.2 仲裁时序

Master A: 发送 1 → 检测到 0 → 失去仲裁 → 释放总线
Master B: 发送 0 → 检测到 0 → 继续发送 → 赢得仲裁
  • 仲裁只发生在地址和数据传输阶段
  • 仲裁失败的主设备可以在总线空闲时重试

9. 常见问题与调试

9.1 硬件问题

问题 可能原因 解决方案
无 ACK 从设备未连接/地址错误 检查接线和地址
通信不稳定 上拉电阻过大/过小 调整上拉电阻值
波形畸变 总线电容过大 减少总线长度/设备数量
电平不匹配 电压等级不一致 使用电平转换器

9.2 软件问题

问题 可能原因 解决方案
超时 从设备无响应 添加超时处理
数据错误 时序不匹配 调整 I2C 时钟频率
死锁 总线被占用 实现总线恢复机制

9.3 调试技巧

  1. 使用逻辑分析仪:捕获完整时序
  2. 检查上拉电阻:确保阻值合适
  3. 验证地址:确认从设备地址正确
  4. 测量波形:检查上升/下降时间
  5. 简化测试:先单设备通信,再增加设备

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. 参考资料


文档版本:1.0 创建日期:2026-04-07 作者:狗剩 🌱

欢迎关注公众号,第一时间收到更新

本文首发于 monykate.com · 全文与代码格式以博客为准

通讯更新

请输入您的电子邮件地址进行订阅

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注