一、I2C基础原理
I2C(Inter-Integrated Circuit)是同步、半双工、串行通信协议,由Philips(现NXP)于1982年提出。核心特点:
- 仅需两根线:SDA(数据线)、SCL(时钟线)
- 多设备支持:支持多达128个设备(7位地址)
- 主从架构:1个主设备控制多个从设备(如ESP32为主,传感器为从)
- 速度分级 :
- 标准模式:100 kbps
- 快速模式:400 kbps
- 高速模式:3.4 Mbps(需特殊硬件)
💡 关键区别 :I2C是开漏输出(需上拉电阻),与SPI(推挽输出)不同。
二、物理层与电气特性
1. 硬件连接要求
| 信号线 | 说明 | 必须配置 |
|---|---|---|
| SDA | 数据线(双向) | 4.7kΩ上拉电阻(接VCC) |
| SCL | 时钟线(主设备输出) | 4.7kΩ上拉电阻(接VCC) |
| GND | 公共地 | 必须连接 |
⚠️ 致命错误:未加4.7kΩ上拉电阻 → 总线无法正常工作(SDA/SCL悬空)。
2. 信号时序关键点
| 事件 | 电平变化 | 说明 |
|---|---|---|
| 起始条件 | SCL高时,SDA从高→低 | 标志通信开始 |
| 停止条件 | SCL高时,SDA从低→高 | 标志通信结束 |
| 数据有效 | SCL高时,SDA稳定 | 读写数据必须在SCL高电平时有效 |
| ACK/NACK | SCL高时,SDA低=ACK | 从设备响应(0=ACK,1=NACK) |
📌 时序示例(SDA/SCL波形):
text
编辑
SCL: _‾_‾_‾_‾_‾_‾_‾ SDA: _‾ _‾ _‾ _‾ (起始后数据)
三、地址与数据传输格式
1. 设备地址(7位)
- 7位地址:0x00 ~ 0x7F(128个地址)
- 实际传输8位:7位地址 + 1位R/W位(0=写,1=读)
- 示例:设备地址0x50(1010000),写操作=0xA0(10100000)
2. 数据传输流程
- 主设备发送起始条件
- 主设备发送7位地址 + R/W位(如0xA0=写)
- 从设备发送ACK
- 主设备发送数据字节(可多字节)
- 从设备每字节发送ACK
- 主设备发送停止条件
💡 实际案例:读取MPU6050加速度计:
- 地址:0x68(写操作=0xD0)
- 寄存器地址:0x3B(X轴高8位)
- 读取数据:0x3B → 0x3C(连续读取)
四、ESP32实现示例(esp-idf)
1. 初始化I2C(主设备模式)
cpp
#include "driver/i2c.h"
#define I2C_MASTER_SCL_IO 22 // SCL引脚
#define I2C_MASTER_SDA_IO 21 // SDA引脚
#define I2C_MASTER_NUM I2C_NUM_0
#define I2C_MASTER_FREQ_HZ 100000 // 100 kbps
void i2c_master_init() {
i2c_config_t conf = {
.mode = I2C_MODE_MASTER,
.sda_io_num = I2C_MASTER_SDA_IO,
.scl_io_num = I2C_MASTER_SCL_IO,
.sda_pullup_en = GPIO_PULLUP_ENABLE,
.scl_pullup_en = GPIO_PULLUP_ENABLE,
.master.clk_speed = I2C_MASTER_FREQ_HZ,
};
i2c_param_config(I2C_MASTER_NUM, &conf);
i2c_driver_install(I2C_MASTER_NUM, conf.mode, 0, 0, 0);
}
2. 读取设备数据(通用函数)
cpp
uint8_t i2c_read_reg(uint8_t dev_addr, uint8_t reg_addr) {
i2c_cmd_handle_t cmd = i2c_cmd_link_create();
i2c_master_start(cmd);
i2c_master_write_byte(cmd, (dev_addr << 1) | I2C_MASTER_WRITE, true);
i2c_master_write_byte(cmd, reg_addr, true);
i2c_master_start(cmd);
i2c_master_write_byte(cmd, (dev_addr << 1) | I2C_MASTER_READ, true);
uint8_t data;
i2c_master_read_byte(cmd, &data, I2C_MASTER_NACK);
i2c_master_stop(cmd);
i2c_cmd_link_delete(cmd);
return data;
}
3. 设备扫描(验证总线)
cpp
void i2c_scan() {
printf("Scanning I2C bus...\n");
for (int addr = 0; addr < 128; addr++) {
i2c_cmd_handle_t cmd = i2c_cmd_link_create();
i2c_master_start(cmd);
i2c_master_write_byte(cmd, (addr << 1) | I2C_MASTER_WRITE, true);
i2c_master_stop(cmd);
esp_err_t ret = i2c_cmd_link_exec(cmd, 100);
if (ret == ESP_OK) {
printf("Found device at 0x%02X\n", addr);
}
i2c_cmd_link_delete(cmd);
}
}
五、常见问题与解决方案
| 问题 | 原因 | 解决方案 |
|---|---|---|
| 总线挂起(无响应) | SDA/SCL被拉低(如设备故障) | 1. 检查上拉电阻 2. 用万用表测SDA/SCL电平 |
| 地址错误(NACK) | 设备地址错误或未连接 | 1. 用i2c_scan确认设备地址 2. 检查硬件连接 |
| 数据错误 | 时序超时或速度过快 | 1. 降低I2C速度(如100 kbps) 2. 确保SCL/SDA线长<30cm |
| 多主设备冲突 | 两个主设备同时发送 | 1. 仅保留1个主设备 2. 添加仲裁逻辑(复杂) |
| 高功耗设备干扰 | 设备电流过大 | 1. 用5V设备时加电平转换器 2. 降低上拉电阻(如2.2kΩ) |
💡 实测经验:ESP32连接OLED屏(地址0x3C)时,因未加4.7kΩ上拉电阻导致通信失败,加电阻后立即解决。
六、最佳实践建议
- 上拉电阻 :必须使用4.7kΩ(总线长度>10cm时用2.2kΩ)
- 速度选择 :
- 传感器:100 kbps(标准模式)
- 高速设备:400 kbps(快速模式)
- 硬件设计 :
- SDA/SCL线等长(减少时序偏差)
- 远离高频信号线(如Wi-Fi天线)
- 调试技巧 :
- 用示波器观察SDA/SCL波形
- 用
i2c_scan确认设备存在 - 逐步增加传输字节数(避免大包失败)
七、I2C vs 其他总线对比
| 特性 | I2C | SPI | UART |
|---|---|---|---|
| 信号线数 | 2 | 4 | 2 |
| 速度 | 100-3.4 kbps | 100+ Mbps | 115200 bps |
| 地址 | 7位(多设备) | 1位(片选) | 无地址 |
| 通信 | 半双工 | 全双工 | 全双工 |
| 适用场景 | 传感器、EEPROM | 显示屏、Flash | 串口通信 |
✅ 选择建议:
- 传感器/小设备 → I2C(省引脚)
- 高速存储 → SPI(速度快)
- 串口调试 → UART
八、实战案例:ESP32读取BME280传感器
cpp
// 读取温度(地址0x76)
uint8_t temp_data[3];
i2c_read_reg(0x76, 0xF7, temp_data, 3); // 读取3字节
// 转换为实际温度值
int32_t raw_temp = (temp_data[0] << 16) | (temp_data[1] << 8) | temp_data[2];
float temperature = raw_temp / 5120.0; // BME280公式
printf("Temp: %.2f°C\n", temperature);
💬 结果:成功读取25.3°C(环境温度),验证I2C通信正常。
一句话总结
I2C = 2根线+4.7kΩ上拉电阻+100 kbps速度+7位地址,是嵌入式设备连接传感器的黄金标准。
避开上拉电阻和地址错误,90%的I2C问题迎刃而解!