Skip to content

feat: UHF radio port to Semtech USP — v5e board, UspRadio integration, carried patches (central tracking PR) - #439

Open
Mikefly123 wants to merge 56 commits into
mainfrom
feat/usp-radio
Open

feat: UHF radio port to Semtech USP — v5e board, UspRadio integration, carried patches (central tracking PR)#439
Mikefly123 wants to merge 56 commits into
mainfrom
feat/usp-radio

Conversation

@Mikefly123

@Mikefly123 Mikefly123 commented Jul 11, 2026

Copy link
Copy Markdown
Contributor

AWAITING HUMAN REVIEW

This PR was generated by AI and is awaiting human cleanup before formal review and merge!

Summary

Ports the UHF radio path from the Zephyr drivers/lora SX127x stack to Semtech's USP (LoRa Basics Modem RAL/RAC) for the SX1262 on PROVES FCB v5e. This is the central PR for the USP port — companion PRs:

What's here

  • FCB v5e board (boards/bronco_space/proves_flight_control_board_v5e/): RP2350A + SX1262 (E22-400M30S: DIO3 TCXO 1.8 V / 10 ms, LDO reg-mode, external RF-switch GPIOs), DTS + defconfig + Kconfig.
  • DT-selected radio topology: RadioTopology_{Lora,Usp}.fppi variants — v5/v5c/v5d keep the legacy LoRa component untouched; v5e builds UspRadio. Command surface preserved (TRANSMIT, CONTINUOUS_WAVE) + new SET_TX_PROFILE / SET_RX_PROFILE.
  • West manifest + carried patches (patches/, applied via make usp-patches / make usp-core-patches / make submodules):
  • ADRs 0001/0002 (USP-for-SX126x decision, link-profile table), CONTEXT.md flight-radio glossary, FatalHandler direct sys_reboot, FlashWorker guard, host UT wiring.

HWIL verification (two v5e boards, flight ↔ GRC, 2026-07-04 → 07-11)

  • Profile matrix P0/P1/P3 bidirectional PASS through the full F´ com stack; legacy-GRC P0 interop PASS; chip-level auto-revert drill PASS; 20×CW wakeup probe clean.
  • Silent-reboot class root-caused and fixed (patches 0008/0009); regression drill (5× TRANSMIT past the 128 s failsafe window) zero reboots.
  • TX payload length defect root-caused in the RAL (SetPacketParams left at 0xFF placeholder) and fixed per-frame; TX↔RX parity exact at P3 saturation (80,116 == 80,116 bytes / 30 s), frames byte-exact, zero junk.
  • Per-profile saturated goodput sweep on trustworthy counters: P0 2.73 kbps (~94% of airtime ceiling) · P1 0.72 kbps (85–92%, LDRO) · P3 21.63 kbps (59–66%, protocol-limited) — zero drops / ConfigurationFailed / reboots across the final bench day.

Link profiles (table v1) and expected data rates

The versioned, append-only profile table (LinkProfiles.hpp, LINK_PROFILE_TABLE_VERSION = 1) replaces the single fixed LoRa configuration. Selected at runtime via SET_TX_PROFILE / SET_RX_PROFILE (RX changes carry an auto-revert deadline; frame receipt confirms). All profiles are constrained by the IARU UHF coordination limit of ≤125 kHz occupied bandwidth (OBW) — enforced by static_assert gates in the table.

Profile Modulation Raw PHY bitrate Goodput ceiling* Measured (HWIL, saturated) OBW vs 125 kHz cap Role / status
P0_LORA_SF8 (boot default) LoRa SF8, BW 125 kHz, CR 4/5 3.125 kbps ~2.9 kbps 2.73 kbps (~94% — PHY-limited) 125 kHz = at cap (all LoRa BW125 profiles) Nominal uplink/beacon; legacy-link parity. Verified bidirectional
P1_LORA_SF10 LoRa SF10, BW 125 kHz, CR 4/5, LDRO on 0.977 kbps ~0.78–0.85 kbps 0.72 kbps (85–92% — PHY-limited) 125 kHz = at cap Degraded-link fallback (~+5 dB margin over P0). Verified
P2_LORA_SF5 LoRa SF5, BW 125 kHz, CR 4/5 15.625 kbps ~14.8 kbps — not yet exercised over the air 125 kHz = at cap "Fast LoRa" (SX126x-only SF); shortest-range LoRa option. Unverified
P3_GFSK_38K GFSK 38.4 kbps, fdev 20 kHz, BT 0.5, CRC-16, whitening 38.4 kbps 32.7–36.8 kbps 21.63 kbps (59–66% — protocol-limited†) Carson 78.4 kHz (63%); BT0.5 57.6 kHz (46%) Primary bulk-downlink choice — real margin under the cap. Verified bidirectional
P4_GFSK_75K GFSK 75 kbps, fdev 25 kHz, BT 0.5, CRC-16, whitening 75 kbps ~64–72 kbps — no on-air test Carson 125.0 kHz = zero margin; BT0.5 112.5 kHz (90%) Max-rate bulk downlink. Not flight-ready until the spectrum-analyzer OBW mask is measured

* Goodput ceiling = payload bits ÷ single-frame airtime at max frame size (255 B), i.e. back-to-back transmission with zero gap — a PHY upper bound, not a link promise.

