I2C是同步、半双工、多主多从的两线总线。SDA传输数据,SCL提供时钟。两条线均为开漏结构,需外接上拉电阻。示例平台为ESP32-C3,器件为BH1750、AHT20/DHT20和SSD1306。
1. 总线结构
主机发起通信并产生时钟,从机响应地址。所有设备共享SDA和SCL,不需要独立片选。SCL由主机驱动,从机可通过拉低SCL延长时钟低电平。SDA方向随读写切换,因此两条线都必须采用开漏结构。
| 项目 | I2C | SPI | UART |
|---|---|---|---|
| 线数 | 2根,SDA与SCL | 4根起,SCK、MOSI、MISO、CS | 2根,TX与RX |
| 拓扑 | 多主多从,共用总线 | 多从,片选独立 | 点对点 |
| 双工 | 半双工 | 全双工 | 全双工 |
| 速率 | 100kbit/s到3.4Mbit/s | 几Mbit/s到几十Mbit/s | 常用115.2kbit/s到几Mbit/s |
| 时钟 | 有,主机产生 | 有,主机产生 | 无,靠波特率约定 |
| 多设备成本 | 地址寻址,线少 | 每个从机一根CS | 每端一个对端 |
| 典型距离 | 板内到1m级 | 板内 | 板间可以更长 |
SDA和SCL是单端信号,以GND为参考,必须共地。两条线不是电源线,模块的VCC和GND需单独连接。不同模块的排针顺序可能不同,接线前核对丝印。
2. 电气层
2.1 开漏输出与线与
推挽输出高电平时将引脚接VCC,低电平时接GND。总线上若一个设备输出高、另一个输出低,VCC与GND之间形成低阻通路,电流仅受两个输出级导通电阻限制,可能损坏引脚。
I2C采用开漏输出:内部N-MOSFET漏极接总线,源极接GND,栅极由数据控制。器件只能拉低总线,不能主动拉高。总线空闲时由上拉电阻拉高;任一器件导通,总线为低;所有器件释放,总线为高。该逻辑为线与。
| 设备A | 设备B | 总线 |
|---|---|---|
| 释放 | 释放 | 高 |
| 拉低 | 释放 | 低 |
| 释放 | 拉低 | 低 |
| 拉低 | 拉低 | 低 |
开漏结构的作用:
- 多设备共享总线时不会形成推挽冲突。
- 线与逻辑支持多主仲裁和时钟拉伸。
- 两侧使用各自的上拉,可实现电平转换。
二极管OR可实现类似逻辑,但存在压降、结电容和成本问题,I2C未采用。
2.2 上拉电阻
总线等效为RC网络。上拉电阻R对总线电容Cb充电,电压按指数上升。I2C规范以0.3VDD到0.7VDD的上升时间tr作为指标:
tr ≈ 0.8473 × R × Cb
按3.3V、Cb=200pF计算:
| 上拉电阻 | tr | 低电平电流 | 适用场景 |
|---|---|---|---|
| 1.5kΩ | 0.25µs | 1.9mA | 400kHz,短总线,电容小 |
| 2.2kΩ | 0.37µs | 1.3mA | 400kHz边缘,线短且器件少 |
| 4.7kΩ | 0.80µs | 0.62mA | 100kHz |
| 10kΩ | 1.69µs | 0.29mA | 低电容低速总线 |
规范上限:100kHz为1000ns,400kHz为300ns。按200pF计算,4.7kΩ满足100kHz,不满足400kHz;2.2kΩ仍高于300ns。提高速率需同时减小R和Cb。
Rmin由灌电流决定:
Rmin = (VDD - VOLmax) / IOL
3.3V、VOLmax=0.4V、IOL=3mA时,Rmin约967Ω。1kΩ以下需核对器件手册。
多个模块自带4.7kΩ上拉并联后等效电阻下降。三个并联为1.57kΩ,低电平电流约1.9mA;五个并联为940Ω,约3.1mA,接近或超过部分器件的IOL。总线上应只保留一组上拉,位置靠近主机。
MCU内部上拉通常为30kΩ到50kΩ。按45kΩ、200pF计算,tr约7.6µs,超过100kHz的1µs上限。内部上拉可用于引脚状态检测,不能作为I2C总线的主要上拉。
5V模块需确认上拉接至哪一电压。若模块上拉接5V,而主机为3.3V,总线空闲电平为5V,主机GPIO过压。处理方式:模块改3.3V供电;拆除模块上拉并在3.3V侧加4.7kΩ;或使用双向电平转换。
2.3 总线电容与线长
总线电容包括引脚、PCB走线、线缆、面包板和器件输入电容。标准模式和快速模式上限为400pF,快速+为550pF,高速模式通常按100pF级设计。
线长没有规范上限,受限的是电容和干扰。100kHz在质量良好的双绞线上可延伸数米;400kHz通常控制在1m以内。更长距离应降速、加I2C缓冲器(如PCA9517)、使用双绞线并增加地线。SDA与SCL上串联22Ω到100Ω可抑制振铃,但会增加上升时间。
避免带电插拔。上电顺序为GND、VCC、SDA/SCL;断电顺序相反。
3. 协议层
3.1 START、STOP与Sr
总线空闲时SDA和SCL均为高。SCL为高期间,SDA由高变低为START;SDA由低变高为STOP。START和STOP由主机产生。
数据有效性:SCL为高期间SDA必须稳定;SDA只在SCL为低期间改变。START、Sr、STOP是例外。
Sr用于在一次通信中切换方向而不释放总线。作用:
- 防止其他主机在切换期间获得总线。
- 保持器件内部命令或地址指针状态。
3.2 位传输与ACK
每字节8位,MSB先发。发送方在SCL低电平期间更新SDA,接收方在SCL高电平期间采样。第9个时钟为ACK:接收方拉低SDA表示ACK,释放SDA表示NACK。
写地址或写数据时,ACK由从机产生;读数据时,ACK由主机产生。主机读取最后一个字节时返回NACK,然后发STOP。NACK在此表示结束传输,不是错误。
若地址ACK但数据NACK,检查寄存器地址、器件忙状态、供电和上拉。
3.3 时钟拉伸
从机可在主机释放SCL后继续拉低SCL,主机必须等待。该机制为时钟拉伸。
硬件I2C外设通常自动处理时钟拉伸;软件模拟I2C必须在释放SCL后读回引脚电平,确认SCL为高再继续。
并非所有从机使用时钟拉伸。BH1750、AHT20通常不拉伸;SHT3x、SHTC3在测量期间可能拉伸。部分MCU的硬件I2C不支持时钟拉伸,选型时需确认。
3.4 多主仲裁
多主机同时发送时,各主机在输出每一位的同时回读SDA。若输出1(释放)而回读为0,说明另一主机拉低总线,该主机退出仲裁。
SCL同步:低电平取所有主机中最长的低电平,高电平取最短的高电平。
退出仲裁的主机停止驱动SDA和SCL,转为从机状态,可被胜方寻址。仲裁期间已发送的位不受影响。开漏是仲裁的前提;推挽输出在冲突时形成短路。
3.5 地址与读写位
标准I2C使用7位地址,地址后跟1位R/W。地址字节bit7到bit1为地址,bit0为R/W:0写,1读。0x38写为0x70,读为0x71。
保留地址:0x00通用广播,0x01 CBUS,0x02到0x07为不同总线格式,0x78到0x7F为10位地址前缀和高速模式代码。可用7位地址为0x08到0x77,共112个。
10位地址:第一字节为11110、A9、A8、R/W,第二字节为A7到A0,共1024个地址。传感器中较少使用。
地址冲突处理:地址选择脚、TCA9548A多路复用器、独立I2C控制器。
扫描到地址只说明地址阶段有ACK。器件可能处于睡眠(SHTC3需0x3517唤醒)、忙(EEPROM写周期)或命令未就绪。地址ACK不等于可读。
4. 读写流程
BH1750地址0x23,连续高分辨率模式命令0x10。读流程:
- START,发送0x46(0x23左移一位,最低位0),收ACK。
- 发送0x10,收ACK。
- 发送Sr,发送0x47(最低位1),收ACK。
- 读Data H,回ACK。
- 读Data L,回NACK。
- 发送STOP。
H分辨率模式测量时间典型120ms,最大180ms。读回16位原始值,lux=raw/1.2。
使用Sr而不是STOP加START:保持总线控制权;部分器件的命令状态在STOP后复位。
其他器件:
- AHT20:0xAC触发测量,等待80ms,读状态和数据;带CRC的驱动读7字节并校验。
- SHT3x:16位命令,如0x2400为高重复性测量、不拉伸时钟。
- SSD1306:控制字节区分命令和数据,无寄存器地址。
5. 速率与实现
| 模式 | 最高速率 | 总线电容上限 | 典型用途 |
|---|---|---|---|
| 标准模式 | 100kbit/s | 400pF | 传感器、EEPROM |
| 快速模式 | 400kbit/s | 400pF | 传感器、小屏 |
| 快速+模式 | 1Mbit/s | 550pF | 短总线高速器件 |
| 高速模式 | 3.4Mbit/s | 通常100pF级 | 板内高速 |
SMBus与I2C电气兼容,但SMBus定义35ms超时、PEC校验和更严格的电平要求。若主机启用SMBus超时,长时间时钟拉伸可能被复位。
ESP32-C3只有一个I2C控制器;经典ESP32和S3有两个。C3的GPIO11到GPIO17接SPI Flash,GPIO18和GPIO19为USB差分线,GPIO1和GPIO3为串口控制台。GPIO8为strapping脚,很多板载LED接在该脚;若配置为推挽输出,会与SDA冲突。
项目实现:
- 启动时遍历候选引脚对,包含SDA/SCL互换;对已知地址探测两次,避免浮空引脚误ACK;结果写入NVS。
- 经典ESP32和S3可用Wire和Wire1组成双总线;C3只有一个控制器,不能同时使用两条硬件总线。
- 全引脚扫描:经典ESP32共380组,约2分钟。若某组出现连续多个地址应答,通常为总线被拉低,不是多设备。
6. 故障定位
| 现象 | 常见原因 | 检查项 |
|---|---|---|
| 无地址应答 | 未供电、未共地、SDA/SCL接反、无上拉、引脚被占用 | VCC、GND、SDA/SCL空闲电平 |
| 连续多个地址应答 | 总线被拉低、模块未供电、SDA/SCL短路、ESD二极管钳位 | 断电测SDA-SCL、SDA-GND、SCL-GND |
| 地址ACK但读失败 | 上拉过弱、供电跌落、线过长、命令错误、器件忙 | 降速、换短线、核对时序 |
| 间歇性失败 | 接触不良、电源纹波、WiFi发射瞬时掉压 | 换线、示波器看VCC和SCL边沿 |
| 地址冲突 | 两个器件固定同一地址 | ADDR脚、多路复用器、双总线 |
| 总线锁死 | 从机传输中途复位,SDA持续为低 | 9时钟bus clear后发STOP |
| 5V模块异常 | 模块上拉接5V,主机不耐5V | 改3.3V供电或电平转换 |
6.1 连续地址应答
正常扫描每个设备只返回一个地址。若0x08到0x77全部应答,优先检查电气:
- 模块未供电,SDA/SCL通过ESD二极管钳位。
- SDA与SCL短路,或某根线被拉到GND。
- 某器件在复位中途持续拉低SDA。
断电测通断,上电测空闲电平。
6.2 总线锁死与9时钟恢复
从机在传输中途复位时可能持续拉低SDA,主机无法发START。恢复方法:在SDA释放的情况下,向SCL发送最多9个时钟,使从机输出剩余位,然后发送STOP。
static void driveLow(uint8_t pin) {
pinMode(pin, OUTPUT);
digitalWrite(pin, LOW);
}
static void release(uint8_t pin) {
pinMode(pin, INPUT_PULLUP);
}
void i2cBusClear(uint8_t sda, uint8_t scl) {
release(sda);
for (int i = 0; i < 9 && digitalRead(sda) == LOW; i++) {
driveLow(scl);
delayMicroseconds(5);
release(scl);
delayMicroseconds(5);
}
// STOP:SCL低时拉低SDA,SCL高时释放SDA
driveLow(scl);
delayMicroseconds(5);
driveLow(sda);
delayMicroseconds(5);
release(scl);
delayMicroseconds(5);
release(sda);
}
release()使用输入模式,避免推挽输出高电平与从机冲突。若9个时钟后SDA仍为低,检查短路、器件损坏或持续拉低。
6.3 扫描与引脚复用
扫描代码应对同一地址探测两次。浮空引脚或弱上拉可能使单次探测产生误ACK。扫描结果应写入NVS,后续启动直接使用,避免每次全量扫描。
全引脚扫描用于确认传感器是否接在候选引脚之外。项目中使用经典ESP32遍历所有可用引脚的两两组合,包含SDA/SCL互换,共380组。若目标地址只在少数组合出现,说明该组合与器件存在电气连接;若某组出现连续地址,说明该组总线被拉低。
引脚复用检查:
- GPIO8:strapping脚,板载LED常接在此脚;推挽输出会拉高SDA。
- GPIO18/19:USB差分线,配置为I2C会断开USB CDC。
- GPIO1/3:串口控制台,占用后日志静默。
- 经典ESP32的GPIO34到GPIO39为输入-only,不能作为SDA/SCL。
6.4 万用表与示波器
万用表用于静态检查:VCC电压、SDA/SCL空闲电平、共地、短路。它无法观察50kHz以上的边沿,只能读到平均电压。
示波器或逻辑分析仪用于确认START/STOP、ACK、时钟拉伸和上升时间。排查顺序:供电与共地、短路、上拉、地址扫描、波形。
7. 代码示例
示例基于ESP32-C3,SDA接GPIO8,SCL接GPIO9。若GPIO8被LED或strapping功能占用,更换引脚。
#include <Wire.h>
void scanBus() {
for (uint8_t addr = 0x08; addr <= 0x77; addr++) {
Wire.beginTransmission(addr);
uint8_t err = Wire.endTransmission();
if (err == 0) {
Serial.printf("found 0x%02X\n", addr);
}
}
}
uint16_t readBh1750() {
Wire.beginTransmission(0x23);
Wire.write(0x10); // 连续高分辨率模式
if (Wire.endTransmission() != 0) {
return 0;
}
delay(180); // 手册最大值180ms
if (Wire.requestFrom(0x23, 2) != 2) {
return 0;
}
uint16_t raw = (Wire.read() << 8) | Wire.read();
return raw; // lux = raw / 1.2
}
void setup() {
Serial.begin(115200);
Wire.begin(8, 9);
Wire.setClock(100000);
scanBus();
}
void loop() {
uint16_t raw = readBh1750();
Serial.printf("raw=%u lux=%.1f\n", raw, raw / 1.2);
delay(1000);
}
Wire.endTransmission()返回值:0为ACK,2为地址NACK,3为数据NACK,5为超时。不同核心的编号可能不同,以所用核心文档为准。
Wire.requestFrom()返回实际读取字节数。返回0通常表示地址无应答或总线被拉低。
Wire.setClock()在Wire.begin()之后调用。降低速率可用于区分时序问题与电气问题。
8. 检查清单
- 断电测VCC-GND、SDA-SCL、SDA-GND、SCL-GND是否短路。
- 上电测模块VCC,确认在3.3V附近。
- 测SDA和SCL空闲电平,确认接近VCC。
- 确认主机与模块共地。
- 确认SDA/SCL未接反。
- 确认总线上有一组上拉,阻值按速率和电容计算。
- 检查模块自带的上拉是否并联过多或接到5V。
- 扫描地址,确认目标器件在0x08到0x77之间应答。
- 降速到100kHz或50kHz复测。
- 换短线、换模块、换电源,排除接触和供电问题。
- 读失败时核对命令、寄存器地址、等待时间和读写顺序。
- 总线锁死时发9个时钟恢复;仍不释放则断电查短路。
- 需要观察边沿、ACK和时钟拉伸时使用示波器或逻辑分析仪。
附录A:术语
| 术语 | 含义 |
|---|---|
| SDA | Serial Data,数据线,双向,开漏 |
| SCL | Serial Clock,时钟线,主机驱动,从机可拉伸 |
| 主机 | 产生时钟和START/STOP,发起通信 |
| 从机 | 靠地址被寻址,回应ACK |
| 开漏 | 只能拉低,不能主动输出高 |
| 上拉 | 将总线在空闲时拉回高电平的电阻 |
| 线与 | 所有输出为高时总线才高 |
| START | SCL高时SDA由高变低 |
| STOP | SCL高时SDA由低变高 |
| Sr | 不释放总线的重复起始 |
| ACK | 第9个时钟拉低SDA |
| NACK | 第9个时钟保持SDA高 |
| 时钟拉伸 | 从机拉低SCL使主机等待 |
| 仲裁 | 多主同时发送时靠线与决出胜方 |
| Cb | 总线电容 |
附录B:常见I2C地址
| 器件 | 7位地址 | 说明 |
|---|---|---|
| BH1750 | 0x23或0x5C | ADDR接地为0x23,接VCC为0x5C |
| AHT20/DHT20 | 0x38 | 固定地址 |
| SHT3x | 0x44或0x45 | ADDR脚选择 |
| SHTC3 | 0x70 | 上电睡眠,需先唤醒 |
| SSD1306 | 0x3C或0x3D | 地址由模块跳线决定 |
| BMP280/BME280 | 0x76或0x77 | 地址由SDO决定 |
| PCF8574 | 0x20到0x27 | 地址由A2/A1/A0决定 |
| DS3231 | 0x68 | RTC |
| AT24C32 | 0x50到0x57 | 地址由A2/A1/A0决定 |
| TCA9548A | 0x70到0x77 | I2C多路复用器 |
地址以模块丝印和手册为准。同一型号不同模块可能因跳线或ADDR脚不同而变化。
