Pranav-Mandava f4be7f06ad Add ESP32-S3 Super Mini support, make BOOT check opt-in
- New esp32-s3-supermini board profile: ESP32-S3FH4R2, 4MB flash,
  2MB in-package PSRAM, WS2812 on GPIO48, native USB-Serial/JTAG.
  Covers 35 exposed GPIOs; USB, flash/PSRAM bus and BOOT stay untouched.
- Bundle rebuilt S3 images plus an Xtensa sdkconfig defaults file, and
  teach the build/idf helpers about the esp32s3 target.
- BOOT button is now opt-in via --confirm-button and reports SKIP
  otherwise, so a missed press can no longer fail a healthy board. The
  wait is split into short windows to survive a dropped serial request.
- ADC check biases each pad with the internal pull-up/pull-down and
  requires the reading to move, replacing the flaky 0/4095 heuristic.
- Fix BOOT pin configuration (pull-up only) so presses are detected.
- Add `j probe` bring-up helper for mapping header pads.
- Refresh firmware images for C6 and P4; extend tests and README.
2026-11-07 16:23:18 -07:00

espcheck — end-to-end ESP32 board validator

One command finds a supported board, confirms the chip and factory fuse state, flashes a board-specific self-test firmware, runs every on-chip test, and optionally asks you to press the BOOT button (--confirm-button). You get a single pass/fail table — no serial spew. Only a USB cable is needed; no headers, jumpers or external peripherals.

The project was previously c6check; the CLI is now espcheck and picks the board profile from the chip it detects.

Install

Requires uv and Python 3.10 or newer. Run it straight from the repository, without cloning:

uvx --from git+https://git.arc-systems.org/ARCS/esp32-diagnostics espcheck

Or clone it for development:

git clone https://git.arc-systems.org/ARCS/esp32-diagnostics
cd esp32-diagnostics
uv run espcheck

uv run creates the virtualenv and installs esptool, pyserial and rich automatically.

The board is detected from the chip identity, so the same command works for every board in the registry:

Profile Board Chip Flash PSRAM Radios Programming port
esp32-c6-supermini ESP32-C6 Super Mini ESP32-C6FH4 (QFN32) 4 MB — Wi-Fi 6, BLE, 802.15.4 native USB-Serial/JTAG
esp32-p4-pico Waveshare ESP32-P4-Pico ESP32-P4 32 MB 32 MB none CH343P USB-UART
esp32-s3-supermini ESP32-S3 Super Mini ESP32-S3 (QFN56) 4 MB 2 MB Wi-Fi, BLE native USB-Serial/JTAG
uv run espcheck --list-boards     # show the registry and exit
uv run espcheck --list            # show serial ports and which board they look like
uv run espcheck -b p4             # force a profile; the chip must still match

-b accepts the full slug or an alias such as c6, p4, s3, p4-pico or esp32s3. Without -b, the profile is chosen from the detected chip and flash size. If no profile matches, nothing is flashed and the tool explains what it found.

Example: ESP32-P4-Pico

╭───────────────────────────────────────────────╮
│  ESP32-P4-Pico                                │
│  ESP32-P4 v3.2 · /dev/cu.usbmodem5C931270381  │
╰───────────────────────────────────────────────╯
Check                   Result  Details
HARDWARE
  USB port              ✓ PASS  /dev/cu.usbmodem5C931270381  USB Single Serial
  Chip                  ✓ PASS  ESP32-P4 v3.2
  Flash                 ✓ PASS  32MB
  Base MAC              ✓ PASS  80:f1:b2:d7:f8:00
  Security fuses        ✓ PASS  secure boot off, flash encryption off, no locks
  Firmware image        ✓ PASS  3 image(s) written
  Test firmware         ✓ PASS  esp32-p4-pico / v6.1
  MAC cross-check       ✓ PASS  firmware and bootloader agree
  Radios                ✓ PASS  none - no radio hardware
  PSRAM                 ✓ PASS  32 MB (expected 32 MB)
ON-CHIP TESTS
  GPIO pads             ✓ PASS  27/27 pads respond
  ADC1 (GPIO16-23)      ✓ PASS  8/8 channels, calibrated
  Temperature sensor    ✓ PASS  31.7 °C
  I2C bus (SDA7/SCL8)   ✓ PASS  ES8311 codec (0x18)
MANUAL CHECKS
  BOOT button (GPIO35)  ✓ PASS  press detected
  26.6s
  15 passed

Board passed validation.

Example: ESP32-C6 Super Mini

