Skip to content

Update firmware over USB (OTA)

Goal: Go beyond the first OTA flash: release images, delta updates, the assets partition, a flash with automatic verification and the way back to the previous firmware.

You need: Start here done, the board running and on USB.

Run every command from the project root (~/CrossPad, Windows C:\CrossPad\firmware) with ESP-IDF loaded as in 2. Build the firmware. Used the one-line installer? Use crosspad-idf in place of idf.py.

The board stays in charge of every update. It runs the new image on trial first and confirms it after 10 seconds; a reset before that boots the previous image again.

  1. Flash a release image

    A release image is a firmware file from a CrossPad release, named CrossPad-<tag>-<rev>-<kit>.bin. Pass --board with the image’s revision: the script needs it when crosspad-hil cannot report the board’s own, and it is always safe.

    Terminal window
    python3 tools/ota_flash.py path/to/CrossPad-<tag>-v2-<kit>.bin --board v2

    Answer the board’s prompts as in 3. Flash the board, steps 2 to 4: Allow (USB serial mode) within 10 s, then Update within 60 s, then leave it alone for 10 s. The delta (step 2) and the verified flash (step 4) ask the same.

    You should see:

    Auto-detected CrossPad on <port>
    Board: dev_… v2 -> image v2: path/to/CrossPad-<tag>-v2-<kit>.bin
    Firmware: path/to/CrossPad-<tag>-v2-<kit>.bin (… bytes, … MB)
    Connecting to <port>...
    Sending: OTA_BEGIN <size> <version>
    Waiting for user confirmation on device...
    Device ready. Sending firmware...
    OTA complete! (…s, … KB/s)
    Device restarting...
    Running slot: trial
    do not reset the board for ~10 s, the image is on trial — a reset before it confirms rolls it back to the other slot

    With Auto-accept updates ticked, the Waiting for user confirmation line does not appear. Without crosspad-hil, the Board: line reads board revision unknown in place of dev_… v2.

    If not: OTA flash cannot find the board. If it says … is a v1 board and the image is v2: OTA refuses an image for the other revision. If it stops at expected OTA_READY: OTA times out waiting for confirmation.

  2. Send a delta update

    A delta update sends only the difference from the image on the board, so --base-fw must be that exact image. Copy build_v2/CrossPad.bin aside after each flash to use as the next base.

    Terminal window
    python3 tools/ota_flash.py --delta --base-fw <bin on device>

    You should see:

    Auto-detected CrossPad on <port>
    Board: dev_… v2 -> image v2: …/build_v2/CrossPad.bin
    Generating delta patch...
    Base: <bin on device> (… bytes)
    New: …/build_v2/CrossPad.bin (… bytes)
    Patch: … (… bytes, …% of full firmware)
    Patch size: … bytes (… KB)
    Connecting to <port>...
    Sending: OTA_DELTA <size> <version>
    Waiting for user confirmation on device...
    Device ready. Sending delta patch...
    OTA complete! (…s, … KB/s)
    Device restarting...
    Running slot: trial

    The first delta run may print Installing detools... right before Generating delta patch.... To send a patch you already have, pass --patch <file> in place of --base-fw; the Generating delta patch lines are then skipped. With Auto-accept updates ticked, the Waiting for user confirmation line does not appear.

    If not: Delta update fails. If it says the revision of the board is unknown: OTA flash says the revision of the board is unknown. If it stops at expected OTA_READY: OTA times out waiting for confirmation.

  3. Update the assets partition

    App icons, the bundled STM32 image and the on-board docs live in the assets partition, which you send separately. The board does not ask for Update here; it restarts once the image is written.

    Terminal window
    python3 tools/fs_transfer.py assets

    You should see:

    Auto-detected CrossPad on <port>
    Board v2 (device dev_…, PCB …, firmware v2) -> build_v2/assets.bin
    Assets image: build_v2/assets.bin (… bytes, crc …)
    Device ready. Streaming (nothing is erased until the CRC matches)...
    …
    Assets written and verified. The device is restarting.

    If not: OTA flash cannot find the board. If it says Error: no assets.bin for this board, build first: OTA says the image is not built.

  4. Flash and verify in one go

    With the crosspad-hil venv from 1. Set up the toolchain active (one-line installer: crosspad-hil already works), tools/bench.py flash does the OTA, waits for the trial confirm, compares the image hash and runs the smoke test. Use it to prove the board runs your latest build.

    Terminal window
    python3 tools/bench.py flash --assets

    You should see:

    flashing build_v2/CrossPad.bin
    …
    OTA complete! (…s, … KB/s)
    …
    on the board: LIBVER: …
    trial: the running slot was already confirmed
    image: matches build_v2/CrossPad.elf (…)
    flashing assets from build_v2/assets.bin (OTA does not touch this partition)
    …
    Assets written and verified. The device is restarting.
    /assets holds … entries
    PASS: HIL SMOKE: PASS

    On a slow boot the trial line reads trial: confirmed after … s instead.

    If not: bench not ready — nothing was flashed means crosspad-hil is missing or sees no board: crosspad-hil install fails. image: MISMATCH means the board went back to the old image: Board rolls back to the old firmware. A failed smoke run: Smoke test prints MISSING MARKER, FATAL or BOOT LOOP.

  5. Go back to the previous firmware

    The board keeps the firmware it ran before in its second slot, and the CP Tools doctor shows it.

    Terminal window
    python3 tools/app_manager.py doctor

    You should see:

    [ ? ] Previous firmware the board still has the one before in its second slot (<version>)
    -> [Enter] switch back to it — seconds, no build

    To switch, open CP Tools (the same command without doctor), press 3 (Something’s wrong), pick Previous firmware and press Enter, then confirm. From the crosspad-hil venv you can send the same request directly:

    Terminal window
    crosspad-hil cdc FW_SLOTS
    crosspad-hil cdc FW_SWITCH

    You should see: two FWSLOT: slot=… running=… state=… pkg=… ver=… kit=… rows and FWSLOT: rollback=1, then OK reboot=1. The board restarts into the other slot. Wait until a fresh flash has confirmed itself (10 s) before you switch: a slot still on trial cannot be left this way.

    If not: Switching back to the previous firmware is refused

The script ends with OTA complete! and Running slot: trial. After 10 s, open the console on the STM32 bridge port (0483:5740) and look for the success line. Opening the port restarts the board, so wait the full 10 s first.

Terminal window
idf.py -B build_v2 -DSDKCONFIG=sdkconfig.v2 -p <port> monitor

You should see: I (…) crosspad_main: All systems operational - Crosspad ready. Press Ctrl+] to leave the monitor.

If not: No boot log on the console