SDA/SCL(I²C)

I2C是同步、半双工、多主多从的两线总线。SDA传输数据,SCL提供时钟。两条线均为开漏结构,需外接上拉电阻。示例平台为ESP32-C3,器件为BH1750、AHT20/DHT20和SSD1306。

面包板上的I2C接线与BH1750光照传感器
图0:面包板上的I2C接线,BH1750光照模块通过SDA与SCL接到主控。

1. 总线结构

I2C总线拓扑
图1:三个从设备并联在SDA/SCL上,通过7位地址区分。

主机发起通信并产生时钟,从机响应地址。所有设备共享SDA和SCL,不需要独立片选。SCL由主机驱动,从机可通过拉低SCL延长时钟低电平。SDA方向随读写切换,因此两条线都必须采用开漏结构。

项目I2CSPIUART
线数2根,SDA与SCL4根起,SCK、MOSI、MISO、CS2根,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总线
释放释放高
拉低释放低
释放拉低低
拉低拉低低
开漏输出与上拉电阻
图2:开漏输出只能拉低。写1为释放总线,由上拉电阻拉高;写0为导通MOSFET,将总线拉到GND。

开漏结构的作用:

  1. 多设备共享总线时不会形成推挽冲突。
  2. 线与逻辑支持多主仲裁和时钟拉伸。
  3. 两侧使用各自的上拉,可实现电平转换。

二极管OR可实现类似逻辑,但存在压降、结电容和成本问题,I2C未采用。

2.2 上拉电阻

总线等效为RC网络。上拉电阻R对总线电容Cb充电,电压按指数上升。I2C规范以0.3VDD到0.7VDD的上升时间tr作为指标:

tr ≈ 0.8473 × R × Cb
上拉电阻与上升时间
图3:Cb=200pF时不同上拉电阻的上升曲线。R减小则tr减小,低电平灌电流增大。

按3.3V、Cb=200pF计算:

上拉电阻tr低电平电流适用场景
1.5kΩ0.25µs1.9mA400kHz,短总线,电容小
2.2kΩ0.37µs1.3mA400kHz边缘,线短且器件少
4.7kΩ0.80µs0.62mA100kHz
10kΩ1.69µs0.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由主机产生。

START与STOP时序
图4:START、重复起始Sr和STOP的边沿。灰色区间为SCL低电平,SDA可在此区间改变。

数据有效性:SCL为高期间SDA必须稳定;SDA只在SCL为低期间改变。START、Sr、STOP是例外。

Sr用于在一次通信中切换方向而不释放总线。作用:

  1. 防止其他主机在切换期间获得总线。
  2. 保持器件内部命令或地址指针状态。

3.2 位传输与ACK

每字节8位,MSB先发。发送方在SCL低电平期间更新SDA,接收方在SCL高电平期间采样。第9个时钟为ACK:接收方拉低SDA表示ACK,释放SDA表示NACK。

一位数据与ACK
图5:一个字节为8位数据加1位ACK,共9个时钟。采样点位于SCL高电平中点。

写地址或写数据时,ACK由从机产生;读数据时,ACK由主机产生。主机读取最后一个字节时返回NACK,然后发STOP。NACK在此表示结束传输,不是错误。

若地址ACK但数据NACK,检查寄存器地址、器件忙状态、供电和上拉。

3.3 时钟拉伸

从机可在主机释放SCL后继续拉低SCL,主机必须等待。该机制为时钟拉伸。

时钟拉伸
图6:从机拉低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。

I2C地址帧格式
图7:7位地址加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。读流程:

  1. START,发送0x46(0x23左移一位,最低位0),收ACK。
  2. 发送0x10,收ACK。
  3. 发送Sr,发送0x47(最低位1),收ACK。
  4. 读Data H,回ACK。
  5. 读Data L,回NACK。
  6. 发送STOP。
BH1750读取流程
图8:先写命令,再用重复起始切换为读,读两个字节后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/s400pF传感器、EEPROM
快速模式400kbit/s400pF传感器、小屏
快速+模式1Mbit/s550pF短总线高速器件
高速模式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. 检查清单

  1. 断电测VCC-GND、SDA-SCL、SDA-GND、SCL-GND是否短路。
  2. 上电测模块VCC,确认在3.3V附近。
  3. 测SDA和SCL空闲电平,确认接近VCC。
  4. 确认主机与模块共地。
  5. 确认SDA/SCL未接反。
  6. 确认总线上有一组上拉,阻值按速率和电容计算。
  7. 检查模块自带的上拉是否并联过多或接到5V。
  8. 扫描地址,确认目标器件在0x08到0x77之间应答。
  9. 降速到100kHz或50kHz复测。
  10. 换短线、换模块、换电源,排除接触和供电问题。
  11. 读失败时核对命令、寄存器地址、等待时间和读写顺序。
  12. 总线锁死时发9个时钟恢复;仍不释放则断电查短路。
  13. 需要观察边沿、ACK和时钟拉伸时使用示波器或逻辑分析仪。

附录A:术语

术语含义
SDASerial Data,数据线,双向,开漏
SCLSerial Clock,时钟线,主机驱动,从机可拉伸
主机产生时钟和START/STOP,发起通信
从机靠地址被寻址,回应ACK
开漏只能拉低,不能主动输出高
上拉将总线在空闲时拉回高电平的电阻
线与所有输出为高时总线才高
STARTSCL高时SDA由高变低
STOPSCL高时SDA由低变高
Sr不释放总线的重复起始
ACK第9个时钟拉低SDA
NACK第9个时钟保持SDA高
时钟拉伸从机拉低SCL使主机等待
仲裁多主同时发送时靠线与决出胜方
Cb总线电容

附录B:常见I2C地址

器件7位地址说明
BH17500x23或0x5CADDR接地为0x23,接VCC为0x5C
AHT20/DHT200x38固定地址
SHT3x0x44或0x45ADDR脚选择
SHTC30x70上电睡眠,需先唤醒
SSD13060x3C或0x3D地址由模块跳线决定
BMP280/BME2800x76或0x77地址由SDO决定
PCF85740x20到0x27地址由A2/A1/A0决定
DS32310x68RTC
AT24C320x50到0x57地址由A2/A1/A0决定
TCA9548A0x70到0x77I2C多路复用器

地址以模块丝印和手册为准。同一型号不同模块可能因跳线或ADDR脚不同而变化。