† Measured with receiver-side counters over 120 s saturated windows on the post-length-fix build (counters proven exact: TX↔RX 80,116 == 80,116). The P0/P1 numbers show the link is the modulation — software overhead is negligible at those airtimes. The P3 gap to ceiling is per-frame software protocol overhead (one-in-flight comStatus + per-frame radio task setup incl. a ~10 ms TCXO restart); the radio itself sustained 33.5 kbps (91–102% of ceiling) in an airtime-saturation test. Closing the gap is the TX-pipelining follow-up below — it is also what makes P4 worthwhile (at 75 kbps the airtime is ~28 ms/frame, so today's ~38 ms overhead would cap P4 below 50% of ceiling).

Net span of the table: roughly two orders of magnitude (P1 0.72 kbps ↔ P4 ~64+ kbps projected), trading data rate against link margin in the same direction. Note: un-saturated workloads (e.g. nominal telemetry cadence) will measure far below these numbers by design — early bench rungs saw ~67 bps effective on P0 simply because the TM pipeline handed the radio one small frame every ~7.5 s; the table's measured column is from deliberately saturated sweeps.

Outstanding work (tracked here)

Verification

  • Soak Fix issues with Makefile #5 duration run (multi-hour, STANDARD flight build) — DONE 2026-07-11 (5h50m, 7 cycles, 0 reboots/0 CF/0 drops; no recurrence of the 19:58 reboot) — see PR comment for full verdict
  • P2 (LoRa SF5): never exercised over the air
  • P4 (GFSK 75k): blocked on spectrum-analyzer OBW mask check (Carson estimate sits exactly at the 125 kHz IARU cap) — not flight-ready until measured
  • Flight-TX saturation numbers through the full ComCcsds chain (blocked on the Radio-TM quirk below)

Defects (non-blocking, filed/known)

  • Flight Radio TM packet (id 8, group 2: BytesSent/TxProfile/RxDropped) never emits even after SET_LEVEL 3 (LevelSet confirmed) — TlmPacketizer withholds it; use GRC counters + flight Beacon meanwhile
  • GRC/macOS control-CDC OUT wedge ~3–9 min post-enumeration (bench-host issue, firmware hunt open)

Performance follow-up (designed, not built)

  • TX pipelining slices 18a–18e per DESIGN-usp-tx-pipelining.md (gs-project): P3 is protocol-limited at ~59–66% of ceiling; largest known per-frame cost is the ~10 ms TCXO restart (planner force-sleeps at every task free + tcxo-wakeup-time = <10>); plan = instrument → TCXO calibration → accept-time comStatus (N=2) → RAC TX task chaining (32–35 kbps projected). Note: naive comStatus credit-priming is vetoed — Svc::ComQueue FW_ASSERTs on status-while-READY.

Design debt (deliberate deferrals)

  • run port is sync → radio tick work runs on the rate-group thread (make async)
  • DIO1 own-thread refactor; CW via k_timer instead of blocking the component queue (≤10 s durations acceptable meanwhile)

Upstreaming

  • Patches 0008/0009 → Lora-net (usp / usp_zephyr)
  • ComAggregator bounded-timeout + sched-tick-drop → nasa/fprime (fork branches staged, pending CCB issue)
  • openocd rp2xxx CPACR clobber fix (save/restore, local commit f8bc799) → Gerrit

Housekeeping

  • Bench RF overrides (frequency/power, settings.ini) deliberately not committed
  • fprime-zephyr usp-radio-port-carry branch: delete once GRC re-points at main

🤖 Generated with Claude Code

Mikefly123 and others added 30 commits July 4, 2026 14:27
Add CONTEXT.md (canonical flight-radio vocabulary) and two architecture
decision records:
  ADR 0001 — adopt Semtech USP (usp_zephyr) for SX126x radio path
  ADR 0002 — versioned Link Profile table shared between flight and ground

These were authored during the Phase 0 grill-with-docs session and are
the design baseline for all Phase 1+ implementation work.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Port the v5e board from GRC into proves-core-reference, adapted for the
USP radio driver:

- compatible "semtech,sx1262-new" (USP) instead of "semtech,sx1262"
  (in-tree Zephyr driver) — prevents driver collision at both DT and
  Kconfig level (LORA_BASICS_MODEM_DRIVERS depends on !LORA)
- dio3-as-tcxo-control + tcxo-voltage/tcxo-wakeup-time (USP property
  renames from the in-tree binding)
- reg-mode = SX126X_REG_MODE_LDO (required for E22-400M30S, no DC-DC)
- rx-boosted retained
- tx/rx-enable-gpios retained (gpio0 21/22); carried via our RF-switch
  HAL patch (spikes/patches/0001-feat-…)
- spi-max-frequency kept at 125000 Hz (GRC bring-up value; revisit
  comment added)
- Flash partitions inherited from proves_flight_control_board_v5.dtsi
  (MCUboot layout: boot 1M / slot0 1M / slot1 1M / storage beyond);
  storage_partition present — satisfies smtc_modem_hal_storage.c
- defconfig: bases on v5d; removes CONFIG_LORA/CONFIG_LORA_SX127X;
  adds CONFIG_LORA_BASICS_MODEM_DRIVERS + CONFIG_USP + RAL/RALF/trigger

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
west.yml additions:
  usp_zephyr  lib/zephyr-workspace/modules/lib/usp_zephyr
              pinned at bfacd43 (upstream; below our RF-switch patch)
              https://github.com/Lora-net/usp_zephyr
  usp         lib/zephyr-workspace/modules/lib/usp
              pinned at 351b201 (upstream HEAD)
              https://github.com/Lora-net/usp

Three patches carried in patches/:
  0001 — RF-switch GPIO support (tx/rx-enable-gpios) in USP SX126x HAL
          (spike commit a23856a; E22-400M30S needs external PA/LNA switch)
  0002 — Zephyr 4.3 compat: drop select ZEPHYR_LORA_BASICS_MODEM_MODULE
          (symbol internalized in 4.3; causes fatal Kconfig abort)
  0003 — LR_FHSS_SRC_PATH fix for flattened upstream usp directory layout

Patch mechanism: 'make usp-patches' follows the same git-apply + idempotency
check pattern as the existing submodules/fprime-gds patch targets. Apply
after 'west update usp_zephyr usp'.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Nested /* */ inside an outer block comment is a DTS parse error.
Convert the inner note to a plain-text parenthetical.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Adds test_LinkProfiles to the cmake -S PROVESFlightControllerReference/test/unit-tests
build so `make test` includes Phase 2 profile-table verification.  The test
file lives in the fprime-zephyr submodule; the cmake target references it
by absolute path following the existing pattern for cross-repo tests.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Adds test_ProfilePolicy target (28 tests: TX switch, RX confirm-by-frame,
revert expiry, revert counter, invalid index, no-interference between TX/RX
pending state, zero-revert_s, profile table spot-checks).
LINK_PROFILES_USE_HOST_TYPES injected via target_compile_definitions so
ProfilePolicy.cpp and the test source share the host-type typedefs without
a source-level #define.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Points to the Phase 4 fix commit on feat/usp-radio:
- Declares RalSessionImpl::applyLoRa_or_Gfsk() in the header
- Drops LoRaCfg.hpp include (incompatible with v5e CONFIG_LORA=n)

Previous Phase 3 pointer: 67fab55

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
UspRadio constructor refactor: configure(RalSession&) injection pattern
allows FPP autocoder to instantiate the component without USP headers.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Selection mechanism: CMake-selected FPP files (RadioInstances_*.fpp +
RadioTopology_*.fpp), chosen in Top/CMakeLists.txt based on the Kconfig
symbol CONFIG_LORA_BASICS_MODEM_DRIVERS (set in v5e defconfig, absent on
v5c/v5d).  No ifdefs in the shared subtopology; both radio types use the
same ComCcsdsLora subtopology surface (allocate/dataIn/dataOut/comStatus).

Wiring diagram:
  v5e: uspRadio (active, base 0x1001F000, q16 s4K pri11)
         -> ComCcsdsLora.commsBufferManager (allocate/deallocate)
         -> ComCcsdsLora.frameAccumulator   (uplink)
         -> ComCcsdsLora.framer             (downlink, direct — no retry shim)
         -> downlinkDelay -> framer.comStatusIn
         rateGroup1Hz[20] -> uspRadio.run  (1 Hz revert-tick + tlm flush)
  v5c/v5d: lora (passive) + loraRetry shim — unchanged from pre-Phase-4

Startup (v5e path):
  RalSessionImpl constructed at file scope (freq=915 MHz, pwr=+14 dBm).
  setupTopology() calls uspRadio.configure(s_ralSession) then
  uspRadio.start(DISABLED).  RalSessionImpl::init() calls
  zephyr_usp_initialization_wait() + zephyr_smtc_rac_init() internally.
  TX stays DISABLED until startup-sequence sends TRANSMIT(ENABLED)
  (identical gating to legacy LoRa path via StartupManager).

TopologyState: #ifdef CONFIG_LORA_BASICS_MODEM_DRIVERS selects between
  uspFreqHz/uspTxPowerDbm (v5e) and loraDevice (v5c/v5d).
Main.cpp: lora device DT_GET guarded by #ifndef CONFIG_LORA_BASICS_MODEM_DRIVERS.

Kconfig/prj.conf: no deployment-level additions needed; v5e defconfig from
Phase 1 already enables LORA_BASICS_MODEM_DRIVERS + USP + RAL_RALF +
EVENT_TRIGGER_GLOBAL_THREAD.  DIO1 trigger stays GLOBAL_THREAD (Phase 5
can switch to own thread if latency warrants it; RAM budget allows it).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…5d keep Zephyr::LoRa

Per-board radio selection via CMake symlinks + FPP include files:
- CMakeLists.txt creates RadioInstances.fppi / RadioTopology.fppi symlinks at
  configure time based on CONFIG_LORA_BASICS_MODEM_DRIVERS (USP) vs absent (Lora).
- Renamed Radio*.fpp → Radio*.fppi (text includes, not standalone FPP modules).
- Added RadioPacketsBytesReceived_{Lora,Usp}.fppi and RadioPacketsRadio_{Lora,Usp}.fppi
  so the packet set compiles with the correct per-board instance names.
- instances.fpp / topology.fpp now include the symlink instead of hard-coding lora.
- RadioPacketsRadio_Usp.fppi covers all 7 UspRadio telemetry channels (including
  ProfileTableVersion + RxReverts to satisfy FPP packet set completeness check).

v5e startup wiring:
- ReferenceDeploymentTopology.cpp: #ifdef CONFIG_LORA_BASICS_MODEM_DRIVERS path
  calls uspRadio.configure(s_ralSession) then uspRadio.startRadio(DISABLED).
- s_ralSession is a static RalSessionImpl(915 MHz, 14 dBm) at file scope.
- #else path keeps existing lora.start(device, DISABLED) unchanged.

v5e board config:
- DTS chosen { zephyr,lorawan-transceiver = &lora0_usp } for USP sw platform.
- defconfig: add CONFIG_USP_MAIN_THREAD=y to build the RAC run loop thread
  (provides zephyr_usp_initialization_wait() + smtc_rac_run_engine loop).

Build results:
- v5e: 697 KB flash / 346 KB RAM — links cleanly
- v5d: 681 KB flash / 337 KB RAM — no regressions
- Dictionary: 7 uspRadio TLM channels + 4 commands confirmed

.gitignore: add the 4 CMake-generated Radio*.fppi symlinks.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…nch.conf

hwil-bench.conf sets CONFIG_IMG_MANAGER=n (bench boards lack MCUBoot;
direct-flash to 0x10000000).  Without the guard, the v5e build fails
to compile FlashWorker.cpp which includes <zephyr/dfu/flash_img.h>
and references CONFIG_IMG_BLOCK_BUF_SIZE (only defined when IMG_MANAGER=y).

hwil-bench.conf committed as a bench build artifact so the EXTRA_CONF_FILE
in CMakeCache.txt references a tracked file.  Do not merge to main.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…fix)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…-path callback)

