ESP32 bring-up (S3 and C5)
asxeem/openaliro
Hardware

ESP32 bring-up (S3 and C5)

One page, match-the-table. The pin map's source of truth is ports/esp32/components/woz_uwb/port/board_pins.h; if you change it there, change it here.

1. Wire it (11 connections)

Power the EVB from the ESP32 board's 3V3 pin, not 5 V — the DW3000 is a 3.3 V part. Share a common ground and USB-power the board during bring-up.

DWM3000EVB (Arduino pin)SignalESP32-S3ESP32-C5
D13SCLKGPIO12GPIO8
D11MOSIGPIO11GPIO9
D12MISOGPIO13GPIO23
D10CSGPIO10GPIO10
D8IRQGPIO5GPIO5
D7RSTnGPIO4GPIO4
D9WAKEUPGPIO6GPIO6
3V3power3V33V3
GNDgroundGNDGND
D1SPI-POLGND (mode-0 strap)same
D0SPI-PHAGND (mode-0 strap)same

Mode-0 strap: the DW3000 SPI must run CPOL=0/CPHA=0. Tie D0 and D1 to GND unless your EVB revision already fixes the mode on the shield — check the EVB manual before soldering.

GPIO 4, 5, 6, and 10-13 are clear of the octal PSRAM pins. SPI2 routes through the GPIO matrix on both chips, so any of these can be remapped in board_pins.h if your board does not break them out.

Why the C5 data pins differ: on the C5 the S3's GPIO11/12 are the UART0 console and GPIO13 is USB-Serial-JTAG; GPIO8/9/23 also avoid the strapping pins (2/7/25/27/28, plus 3/26 per the DevKitC-1 guide) and the GPIO27 RGB LED. The C5 build targets the 4 MB flash floor of the WROOM-1 family (partitions_4mb.csv); if your module has more, raise CONFIG_ESPTOOLPY_FLASHSIZE in sdkconfig.defaults.esp32c5.

Check the EVB power-select jumper

Do this before anything else. Correct wiring is not enough if the EVB's own power-select jumper picks the wrong source: SPI then fails silently, with no valid device ID and a responder that never listens, and it looks exactly like a software fault. This cost days of debugging once. Check the jumper first.

2. Build and flash

$cd ports/esp32/apps/reader
$idf.py set-target esp32s3
$make build
$make flash
$make monitor

idf.py set-target runs once per checkout. ESP-IDF is expected at ~/esp/esp-idf; override with IDF_EXPORT=. The port is auto-detected and SEGGER/J-Link ports are refused; make ports lists what is attached and how each is classified.

3. What good output looks like

The bench app brings the radio up, binds a canned URSK, and starts the CCC DS-TWR responder:

I (xxx) woz_esp32s3: woz_uwb_start_aliro() = 0 (DW3000 up, responder listening)

  • = 0 — SPI, DW3000, and the CCC init path all came up. The engine is talking to the

chip. With no peer present there are no range lines, which is expected.

  • = <nonzero> (FAILED -- check wiring/SPI) — the DW3000 did not answer. In order:

recheck the power-select jumper, then CS/SCLK/MOSI/MISO, then the mode-0 strap, then drop to slow-only by setting WOZ_DW3000_SPI_FAST_HZ to 2000000 in board_pins.h.

4. Prove a real range

Ranging needs a peer to drive the DS-TWR exchange: an Aliro-capable iPhone with a key provisioned for this reader, or a second DW3000 board acting as initiator. With a peer, range: NN cm lines appear and status reports a trusted range.

For the full approach-unlock path — commissioning, a key in the phone's wallet, and the Wallet unlock animation — use the Matter app in ports/esp32/apps/matter-lock instead. This bench app has no Matter layer, so nothing provisions a real credential into a phone for it.

No antenna calibration was needed on this hardware. If distances come out negative or absurd, read docs/esp32-gotchas.md §6.4 before reaching for a calibration constant — it was a timestamp-pairing bug, not a physical offset.