1. I2C总线基础概念
I2C(Inter-Integrated Circuit)是一种由飞利浦公司(现恩智浦半导体)在1980年代开发的同步、多主/多从、单端、串行通信总线。这个双线制总线协议已经成为嵌入式系统和电子设备中最常用的通信标准之一。
I2C总线仅需两条信号线:
- SCL(Serial Clock):时钟线,由主设备控制
- SDA(Serial Data):双向数据线
这种简洁的设计使得I2C特别适合板级设备间的短距离通信(通常不超过1米)。总线采用开漏输出结构,需要外接上拉电阻(典型值4.7kΩ),这种设计实现了"线与"逻辑,允许多个设备共享总线而不产生信号冲突。
实际工程中选择上拉电阻值时需要考虑总线电容和通信速度的平衡。过大的电阻会导致上升沿过缓,限制通信速率;过小的电阻则会增加功耗。对于标准模式(100kHz),总线电容一般不超过400pF。
2. I2C协议详解
2.1 基本通信时序
I2C通信遵循严格的时序规范,每个传输由以下几个基本元素构成:
- 起始条件(START):SCL为高时,SDA从高变低
- 地址帧:7位或10位从机地址 + 1位读写方向(0写,1读)
- 数据帧:8位数据 + 1位应答(ACK/NACK)
- 停止条件(STOP):SCL为高时,SDA从低变高
一个典型的I2C写操作时序如下:
[S][Addr+W][ACK][Data1][ACK]...[DataN][ACK][P]而读操作通常采用复合格式:
[S][Addr+W][ACK][Command][ACK][Sr][Addr+R][ACK][Data1][ACK]...[DataN][NACK][P]2.2 地址分配机制
I2C采用7位或10位地址方案:
7位地址空间:
- 理论上有112个可用地址(0x08-0x77)
- 实际应用中,许多地址已被标准设备占用(如EEPROM常用0x50-0x57)
- 部分地址范围有特殊用途:
- 0x00-0x07:系统用途(广播呼叫、起始字节等)
- 0x78-0x7F:10位地址保留
10位地址扩展:
- 通过特殊地址前缀(0b11110xx)标识
- 实际地址由后续字节组合形成,可支持多达1024个地址
- 实际应用中较少见,多数MCU外设仍使用7位地址
实际工程中,当遇到地址冲突时,可以考虑:
- 选择支持地址引脚配置的器件
- 使用I2C多路复用器(如PCA9548A)
- 将总线分段,不同段使用不同地址的设备
3. I2C工作模式与速度等级
现代I2C支持多种速度模式,满足不同应用场景需求:
| 模式 | 速率 | 特性说明 |
|---|---|---|
| 标准模式 | 100kHz | 最基础模式,所有设备必须支持 |
| 快速模式 | 400kHz | 时序要求更严格,广泛兼容 |
| 快速模式Plus | 1MHz | 需要更强的驱动能力 |
| 高速模式 | 3.4MHz | 需要特殊的主机支持 |
| 超快模式 | 5MHz | 单向通信,推挽输出 |
时钟拉伸(Clock Stretching): 这是I2C的一个重要特性,允许从设备在需要更多处理时间时保持SCL为低电平。主设备必须检测并等待SCL被释放后才能继续传输。常见于:
- 从设备MCU需要中断处理
- EEPROM完成内部写入周期
- ADC完成转换过程
4. I2C硬件实现要点
4.1 电气特性
I2C总线采用开漏设计,具有以下关键参数:
| 参数 | 标准模式 | 快速模式 | 单位 |
|---|---|---|---|
| VIL(max) | 0.3VDD | 0.3VDD | V |
| VIH(min) | 0.7VDD | 0.7VDD | V |
| 上升时间(最大) | 1000 | 300 | ns |
| 下降时间(最大) | 300 | 300 | ns |
实际设计建议:
- 对于长走线或高容性负载,考虑使用电流源替代上拉电阻
- 在噪声环境中,可串联22-100Ω电阻抑制振铃
- 高速模式下建议使用专用I2C缓冲器(如PCA9515)
4.2 多主设备仲裁
I2C支持多主设备通过仲裁机制共享总线:
- 所有主设备同时发送起始位
- 每个主设备在发送每位后检查SDA状态
- 当检测到实际电平与自身发送不符时,该主设备退出
- 最终只有一个主设备获得总线控制权
仲裁完全基于硬件实现,不会丢失数据。典型应用场景包括:
- 多个MCU共享传感器数据
- 热插拔控制器切换
- 冗余系统设计
5. 常见I2C设备与应用
5.1 典型I2C设备类型
| 设备类别 | 常见型号 | 典型地址范围 |
|---|---|---|
| EEPROM | 24LCxx, AT24Cxx | 0x50-0x57 |
| 温度传感器 | LM75, TMP102 | 0x48-0x4F |
| IO扩展器 | MCP23008, PCF8574 | 0x20-0x27 |
| 实时时钟 | DS1307, PCF8563 | 0x68, 0x51 |
| ADC/DAC | ADS1115, MCP4725 | 0x48-0x4B |
5.2 实际应用示例:读取TMP102温度传感器
以下是典型代码框架(基于Arduino Wire库):
#include <Wire.h> #define TMP102_ADDR 0x48 void setup() { Wire.begin(); Serial.begin(9600); } void loop() { // 启动温度转换 Wire.beginTransmission(TMP102_ADDR); Wire.write(0x00); // 指向温度寄存器 Wire.endTransmission(); // 读取2字节温度数据 Wire.requestFrom(TMP102_ADDR, 2); if(Wire.available() >= 2) { int16_t temp = (Wire.read() << 8) | Wire.read(); float celsius = temp >> 4; // 12位精度,LSB=0.0625°C celsius *= 0.0625; Serial.print("Temperature: "); Serial.print(celsius); Serial.println(" °C"); } delay(1000); }6. I2C调试与故障排除
6.1 常见问题及解决方案
总线锁死:
- 现象:SCL或SDA线被拉低无法恢复
- 原因:设备崩溃或程序错误
- 解决:发送9个时钟脉冲尝试恢复,或硬件复位
无应答(NACK):
- 检查设备地址是否正确(包括R/W位)
- 确认设备电源和上拉电阻正常
- 用示波器观察总线波形
数据错误:
- 检查时序是否符合规格
- 降低总线速度测试
- 检查总线电容是否过大
6.2 实用调试工具
逻辑分析仪:
- 推荐Saleae Logic或DSView
- 设置采样率至少4倍于总线频率
- 使用专用I2C协议解码插件
I2C扫描工具:
// Arduino I2C扫描程序 void scanI2C() { for(uint8_t addr=1; addr<127; addr++) { Wire.beginTransmission(addr); if(Wire.endTransmission()==0) { Serial.print("Found device at 0x"); Serial.println(addr,HEX); } } }示波器测量:
- 检查信号上升时间
- 观察起始/停止条件是否干净
- 确认ACK/NACK时序正确
7. I2C进阶主题
7.1 总线扩展技术
当系统需要连接大量I2C设备时,可采用以下方案:
多路复用器:
- PCA9548A:8通道I2C开关
- TCA9548A:带中断支持的升级版
- 典型应用:
// 选择通道2 Wire.beginTransmission(0x70); Wire.write(1<<2); Wire.endTransmission();
总线缓冲器:
- PCA9515:支持热插拔
- P82B715:长距离驱动
双总线架构:
- 将高速和低速设备分开
- 使用不同电压等级的总线
7.2 与SPI的对比
| 特性 | I2C | SPI |
|---|---|---|
| 线路数量 | 2(SCL+SDA) | 4+(SCLK+MOSI+MISO+SS) |
| 速度 | 标准100kHz-5MHz | 通常1-50MHz |
| 寻址方式 | 软件寻址 | 硬件片选 |
| 复杂度 | 协议较复杂 | 硬件接口简单 |
| 功耗 | 静态功耗低 | 动态功耗低 |
| 适用场景 | 中低速多设备 | 高速点对点 |
7.3 I2C子系统(Linux)
在Linux系统中,I2C以子系统形式实现:
# 查看I2C适配器 $ i2cdetect -l # 扫描总线1上的设备 $ i2cdetect -y 1 # 读取设备寄存器 $ i2cget -y 1 0x48 0x00 w # 写入配置 $ i2cset -y 1 0x48 0x01 0x60驱动开发通常通过实现i2c_driver结构体,注册设备树节点完成。现代内核推荐使用设备树描述I2C设备:
&i2c1 { status = "okay"; clock-frequency = <400000>; temp_sensor: tmp102@48 { compatible = "ti,tmp102"; reg = <0x48>; }; };