Includes the stripped functional fix for USP LOCK semantics:
- e7b9e8f fix(phase5): onPostRx/onPostTx three-path callback design for USP LOCK semantics
  Strip debug traces from f7bf1f7+5465195 working tree; keep RP_STATUS_RADIO_LOCKED
  path for packet harvest + re-arm, three-path TX done handling.
  Verified: rebuilt + reflashed PROBE_TWO; BytesReceived advancing on rung 3 recheck.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…atches target

Add patches/0004-fix-usbd-cdc-acm-poll-mode-tx-drain-on-class-enable.patch:
- In usbd_cdc_acm_enable(), schedule tx_fifo_work when not in IRQ mode and
  TX ring buffer is non-empty (else-if branch after the IRQ-mode path).
- Root cause: F Prime ComCcsdsUart writes to FIFO during early boot before
  USB host enumerates; class enables later; poll-mode path never re-drains.
  Without fix: board sends 8-12 AOS frames then goes silent over USB CDC.

Add Makefile zephyr-patches target to apply the patch to lib/zephyr-workspace/zephyr
(mirrors usp-patches pattern; idempotent via forward/reverse check).

Working tree remains patched. Dies on next `west update zephyr` without re-running
`make zephyr-patches`.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…etry

Patch 0005 adds two changes on top of 0004 for the USB CDC-ACM secondary
silence on RP2350/macOS:

