Skip to content

Updating the firmware

The console updates its four chips by itself — RP1, RP2, RP3 and the ESP32 — from the micro-SD card and the network, with no PC and without taking the card out. This page says how it works, how a version is published, and what has to be done once on a board that never had partitions.

What the user sees

When the dashboard starts with Wi-Fi up, the console reads the published manifest (/api/firmware) and compares its build with the lowest build its chips report. If it is higher, a line shows on the menu: V… DISPONIBLE : MISE A JOUR. On the MISE A JOUR screen:

  • the four installed builds (what each chip says it runs: its boot banner, the ESP's hello), the version online, the one on the card;
  • TELECHARGER: the images go to papydeck/firmware/ (<chip>-<build>.bin), each checked against its sha256; manifest.json is written last — a card with a manifest is a complete card;
  • INSTALLER, after confirmation: ESP32, RP2, RP3, then RP1, which reboots. About two minutes. Every step is shown; a chip already at that build is skipped, which is how an interrupted update resumes.

Nothing beyond the check is automatic: downloading and installing are choices.

Why it cannot brick

The three RP2350s have A/B partitions and the bootrom's try before you buy (RP2350 datasheet §5.1.17): the new image is written to the partition not in use, read back and hashed, then the bootrom boots it once. It must then "buy" itself (rom_explicit_buy) after its self-tests — for RP1: RP2 and RP3 came up, video runs, the ESP answers, the card reads; for RP2/RP3: RP1 judges (trial banner, then VSYNC or console) and orders the buy. Any reset before the buy — power cut, crash, watchdog, 45 s without passing self-tests — brings the old image back, no persistent state having changed. The ESP32 has the equivalent: two OTA slots, and its bootloader keeps the new image only if it declares itself valid (at RP1's first command).

The rollback was provoked on the bench: an image that does not buy itself comes back to the old one within 45 s; a chip that gives no banner is put back on its old image by RP1.

Publishing a version

From papyDeck_FW, clean tree:

export IDF_PYTHON_ENV_PATH=$HOME/.espressif/python_env/idf5.3_py3.13_env
source ~/esp/esp-idf/export.sh
python3 scripts/fw-publish.py            # --no-upload to only assemble

The script builds the TBYB variants of the three RP2350s (build-tbyb, -DPAPYDECK_TBYB=ON: the bit that makes an image "to be tried") and the ESP, copies the images as dist/fw/<chip>-<build>.bin, computes the sha256s, writes the manifest and sends everything to the server, which serves it under GET /api/firmware. Images first, manifest last.

The manifest is flat, under a kilobyte:

{ "format": 1, "version": "v0.5.0-3-gabc1234", "build": 412, "board": "rev8",
  "rp1": { "file": "rp1-412.bin", "sha256": "…", "size": 440176 },
  "rp2": { "file": "rp2-412.bin", "sha256": "…", "size": 31596 },
  "rp3": { "file": "rp3-412.bin", "sha256": "…", "size": 33584 },
  "esp": { "file": "esp-412.bin", "sha256": "…", "size": 1356224 },
  "notes": "", "signature": "" }

build is git rev-list --count HEAD: the monotonic integer the console compares, and the image version the bootrom compares between A and B. version is git describe, for the eyes. signature is reserved (Ed25519 to come): today integrity rests on the sha256 and on HTTPS, whose certificate the ESP verifies.

Once per board: the partition tables

A new board, or one from before September 2026, has no partitions. The migration is done at a PC, once; never again afterwards. See papyDeck_FW/README.md and CONTRIBUTING.md for the exact commands (RP1 through picotool: the table, then the program at 0x10002000; RP2 and RP3 through their probe, with RP1 put in BOOTSEL first; the ESP through scripts/esp-flash.py, which lays the OTA table).

Two traps that cost an evening: picotool load -p 0 <uf2> does not relocate the image into the partition, it writes it at 0x10000000, over the table; and an image that embeds the table is booted as the table, outside the A/B choice.

The bench

Without the server: scripts/usb-file.py <image> papydeck/firmware/x.bin puts a file on the card over USB (Ctrl-Y), and on RP1's console Ctrl-P fw:<chip>:<path>:<sha256>:<build> runs one step (esp takes an HTTP path). Ctrl-L says part=A|B and tbyb= for RP1, b<build> pA|pB [essai] for the peers.