Skip to content

Flashing the Firmware

greghulette edited this page Jul 28, 2026 · 4 revisions

Flashing the Firmware

NaviCore targets the WCB v3.2 board (ESP32‑S3). There are several ways to get firmware onto a board: the in‑browser flasher (easiest), the ESP Flasher Companion desktop app, the Arduino IDE, or the build scripts / CI.


Option 1 — In‑browser flasher (recommended)

The config tool can flash a connected board directly over USB, pulling the latest compiled binaries from the repo's firmware/ folder.

  1. Open config_tool/index.html in Chrome or Edge (Web Serial is required — Firefox/Safari won't work).
  2. Plug the board in over USB.
  3. Go to Config → Firmware.
  4. The Firmware tab has four actions in two groups.

esptool flash — writes over Web Serial; you'll be prompted to enter bootloader mode (hold BOOT, tap RST, release BOOT):

  • ⬆ Update Firmware — routine update. Writes the bootloader, partition table, and app, and resets the OTA boot selector (otadata) to ota_0, but never touches NVS or the LittleFS /config.json store, so your saved configuration is preserved.
  • ⚠ Full Wipe & Flash — initial programming or recovery. Writes the same image and additionally erases NVS (0x9000, the legacy/migration store). It does not erase the LittleFS config partition, so the current /config.json survives — to clear a bad config use Restore Defaults (RESET_DEFAULTS) instead. Use Full Wipe the first time you program a new board, or to recover from a bricked/corrupt board.

Over‑the‑air (OTA) — no BOOT/RST; streams the app into the board's inactive OTA slot live, then reboots into it (see Over-the-air updates below):

  • ⚡ Update over USB (OTA) — updates the board you're directly connected to over the live serial session (?OTALOCAL). Direct‑USB only.
  • 📡 Update over WCB (OTA) — updates a remote NaviCore through your USB‑tethered relay WCB, forwarded over ESP‑NOW (?OTA). Via‑WCB only.

Both esptool modes write bootloader + partition table + app and reset the OTA boot selector (otadata @ 0xE000) to ota_0; the only difference is that Full Wipe additionally erases NVS (0x9000). Neither mode touches the LittleFS config partition. The flasher reuses the port you're already connected on (no second port picker) and reconnects to the board automatically once it reboots — no need to re‑select the port afterward.

The page fetches binaries from main by default. (Developers can target another branch by setting localStorage.rc_fw_branch = '<branch>' in DevTools.)

⚠️ The esptool flash (Update Firmware / Full Wipe & Flash) needs a direct USB connection — those two buttons are disabled while Via WCB is active. The OTA buttons mirror the transport instead: Update over USB (OTA) is enabled only on a direct‑USB link, and Update over WCB (OTA) is enabled only while Via WCB is active — it's the one firmware path that reaches a board through the bridge.

Over-the-air updates

The two OTA buttons skip esptool entirely — no BOOT/RST, no bootloader mode. They fetch the same app image the esptool flash uses, stream it into the board's inactive OTA slot, verify it (SHA), flip the boot selector, and reboot into the new image. Only the app is updated this way; the bootloader and partition table are not (run an esptool flash if either of those changed).

  • ⚡ Update over USB (OTA) — for the board you're directly connected to. The image streams over the live serial session, so there's no port re‑pick and the tool reconnects across the reboot automatically. The first update erases the ~2 MB inactive slot before streaming, which can block the board for up to ~40 s. Requires a direct USB connection.
  • 📡 Update over WCB (OTA) — for a remote NaviCore you can't reach over USB. Your USB‑tethered relay WCB forwards the image over ESP‑NOW to the target, which writes its inactive slot, verifies, ACKs, and reboots on its own. The relay WCB does not reboot — only the remote NaviCore does — so there's no USB reconnect. Enabled only while Via WCB is active. See Remote Management over WCB.

Option 2 — ESP Flasher Companion (desktop)

A small desktop app (greghulette/ESP-Flasher-Companion) that flashes the WCB‑family boards — WCB, NaviCore, and the SBUS Controller — over USB from a single window. Each board row has its own port, a Build + Flash, a Restore Bootloader, and an Identify button, with a shared flash log.

Why it matters for NaviCore: Build + Flash writes the custom short‑WDT bootloader and the partition table (plus OTA data and the app) — not just the app — the same bootloader/partition pairing the WCB hardware needs. Restore Bootloader re‑installs just that bootloader if a stock one ever overwrites it. It flashes with --after watchdog-reset so the ESP32‑S3 doesn't bounce straight back into download mode after writing.

The ESP Flasher Companion desktop app — per‑board Build+Flash / Restore Bootloader / Identify for WCB, NaviCore, and SBUS Controller, with a live flash log


Option 3 — Arduino IDE

  1. Open NaviCore.ino in the Arduino IDE.
  2. Tools → Board → ESP32S3 Dev Module (or your usual WCB v3.2 board definition).
  3. Tools → USB Mode → Hardware CDC and JTAG (USBMode=hwcdc) — the WCB v3.2's OTG USB‑C port is wired to the ESP32‑S3's native USB pins. Without this, Serial routes to the UART bridge instead and the OTG port stays silent (web‑flashed builds then look "dead"). This is the IDE default, but set it explicitly.
  4. Tools → USB CDC On Boot → Enabled (so Serial is native USB‑CDC; the firmware relies on this for the SBUS‑out UART allocation).
  5. Tools → Flash Size → 16 MB (128 Mb)required by the custom partition table below.
  6. Tools → Partition Scheme → Custom — uses the partitions.csv in the sketch folder (PartitionScheme=custom). It's the stock min_spiffs layout (ota_0/ota_1 app slots + a 128 KB spiffs config LittleFS) plus a 12 MB clips LittleFS at 0x400000 for the record/replay clip library. Build with a stock scheme (or 4 MB flash) and the clips partition is missing — record/replay clips then fall back to RAM‑only.
  7. Tools → PSRAM → OPI PSRAMrequired. The firmware allocates its ~210 KB runtime configuration in external PSRAM at boot; if PSRAM is disabled (or set to QSPI) the allocation fails and the board halts with a solid red status LED. The WCB v3.2's ESP32‑S3‑WROOM‑1 N16R8 module has 8 MB of octal PSRAM, hence "OPI".
  8. Sketch → Upload, or Export Compiled Binary to produce the .bin files.

Required libraries

Install via Library Manager (versions pinned to match CI — ArduinoJson must be v7; v6's DynamicJsonDocument allocation behavior breaks SET_CONFIG parsing):

  • ArduinoJson (Benoit Blanchon) — 7.4.3
  • EspSoftwareSerial8.1.0 (for S3/S4)
  • Adafruit NeoPixel1.15.4
  • PololuMaestro

Local libraries (drop into your sketchbook libraries/ folder — CI installs these straight from GitHub):

  • WCB_Client + WCBStream (from the greghulette/WCBClient repo)
  • WcbCmd (from the greghulette/WcbCmd repo) — the shared device‑command translators (Maestro / MP3 / WLED / HCR). The firmware #includes WcbCmd.h, so a build without it won't compile.

The exact library set + versions live in .github/workflows/build-firmware.yml — that workflow is the source of truth, so check there if a build ever disagrees with this list.


Option 4 — Build scripts & CI

  • CI (default): every push triggers .github/workflows/build-firmware.yml, which compiles NaviCore.ino for ESP32‑S3, reads the version from fw_version.h, and commits the three .bin files back into firmware/ (tagged [skip ci]). Once that lands on main, the in‑browser flasher can use it.
  • Local: run tools/build-firmware.ps1 (Windows) or tools/build-firmware.sh (Linux/macOS/WSL). Both wrap arduino-cli with the correct FQBN + partition scheme and drop versioned bins into firmware/. Prereqs: arduino-cli, the esp32 core, and the library set from the CI workflow.

Firmware files & flash addresses

CI commits three per‑build files each release, matched by suffix (the version prefix changes every build) — but the in‑browser flasher writes only the app and partition table from that set; at 0x0 it flashes a fixed‑name custom bootloader instead:

File Flash address Purpose
…_ESP32S3.bin 0x10000 Application image (ota_0)
WCB_S3_custom_bootloader_16MB_wdt3s.bin 0x0 Second‑stage bootloader (custom short‑WDT 16 MB)
…_ESP32S3_part.bin 0x8000 Partition table

⚠️ At 0x0 the flasher writes the fixed‑name WCB_S3_custom_bootloader_16MB_wdt3s.bin, not the per‑build …_ESP32S3_boot.bin. That tagged boot bin is the stock ESP32‑S3 bootloader and is deliberately ignored — flashing it at 0x0 by hand installs the wrong bootloader. The custom one has a short watchdog timeout (cold‑boot auto‑retry) matched to the firmware's in‑app boot guard, the same bootloader Option 2's Restore Bootloader reinstalls.

NVS lives at 0x9000; OTA data at 0xE000; the saved config now lives in the LittleFS data (spiffs) partition. Update Firmware leaves all of them alone; Full Wipe & Flash erases NVS and OTA data only — the LittleFS config partition is untouched by either mode.


Versioning

The version lives in fw_version.h at the repo root:

#define FW_VERSION_BASE  "v0.2.0"         // bump by hand for releases
#define FW_VERSION_DTG   "011009QJUN26"   // auto-stamped by the pre-commit hook
#define FW_VERSION       FW_VERSION_BASE "_" FW_VERSION_DTG
  • FW_VERSION_BASE — semver; edit by hand when cutting a release (v0.2.0 → v0.2.1 → v0.3.0 → v1.0.0).
  • FW_VERSION_DTG — a Date‑Time‑Group stamped automatically by tools/git-hooks/pre-commit on every commit. Don't edit by hand.

The build embeds both into the .bin filename, and the firmware reports FW_VERSION in its boot banner and in the PONG reply — so the file on disk, the running version, and the source header always match. The config tool shows the connected board's version in its footer and on the Firmware tab.


Clone this wiki locally