1. usbd_cdc_acm_disable(): clear CDC_ACM_TX_FIFO_BUSY so a stuck IN
   transfer (host stopped issuing IN tokens without disconnecting) does not
   permanently block the TX path after the host re-enables the class.

2. cdc_acm_tx_fifo_handler(): self-reschedule with K_MSEC(10) when BUSY is
   already set and the ring buffer is non-empty. Creates a 10 ms retry loop
   that re-arms the IN endpoint as soon as the host resumes polling, instead
   of waiting for another uart_poll_out() call.

Root cause confirmed via socat bench test: macOS USB CDC driver stops issuing
IN tokens after the initial ~1024-byte burst. All upstream Zephyr 4.3.0 fixes
are already present. The 10 ms retry eliminates permanent silence; residual
25 s (now 3-5 min on active GDS sessions) stall is a macOS driver constraint.

Makefile zephyr-patches target updated to apply 0004 then 0005 in order.
Both patches are idempotent (forward/reverse check before apply).

Signed-off-by: Michael Pham <phamlongmichael@gmail.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…splay fix)

ProfilePolicy::tick() no longer overwrites pendingRxProfile with P0 before
the kRevert caller reads it; ProfileReverted log now shows correct "from P1
to P0" instead of "P0 to P0". Observed and confirmed via HWIL rung 5 run.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ening fix)

ral_set_gfsk_pkt_params() does not program the SX126x CRC init/poly registers
or the whitening LFSR seed; this caused GFSK TX/RX CRC mismatches.  Fixed in
401d926 by adding ral_set_gfsk_crc_params(0x1D0F, 0x1021) and
ral_set_gfsk_whitening_seed(0x01FF) calls after pkt_params, matching the Phase-0
known-good ralf_setup_gfsk reference.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The SX126x needs ~340us (t_woff warm start) after the wake-up SPI
transaction before BUSY is trustworthy; polling immediately races it and the
next SPI command is silently dropped (~40% in release builds). In the USP
HAL this manifested as LoRa->GFSK profile-switch failures: the dropped
first-after-wake command is ral_set_pkt_type, a harmless no-op for
LoRa->LoRa (type already LORA) but fatal for GFSK switches.

Port of the GRC-validated fix (k_busy_wait(500) between the wake-up NSS
glitch and the BUSY poll in sx126x_hal_check_device_ready). HWIL evidence
2026-07-10: profile-switch success 1/3 -> 9/10 across an alternating
P1/P3 stress run.

Carried as patches/0006 applied by make usp-patches (idempotent
forward/reverse check; verified to apply cleanly on top of patch 0001 and
reverse-check against the already-patched tree).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
cdc_acm_poll_out sleep-retries UNBOUNDED while a host CDC session is
attached (DTR/flow_ctrl set) and the TX ring is full. With the known macOS
attached-but-stalled sessions (host ceases IN polling for minutes), every
console write then takes the full stall duration. Console writes live in
high-traffic paths (event text logging, assert reporting), so one stall
cascades into a com-stack livelock — root-caused on HWIL 2026-07-10 via a
live thread-walk of a wedged board (rateGroup10Hz stuck re-asserting,
producers pending in blocking queue sends, aggregator ready but starved).

Bound the retry to ~20 ms, then discard — identical to the existing
detached (!flow_ctrl) behavior. Flight-neutral: with no USB host attached
there is no DTR, so flight builds already discard immediately; this only
changes attached ground-test sessions.

Carried as patches/0007 applied by make zephyr-patches (idempotent
forward/reverse check; reverse-check verified against the patched tree).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
FatalReceive previously only logged and stopped the external GPIO-petted
watchdog, relying on that watchdog to reset the board. On benches where the
external WDT is absent or ineffective, a FATAL left the system running in a
degraded zombie state. Now mirrors the fprime-zephyr reference pattern:
stopWatchdog, brief delay for log drain, then sys_reboot(WARM) with a COLD
fallback.

Proof-of-function evidence (2026-07-10): a boot-time FW_ASSERT (task-start
failure) produced a clean reboot loop through this path — the reset fires
whenever a FATAL actually reaches this component. Known residual, tracked
separately: FATAL events can still be lost in the event pipeline under com
backpressure before reaching FatalReceive.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ardening)

Picks up:
- ZephyrUartDriver TX staging ring + writer thread + comStatus flow control
- RalSessionImpl::stopRadio abort-failure propagation

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…mp fprime-zephyr

