Flash ESP32 firmware
draftTargets
Two product lines share the same repo (nSealr/esp32):
| Line | Primary board | Status |
|---|---|---|
| ESP32 USB/NIP-46 signer | LILYGO T-Display S3 (no camera) | display+button drivers under work |
| ESP32 Stateless QR Vault | T-Display S3 Pro OV5640 (camera+display) | pending hardware target |
Both build from the same source tree; the target flag selects firmware features.
Prerequisites
- ESP-IDF v5+ installed and on your PATH. Follow ESP-IDF setup.
esptool.py(ships with ESP-IDF; otherwisepip install esptool).- A USB-C cable that does data, not just power (cheap charge-only cables are a common pitfall).
- macOS / Linux workstation. Windows works with WSL2 — pass the USB
port through with
usbipd-win.
Quick check:
idf.py --version # → ESP-IDF v5.x.x
esptool.py version # → esptool.py vX.Y
Clone & build
git clone https://github.com/nSealr/esp32
cd esp32
make setup
make build TARGET=esp32s3-tdisplay
The TARGET flag corresponds to a board profile in boards/. Other
profiles: esp32s3-tdisplay-pro-ov5640 (QR vault candidate),
esp32-classic-ttgo (compatibility target).
Build outputs land in build/<target>/ — the relevant file is
firmware.bin.
Identify the serial port
| OS | Typical port |
|---|---|
| macOS | /dev/cu.usbserial-XXXX or /dev/cu.usbmodemXXXX |
| Linux | /dev/ttyUSB0 / /dev/ttyACM0 |
Plug the board in and run ls /dev/cu.* (macOS) or dmesg | tail
(Linux) to confirm.
Flash
make flash TARGET=esp32s3-tdisplay PORT=/dev/cu.usbserial-XXXX
This wraps esptool.py with the right offsets. Typical output:
Connecting....
Chip is ESP32-S3 (revision v0.2)
Writing at 0x00010000... (100 %)
Hash of data verified.
Leaving...
Hard resetting via RTS pin...
If Connecting... hangs, hold the BOOT button on the dev board while
flashing (or strap GPIO0 to GND for breakouts that don’t have a button).
Smoke evidence
With the firmware flashed, run the companion smoke tests from your workstation:
# 1. Companion ↔ device serial bring-up
nsealr serial-line exchange \
--port /dev/cu.usbserial-XXXX \
--request ../specs/vectors/requests/request-v0.json \
--out response.json
# → request-bound capture checks pass
# 2. Shared fixture verification
nsealr fixture verify --specs ../specs
# → ✓ approval_digest matches reviewed material
# → ✓ deterministic "signing_disabled" decision
You should see these in the
firmware protocol evidence and
Unicode fallback tracking in nSealr/esp32. They are the smoke
evidence — not a production claim.
Recovery
If the firmware bricks the device, drop to bootloader:
esptool.py --port /dev/cu.usbserial-XXXX erase_flash
make flash TARGET=esp32s3-tdisplay PORT=/dev/cu.usbserial-XXXX
Hold BOOT, tap RESET, release BOOT — board is in bootloader mode and will accept any firmware.
Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
serial.serialutil.SerialException | Wrong port or permission | sudo usermod -a -G dialout $USER on Linux; pick the right /dev/cu.* on macOS |
Failed to connect | Cable is charge-only, or BOOT not held | Swap to a data cable; hold BOOT while flashing |
| Display flickers / wrong colors | SPI clock too high for the panel revision | Lower LCD_SPI_FREQ_HZ in boards/<target>/Kconfig |
signing_disabled despite gates allegedly passing | You’re on dev firmware; production target not built yet | Verify the target — esp32s3-tdisplay is always signing-disabled in this tree |
| Companion can’t see the device | Some USB hubs drop the CDC ACM interface | Plug directly into the host |
Where to go next
- ESP32 USB/NIP-46 signer page — live capability matrix and gates.
- ESP32 Stateless QR Vault signer page — the QR-vault line on the same repo.
- Transports overview — what the bounded USB / serial frames look like on the wire.
- Verify a signed event — the same verification once real signing is enabled.