Files
mmWaveESP/README.md
T
mattandClaude b9162db707 XIAO ESP32-C6 + DFRobot C4001 mmWave presence sensor
Firmware for a Seeed XIAO ESP32-C6 driving a C4001 24GHz radar, with a
web UI for live data and calibration.

- Auto-detects whether the C4001 is on I2C (0x2A/0x2B) or UART, so the
  board's mode switch position doesn't matter. Serves the probe log over
  HTTP when nothing is found.
- Single-page UI inlined into flash (no CDN, so it works in AP mode):
  presence state, target count, range, speed, energy, rolling 60s chart,
  and mode-aware calibration controls.
- Joins WiFi from a gitignored include/secrets.h, falls back to a
  "xiao-mmwave" AP, registers mmwave.local.
- Poll rate adapts to the interface: UART at 9600 costs ~60ms per register
  round-trip, so polling it at the I2C rate starved the HTTP server.

Two hardware gotchas documented in the README:

- The sensor UART must not use D6/D7. Those are GPIO16/17, the C6's
  hard-wired U0TXD/U0RXD. Pointing Serial1 at them deadlocks the UART
  driver and trips the interrupt watchdog, boot-looping the board. UART1
  has no fixed pins, so it routes through the GPIO matrix to D10/D9.
- DFRobot's own wiring table pairs the sensor's RX with the MCU's RX, and
  their library swaps the rxpin/txpin parameter names between the header
  and the .cpp.

Verified on ESP32-C6FH4 rev v0.1, 4MB flash: builds at 57.6% flash /
11.9% RAM, flashes over the native USB Serial/JTAG port, and reads a live
C4001 on I2C @ 0x2A.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-14 10:05:27 -07:00