- patches/fprime-com-aggregator-bounded-timeout.patch: bound timeout-signal
  queue occupancy in Svc::ComAggregator::timeout_handler. Upstream's
  m_allow_timeout guard (fprime #4402) only covers WAIT_STATUS; in FILL a
  stalled dispatch thread still let 10 Hz ticks fill the depth-15 queue in
  ~1.5 s and trip the autocoded queue-full FW_ASSERT (four identical gdb
  captures across HWIL soaks #1-#4). Timeout ticks are periodic and
  idempotent, so they are now skipped unless the queue retains headroom for
  the in-flight flow-controlled fill/status signals. Applied by
  'make submodules' like the fprime-gds version patch.
- bump lib/fprime-zephyr to 1b7b8c4: stopRadio waits for RAC hook FINISHED
  instead of a fixed 50 ms abort window (transient ConfigurationFailed on
  ~1/10 profile switches).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…pRadio hardening bump

- patches/fprime-sched-tick-drop.patch: add 'drop' queue-full behavior to the
  periodic Svc.Sched async inputs of 8 upstream Svc components. gdb tripwire
  captured safeModeSeq (Svc::CmdSequencer schedIn_handlerBase) hitting the
  identical queue-full FW_ASSERT as issue #432 during soak #5 — the defect
  class is any rate-group-fed active component, not just ComAggregator.
  Upstream precedent: ComQueue.run and ActiveRateGroup.CycleIn already drop.
- bump lib/fprime-zephyr: stopRadio waits the full deadline on the abort
  post-callback instead of returning on a freshly-freed hook state (racing
  the in-flight abort callback inside the radio planner).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…instance)

gdb tripwire on PROBE_ONE captured the identical queue-full FW_ASSERT via
Svc::Health's 1 Hz ping: HealthImpl::Run_handler → rateGroup50Hz
PingIn_handlerBase (ActiveRateGroupComponentAc.cpp:686, arg=8) — and the
FATAL propagated to FatalHandler and hard-rebooted, confirming propagation
again. Pings are the third unbounded periodic producer after aggregator
timeout signals and sched ticks. Dropping a ping is Health's designed
unresponsive-component path (ping-timeout policy with tolerance), strictly
better than asserting inside the health check itself. Patch now covers 16
Svc fpp files: 8 Svc.Sched inputs + 14 PingIn/pingIn inputs.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Inert without CONFIG_USE_SEGGER_RTT (not set in flight config); needed by
logs/resume-0710/rtt-debug.conf to capture the silent P3 TX-switch reboot.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Two USP-library defects behind the silent-reboot class, both HWIL-captured
on RP2350 (see logs/resume-0710/SESSION3-0711-rac-mutex.md):

- 0008: usp_zephyr's smtc_modem_hal_protect_api_call()/unprotect were
  bare-metal no-op stubs; the RAC engine thread and API callers mutated
  radio-planner task structs unserialized (torn slots -> RP_FAILSAFE with
  type != LOCK on a lock task, TX launch through the LR-FHSS branch on a
  LoRa profile). Implemented with a k_mutex (recursive by owner, priority
  inheritance); IRQ callbacks never call protect.

- 0009: rp_callback's 128 s failsafe only exempted LOCK_RADIO_ACCESS, but
  unlock_radio_access retypes the still-RUNNING task to UNLOCK before the
  engine processes it -> any lock held > 128 s (continuous RX under raw
  RAC) panicked the board on the first engine pass after the next radio
  command. Exempt UNLOCK as well.

Applied via make usp-patches (0008) and new make usp-core-patches (0009).
Bench-verified: 5x TRANSMIT past the 128 s window zero reboots; fixverify
A/B1/B2 + slice-13 chip-level revert all PASS with zero ConfigurationFailed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ch-verified)

751a1a8 deferredSet{Rx,Tx}Profile honor stopRadio() rc
ee4599d RX re-arm after TX episodes + stale-semaphore/hook-race fixes
bdfcb4a RX auto-revert applies to hardware (retried until it lands)

HANDOFF-0711b step-1 verification PASS on bench 2026-07-11 (with carried
usp patches 0008/0009): (i) TX episodes with ZERO ConfigurationFailed:RX,
(ii) boards hear immediately after TRANSMIT DISABLED both directions,
(iii) slice-13 revert re-arms the chip (post-revert byteflow).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… to fprime-zephyr 320d8ed + RxDropped packet entry

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…th per frame)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@Mikefly123

Mikefly123 commented Jul 24, 2026

Copy link
Copy Markdown
Contributor Author

Carried-patch → fork-PR migration tracker (2026-07-23)

Every carried patch now has a PR on the matching OSSF fork, byte-equivalent to the patch-applied tree (deviations documented per PR). This is the source of truth for the eventual re-pin/delete-patches pass. No re-pin has been done; nothing merged.

Open-Source-Space-Foundation/usp_zephyr (base: main = pin bfacd43)

Open-Source-Space-Foundation/usp (base: main = pin 351b201)

Open-Source-Space-Foundation/zephyr (base: new branch v4.4.1-base @ v4.4.1 tag — fork main tracks upstream main)

Open-Source-Space-Foundation/fprime (base: new branch pcr-usp-base @ submodule pin 8a62e45)

Out of scope pending decision

  • fprime-yamcs-noapp-path.patchout of scope for this effort (owner decision 2026-07-23); remains a carried pip patch, to be handled separately if ever.

HIL regression plan for the fork-branch build (pre-re-pin gate): logs/HIL-REGRESSION-PLAN-fork-migration.md on the bench host.

@Mikefly123

Copy link
Copy Markdown
Contributor Author

Follow-up: new perf/profile PRs (NEW work — separate from the carried-patch migration list above)

These came out of the 2026-07-23/24 downlink throughput-ladder and P5 bench campaigns; none are carried patches:

For completeness, the earlier ladder PRs already open on this repo: #475 (ComDelay divider-0 passthrough, 1.9→2.5 kB/s) and #476 (v5e SPI 4 MHz + TCXO 5 ms, 2.5→4.3 kB/s).

🤖 Generated with Claude Code

@Mikefly123

Copy link
Copy Markdown
Contributor Author

Carried-patch collapse: done. All 12 migrated patches are now consumed via the feat/proves-usp-radio integration branches, pinned by de-patch PR #477 (base = this branch):

