输入输出与编程 / 编程实验发布 内容更新

I2C 扫不到设备?从 SDA、SCL 和地址开始检查

明确S3的GPIO8/GPIO9和Adafruit SHT40的3.3V接法,运行带超时与目标探测的扫描器。逐步复现正常、断SDA与错误地址,保留错误码和恢复路径。

读完能做什么
发现0x44并区分断线、错误目标地址和总线超时,能恢复基线。
开始之前
会断电接线、查看排针丝印并操作串口监视器。
所需器材
Adafruit SHT40 STEMMA QT breakout(产品4885)、四根短导线、可选:万用表、逻辑分析仪
版本组合
Arduino CLI:1.5.1Arduino-ESP32:3.3.12 · 94afccf35fb1e401facddbcf9e13bcf7c76a31d8参考硬件:ESP32-S3-DevKitC-1 v1.1 · WROOM-1-N8R8FQBN:esp32:esp32:esp32s3:FlashSize=8M,PSRAM=opi,FlashMode=qio,PartitionScheme=default_8MB,CDCOnBoot=default,USBMode=hwcdc
适用范围

官方ESP32-S3-DevKitC-1 v1.1、ESP32-S3-WROOM-1-N8R8;Arduino CLI 1.5.1、Arduino-ESP32 3.3.12;USB-to-UART口。

这篇验证到了哪一步

资料核对已通过
docs/esp32-evidence/programming.md#p06版本:Arduino-ESP32 3.3.12验证日期:
构建 / 配置已通过
docs/esp32-evidence/logs/esp32-i2c-scan-wiring-esp32-i2c-scan-wiring.log对象:esp32:esp32:esp32s3:FlashSize=8M,PSRAM=opi,FlashMode=qio,PartitionScheme=default_8MB,CDCOnBoot=default,USBMode=hwcdc版本:Arduino CLI 1.5.1 / core 3.3.12验证日期:
仿真不适用
本篇未运行仿真;外设/电气与实物行为按独立硬件步骤验收,没有仿真通过声明
本站实板待验证
本站未实板验证;正文提供可执行验收步骤

构建通过只说明指定环境中的代码或配置通过检查;实物功能、功耗和稳定性以对应的实板记录为准。

01 / 编程实验

先回答“谁在总线上应答”,再读取数据

传感器库报告“找不到设备”,并不一定是库的问题。接线、电源、上拉、7位地址和总线超时都可能造成相似症状。本篇先不测温湿度,只用Wire发地址探测,建立一个可恢复的I2C基线。

完成物是扫描器与三种故障检查:正常接线、断SDA、程序使用错误目标地址。扫描器分别打印FOUND与TARGET,让你看见“总线确有0x44”与“程序问错0x45”可以同时发生。ACK只证明地址收到应答,不能确认型号、数据格式或CRC。

做到这里,你应该看到
能区分传感器电源、总线应答、地址选择与数据有效性这四层。

参考:[1] Arduino-ESP32 3.3.12:I2C主机、引脚、超时[2] Arduino-ESP32 3.3.12:Wire错误码真实映射

02 / 编程实验

先锁定模块,再连接四根线

本例选Adafruit SHT40 STEMMA QT breakout,产品4885,按其固定PCB提交与pinouts核对。它的默认7位地址为0x44,板上带电平转换与10kΩ上拉。本例VIN接3.3V,因此ESP侧信号保持3.3V域,先不额外并联上拉。其他同名裸板不一定有这些电路,不能照着外观推断。

S3上显式指定GPIO8作为SDA、GPIO9作为SCL,不使用其他ESP32板的默认21/22。先断电,用约10–20厘米短线建立实验基线;这是布置建议,不是宣称I2C只支持或必定支持这个距离。

DevKitC-1 v1.1Adafruit 4885检查
3V3VIN模块供电与上拉处于3.3V域
GNDGND必须共地
GPIO8(J1-12)SDA数据线
GPIO9(J1-15)SCL时钟线

参考:[3] 乐鑫:ESP32-S3-DevKitC-1 v1.1,排针与RGB修订[4] Adafruit SHT40 breakout:供电、10k上拉与0x44地址[5] Adafruit SHT40 PCB固定提交:硬件原理图

03 / 编程实验

探测地址、限时等待与错误码

Wire.begin(8,9,100000)明确引脚和100kHz频率,必须检查初始化返回值。Wire.setTimeOut(20)限制单次事务等待约20毫秒;扫描0x08–0x77共112个非保留地址,再单独探测目标,因此故障时整轮可能耗时约2.3秒加打印开销,不是一个无阻塞实时任务。

固定3.3.12的Wire.cpp将成功映射为0、地址未应答映射为2、超时映射为5,其他错误通常为4。程序保留原始code与摘要统计,不只打印“没找到”。断线导致浮空或总线拉低时,实际错误码可能不同,应依据日志和电平进一步定位。

n把目标设为0x44,w设为0x45,s立即扫描,r释放并重新初始化Wire。改变target只改变程序正在询问谁,不会修改硬件地址。

参考:[1] Arduino-ESP32 3.3.12:I2C主机、引脚、超时[2] Arduino-ESP32 3.3.12:Wire错误码真实映射

04 / 编程实验

完整扫描器,不依赖传感器库

下载中包含Wire扫描源码、固定core的profile、README和test-log.md。代码对地址只做零数据探测,不主动发送温湿度测量命令;限定这一只模块作为基线,不把未知设备总线扫描当作设备识别或业务操作。