177 lines
6.8 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# XIAO ESP32-C6 + DFRobot C4001 mmWave presence sensor
Live-data and calibration rig for a C4001 24 GHz radar on a Seeed XIAO ESP32-C6.
The board hosts a web page showing presence, distance, velocity and signal
energy in real time, with sliders for every sensor parameter.
## Verified on this machine
| Thing | Value |
|---|---|
| Chip | ESP32-C6FH4 (QFN32) rev v0.1, single core + LP core, 160 MHz |
| Flash | 4 MB embedded (mfr 0x20, dev 0x4016) |
| Radios | WiFi 6, BT 5 LE, IEEE 802.15.4 |
| MAC | `e4:b3:23:b4:7b:c4` |
| Port | `/dev/ttyACM0` (native USB Serial/JTAG, VID `303a` PID `1001`) |
| Toolchain | PlatformIO 6.1.19, espressif32 `2026.02.30`, Arduino core 3.3.7 |
| Build | succeeds, 57.6% flash / 11.9% RAM |
Flashing was confirmed working: esptool connected, uploaded its stub loader,
ran it, and read the flash chip ID back.
> **Arduino core version matters.** ESP32-C6 needs Arduino-ESP32 **3.x**. The
> installed platform is the pioarduino fork (`2026.02.30`), which ships 3.3.7 and
> includes the `XIAO_ESP32C6` variant. The old registry `espressif32 @ 6.x` has no
> C6 support — don't downgrade.
## Pin diagram
The XIAO's pads are unlabelled on the top side. Hold the board **component side
up with the USB-C connector at the top** — pin 1 of each column is nearest the
USB connector.
```
┌───┐ USB-C ┌───┐
┌─────┴───┴───────┴───┴─────┐
D0 ────┤ o o ├──── VBUS (5 V from USB)
GPIO0 │ │
D1 ────┤ o o ├──── GND
GPIO1 │ │
D2 ────┤ o seeed studio o ├──── 3V3 (3.3 V out)
GPIO2 │ XIAO-ESP32-C6 │
D3 ────┤ o o ├──── D10 GPIO18
GPIO21 │ │
D4 ────┤ o o ├──── D9 GPIO20
GPIO22 │ │
D5 ────┤ o o ├──── D8 GPIO19
GPIO23 │ │
D6 ────┤ o [u.FL] [ant] o ├──── D7 GPIO17
GPIO16 └───────────────────────────┘
```
Full mapping, from Seeed's official pinout sheet cross-checked against the
Arduino variant header:
| Silkscreen | GPIO | Alt function |
|---|---|---|
| D0 | 0 | ADC1 / A0, LP_GPIO0 |
| D1 | 1 | ADC1 / A1, LP_GPIO1 |
| D2 | 2 | ADC1 / A2, LP_GPIO2 |
| D3 | 21 | — |
| D4 | 22 | **I2C SDA** |
| D5 | 23 | **I2C SCL** |
| D6 | 16 | **U0 TXD** |
| D7 | 17 | **U0 RXD** |
| D8 | 19 | SPI SCK |
| D9 | 20 | SPI MISO |
| D10 | 18 | SPI MOSI |
Not on the headers but used by the firmware: **GPIO15** user LED (active LOW),
**GPIO3** RF switch enable (LOW = on), **GPIO14** antenna select (LOW = on-board
ceramic, HIGH = external u.FL).
## Wiring the C4001
Power the sensor from **3V3, not 5V.** The C4001 accepts 3.3–5 V, but at 5 V its
`OUT` pin can swing to 5 V and the C6's GPIOs are not 5 V tolerant. Running the
sensor at 3.3 V makes every logic level match and removes the question entirely.
The firmware auto-detects the interface at boot, so wire whichever your board's
mode switch is set to.
**UART (works on every C4001 SKU):**
| C4001 | XIAO | Note |
|---|---|---|
| VIN | 3V3 | |
| GND | GND | |
| TX | **D9** (GPIO20) | sensor transmit → board receive |
| RX | **D10** (GPIO18) | sensor receive ← board transmit |
| OUT | D2 (GPIO2) | optional |
**I2C (if your board has an I2C/UART switch set to I2C):**
| C4001 | XIAO |
|---|---|
| VIN | 3V3 |
| GND | GND |
| SCL | **D5** (GPIO23) |
| SDA | **D4** (GPIO22) |
| OUT | D2 (GPIO2), optional |
`OUT` is the sensor's own thresholded presence output. It is not required — the
firmware reads everything over the data link — but wiring it lets you compare the
sensor's internal decision against the raw numbers, which is genuinely useful
while calibrating.
> **Don't use D6/D7 for the sensor UART.** They are GPIO16/GPIO17, this chip's
> hard-wired `U0TXD`/`U0RXD`. UART0 already owns them, so pointing `Serial1` at
> those pins deadlocks the UART driver and trips the interrupt watchdog — the
> board boot-loops before it ever reaches WiFi. Found this the hard way; the
> UART pins above route UART1 through the GPIO matrix to pins with no default
> peripheral function.
> **Heads up on TX/RX.** DFRobot's own example table pairs the sensor's RX with
> the ESP32's RX, which is wrong. Their library also swaps the `rxpin`/`txpin`
> parameter names between the header and the `.cpp`. The table above is the
> electrically correct wiring: **sensor TX → board RX**, sensor RX → board TX.
## Setup
```bash
cp include/secrets.h.example include/secrets.h
$EDITOR include/secrets.h # your WiFi; the file is gitignored
```
Skipping this is fine — the board then goes straight to AP mode.
## Build, flash, monitor
```bash
pio run # build
pio run -t upload # flash /dev/ttyACM0
pio device monitor # 115200 baud
```
If upload fails to auto-reset, hold **B** (boot), tap **R** (reset), release **B**,
then upload.
## Using it
On boot the firmware joins your WiFi; if that fails within 15 s it starts an
access point:
- **SSID** `xiao-mmwave` · **password** `mmwave1234`
- Web UI at the IP printed on serial, or `http://mmwave.local/`
The page shows a presence indicator, live target count / range / speed / energy,
a rolling 60-second chart, and a calibration panel that changes with the active
sensor mode. Serial output at boot tells you which interface was detected and
prints the full probe log if nothing was found.
### The two sensor modes
**Presence mode** (`eExitMode`) reports occupancy only. Parameters: min/max/trigger
range, trigger and keep sensitivity (0–9), trigger delay (×0.01 s), keep timeout
(×0.5 s).
**Speed mode** (`eSpeedMode`) reports target count, distance, velocity and signal
energy. Parameters: min/max range and a detection threshold (×0.1).
Calibrate in speed mode, because it shows you the numbers behind the decision.
Then switch to presence mode for normal operation. The in-page guide walks
through it. Settings apply immediately but are volatile — **Save to sensor flash**
to persist them.
## Layout
```
platformio.ini board, framework, C4001 library from git
include/secrets.h.example template for WiFi creds (real one is gitignored)
src/main.cpp detection, polling, WiFi, HTTP API
src/web_ui.h the single-page UI, inlined into flash
```
HTTP API: `GET /api/live`, `GET /api/config`, `POST /api/config`,
`POST /api/action` (`start`/`stop`/`save`/`reset`/`factory`).