fprime-yamcs-noapp-path.patch remains carried (pip package, out of scope per 2026-07-23 decision). Byte-equivalence, delta classification, build, and bench smoke evidence in #477.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
PROVESFlightControllerReference/test/int/radio_test.py (1)

90-90: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Use timezone-aware event boundaries.

TimeType().set_datetime(datetime.now(), time_base=TimeType.TimeBase("TB_DONT_CARE")) records the host’s local timestamp at lines 90 and 110. If the test and GDS run in different time zones, the zero-timeout await_event start boundary can be too far in the future and miss recent radio events; use datetime.now(timezone.utc) here if GDS event history is anchored to UTC.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@PROVESFlightControllerReference/test/int/radio_test.py` at line 90, Update
the datetime values passed to TimeType.set_datetime in the radio event boundary
setup, including both occurrences around the test flow, to use timezone-aware
UTC timestamps via datetime.now(timezone.utc). Preserve the existing time_base
argument and await_event behavior.

Source: Linters/SAST tools

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.gitmodules:
- Around line 3-6: Update the Makefile submodule initialization flow to run git
submodule sync --recursive before git submodule update, ensuring existing
checkout configuration uses the URLs from .gitmodules.

In `@PROVESFlightControllerReference/test/int/radio_test.py`:
- Around line 78-82: Set retries=1 on both continuous-wave command invocations
in the radio test, including the visible proves_send_and_assert_command call and
the second CW invocation, so failures cannot be hidden by retries.

---

Outside diff comments:
In `@PROVESFlightControllerReference/test/int/radio_test.py`:
- Line 90: Update the datetime values passed to TimeType.set_datetime in the
radio event boundary setup, including both occurrences around the test flow, to
use timezone-aware UTC timestamps via datetime.now(timezone.utc). Preserve the
existing time_base argument and await_event behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: c876d715-3fe7-457c-a3e2-5fd46537a141

📥 Commits

Reviewing files that changed from the base of the PR and between 2b997f8 and 71ceaf3.

📒 Files selected for processing (9)
  • .gitmodules
  • .pre-commit-config.yaml
  • Makefile
  • PROVESFlightControllerReference/test/int/radio_test.py
  • lib/fprime
  • lib/fprime-zephyr
  • lib/zephyr-workspace/zephyr
  • patches/README.md
  • west.yml

Comment thread .gitmodules
Comment on lines +3 to +6
url = https://github.com/Open-Source-Space-Foundation/fprime.git
[submodule "lib/zephyr-workspace/zephyr"]
path = lib/zephyr-workspace/zephyr
url = https://github.com/zephyrproject-rtos/zephyr.git
url = https://github.com/Open-Source-Space-Foundation/zephyr.git

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Synchronize existing submodule checkouts with the new URLs.

Changing .gitmodules does not update URLs already stored in existing clones’ .git/config. Since Makefile:10-13 runs git submodule update without git submodule sync, developers with an existing checkout may continue fetching the old upstream repositories instead of the OSSF forks.

Add git submodule sync --recursive before the update, or explicitly document that migration step.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.gitmodules around lines 3 - 6, Update the Makefile submodule initialization
flow to run git submodule sync --recursive before git submodule update, ensuring
existing checkout configuration uses the URLs from .gitmodules.

Comment on lines 78 to 82
proves_send_and_assert_command(
fprime_test_api,
f"{lora}.CONTINUOUS_WAVE",
f"{radio}.CONTINUOUS_WAVE",
[CW_SECONDS],
)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Do not let retries hide a continuous-wave regression.

proves_send_and_assert_command clears histories before each attempt and retries failures for radio runs. A CW attempt can therefore fail with EXECUTION_ERROR, then pass on a later attempt while this test still succeeds. Use retries=1 for both CW commands, or preserve/assert failures from every attempt.

Proposed fix
     proves_send_and_assert_command(
         fprime_test_api,
         f"{radio}.CONTINUOUS_WAVE",
         [CW_SECONDS],
+        retries=1,
     )

Apply the same retries=1 argument to the second CW invocation.

Also applies to: 92-96

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@PROVESFlightControllerReference/test/int/radio_test.py` around lines 78 - 82,
Set retries=1 on both continuous-wave command invocations in the radio test,
including the visible proves_send_and_assert_command call and the second CW
invocation, so failures cannot be hidden by retries.

* test(int): codify RF mode/profile matrix as integration tests

Adds rf_profile_matrix_test.py covering HIL regression rung 6:
- TX/RX profile sweeps P0->P1->P2->P3 and back (ProfileChanged asserted,
  no ConfigurationFailed/InvalidProfile, board commandable throughout)
- CONTINUOUS_WAVE with clean restore to RX (complements the issue-#207
  regression test by exercising a post-CW RX reconfig)
- profile-switch-as-first-post-wake command hammer (SX126x wakeup-race
  shape; cycle count/idle env-overridable for bench hammer runs)
- post-idle (>=90 s) profile switch sanity
- two-board profile-pairing downlink/uplink tests (new two_board_rf
  marker; self-skip unless USP_GROUND_* env hooks are configured)

All tests are uart_only (profile switches sever the RF link) so they run
in the integration-uart job and are excluded from integration-radio.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* test(int): extend RF profile matrix to P5 + GFSK wedge kill-recipe regression

- PROFILE_SWEEP / PROFILE_IDS now cover P4_GFSK_75K and P5_GMSK_83K
  (profile table v2, validated on the bench in Phase B)