代码 / 配置
// SPDX-License-Identifier: MIT
#include <Arduino.h>
#include <Wire.h>
constexpr uint8_t SDA_PIN = 8, SCL_PIN = 9;
constexpr uint32_t I2C_HZ = 100000;
uint8_t target = 0x44;
bool ready = false;
uint32_t scannedAt = 0;
uint8_t probe(uint8_t address) {
  Wire.beginTransmission(address);
  return Wire.endTransmission(true);
}
const char *meaning(uint8_t result) {
  switch (result) {
    case 0: return "ACK";
    case 2: return "NACK_ADDRESS";
    case 5: return "TIMEOUT";
    default: return "OTHER_ERROR";
  }
}
void scanBus() {
  Serial.printf("SCAN SDA_idle=%d SCL_idle=%d target=0x%02X\n",
    digitalRead(SDA_PIN), digitalRead(SCL_PIN), target);
  uint16_t found = 0, timeouts = 0, others = 0;
  for (uint8_t address = 0x08; address <= 0x77; ++address) {
    const uint8_t result = probe(address);
    if (result == 0) { ++found; Serial.printf("FOUND 0x%02X\n", address); }
    else if (result == 5) ++timeouts;
    else if (result != 2) ++others;
  }
  const uint8_t result = probe(target);
  Serial.printf("TARGET 0x%02X code=%u %s; found=%u timeouts=%u other=%u\n",
    target, result, meaning(result), found, timeouts, others);
}
void setup() {
  Serial.begin(115200);
  ready = Wire.begin(SDA_PIN, SCL_PIN, I2C_HZ);
  Wire.setTimeOut(20);
  Serial.printf("P06 begin=%u SDA=8 SCL=9 Hz=100000 timeout=20ms\n", ready);
  Serial.println("Commands: n target=0x44; w wrong target=0x45; s scan; r reinit");
  if (ready) scanBus();
  scannedAt = millis();
}
void loop() {
  bool request = false;
  while (Serial.available()) {
    const char c = Serial.read();
    if (c == 'n') { target = 0x44; request = true; }
    if (c == 'w') { target = 0x45; request = true; }
    if (c == 's') request = true;
    if (c == 'r') {
      Wire.end();
      ready = Wire.begin(SDA_PIN, SCL_PIN, I2C_HZ);
      Wire.setTimeOut(20);
      Serial.printf("REINIT ready=%u\n", ready);
      request = ready;
    }
  }
  if (ready && (request || uint32_t(millis() - scannedAt) >= 5000)) {
    scanBus();
    scannedAt = millis();
  }
}

参考:[1] Arduino-ESP32 3.3.12:I2C主机、引脚、超时[2] Arduino-ESP32 3.3.12:Wire错误码真实映射

05 / 编程实验

三种情况都要执行,并证明恢复

按README命令编译本篇目录并上传,打开115200串口。先将其他I2C设备移除,只保留指定模块。下面是验收操作和预期判据,本站未做实板测试;test-log.md必须保存实际日志,不能复制预期文字冒充结果。

  1. 正常接线:上电发送n或s,应看到FOUND 0x44与TARGET 0x44 code=0 ACK。记录SDA_idle/SCL_idle及found/timeouts。
  2. 断SDA:断电,只移除GPIO8到SDA的一根线;重上电发送s。应无法得到可信的0x44应答,常见是地址NACK或超时,但浮空总线可能产生异常甚至假ACK;据此需要量电平和上拉,不能保证唯一错误码。
  3. 恢复:断电接回SDA,上电发送n,再次确认0x44 ACK。仅留下断线失败而未复测恢复,不算完成这一项。
  4. 错误地址:保持正常接线发送w。扫描仍应发现0x44,但TARGET 0x45应NACK;发送n后目标0x44应恢复ACK。确认只有这个固定地址模块,避免总线上恰有0x45设备。
  5. 发送r检查驱动重建路径;物理短路或低电平卡住时应断电修线,不能靠反复r宣称恢复。
做到这里,你应该看到
三种状态的原始日志及每次恢复后的正常结果均有记录;扫描结果与单独目标探测一致地解释故障。
06 / 编程实验

按供电、电平、线、地址的顺序排错

完全扫不到:先量VIN和GND,再查SDA/SCL是否接反、面包板是否同组、上拉是否存在且接在正确电源域。空闲时两线应能处于高电平;一条持续低时检查短路、模块无电或占线,不应先把100kHz无限降下去。

timeOut大量出现说明总线事务未正常完成,不等于“地址不存在”。正常扫描有0x44但目标0x45失败时,先改程序配置;不要因为库没找到就反复安装不同版本。得到ACK后才进入器件数据命令和CRC检查。

恢复基线时先断电还原四根线,再用n/r验证。带电拔电源而信号仍连接可能造成非预期供电,所以故障注入也坚持断电操作。后续接新模块时,重做供电、上拉与地址清单,不把这一板的10k上拉通用化。

参考:[1] Arduino-ESP32 3.3.12:I2C主机、引脚、超时[2] Arduino-ESP32 3.3.12:Wire错误码真实映射[4] Adafruit SHT40 breakout:供电、10k上拉与0x44地址

BACK TO THE SOURCE

资料来源与版本说明

资料与固定版本源码核对;构建、仿真与实板为分开的验证维度。资料核对日期:。资料核对、程序构建与实板测试分别记录,不互相替代。

  1. Arduino-ESP32 3.3.12:I2C主机、引脚、超时
  2. Arduino-ESP32 3.3.12:Wire错误码真实映射
  3. 乐鑫:ESP32-S3-DevKitC-1 v1.1,排针与RGB修订
  4. Adafruit SHT40 breakout:供电、10k上拉与0x44地址
  5. Adafruit SHT40 PCB固定提交:硬件原理图