Skip to content

feat(rp2040): deploy parity with Arduino-Pico, PlatformIO, and official picotool #1049

Description

@zackees

Parent: #1040

Goal

Bring fbuild's Pico 1 deploy path to behavioral and safety parity with the proven Arduino-Pico and PlatformIO implementations while keeping fbuild as the only user-facing build/deploy command. The acceptance target is a stock Amazon-purchased Raspberry Pi Pico 1, including first deployment with no serial port and subsequent 1200-bps reset deployments.

This issue was produced from a source-level comparison against:

Gap inventory

Phase 1: canonical UF2 artifact and conversion

  • Emit an inspectable sibling firmware.uf2; do not treat BIN as the canonical deploy artifact.
  • Use the pinned, framework-managed pqt-picotool for ELF -> UF2 with the exact Arduino-Pico 4.5.3 recipe (picotool uf2 convert <ELF> <UF2> --family rp2040). Never search PATH or require a host install. Add --platform rp2040 memory validation only after the pinned framework tool supports it; picotool 4.0.1 rejects that newer flag.
  • Package the matching Windows, Linux x86_64/aarch64, and macOS x86_64/arm64 picotool archives with verified checksums.
  • Prefer ELF conversion. Preserve PT_LOAD physical addresses, sparse pages, overlap/range validation, and official touched-sector padding. The current handwritten dense-image encoder silently zero-fills gaps and is not equivalent to official elf2uf2.
  • Validate existing UF2 input magic, block structure, and RP2040 family ID; reject wrong-family or malformed input before copying.
  • Retain a narrowly tested BIN conversion fallback at the documented 0x10000000 offset only when no ELF exists.

Phase 2: ROM-volume transfer semantics

  • Copy the completed local artifact to exactly NEW.UF2, matching Arduino-Pico.
  • Match Arduino-Pico's direct whole-buffer open(..., wb) / f.write(buf) transfer to NEW.UF2; do not use whole-file copy APIs, repeated small writes, metadata preservation, reopen/fsync, rename, retry, or readback against the ROM volume. Preserve full-byte accounting and require an observed eject before accepting any final-write error.
  • Wait for the selected BOOTSEL volume to disappear and treat disappearance as the expected success transition.
  • Distinguish an accepted transfer/eject race from a true partial-copy or corrupt-volume failure; surface the original actionable OS error when acceptance cannot be established.

Phase 3: stock-board discovery and reset

  • Preserve the already-in-BOOTSEL fast path so first deployment requires no COM port, manual BOOTSEL press, Zadig, or vendor driver.
  • Match Arduino-Pico's bounded 1200-bps/DTR reset behavior for subsequent deployments.
  • Snapshot devices/ports before reset, wait for a new matching BOOTSEL volume, and correlate the selected runtime device to the selected volume where platform metadata permits.
  • Reject multiple plausible targets rather than selecting the first device or flashing every mounted UF2 volume.

Phase 4: CDC handoff and result correctness

  • Snapshot the pre-deploy serial set and return the actual post-deploy port, including when Windows assigns a different COM name.
  • Do not return the stale original port merely because CDC reacquisition timed out.
  • A CDC timeout must be an actionable deployment/recovery failure for firmware expected to expose JSON-RPC, not success: true with port: None.
  • Wait for volume disappearance before accepting runtime CDC so a stale/pre-reset port cannot win the race.

Phase 5: USB identity policy

Phase 6: deterministic tests and portability

  • Add injectable/fake transfer behavior for auto-eject during/after close and for genuine copy failures.
  • Cover malformed/wrong-family UF2, invalid ELF, overlapping/out-of-range segments, zero/multiple volumes, boot timeout, disappearance timeout, changed COM, ambiguous CDC, and CDC timeout.
  • Cover Windows drive roots, Linux /media and /run/media, and macOS /Volumes discovery without relying on the test host's OS.
  • Ensure the editable fbuild development install stages both CLI and daemon binaries so FastLED hardware tests cannot silently run a stale daemon.

Attached-board diagnostic (not normal flash acceptance)

  • Load a flash-independent RAM UF2 through fbuild and prove ROM-volume eject/re-enumeration, a new USB arrival epoch, and zero new Disk 51/153 events. Merged in feat(rp2040): support RAM-only UF2 loads #1072 at 8f7e8609e3fe4d28f7edb7fa97fdc1eb8437c06d; artifact SHA-256 0b28e37067a718504d6fc2841524d751b52bd7e3a5bbae0aea08d74f080e3e97.
  • Compare RAM-target and flash-target UF2 behavior on the unchanged cable, motherboard port, Windows MSC stack, and ROM USB session. The RAM path succeeds repeatedly; only the external-QSPI erase/program path stalls, isolating a board-level QSPI fault.

Phase 7: attached Pico 1 acceptance

  • On a known-good stock Pico 1 whose blank/invalid flash naturally enumerates as ROM RPI-RP2, record 2E8A:0003, INFO_UF2.TXT, exact fbuild commit, command, volume transition, runtime VID:PID/COM, and JSON-RPC response. No physical BOOTSEL press is part of acceptance.
  • Run the applicable pin-free runtime suites from feat(autoresearch): add reusable pin-free JSON-RPC smoke mode FastLED#3628-#3630. RPC smoke, SIMD 85/85, IEEE-754 35/35, and watchdog recovery passed on genuine RP2040 CDC; coroutine was removed as an unrelated unsupported optional runtime.
  • Pass ten consecutive deploy -> reboot -> new/current CDC -> JSON-RPC cycles with no manual action. Verified on COM12 / 2E8A:000A / serial 5303284720C4641C.

Current remaining gate: the original attached serial E0C9125B0D9B has a nonfunctional external-QSPI path. A working replacement (5303284720C4641C) now passes subsequent 1200-bps deploy, runtime CDC, JSON-RPC, watchdog recovery, and 10/10 soak. The user explicitly requested a PlatformIO A/B before fbuild on that replacement, so the as-received blank/invalid-flash -> fbuild-only first-deploy checkbox remains unproven and must not be rewritten as passed.

Out of scope

Completion

Land coherent fbuild milestone PRs, admin-merge after focused tests and attached-board evidence, then publish one fbuild release after all fbuild-owned gaps are resolved. Finally restore FastLED from the editable link to the released pin and rerun the complete Pico 1 acceptance path.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    Status
    Triage

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions