Skip to content

LPC8xx Stage 7: deploy support (lpc21isp / pyocd / cmsis-dap / jlink) #551

Closed

Description

@zackees

Split out of META #487 (Stage 7) so the umbrella can close once the build + framework + CI staircase is in.

Goal

Wire up firmware upload for the LPC8xx family so fbuild deploy tests/platform/lpc845 -e lpc845 flashes a board.

Scope

The board JSON already advertises upload protocols (lpc21isp, cmsis-dap, jlink, pyocd). What's missing is the fbuild-deploy Deployer wiring that actually invokes them:

  • lpc21isp serial ISP path (primary, matches upload.protocol)
  • pyOCD / CMSIS-DAP / J-Link SWD paths
  • Verification on real hardware (LPC845-BRK / LPCXpresso804)

Why it's not done yet

Requires physical boards to verify — can't be exercised in CI or locally without hardware. Build + link path is complete (#477, #479, #528).

Tracking: #487

Activity

  1. zackees commented on Jun 13, 2026

    @zackees
    MemberAuthor

    Added a hard acceptance criterion for this deployer: it must always unwedge the shared VCOM after flashing. On the LPC845-BRK, pyOCD/SWD flashing leaves the composite-device CDC port in Windows error 31 (needs a physical replug), which breaks flash-then-monitor flows like AutoResearch. Details + proposed approach (prefer lpc21isp ISP-over-UART; if SWD, skip pyocd's post-flash reset and force USB re-enumeration like the TeensyDeployer): #565.

  2. zackees commented on Jun 15, 2026

    @zackees
    MemberAuthor

    /clud-fix investigation report — hardware-gated, no PR

    /clud-fix was invoked from the #586 burn-down. Stopping at the intake gate because the issue body itself states:

    Requires physical boards to verify — can't be exercised in CI or locally without hardware.

    That's a definitional fail on /clud-fix step 7 (validation against the reported repro). The implementation can be drafted blind, but the validation surface is the bench. No PR opened in this run; this report documents the planned-fix shape so the work is ready when a bench session opens up.

    Current state

    Layer State Evidence
    Build + link ✅ Done tests/platform/lpc845/lpc845.ino Blink fixture builds green; CI un-stubbed via #548 / #549.
    Deployer wiring ❌ Missing crates/fbuild-daemon/src/handlers/operations/deploy.rs has a catch-all _ => Err(DeployFailed("deployer for {:?} not yet implemented", platform)) that swallows Platform::NxpLpc.
    Existing parallel ✅ Reference crates/fbuild-deploy/src/teensy/mod.rs — the closest-shaped deployer with the same problem space (USB CDC handshake + magic-baud reboot + USB re-enumeration wait).
    #565 VCOM unwedge ❌ Missing No LPC deployer at all, so no unwedge step.

    Planned fix shape

    New crate-internal module: crates/fbuild-deploy/src/lpc/ (mirrors crates/fbuild-deploy/src/teensy/):

    crates/fbuild-deploy/src/lpc/
      mod.rs          -- LpcDeployer impl + LpcDeployParams config
      lpc21isp.rs     -- ISP-over-UART path: spawn lpc21isp subprocess; this is
                         the primary path because it never opens the composite
                         CDC device, side-stepping #565 entirely. Matches the
                         `upload.protocol = lpc21isp` advertised in the LPC8xx
                         board JSONs.
      swd.rs          -- SWD path: pyOCD invocation (cmsis-dap probe selector),
                         with J-Link as a labeled alternative. Required for
                         boards without an exposed UART (rare on LPC8xx but
                         possible on bring-up).
      vcom_unwedge.rs -- The #565 acceptance: deterministic CDC recovery on
                         LPC845-BRK's composite USB device. Modeled on the
                         TeensyDeployer post-flash USB-re-enumeration wait
                         plus a Windows-specific device-manager touch when
                         `error 31` is observed.
      README.md
    

    Daemon dispatch wiring:

    In deploy.rs (the file with the fall-through), add an arm before _ => Err(...):

    fbuild_core::Platform::NxpLpc => {
        let deployer = fbuild_deploy::lpc::LpcDeployer::from_board_config(&board_cfg)?;
        deployer.deploy(/* … */)
    }

    Path selection inside LpcDeployer::deploy is driven by board_cfg.upload.protocol (defaults to lpc21isp; SWD only if explicitly requested or if lpc21isp fails handshake within a deterministic retry window).

    Why this can't be shipped from /clud-fix today

    Every step of validation requires the bench:

    1. lpc21isp handshake is timing-sensitive (DTR/RTS sequencing into the LPC845-BRK's ISP-mode jumper). No way to dry-run.
    2. pyOCD flash on LPC845-BRK leaves the CDC port in Windows error 31 today (LPC845-BRK: deploy must always unwedge the shared VCOM (pyocd/SWD flash leaves CDC in Windows error 31) #565's report). The unwedge logic has to be measured against actual error-31 observation, not guessed.
    3. VCOM unwedge needs Device Manager / Setupapi calls that error-out without an actual device handle.
    4. The acceptance criterion in LPC845-BRK: deploy must always unwedge the shared VCOM (pyocd/SWD flash leaves CDC in Windows error 31) #565's comment ("must always unwedge the shared VCOM") needs N consecutive bench runs to claim "always."

    CI green for the Rust-side compile + unit-tested handshake mocking is necessary but not sufficient.

    Recommended path

    1. Open feat/551-lpc-deployer branch with the crate structure above, mocked handshake unit tests, and the dispatch wiring. Mark the PR draft until bench validation lands.
    2. During a bench session: connect the LPC845-BRK on a known COM port; run fbuild deploy tests/platform/lpc845 -e lpc845 end-to-end; observe (a) flash succeeds, (b) reset succeeds, (c) the AutoResearch-style flash-then-monitor cycle on the same VCOM port doesn't hit error 31 over N=10 iterations.
    3. Move PR to ready, merge.

    This shape is well-bounded — ~600–1200 LOC of Rust — but it's a multi-session task with the hardware loop in the middle, not a one-shot /clud-fix.

    Decision point

    If you'd like me to land the draft scaffold anyway (everything except vcom_unwedge.rs and the SWD path's flash-write, both of which are gated on bench observations), say so and I'll re-invoke /clud-fix on this issue with explicit "scaffold-only" scope. Otherwise this stays parked until the bench session.

    The hard-blocked nature of #565 (the VCOM unwedge ack criterion) makes me prefer to wait — landing scaffold without the unwedge sets up a follow-up PR that has to re-touch the same files.

    Filed via Claude Code — /clud-fix skill intake-gate report.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions