先回答“谁在总线上应答”,再读取数据
传感器库报告“找不到设备”,并不一定是库的问题。接线、电源、上拉、7位地址和总线超时都可能造成相似症状。本篇先不测温湿度,只用Wire发地址探测,建立一个可恢复的I2C基线。
完成物是扫描器与三种故障检查:正常接线、断SDA、程序使用错误目标地址。扫描器分别打印FOUND与TARGET,让你看见“总线确有0x44”与“程序问错0x45”可以同时发生。ACK只证明地址收到应答,不能确认型号、数据格式或CRC。
- 做到这里,你应该看到
- 能区分传感器电源、总线应答、地址选择与数据有效性这四层。
参考:[1] Arduino-ESP32 3.3.12:I2C主机、引脚、超时[2] Arduino-ESP32 3.3.12:Wire错误码真实映射
先锁定模块,再连接四根线
本例选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.1 | Adafruit 4885 | 检查 |
|---|---|---|
| 3V3 | VIN | 模块供电与上拉处于3.3V域 |
| GND | GND | 必须共地 |
| 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固定提交:硬件原理图
探测地址、限时等待与错误码
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错误码真实映射
完整扫描器,不依赖传感器库
下载中包含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错误码真实映射
三种情况都要执行,并证明恢复
按README命令编译本篇目录并上传,打开115200串口。先将其他I2C设备移除,只保留指定模块。下面是验收操作和预期判据,本站未做实板测试;test-log.md必须保存实际日志,不能复制预期文字冒充结果。
- 正常接线:上电发送n或s,应看到FOUND 0x44与TARGET 0x44 code=0 ACK。记录SDA_idle/SCL_idle及found/timeouts。
- 断SDA:断电,只移除GPIO8到SDA的一根线;重上电发送s。应无法得到可信的0x44应答,常见是地址NACK或超时,但浮空总线可能产生异常甚至假ACK;据此需要量电平和上拉,不能保证唯一错误码。
- 恢复:断电接回SDA,上电发送n,再次确认0x44 ACK。仅留下断线失败而未复测恢复,不算完成这一项。
- 错误地址:保持正常接线发送w。扫描仍应发现0x44,但TARGET 0x45应NACK;发送n后目标0x44应恢复ACK。确认只有这个固定地址模块,避免总线上恰有0x45设备。
- 发送r检查驱动重建路径;物理短路或低电平卡住时应断电修线,不能靠反复r宣称恢复。
- 做到这里,你应该看到
- 三种状态的原始日志及每次恢复后的正常结果均有记录;扫描结果与单独目标探测一致地解释故障。
按供电、电平、线、地址的顺序排错
完全扫不到:先量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地址
资料来源与版本说明
资料与固定版本源码核对;构建、仿真与实板为分开的验证维度。资料核对日期:。资料核对、程序构建与实板测试分别记录,不互相替代。