╭──────────────────────────────────────────────────╮
│  ESP32-C6 Super Mini                             │
│  ESP32-C6FH4 (QFN32) v0.2 · /dev/cu.usbmodem2101 │
╰──────────────────────────────────────────────────╯
Check                Result  Details
HARDWARE
  USB port           ✓ PASS  /dev/cu.usbmodem2101  USB-Serial/JTAG
  Chip               ✓ PASS  ESP32-C6FH4 (QFN32) v0.2
  Flash              ✓ PASS  4MB
  Base MAC           ✓ PASS  8c:fd:49:10:9a:fc
  Security fuses     ✓ PASS  secure boot off, flash encryption off, no locks
  Firmware image     ✓ PASS  3 image(s) written
  Test firmware      ✓ PASS  esp32-c6-supermini / v6.1
  MAC cross-check    ✓ PASS  firmware and bootloader agree
  Radios             ✓ PASS  WiFi, BLE, 802.15.4
ON-CHIP TESTS
  GPIO pads          ✓ PASS  21/21 pads respond
  ADC1 (GPIO0-6)     ✓ PASS  7/7 channels, calibrated
  Temperature sensor ✓ PASS  31.9 °C
  Wi-Fi scan         ✓ PASS  5 AP(s), best -72 dBm
  RGB LED signal     ✓ PASS  24-bit frame decoded intact (0x123456)
MANUAL CHECKS
  BOOT button (GPIO9)✓ PASS  press detected
  18.9s
  15 passed

Board passed validation.

Both examples were run with --confirm-button. Without it, the BOOT row is reported as SKIP --confirm-button not set and does not affect the result.

Exit code is 0 when the board passes, 1 when a check fails, and 2 when the board or the toolchain cannot be used.

What it checks

Check How
Board present USB VID/PID of the programming port (Espressif native USB or WCH CH343)
Exact model esptool chip name, package where reported, and flash size; anything else is refused before flashing
Flash reported by the bootloader and cross-checked by the firmware
MAC base MAC read by the bootloader, cross-checked against the firmware
Security fuses secure boot, flash encryption, WR_DIS, RD_DIS
PSRAM boards that have it report the detected size (P4-Pico: 32 MB; S3 Super Mini: 2 MB)
Radios Wi-Fi / BLE / 802.15.4 feature bits, checked against what the board actually has
GPIO pads every exposed GPIO, pulled up, pulled down, driven low and driven high
ADC every ADC1 channel is biased with the internal pull-up and pull-down; the reading must move, which catches a shorted or stuck pad
Temperature internal sensor returns a plausible value
I2C scan the onboard bus and confirm the expected device answers (P4-Pico: ES8311 codec at 0x18)
Wi-Fi active scan validates the RF path and PHY calibration (radio boards only)
RGB LED RMT loopback: transmit a known 24-bit WS2812 frame and decode it back on the same pin (boards with a user LED only)
BOOT button optional, with --confirm-button: you press it; the firmware reports the edge

Deliberately never driven:

  • ESP32-C6 Super Mini: GPIO11 (VDD_SPI), GPIO12/13 (USB D−/D+), GPIO24–30 (in-package flash bus).
  • ESP32-S3 Super Mini: GPIO0 (BOOT button, not on a header), GPIO19/20 (USB D−/D+), GPIO26–32 (in-package flash/PSRAM bus). Strapping pins that are broken out (3, 45, 46, 47) and the UART0/JTAG pins (39–44) are swept as ordinary pads only after the application has booted.
  • ESP32-P4-Pico: the in-package flash/PSRAM bus and the dedicated UART0/BOOT/PA-control pins are not in the header list. GPIO24/25 (USB OTG D−/D+) are tested as ordinary pads because only the Type-C UART cable is assumed to be connected.

Options

Flag Meaning
--board, -b force a board profile instead of auto-detecting
--list-boards list supported profiles and exit
--port, -p pick a serial port instead of auto-detecting
--list list detected serial ports and exit
--no-flash skip flashing; assume the self-test firmware is installed
--skip-manual skip the optional manual checks (BOOT button / LED confirmation)
--confirm-led also ask a human to confirm the LED colours (signal already checked automatically; boards with a user LED only)
--confirm-button also ask a human to press the BOOT button (opt-in; skipped by default and not part of the pass/fail gate)
--json machine-readable report (implies --skip-manual)
--btn-timeout N seconds to wait for a button press (default 30)
--timeout N per-test timeout in seconds (default 40)
--verbose, -v echo protocol traffic
--version print the tool version and exit

Use --json in CI:

uv run espcheck --json | jq -e '.ok'

Requirements

  • macOS or Linux with a data-capable USB-C cable.
  • Python 3.10 or newer and uv.
  • No ESP-IDF is needed to run the validator: the prebuilt self-test images are bundled under src/espcheck/data/<profile>/.
  • Rebuilding the firmware needs ESP-IDF v6.1; see Rebuilding the firmware.

The tool flashes the board, overwriting whatever firmware is on it. Don't point it at a board you care about. The self-test firmware is harmless and leaves the eFuses untouched.

