A firmware selector for the Heltec WiFi LoRa 32 V4.2 (ESP32-S3) that lets you switch between multiple firmwares at boot time — Meshtastic, MeshCore, Reticulum/RNode, or anything else.
The 16MB flash is divided into a factory partition (boot selector app) and 4 OTA slots that share a 12 MB region. By default each slot is 3 MB, but the installer wizard can resize them in 64 KB steps — e.g. 5 MB / 2 MB / 2 MB / 3 MB — to fit larger firmwares. On power-up, the selector shows a menu on the OLED labelled with each slot's .bin filename. Pick one with the USER button and the device reboots into it; the selection is remembered for auto-boot next time.
Flash Layout (16MB):
┌──────────────┬──────────┬────────────────────────────────┐
│ 0x000000 │ 32KB │ Bootloader │
│ 0x008000 │ 4KB │ Partition Table │
│ 0x009000 │ 4KB │ Selector Config (sel_cfg) │
│ 0x00E000 │ 8KB │ OTA Data (boot selection) │
│ 0x010000 │ 1MB │ Boot Selector (factory) │
│ 0x110000 │ 12MB │ OTA Slots 0-3 (configurable) │
│ 0xD10000 │ 544KB │ SPIFFS (active) │
│ 0xD98000 │ 4×544KB │ Per-slot FS Backups │
│ 0xFB8000 │ 32KB │ NVS (active) │
│ 0xFC0000 │ 4× 32KB │ Per-slot NVS Backups │
└──────────────┴──────────┴────────────────────────────────┘
- PlatformIO (CLI or IDE)
- Heltec WiFi LoRa 32 V4.2
- USB-C cable
- Firmware
.binfiles for the firmwares you want to load
pio runThe script defaults to /dev/ttyACM1 and baud 115200 (required for the ESP32-S3's native USB-Serial-JTAG). Override with -p / -b if your device enumerates elsewhere.
python3 scripts/flash_firmware.py installWalks you through:
- Picking a
.binfromfirmware/for each of the four slots (or leaving slots empty). - Reading each selected
.bin, computing its app size, and recommending a per-slot size (firmware + ~25% headroom, rounded up to 64 KB). Any leftover 12 MB budget is added to the slot holding the largest firmware — the one most likely to grow on future updates. Empty slots are given the 64 KB minimum. - Sizing each slot — press Enter to accept the recommendation, or type
3,3M,2048K,0x300000, orautoon one slot to absorb the remainder. Sum must be 12 MB and each size a multiple of 64 KB. - A diff vs the current
partitions.csv. - Regenerating
partitions.csv, runningpio runto rebuild the selector, and flashing everything.
If the layout is unchanged, the rebuild step is skipped.
python3 scripts/flash_firmware.py flash-all \
--slot0 firmware/meshtastic.bin \
--slot1 firmware/meshcore.bin \
--slot2 firmware/rnode.bin \
--slot3 firmware/reticulum.binFlashes the bootloader, partition table, selector app, and all four firmware slots in one go.
python3 scripts/flash_firmware.py flash-slot 0 firmware/meshtastic-new.binpython3 scripts/flash_firmware.py info- Press RST or power on — the boot selector always appears
- The splash screen appears for 1.5 seconds
- If a firmware was previously selected, it auto-boots after 5 seconds with a countdown bar
- Short press USER to cycle through firmware slots
- Long press USER to boot the highlighted firmware
- Press any button during countdown to cancel auto-boot and browse the menu
Just press RST. The custom bootloader hook detects hardware resets and always boots the selector. This works because:
- RST button / power cycle → bootloader erases otadata → selector runs
- Software restart (from selector to firmware) → otadata preserved → firmware boots
- Firmware crashes / watchdog → selector runs (safe fallback)
To skip auto-boot and force the full menu: hold USER while pressing RST.
| Firmware | Where to get .bin |
|---|---|
| Meshtastic | meshtastic.org/downloads — pick heltec-v3 variant |
| MeshCore | flasher.meshcore.dev — pick Heltec V4 |
| Reticulum/RNode | Install rnodeconf and extract the firmware, or build from source |
- The four OTA slots share a 12 MB region. Default layout is 4×3 MB, but the installer can reassign within that budget (e.g. one 5 MB slot for Meshtastic + three 2 MB slots). Each size must be a multiple of 64 KB.
- Per-firmware data isolation: Each firmware's NVS (settings, keys, IDs) and filesystem data are saved/restored automatically when switching. Meshtastic, MeshCore, RNode etc. each keep their own independent config.
- Built-in OTA updates within a firmware (e.g. Meshtastic's own OTA) won't work because the partition labels differ. Update via the flash script instead.
- The active SPIFFS partition is 544 KB and each slot has a matching 544 KB filesystem backup area plus a 32 KB NVS backup area — all fixed, regardless of how the 12 MB OTA region is carved up.
- The OLED menu labels each slot with the
.binfilename the installer was given, not hardcoded firmware names.
├── platformio.ini # Build config
├── partitions.csv # Flash partition table
├── boards/ # Custom board definition
├── src/
│ ├── main.cpp # Entry point
│ ├── config.h # Pin defs, constants
│ ├── display.h/cpp # OLED driver & UI rendering
│ ├── button.h/cpp # USER button input
│ ├── firmware_manager.h/cpp # Partition scanning & OTA
│ └── menu.h/cpp # Menu state machine
├── scripts/
│ └── flash_firmware.py # Flash utility
└── firmware/ # Put your .bin files here
If you find this project helpful, consider donating:
