- README: rewrite Install (from source, submodules, deps, Pi/aarch64); new 'Flashing firmware (pm3flash)' section — CLI usage, --build, image resolution, cross-compile + PLATFORM note, Raspberry Pi bring-up, programmatic API; point Firmware Compatibility at it; add a License section (MIT). - CLAUDE.md: flash.py/_firmware.py/flash_cli.py in the structure; a 'Firmware flashing' section with the maintainer gotchas (OLD frame, fullimage-only, page-merge, platform != is_rdv4, the pin). - roadmap: new 2026-07-05-firmware-flasher plan doc + a Firmware-flashing row. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
51 lines
2.8 KiB
Markdown
51 lines
2.8 KiB
Markdown
# Pure-Python firmware flasher + `pm3flash`
|
|
|
|
**Status:** ✅ Landed (hardware-validated on a PM3 Easy) · **Date:** 2026-07-05
|
|
Related: [firmware-fork DT-Gitea migration](2026-07-05-firmware-fork-dt-migration.md)
|
|
|
|
## Goal
|
|
|
|
Let pm3py get the fork firmware onto a device by itself — no C `pm3-flash`/DFU/JTAG — so
|
|
`pip install pm3py` + one command flashes the build this pm3py corresponds to. Supports the
|
|
project "in the meantime", before tagged firmware releases exist on the DT Gitea.
|
|
|
|
## What landed
|
|
|
|
- **`Flasher`** (`pm3py/core/flash.py`) over the legacy 544-byte **OLD frame** (bootloader
|
|
protocol, distinct from the NG frames the rest of the client speaks; OLD-frame plumbing in
|
|
`core/transport.py`). Automatic OS→bootloader handover (`CMD_START_FLASH`, no button),
|
|
`CMD_FINISH_WRITE` block loop with per-block ACK/NACK, reset, reopen-and-verify.
|
|
- **Safety:** fullimage-only by default; the bootrom region is refused unless
|
|
`allow_bootrom=True`. A bad OS write is recoverable (proven — recovered a corrupted-OS
|
|
device); a bad bootrom write bricks to JTAG.
|
|
- **Firmware pin** (`pm3py/_firmware.py`): `FIRMWARE_PIN` = the fork build pm3py corresponds
|
|
to (currently the submodule SHA). `pm3.firmware.matches_pin` / `.expected_firmware` on
|
|
connect.
|
|
- **`pm3flash` CLI** (`pm3py/flash_cli.py`): detect → resolve/build image → flash → verify.
|
|
`--image` (prebuilt), `--build PLATFORM` (`make` from the submodule; platform explicit —
|
|
never inferred from `is_rdv4`, which reports the running firmware, not the board),
|
|
`--force`, `--allow-bootrom`.
|
|
|
|
## Lessons (found on hardware / by adversarial review)
|
|
|
|
- **Merge segments sharing a flash page.** The SAM7 erase-programs whole pages; two adjacent
|
|
PT_LOAD segments in one page must be written once with both segments' data, else 0xFF
|
|
padding clobbers real bytes (bug: the flashed OS booted but had invalid version info).
|
|
- **`--build` must flash what it built,** not gate on the pin — a dirty rebuild keeps the same
|
|
base SHA, so `matches_pin` can't distinguish it (`--build` therefore implies `--force`).
|
|
- **No CWD-relative firmware discovery** — running `make` on a stray `./firmware` is arbitrary
|
|
code execution; only an explicit arg / `$PM3PY_FIRMWARE_SRC` / the package repo root.
|
|
|
|
## Open follow-ups
|
|
|
|
- **Auto-download** of the pinned release asset (toolchain-free install) — needs a tagged
|
|
firmware release to exist; rides on the DT-Gitea migration. Drops into `resolve_image`.
|
|
- **Read-back verify** via `CMD_READ_MEM_DOWNLOAD` (v1 trusts per-block ACK/NACK, like the C
|
|
flasher).
|
|
|
|
## Provenance
|
|
|
|
Built + hardware-validated in one session; the `--build` layer was adversarially reviewed by
|
|
a multi-agent workflow (correctness / subprocess-safety / CLI-UX lenses with a verify pass),
|
|
which surfaced the pin-skip, make-missing, and CWD-Makefile issues fixed above.
|