Add I2C bus scan and pull-up presence test; fix S3 board orientation in README

Detection previously reported only "no ACK at 0x2a/0x2b", which cannot tell a
sensor at an unexpected address from one that is not wired at all.

- Sweep the full 1..126 address range and log any responders.
- Before enabling the internal pull-ups, sample SDA/SCL as plain inputs. A
  powered C4001 breakout holds them high through its own pull-ups, so an
  all-low reading means nothing powered is on those pins -- a wiring answer
  rather than a protocol one. This immediately identified a miswire that the
  ACK-only log had left ambiguous.
- Drop the \r\n from the raw UART probe. DFRobot_C4001_UART::writeReg sends the
  bare command string with no terminator, so the probe should match it.

README had the YD-ESP32-S3 header order right but the orientation wrong: it
said USB-C at the top when the module is at the top and both USB-C sockets are
at the bottom. Read upside down that walks the sensor into the PSRAM pins.
Also note the separate plain LED on GPIO38 vs the RGB on GPIO48.

Verified on hardware: C4001 detected at I2C 0x2a, init ok, streaming range,
velocity and energy in speed mode.

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
mattandClaude committed 2026-08-14 12:07:26 -07:00
1 parent 9c1c3fe1e8
commit 9b5f98ae7b
2 files changed
+43 -2

No files matched your search

+11 -1
View File
@@ -63,9 +63,14 @@ itself — the header order below is only for orientation.
| RX (UART) | **GPIO17** | P1 | | RX (UART) | **GPIO17** | P1 |
| OUT | GPIO16 | P1, optional | | OUT | GPIO16 | P1, optional |
Header order, USB-C end at the top: Header order. **Orientation matters: hold the board with the ESP32-S3-WROOM-1
module and its antenna at the TOP, and both USB-C sockets at the BOTTOM.**
Pin 1 of each column is the end nearest the module, furthest from the USB
sockets. (Getting this upside down is an easy way to wire the sensor into the
PSRAM pins.)
``` ```
[ ESP32-S3-WROOM-1 module + antenna ] <- TOP
P1 (left) P2 (right) P1 (left) P2 (right)
1 3V3 1 GND 1 3V3 1 GND
2 3V3 2 GPIO43 U0TXD -> CH343 2 3V3 2 GPIO43 U0TXD -> CH343
@@ -89,8 +94,13 @@ Header order, USB-C end at the top:
20 GPIO14 20 GPIO19 USB D- 20 GPIO14 20 GPIO19 USB D-
21 5V 21 GND 21 5V 21 GND
22 GND 22 GND 22 GND 22 GND
[ USB-C: COM (CH343) | USB ] <- BOTTOM
``` ```
Reference pinout: https://mischianti.org/vcc-gnd-studio-yd-esp32-s3-devkitc-1-clone-high-resolution-pinout-and-specs/
Note that board also has a plain built-in LED on **GPIO38**, separate from the
RGB WS2812 on GPIO48 that this firmware drives.
Do not use for I/O: **GPIO35/36/37** (octal PSRAM — the R8 in N16R8), Do not use for I/O: **GPIO35/36/37** (octal PSRAM — the R8 in N16R8),
**GPIO43/44** (UART0 to the CH343), **GPIO19/20** (native USB), GPIO0/45/46 **GPIO43/44** (UART0 to the CH343), **GPIO19/20** (native USB), GPIO0/45/46
(strapping), GPIO48 (RGB LED), GPIO26–32 (flash, not broken out). (strapping), GPIO48 (RGB LED), GPIO26–32 (flash, not broken out).
+32 -1
View File
@@ -104,6 +104,24 @@ static bool i2cProbe(uint8_t addr) {
// setup() before WiFi ever starts. Check the lines are actually high with // setup() before WiFi ever starts. Check the lines are actually high with
// pull-ups on before handing them to Wire. // pull-ups on before handing them to Wire.
static bool i2cBusIdle() { static bool i2cBusIdle() {
// First, with the internal pull-ups OFF. A powered C4001 breakout has its
// own pull-ups, so the lines stay high. If they sag to 0 with nothing
// driving them, there is no powered module on these pins at all -- which
// separates "sensor wired but mute" from "sensor not actually connected".
pinMode(PIN_I2C_SDA, INPUT);
pinMode(PIN_I2C_SCL, INPUT);
delayMicroseconds(500);
int sdaHi = 0, sclHi = 0;
for (int i = 0; i < 8; i++) {
sdaHi += digitalRead(PIN_I2C_SDA);
sclHi += digitalRead(PIN_I2C_SCL);
delayMicroseconds(100);
}
sensor.detectLog += "I2C: external pull-ups sda=" + String(sdaHi) + "/8 scl=" +
String(sclHi) + "/8 " +
((sdaHi == 8 && sclHi == 8) ? "(module present)"
: "(NO powered module on these pins)") + "\n";
pinMode(PIN_I2C_SDA, INPUT_PULLUP); pinMode(PIN_I2C_SDA, INPUT_PULLUP);
pinMode(PIN_I2C_SCL, INPUT_PULLUP); pinMode(PIN_I2C_SCL, INPUT_PULLUP);
delayMicroseconds(200); delayMicroseconds(200);
@@ -135,7 +153,8 @@ static bool uartSensorPresent() {
while (Serial1.available()) Serial1.read(); // flush boot noise while (Serial1.available()) Serial1.read(); // flush boot noise
// A running C4001 streams frames continuously; nudge it in case it's stopped. // A running C4001 streams frames continuously; nudge it in case it's stopped.
Serial1.print("sensorStart\r\n"); // No terminator -- DFRobot_C4001_UART::writeReg sends the bare string.
Serial1.print("sensorStart");
uint32_t t0 = millis(); uint32_t t0 = millis();
while (millis() - t0 < 1500) { while (millis() - t0 < 1500) {
@@ -171,6 +190,18 @@ static void detectSensor() {
sensor.detectLog += "I2C: no ACK at 0x" + String(a, HEX) + "\n"; sensor.detectLog += "I2C: no ACK at 0x" + String(a, HEX) + "\n";
} }
} }
// Neither expected address answered. Sweep the whole bus so the log can
// distinguish "sensor at an unexpected address" from "nothing on the bus
// at all" -- which usually means power, swapped SDA/SCL, or the module's
// mode switch is set to UART.
String found;
for (uint8_t a = 1; a < 127; a++) {
Wire.beginTransmission(a);
if (Wire.endTransmission() == 0) found += " 0x" + String(a, HEX);
}
sensor.detectLog += found.length() ? "I2C: bus scan ->" + found + "\n"
: "I2C: bus scan found no devices\n";
} // end if (i2cBusIdle()) } // end if (i2cBusIdle())
Serial.println(" probing UART ..."); Serial.println(" probing UART ...");