How it works

  1. esptool flash-id identifies the chip, package, MAC and flash size; espefuse summary --format json reads the fuse state. Both leave the chip in the ROM bootloader.
  2. The identity is matched to a board profile. If the chip, package, flash size or minimum chip revision does not match, the board is reset back to its application and the tool exits without flashing.
  3. The profile's bundled manifest is written with esptool write-flash at the offsets the chip needs (the P4 bootloader lives at 0x2000, the C6 bootloader at 0x0), then the board is reset.
  4. The CLI opens the console and drives the firmware over a small line protocol: every reply is a single JSON object prefixed with @, terminated by {"t":"end","cmd":"…"}. Human-readable output is ignored, so the CLI never depends on log formatting.
  5. Each board's board_config.h decides which GPIOs are swept, which ADC channels exist, whether there is an LED or radio, and which pin is BOOT.
  6. The only manual step is the optional BOOT button check (--confirm-button): the firmware waits for a press and exits as soon as it sees one.

Rebuilding the firmware

Both images are built from the shared project in firmware/:

./tools/build-firmware.sh              # every board
./tools/build-firmware.sh esp32p4      # just one target

To use ESP-IDF interactively (build, flash, monitor, menuconfig):

./tools/idf.sh esp32p4 build
./tools/idf.sh esp32p4 -p /dev/cu.usbmodem5C931270381 flash monitor
./tools/idf.sh esp32s3 -p /dev/cu.usbmodem2101 flash monitor
./tools/idf.sh esp32c6 menuconfig

Building the ESP32-S3 image needs the Xtensa toolchain, which an ESP-IDF installation set up only for RISC-V targets may not have. If idf.py reports that xtensa-esp32s3-elf-gcc is missing, install it with:

python "$IDF_PATH/tools/idf_tools.py" install --targets=esp32s3

The firmware keeps an interactive console on the same port. idf.py monitor sends CR on Enter, which the console expects.

Command Action
info chip / flash / PSRAM / MAC summary
pins re-run the GPIO pad test
adc ADC channel readings
temp internal temperature sensor
i2c scan the onboard I2C bus
led [pin] cycle the WS2812 (boards with a user LED)
btn [sec] watch the BOOT button, default 10 s
wifi scan for access points (radio boards only)
sleep [sec] deep sleep, CPU/radios off (default 30 s)
all everything except btn and loop
loop A B jumper-wire continuity test between two header pins

loop A B drives a pair of header pins against each other. Jumper them together and it reports CONNECTED or no connection, which is useful for checking a specific header solder joint. It is optional; the automated validator never needs it.

The j … commands (j info, j pads, j adc, j temp, j i2c, j wifi, j led start|stop, j btn [sec], j probe [sec]) emit the JSON protocol the CLI consumes. j probe is a bring-up helper: it configures every exposed GPIO as an input with a pull-up and reports any level change, which is useful for confirming which pin a button or header pad is actually wired to.

Tests for the report and board-matching logic:

uv run python -m unittest discover -s tests

Adding a board

  1. Add a BoardProfile in src/espcheck/boards.py (chip, flash, header pins, ADC channels, BOOT pin, USB IDs, radios/LED/PSRAM/onboard I2C).
  2. Add a target block to firmware/main/board_config.h.
  3. Add firmware/sdkconfig.defaults.<target> and one entry to ALL_BOARDS in tools/build-firmware.sh.
  4. Run ./tools/build-firmware.sh <target> to produce src/espcheck/data/<slug>/.

If the board will not enter download mode

esptool sometimes cannot reset a board that is running an application and reports Serial data stream stopped or Failed to connect. Unplug the USB cable, plug it back in, and run the command again. If that still fails, hold the BOOT button while plugging the cable in, then release it.

Board notes

ESP32-S3 Super Mini

Item Value
Chip ESP32-S3FH4R2 (QFN56), revision v0.2
Cores 2 Xtensa LX7 + 1 LP core, 240 MHz
Radios Wi-Fi 4 (2.4 GHz), BLE 5
Flash 4 MB in-package (XMC), quad
PSRAM 2 MB quad in-package, 80 MHz
Crystal 40 MHz
USB native USB-Serial/JTAG
GPIOs 35 exposed across the side headers and rear pads
LED onboard WS2812 on GPIO48
BOOT GPIO0 (button; not broken out)

The 35 exposed GPIOs are 1–18, 21, 33–48. The profile deliberately leaves out GPIO0 (BOOT), GPIO19/20 (USB D−/D+) and GPIO26–32 (the in-package flash/PSRAM bus). ADC1 is on GPIO1–10, which are all on the side headers. GPIO33–37 are broken out on the rear pads and are usable here because the FH4R2 uses quad, not octal, PSRAM.

