feat: UHF radio port to Semtech USP — v5e board, UspRadio integration, carried patches (central tracking PR) - #439
feat: UHF radio port to Semtech USP — v5e board, UspRadio integration, carried patches (central tracking PR)#439Mikefly123 wants to merge 56 commits into
Conversation
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>
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:
|
|
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 |
|
Carried-patch collapse: done. All 12 migrated patches are now consumed via the
|
There was a problem hiding this comment.
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 winUse 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-timeoutawait_eventstart boundary can be too far in the future and miss recent radio events; usedatetime.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
📒 Files selected for processing (9)
.gitmodules.pre-commit-config.yamlMakefilePROVESFlightControllerReference/test/int/radio_test.pylib/fprimelib/fprime-zephyrlib/zephyr-workspace/zephyrpatches/README.mdwest.yml
| 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 |
There was a problem hiding this comment.
🎯 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.
| proves_send_and_assert_command( | ||
| fprime_test_api, | ||
| f"{lora}.CONTINUOUS_WAVE", | ||
| f"{radio}.CONTINUOUS_WAVE", | ||
| [CW_SECONDS], | ||
| ) |
There was a problem hiding this comment.
🎯 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>
|
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. |
…wedge-fix host UTs) (#480)
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).
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. |
…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>
|
Before Merging:
|
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 (the
|
📍 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
|
| 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) |
| Old collapse PR | Closed — superseded by the stack above; tree-identical, no content lost | |
| 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.
zephyr — nothing 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. Theirfeat/proves-usp-radiobranches 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
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/loraSX127x 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:Zephyr::UspRadiocomponent (merge first)What's here
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.RadioTopology_{Lora,Usp}.fppivariants — 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.patches/, applied viamake usp-patches/make usp-core-patches/make submodules):protect_api_call→ k_mutex): torn radio-planner task slotsCONTEXT.mdflight-radio glossary, FatalHandler directsys_reboot, FlashWorker guard, host UT wiring.HWIL verification (two v5e boards, flight ↔ GRC, 2026-07-04 → 07-11)
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 viaSET_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 bystatic_assertgates in the table.* 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
Defects (non-blocking, filed/known)
SET_LEVEL 3(LevelSet confirmed) — TlmPacketizer withholds it; use GRC counters + flight Beacon meanwhilePerformance follow-up (designed, not built)
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::ComQueueFW_ASSERTs on status-while-READY.Design debt (deliberate deferrals)
runport is sync → radio tick work runs on the rate-group thread (make async)Upstreaming
Housekeeping
usp-radio-port-carrybranch: delete once GRC re-points at main🤖 Generated with Claude Code