- New test_08_gfsk_wedge_kill_recipe: healthy P0 traffic -> brief idle ->
  P0->P4/P5 switch -> immediate TX, asserting no SendFailed/
  ConfigurationFailed and BytesSent channel movement at the target
  profile and after return to P0 (regression for the Phase B anomaly-B
  SX126x active-RX SetTx wedge, fixed in fprime-zephyr PR #21)
- Repeat count / idle window parameterized via RF_WEDGE_KILL_CYCLES /
  RF_WEDGE_IDLE_S (defaults CI-small)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
@Mikefly123

Copy link
Copy Markdown
Contributor Author

FYI: the fprime-zephyr USP PR set has been converted to a native GitHub stacked-PR stack as a pilot (stack #24: Open-Source-Space-Foundation/fprime-zephyr#19#23#20 on feat/usp-radio; integration PR #22 closed, branch retained for the pcr submodule pin). If the review experience is good, the other forks will follow the same recipe.

@Mikefly123

Copy link
Copy Markdown
Contributor Author

Stacked-PR conversion complete (2026-07-30)

All fork PR sets and the pcr ComDelay chain are now native GitHub stacked PRs (public-preview feature). Constituent PRs remain the per-change review record; each stack top is tree-verified against its retired integration branch; integration PRs closed but their branches RETAINED (pcr pins still reference them until each stack cascade-merges and pcr re-pins).

Repo Stack Layers (bottom→top) Retired integration PR
fprime-zephyr #24 #19 semaphore ← #23 GFSK wedge fix (replaces #21) ← #20 P5 GMSK (draft) #22
usp #4 #1 failsafe UNLOCK ← #2 lazy-sleep hysteresis #3
fprime #6 #1 ComAggregator ← #2 sched tick-drop ← #3 TlmPacketizer I16 #5
usp_zephyr #8 #1 RF-switch ← #2 Kconfig ← #3 LR_FHSS ← #4 wakeup settle ← #5 RAC mutex ← #6 board.yml #7
zephyr #4 #1 CDC TX-FIFO drain ← #2 poll-out bound #3
proves-core-reference #484 #475 ComDelay divider-0 passthrough (draft) ← #479 ComDelayLogic UTs (now incl. passthrough tests; 13/13)

Notes: every stack top verified tree-identical to its integration branch (pcr #484 excepted — it's new integration: passthrough ported into ComDelayLogic + 4 new tests). fprime-zephyr #21 could not join its stack (GitHub permanently records it merged from the reverted fc0d0f0 accident) — #23 supersedes it on the same branch. Merge path per repo: cascade-merge the stack bottom-up, then pcr re-pins to the resulting branch head and the feat/proves-usp-radio branches can be deleted.

Mikefly123 and others added 2 commits August 2, 2026 16:31
…ranches (#487)

Drops two changes from the USP radio changeset as part of the pre-merge
blast-radius trim. Both were pruned on the merits; neither costs any
measured throughput.

fprime-zephyr c81485f -> 3da24b8 (feat/proves-usp-radio-trimmed)
  Removes the re-arm semaphore (OSSF/fprime-zephyr#19, closed). It had a
  measured null result — 35.8 s before and after, 1.00x on the downlink
  ladder — and the PR body itself retracted its premise: the k_sleep poll
  it replaced never actually slept at saturation.

  The GFSK RX->TX wedge fix (#23) was rebased off it onto feat/usp-radio.
  Verified: the rebased tree differs from the pre-trim tree by exactly the
  semaphore's 30 lines in UspRadio.cpp — quiesceRadio(), the TX_DONE
  timeout recovery, the comStatus-SUCCESS-after-drop path and the
  TxOutcomePolicy UTs are all intact. P5_GMSK_83K (#20, draft) was rebased
  onto the new #23 head and is retained unchanged.

zephyr 3838a28 -> 144acbc (feat/proves-usp-radio-trimmed)
  Removes the cdc_acm poll_out bounded wait (OSSF/zephyr#2, closed). The
  added bound sits behind data->flow_ctrl, which is false on the v5e from
  both directions: hw-flow-control is absent from the cdc_acm_uart0 node
  (verified in the generated devicetree, not just source), and the sole
  uart_configure() caller sets UART_CFG_FLOW_CTRL_NONE. The code was
  unreachable in the flight image.

  The TX-FIFO drain fix (#1) is explicitly RETAINED — it is reachable
  wherever a USB host is enumerated, which includes the self-hosted
  integration-uart/integration-radio runners and their Korad power-cycle
  and re-enumeration steps. Fork delta drops from +21/-1 to +11/-0.

Old branches feat/proves-usp-radio are retained on both forks pending
review of this pin; pruned branches are retained for recoverability.

Co-authored-by: Claude <noreply@anthropic.com>
Resolves the two conflicts from `git merge origin/main`:

1. ReferenceDeployment/Top/topology.fpp — refactor-vs-edit. Kept our
   `include "RadioTopology.fppi"` (CMake selects the per-board variant
   before the FPP autocoder runs) and ported ALL of main's PR #443
   startup-quiescence wiring into BOTH RadioTopology_{Lora,Usp}.fppi.
   Nothing from either side was dropped.

   Also adopts main's StartupManager port renames:
     sequenceStarted  -> startupsequenceStarted
     completeSequence -> startupCompleteSequence

   Usp variant binds the radio-side quiescence ports to uspRadio, whose
   first-start signal is named radioFirstStart (radio-agnostic); both
   ends are Fw.Signal so it binds to startupManager.loraFirstStart.

2. lib/fprime-zephyr — pinned to 9e3bedc (feat/proves-usp-radio-trimmed:
   fprime-zephyr main/PR #18 merged in, GFSK RX/TX wedge fix PR #23,
   P5_GMSK_83K profile PR #20).

Also:
- RadioPacketsRadio_Usp.fppi: drop uspRadio.ProfileTableVersion. That
  constant telemetry channel was deleted upstream in fprime-zephyr
  2830b73 ("trim UspRadio changeset for minimal diff"); leaving it here
  fails fpp-check against the new pin.
- west.yml: refresh the stale comment above the zephyr entry. The
  revision has pointed at stock upstream v4.4.1 since #487; the comment
  still described the OSSF fork and its carried patches.

Verified:
- fpp-check clean (rc=0) for BOTH deployment variants, with the CMake
  RadioTopology/RadioInstances/RadioPackets* symlinks pointed at each
  variant in turn. No startupManager/radio quiescence port appears in
  the fpp-check --unconnected output for either variant.
- make test-unit: 12/12 pass, including test_TxOutcomePolicy.

Co-Authored-By: Claude <noreply@anthropic.com>
@ineskhou

ineskhou commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

Before Merging:

  • Ensure we can do a bootloader swap with this branch
  • Ensure Command Loss timer works
  • ensure hardcoded startup works

@Mikefly123

Copy link
Copy Markdown
Contributor Author

Status update — related-PR map across forks (2026-08-05)

Posting this as the current source of truth since the fix-forward work on the radio component has spread across a few stacked/parallel PRs in fprime-zephyr. Links below are the direct PR URLs, not shorthand.

fprime-zephyr (the Zephyr::UspRadio component)

proves-core-reference (this repo)

ground-radio-controller

Net: what's actually outstanding

  1. Get Move closer to D as default #26's build result (in progress via chore: bump lib/fprime-zephyr to fprime-zephyr#26 head (CI validation only, do not merge) #506).
  2. Once green, merge Move closer to D as default #26 into fprime-zephyr's feat/usp-radio, then update this PR's (feat: UHF radio port to Semtech USP — v5e board, UspRadio integration, carried patches (central tracking PR) #439) submodule pin to that post-merge head for real (replacing the throwaway chore: bump lib/fprime-zephyr to fprime-zephyr#26 head (CI validation only, do not merge) #506 pin).
  3. Close/clean up chore: bump lib/fprime-zephyr to fprime-zephyr#26 head (CI validation only, do not merge) #506 once superseded.

🤖 Generated with Claude Code

@Mikefly123

Mikefly123 commented Aug 5, 2026

Copy link
Copy Markdown
Contributor Author

📍 Review map: source of truth for the USP port across the org forks (updated 2026-08-05)

The earlier per-repo "collapse" integration PRs have all been closed in favor of native GitHub stacked PRs — the stacks below are now the canonical review surface. This PR remains the central tracking PR (HWIL evidence, profile table, outstanding work).

Recommended merge order: fprime-zephyr #14 → this PR (#439) → ground-radio-controller #25 (then re-point the GRC submodule at fprime-zephyr main).

fprime-zephyr — the Zephyr::UspRadio component

PR What Status
#14 Zephyr::UspRadio — the core USP (RAL/RAC) radio component for SX126x Open — review first
#26 Async run port, RX re-apply on re-arm, comStatus one-in-flight invariant Open — follow-up fix layer
#25 ZephyrUartDriver TX-writer thread (drop-not-block on stalled CDC console) Open
#20 P5_GMSK_83K profile Draft — flight-adoption conditions listed in the PR
#23 SX126x standby-on-stop + TX_DONE-timeout recovery (HWIL anomaly B) Merged (replaces the merged-then-reverted #21)
#22 Old collapse PR Closed — superseded by the stack above; tree-identical, no content lost
#19 Semaphore re-arm-skip wait Closed as pruned (measured 1.00× null effect) — do not review

ground-radio-controller — ground-side port

PR What Status
#25 GRC port to UspRadio (SX1262 / FCB v5e), carried patches, link-profile parity Open — review after this PR
#18 FCB v5e board support (E22-400M30S wiring) — base layer under #25 Open

usp_zephyr — carried Zephyr-glue patches (stacked, bottom→top)

#1 SX126x external RF-switch GPIOs → #2 Zephyr 4.3 module-select fix → #3 LR-FHSS src-path fix → #4 SX126x wake-up BUSY-race settle delay → #5 RAC API mutex → #6 xiao_nrf54l15 board.yml schema fix. All open; stack-top verified tree-identical to the pinned feat/proves-usp-radio branch.

usp — LoRa Basics Modem core (stacked)

#1 radio-planner failsafe: exempt UNLOCK_RADIO_ACCESS from the panic (half of the silent-reboot fix, with the usp_zephyr RAC mutex) → #2 radio-planner lazy-sleep hysteresis via STDBY_XOSC. Both open.

fprime — Svc robustness under comms overload (stacked #1#2#3, plus #4)

#1 ComAggregator bounded timeout-signal enqueue (issue #432) → #2 drop periodic Sched/Ping ticks on queue-full instead of asserting → #3 TlmPacketizer packetOffset storage shrink. All three open, based on pcr-usp-base. Note the 2026-08-01 changeset-trim pass (which pruned fprime-zephyr #19 and the zephyr CDC patches, validated by pcr #487) left an open question on #2: whether all 16 .fpp sched-tick-drop annotations are needed or only the components actually in our deployment's rate groups — reviewers may see that trimmed before merge.

Separately, #4 (never FATAL on comms overload) is not part of the USP stack — it's based on devel and targets upstream nasa/fprime for the issue #471 assert-cascade; review it on its own track.

fprime-extras

#1 BUFFER_FANOUT_MAX_BUFFERS_IN_FLIGHT 10 → 40. Open.

zephyrnothing to review

Both CDC-ACM patches were pruned after a CI negative control (pcr #487: stock Zephyr v4.4.1, full green incl. integration-uart / integration-radio on hardware): #1 validated unnecessary, #2 unreachable with flow_ctrl off. Branches retained for recoverability.

Note for reviewers: the closed feat: PROVES USP radio integration (collapses …) PRs in each fork (fprime-zephyr #22, usp_zephyr #7, usp #3, fprime #5, zephyr #3) are superseded, each verified tree-identical to its replacement stack before closing. Their feat/proves-usp-radio branches stay alive only because this repo's manifests still pin them; they'll be deleted once the stacks cascade-merge and the pins move to main.

🤖 Generated with Claude Code

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

Labels

None yet

Projects

Status: New Features

Development

Successfully merging this pull request may close these issues.

2 participants