The ESP32-S3FH4R2 ships with 2 MB of PSRAM that most board definitions leave disabled. The bundled image enables quad PSRAM at 80 MHz, so the self-test reports and validates it (expected 2 MB). If a clone carries a different in-package variant with no PSRAM, the PSRAM check fails rather than silently passing — use a board profile that matches that variant.

GPIO48 drives the onboard WS2812, and the RGB LED signal check works the same way as on the C6: the RMT transmitter and receiver are both routed to the pin, and the 24-bit GRB frame is decoded back.

ESP32-C6 Super Mini

Item Value
Chip ESP32-C6FH4 (QFN32), revision v0.2
Cores 1 HP RISC-V + 1 LP core, 160 MHz
Radios Wi-Fi 6 (2.4 GHz), BLE 5, IEEE 802.15.4
Flash 4 MB in-package, DIO @ 80 MHz
Crystal 40 MHz
USB native USB-Serial/JTAG
LED onboard WS2812 on GPIO8
BOOT GPIO9

Factory state (recorded before flashing). The flash was blank: the full 4 MB dump was 0xFF except a 24-byte 5A 00 FF A5 test pattern at 0x50000. No bootloader, partition table or app. The dump is saved as factory-dump/factory-4MB.bin. The eFuses were factory-pristine: secure boot and flash encryption off, WR_DIS = 0, USB-Serial/JTAG and download mode enabled, ADC1 and temperature calibration present, and a unique 128-bit ID. Nothing was burned or locked. To return the board to that state:

uv run esptool --port /dev/cu.usbmodem2101 erase-flash

The green BAT LED flickers with no battery. It is driven by the onboard charger IC, not by a GPIO. With no cell attached the charger cannot terminate a charge cycle, so it hunts and the LED flickers at tens of Hz. This is not a fault. A deep-sleep test confirmed it: with the ESP32 in deep sleep (CPU, radios and USB off, and the USB port dropped), the LED flickered exactly as before. Asked for a 20 s sleep, the port was gone for 19.8 s and came back on its own.

Over USB-Serial/JTAG a deep-sleep wake is indistinguishable from a fresh boot: reattaching the USB host issues a USB_UART_HPSYS reset (reset reason 21), which overwrites the stored wake cause. A battery-only setup, or UART0 on the header, reports the timer wake correctly.

Waveshare ESP32-P4-Pico

Item Value
Chip ESP32-P4NRW32X, revision v3.2
Cores 2 HP RISC-V + 1 LP core, 360/400 MHz
Radios none (this board carries no ESP32-C6 companion)
Flash 32 MB NOR (GD25Q256)
PSRAM 32 MB stacked, 200 MHz hex mode
USB Type-C CH343P USB-UART on UART0 (GPIO37 TX / GPIO38 RX)
Header 40-pin Pico-style, 27 GPIOs
BOOT GPIO35
LED power indicator only, not GPIO-controlled

The P4-Pico's Type-C port is a CH343P USB-to-UART bridge, not native USB, so the self-test firmware uses UART0 for its console. The separate 4-pin USB OTG header is not used.

The 27 header GPIOs are 2, 3, 4, 5, 6, 7, 8, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 32, 33, 46, 47, 48, 54. GPIO7/8 are the onboard I2C bus (with external pull-ups) and GPIO9–13 feed the audio codec; the pad test duly reports them as externally pulled/driven rather than free. ADC1 is on GPIO16–23. The I2C check probes that bus and expects the ES8311 codec at 0x18, which confirms the codec and its bus without a speaker or microphone.

The P4 has no Wi-Fi, BLE or 802.15.4 hardware, so the radio check and Wi-Fi scan do not apply. It also has no user-controllable LED, so the LED signal check is skipped — only the hard-wired power indicator is present.

Observed state. The MAC is 80:f1:b2:d7:f8:00 and the flash is a 32 MB GigaDevice GD25Q256. The eFuses were factory-pristine: secure boot and flash encryption off, WR_DIS/RD_DIS 0. The original factory firmware was replaced by the self-test during bring-up; Waveshare's ESP32-P4-Platform demo can be rebuilt and flashed to restore it.

Chip revision caveat. ESP-IDF cannot build one image for both pre-v3.0 and v3.0+ P4 revisions. The bundled image is built for the v3.0+ family with a minimum of v3.1 (which covers the current P4NRW32X boards). espcheck detects an older revision and refuses to flash it; that board needs its own target build.

License

espcheck is released under the MIT License. The bundled firmware images contain code from ESP-IDF and other third-party components; see THIRD_PARTY_NOTICES.md.

S
Description
No description provided
Readme MIT
2.2 MiB
0 Stars 4 Watchers 0 Forks
Languages
Python 56.5%
C 39.2%
Shell 3.5%
CMake 0.8%