From 6123816f0d31c3eca57d9a01cf5563b4412a318e Mon Sep 17 00:00:00 2001 From: Michael Pham <61564344+Mikefly123@users.noreply.github.com> Date: Sat, 4 Jul 2026 14:27:26 -0700 Subject: [PATCH 01/51] docs: USP port ADRs + flight-radio glossary MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- CONTEXT.md | 30 +++++++++++ .../0001-semtech-usp-for-sx126x-radio-path.md | 54 +++++++++++++++++++ docs/adr/0002-link-profile-table.md | 43 +++++++++++++++ 3 files changed, 127 insertions(+) create mode 100644 CONTEXT.md create mode 100644 docs/adr/0001-semtech-usp-for-sx126x-radio-path.md create mode 100644 docs/adr/0002-link-profile-table.md diff --git a/CONTEXT.md b/CONTEXT.md new file mode 100644 index 00000000..c2fc1394 --- /dev/null +++ b/CONTEXT.md @@ -0,0 +1,30 @@ +# CONTEXT — PROVES Flight Radio + +Glossary for the flight-software radio domain. Terms here are canonical; use them in code, docs, commands, and telemetry names. + +## Radio paths + +- **USP Radio Path** — the radio stack built on Semtech's Unified Software Platform (USP). Applies to SX126x-class boards (FCB v5e onward). Multi-modulation capable. +- **Legacy Radio Path** — the existing loramac-node-backed Zephyr `drivers/lora` stack wrapped by the `Zephyr::LoRa` component. Applies to SX127x-class boards (FCB v5/v5c/v5d). LoRa modulation only. Kept buildable, not extended. +- **USP (Unified Software Platform)** — Semtech's radio software platform (radio drivers + RAL + radio access arbitration + LoRa Basics Modem). Not to be confused with a Zephyr-project product; `usp_zephyr` is its Zephyr integration module. +- **LBM (LoRa Basics Modem)** — Semtech's modem library bundled inside USP; its LoRaWAN stack is unused by PROVES (we fly raw CCSDS point-to-point). +- **RAL (Radio Abstraction Layer)** — USP's chip-agnostic radio API. The seam the flight component talks to (and the seam mocked in unit tests). + +## Link configuration + +- **Link Profile** — a complete, named radio configuration: modulation plus every parameter needed for two radios to interoperate (e.g. for GFSK: bitrate, deviation, BT, sync word, CRC, preamble). Identified by index into the Profile Table. Profiles are switched atomically; individual RF parameters are never commanded piecemeal in operations. +- **Profile Table** — the versioned, checked-in list of Link Profiles shared verbatim by flight and ground builds. Both ends must be built from the same table version for a profile index to mean the same thing. +- **TX Profile / RX Profile** — the Link Profile currently applied to the transmit and receive directions independently. The link is asymmetric by design (e.g. robust LoRa uplink, high-rate GFSK downlink). +- **Boot-Default Profile** — the profile each direction starts in at boot, and the profile RX Auto-Revert falls back to. Chosen for maximum link robustness, not throughput. +- **RX Auto-Revert** — safety mechanism: after an RX Profile change, if no valid frame is received within the commanded revert window, the RX Profile reverts to the Boot-Default Profile. Receiving a valid frame on the new profile confirms it. + +## Modulations + +- **CW (Continuous Wave)** — unmodulated carrier transmission for beacons, range testing, and RF debug. A test mode, not a Link Profile. +- **GFSK** — Gaussian FSK packet modulation; the high-throughput downlink option on the USP Radio Path. Capped at ~75–80 kbps by the Band Constraint. +- **Band Constraint** — IARU coordination limits PROVES UHF emissions to ≤125 kHz occupied bandwidth. Every Link Profile must satisfy it. +- **LR-FHSS** — long-range frequency-hopping modulation. SX126x can transmit but never receive it, so it is out of scope until gateway-grade or LR20xx receive hardware exists in the ground segment. + +## Ground segment + +- **GRC (Ground Radio Controller)** — the station-local Zephyr radio box (SX1262) that terminates the RF link. Must consume the same Profile Table as flight. diff --git a/docs/adr/0001-semtech-usp-for-sx126x-radio-path.md b/docs/adr/0001-semtech-usp-for-sx126x-radio-path.md new file mode 100644 index 00000000..b03a0049 --- /dev/null +++ b/docs/adr/0001-semtech-usp-for-sx126x-radio-path.md @@ -0,0 +1,54 @@ +# 0001 — Adopt Semtech USP (RAL layer) for the SX126x radio path; retain loramac-node for SX127x + +Date: 2026-07-04 +Status: Accepted + +## Context + +The UHF radio component (`Zephyr::LoRa` in lib/fprime-zephyr) wraps Zephyr's classic +`drivers/lora` API backed by loramac-node. That API can only express LoRa modulation, +loramac-node is in maintenance mode upstream, and the project wants GFSK (high-rate +downlink) and a clean CW path. + +Three integration options existed: + +1. **Semtech `usp_zephyr` module, component talks to RAL/RAC directly.** Full + modulation menu (LoRa/GFSK/LR-FHSS/CW), future LR20xx path. Supports SX126x/LR11xx/ + LR20xx only — no SX127x, no SX128x. Validated on Zephyr 4.2 (we run 4.3). +2. **In-tree Zephyr 4.3 LBM backend** (`drivers/lora/lora_basics_modem`, needs the + `lora-basics-modem` west module). Zero component change, covers SX126x *and* SX127x — + but sits behind the standard `lora_modem_config` API, so no new modulations. +3. **Two-phase**: option 2 first for all boards, option 1 later. Touches every board + twice, roughly doubles schedule. + +Hardware reality: FCB v5/v5c/v5d carry SX1276 (SX127x — unsupported by USP forever; +Semtech has ended new SX127x software). FCB v5e carries SX1262. The S-band component +(SX1280 via RadioLib) is unsupported by USP until LR2021-class hardware exists. + +## Decision + +- New F´ component (`Zephyr::UspRadio`, sibling of `Zephyr::LoRa` in lib/fprime-zephyr) + targets **USP's RAL directly** via the `usp_zephyr` west module, bypassing the standard + Zephyr `drivers/lora` API. +- **SX127x boards keep the Legacy Radio Path unchanged** (`Zephyr::LoRa` + loramac-node). + Board/topology config selects which component is instantiated; no ifdef sharing of one + implementation. +- The new component keeps the existing Svc.Com + Svc.BufferAllocation port surface and + the `TRANSMIT` / `CONTINUOUS_WAVE` command names and arguments verbatim, adding + profile commands on top (see ADR 0002). +- S-band stays on RadioLib; LR2021 is noted as the future convergence path. +- Component is **active (queued)** using the SBand deferred-handler pattern: all + RAL/SPI work runs on the component thread, callbacks only enqueue. (The SX126x + post-sleep first-SPI-command drop we diagnosed is the class of bug this prevents.) + +## Consequences + +- Modulation features land only on v5e+; v5c/v5d get no new radio capability, ever. +- Two radio components coexist in lib/fprime-zephyr indefinitely; ground dictionaries + differ per board revision. +- We take on Zephyr 4.3-vs-4.2 validation risk for usp_zephyr ourselves (Phase 0 spike + gates the plan). +- Devicetree must ensure exactly one driver binds the `semtech,sx1262` node when both + the in-tree Zephyr driver and USP's driver are present in the tree. +- The standard Zephyr LoRa shell/API tooling does not see the USP radio; all operations + go through F´ commands. diff --git a/docs/adr/0002-link-profile-table.md b/docs/adr/0002-link-profile-table.md new file mode 100644 index 00000000..6fd9aebe --- /dev/null +++ b/docs/adr/0002-link-profile-table.md @@ -0,0 +1,43 @@ +# 0002 — Versioned Link Profile table, split TX/RX selection, RX auto-revert + +Date: 2026-07-04 +Status: Accepted + +## Context + +GFSK introduces many coupled RF parameters (bitrate, deviation, BT, sync word, CRC, +preamble). A single mismatched field between spacecraft and ground silently kills the +link, and a bad RX configuration on the spacecraft strands it (deaf to commands). +Free-form per-parameter F´ params (the current `CODING_RATE`/`DATA_RATE`/`BANDWIDTH_*` +approach, extended) would make mismatches easy and atomic switches impossible. + +The dominant operational use case is asymmetric: keep the uplink (spacecraft RX) on +robust LoRa while switching only the downlink (spacecraft TX) to GFSK for bulk data. +The existing component already has separate TX/RX bandwidth params — the asymmetry +precedent exists. + +## Decision + +- Radio configuration is selected only by **Link Profile index** into a **versioned + Profile Table** checked into one shared artifact consumed by both the flight build + and the GRC build. The table version is downlinked as telemetry. +- Selection is **per direction**: `SET_TX_PROFILE(idx)` and `SET_RX_PROFILE(idx, + revert_s)`. +- `SET_TX_PROFILE` is unguarded — worst case is a lost downlink until the next command. +- `SET_RX_PROFILE` is guarded by **RX Auto-Revert**: if no valid frame is received + within `revert_s`, RX reverts to the Boot-Default Profile. A valid frame received on + the new profile confirms it. There is no separate confirm command. +- Individual RF parameters are not commandable in operations; effective parameters are + visible read-only via telemetry. (A lab-only raw-config path was considered and + deferred — bench experiments can rebuild the table instead.) + +## Consequences + +- Adding or changing a profile is a coordinated flight+ground release (table version + bump), not an on-orbit parameter tweak. This is deliberate friction. +- The uplink can be experimented with safely; a failed RX experiment self-heals within + the revert window. +- The legacy `Zephyr::LoRa` params remain only on SX127x boards; the new component does + not carry them. +- Profile indices become part of ops vocabulary and sequences; renumbering existing + entries is forbidden (append-only table). From 52264f9ceabb6d725c540753e038f1b2ba532994 Mon Sep 17 00:00:00 2001 From: Michael Pham <61564344+Mikefly123@users.noreply.github.com> Date: Sat, 4 Jul 2026 14:29:25 -0700 Subject: [PATCH 02/51] feat(board): add proves_flight_control_board_v5e with USP SX1262 binding MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- .../Kconfig.proves_flight_control_board_v5e | 5 ++ .../board.cmake | 20 ++++++ .../proves_flight_control_board_v5e/board.yml | 6 ++ ...s_flight_control_board_v5e_rp2350a_m33.dts | 68 +++++++++++++++++++ ..._flight_control_board_v5e_rp2350a_m33.yaml | 22 ++++++ ...ht_control_board_v5e_rp2350a_m33_defconfig | 62 +++++++++++++++++ 6 files changed, 183 insertions(+) create mode 100644 boards/bronco_space/proves_flight_control_board_v5e/Kconfig.proves_flight_control_board_v5e create mode 100644 boards/bronco_space/proves_flight_control_board_v5e/board.cmake create mode 100644 boards/bronco_space/proves_flight_control_board_v5e/board.yml create mode 100644 boards/bronco_space/proves_flight_control_board_v5e/proves_flight_control_board_v5e_rp2350a_m33.dts create mode 100644 boards/bronco_space/proves_flight_control_board_v5e/proves_flight_control_board_v5e_rp2350a_m33.yaml create mode 100644 boards/bronco_space/proves_flight_control_board_v5e/proves_flight_control_board_v5e_rp2350a_m33_defconfig diff --git a/boards/bronco_space/proves_flight_control_board_v5e/Kconfig.proves_flight_control_board_v5e b/boards/bronco_space/proves_flight_control_board_v5e/Kconfig.proves_flight_control_board_v5e new file mode 100644 index 00000000..4309c70e --- /dev/null +++ b/boards/bronco_space/proves_flight_control_board_v5e/Kconfig.proves_flight_control_board_v5e @@ -0,0 +1,5 @@ +config BOARD_PROVES_FLIGHT_CONTROL_BOARD_V5E + bool "PROVES Flight Control Board v5e" + default y + +rsource "../../../boards/bronco_space/proves_flight_control_board_v5/Kconfig.defconfig" diff --git a/boards/bronco_space/proves_flight_control_board_v5e/board.cmake b/boards/bronco_space/proves_flight_control_board_v5e/board.cmake new file mode 100644 index 00000000..dc6406b4 --- /dev/null +++ b/boards/bronco_space/proves_flight_control_board_v5e/board.cmake @@ -0,0 +1,20 @@ +# SPDX-License-Identifier: Apache-2.0 + +if("${RPI_PICO_DEBUG_ADAPTER}" STREQUAL "") + set(RPI_PICO_DEBUG_ADAPTER "cmsis-dap") +endif() + +board_runner_args(openocd --cmd-pre-init "source [find interface/${RPI_PICO_DEBUG_ADAPTER}.cfg]") +board_runner_args(openocd --cmd-pre-init "source [find target/rp2350.cfg]") + +# The adapter speed is expected to be set by interface configuration. +# The Raspberry Pi's OpenOCD fork doesn't, so match their documentation at +# https://www.raspberrypi.com/documentation/microcontrollers/debug-probe.html#debugging-with-swd +board_runner_args(openocd --cmd-pre-init "set_adapter_speed_if_not_set 5000") + +board_runner_args(jlink "--device=RP2350_M33_0") +board_runner_args(uf2 "--board-id=RP2350") + +include(${ZEPHYR_BASE}/boards/common/openocd.board.cmake) +include(${ZEPHYR_BASE}/boards/common/jlink.board.cmake) +include(${ZEPHYR_BASE}/boards/common/uf2.board.cmake) diff --git a/boards/bronco_space/proves_flight_control_board_v5e/board.yml b/boards/bronco_space/proves_flight_control_board_v5e/board.yml new file mode 100644 index 00000000..52e22ac7 --- /dev/null +++ b/boards/bronco_space/proves_flight_control_board_v5e/board.yml @@ -0,0 +1,6 @@ +board: + name: proves_flight_control_board_v5e + full_name: PROVES Flight Control Board v5e + vendor: Bronco Space + socs: + - name: rp2350a diff --git a/boards/bronco_space/proves_flight_control_board_v5e/proves_flight_control_board_v5e_rp2350a_m33.dts b/boards/bronco_space/proves_flight_control_board_v5e/proves_flight_control_board_v5e_rp2350a_m33.dts new file mode 100644 index 00000000..0ab5e0c8 --- /dev/null +++ b/boards/bronco_space/proves_flight_control_board_v5e/proves_flight_control_board_v5e_rp2350a_m33.dts @@ -0,0 +1,68 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * PROVES Flight Control Board v5e — SX1262 (E22-400M30S) via USP driver. + * + * Same SoC + board base as v5d (inherits all v5 hardware: I2C sensors, + * UART, SPI0/SD-card, flash partitions, MCUboot layout), but the radio + * node on SPI1 is replaced with a USP-binding SX1262 node. + * + * Flash partition layout is inherited from proves_flight_control_board_v5.dtsi + * (4 MiB: boot 1M / slot0 1M / slot1 1M / test+storage beyond — see dtsi). + * smtc_modem_hal_storage.c requires storage_partition; that node is defined + * in the shared dtsi at 0x400000 so no extra include is needed here. + */ + +/dts-v1/; + +#include +#include + +#include "../proves_flight_control_board_v5/proves_flight_control_board_v5.dtsi" + +/* + * Replace the v5 SX1276 radio node with the USP SX1262 node. + * The v5.dtsi enables &spi1 with cs-gpios, pinctrl, and lora0 (sx1276@0). + * Delete the SX1276 node and add the USP-compatible SX1262 node at the + * same SPI1 CS0 slot. + */ +#include + +/* Remove the legacy sx1276 node inherited from v5.dtsi */ +/delete-node/ &lora0; + +&spi1 { + /* + * USP SX1262 node — E22-400M30S module, SPI1 CS0 (same as v5 SX1276). + * + * Property notes (see REPORT-devicetree.md for mapping table): + * compatible: "semtech,sx1262-new" — USP binding; prevents the + * in-tree Zephyr lora driver (semtech,sx1262) from + * claiming this device. LORA_BASICS_MODEM_DRIVERS + * depends on !LORA so exactly one driver binds. + * spi-max-frequency: kept at 125000 Hz from the GRC bring-up value; + * may be a board-level SPI signal-integrity workaround. + * /* kept from v5e bring-up; revisit */ + * tx/rx-enable-gpios: USP upstream has no external RF-switch support; + * carried via our patch (spikes/patches/0001-feat-…). + * dio3-as-tcxo-control + tcxo-voltage: replaces dio3-tcxo-voltage in + * the in-tree binding. + * tcxo-wakeup-time: replaces tcxo-power-startup-delay-ms. + * reg-mode: LDO required on E22-400M30S (no DC-DC inductor). + */ + lora0_usp: sx1262_usp@0 { + compatible = "semtech,sx1262-new"; + reg = <0>; + spi-max-frequency = <125000>; /* kept from v5e bring-up; revisit */ + reset-gpios = <&gpio0 6 GPIO_ACTIVE_LOW>; + busy-gpios = <&gpio0 13 GPIO_ACTIVE_HIGH>; + dio1-gpios = <&gpio0 14 (GPIO_ACTIVE_HIGH | GPIO_PULL_DOWN)>; + dio3-as-tcxo-control; + tcxo-voltage = ; + tcxo-wakeup-time = <10>; + reg-mode = ; + rx-boosted; + tx-enable-gpios = <&gpio0 21 GPIO_ACTIVE_HIGH>; + rx-enable-gpios = <&gpio0 22 GPIO_ACTIVE_HIGH>; + }; +}; diff --git a/boards/bronco_space/proves_flight_control_board_v5e/proves_flight_control_board_v5e_rp2350a_m33.yaml b/boards/bronco_space/proves_flight_control_board_v5e/proves_flight_control_board_v5e_rp2350a_m33.yaml new file mode 100644 index 00000000..e9a8a709 --- /dev/null +++ b/boards/bronco_space/proves_flight_control_board_v5e/proves_flight_control_board_v5e_rp2350a_m33.yaml @@ -0,0 +1,22 @@ +identifier: proves_flight_control_board_v5e/rp2350a/m33 +name: PROVES Flight Control Board v5e (RP2350, Cortex-M33) +type: mcu +arch: arm +flash: 4096 +ram: 520 +toolchain: + - zephyr + - gnuarmemb +supported: + - adc + - clock + - counter + - dma + - gpio + - hwinfo + - i2c + - pwm + - spi + - uart + - usbd + - watchdog diff --git a/boards/bronco_space/proves_flight_control_board_v5e/proves_flight_control_board_v5e_rp2350a_m33_defconfig b/boards/bronco_space/proves_flight_control_board_v5e/proves_flight_control_board_v5e_rp2350a_m33_defconfig new file mode 100644 index 00000000..62a79813 --- /dev/null +++ b/boards/bronco_space/proves_flight_control_board_v5e/proves_flight_control_board_v5e_rp2350a_m33_defconfig @@ -0,0 +1,62 @@ +CONFIG_BUILD_OUTPUT_HEX=y +CONFIG_BUILD_OUTPUT_UF2=y +CONFIG_CLOCK_CONTROL=y +CONFIG_CONSOLE=y +CONFIG_GPIO=y +CONFIG_RESET=y +CONFIG_SERIAL=y +CONFIG_UART_CONSOLE=y +CONFIG_UART_INTERRUPT_DRIVEN=y +CONFIG_USE_DT_CODE_PARTITION=y + +# USB Next Stack +CONFIG_USBD_CDC_ACM_CLASS=y +CONFIG_USB_DEVICE_STACK_NEXT=y +CONFIG_CDC_ACM_SERIAL_ENABLE_AT_BOOT=y +CONFIG_CDC_ACM_SERIAL_INITIALIZE_AT_BOOT=y +CONFIG_CDC_ACM_SERIAL_PID=0x000F +CONFIG_CDC_ACM_SERIAL_VID=0x0028 +CONFIG_CDC_ACM_SERIAL_PRODUCT_STRING="PROVES Flight Control Board v5e" + +CONFIG_I2C_TCA954X=y +CONFIG_I2C_TCA954X_ROOT_INIT_PRIO=70 +CONFIG_I2C_TCA954X_CHANNEL_INIT_PRIO=71 + +# Sensors +CONFIG_SENSOR=y +CONFIG_LSM6DSO=y +CONFIG_LSM6DSO_ENABLE_TEMP=y +CONFIG_LIS2MDL=y +CONFIG_INA219=y +CONFIG_TMP112=y +CONFIG_VEML6031=y + +# Radio — USP path (SX1262, E22-400M30S) +# CONFIG_LORA is intentionally omitted: USP uses "semtech,sx1262-new" binding +# and LORA_BASICS_MODEM_DRIVERS depends on !LORA. Exactly one driver binds. +CONFIG_LORA_BASICS_MODEM_DRIVERS=y +CONFIG_LORA_BASICS_MODEM_DRIVERS_RAL_RALF=y +CONFIG_LORA_BASICS_MODEM_DRIVERS_EVENT_TRIGGER_GLOBAL_THREAD=y +CONFIG_USP=y + +# RTC +CONFIG_RTC=y +CONFIG_RTC_RV3028=y +CONFIG_RTC_ALARM=y + +# Bootloader settings +CONFIG_BOOTLOADER_MCUBOOT=y +CONFIG_MCUBOOT_BOOTUTIL_LIB=y +# Use an offset to swap images +CONFIG_MCUBOOT_BOOTLOADER_MODE_SWAP_USING_OFFSET=y +# Allow downgrade (fallback) +CONFIG_MCUBOOT_BOOTLOADER_NO_DOWNGRADE=n +# Hyper important. Adjust and fail. +CONFIG_ROM_END_OFFSET=0x1150 +CONFIG_MCUBOOT_UPDATE_FOOTER_SIZE=0x1000 + +# Settings to allow flash image writes +CONFIG_FLASH=y +CONFIG_FLASH_MAP=y +CONFIG_STREAM_FLASH=y +CONFIG_IMG_MANAGER=y From 986fa02514cf1050df0fc35f9edeb4258ae2e334 Mon Sep 17 00:00:00 2001 From: Michael Pham <61564344+Mikefly123@users.noreply.github.com> Date: Sat, 4 Jul 2026 14:30:54 -0700 Subject: [PATCH 03/51] feat(west+patches): add usp_zephyr + usp west modules + carry patches MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- Makefile | 24 ++ ...external-RF-switch-GPIO-support-tx-r.patch | 251 ++++++++++++++++++ ...emove-select-ZEPHYR_LORA_BASICS_MODE.patch | 36 +++ ...5-fix-LR_FHSS_SRC_PATH-for-flattened.patch | 34 +++ west.yml | 16 ++ 5 files changed, 361 insertions(+) create mode 100644 patches/0001-feat-sx126x-add-external-RF-switch-GPIO-support-tx-r.patch create mode 100644 patches/0002-fix-zephyr-4.3-remove-select-ZEPHYR_LORA_BASICS_MODE.patch create mode 100644 patches/0003-fix-usp-main-2025-fix-LR_FHSS_SRC_PATH-for-flattened.patch diff --git a/Makefile b/Makefile index b6135da1..e5d75728 100644 --- a/Makefile +++ b/Makefile @@ -60,6 +60,30 @@ zephyr-setup: fprime-venv ## Set up Zephyr environment $(UV) pip install --prerelease=allow -r lib/zephyr-workspace/bootloader/mcuboot/zephyr/requirements.txt; \ } +# USP_ZEPHYR_DIR: west places usp_zephyr at this path (see west.yml). +USP_ZEPHYR_DIR ?= $(shell pwd)/lib/zephyr-workspace/modules/lib/usp_zephyr + +.PHONY: usp-patches +usp-patches: ## Apply usp_zephyr patches (RF-switch GPIO + Zephyr 4.3 compat) + @if [ ! -d "$(USP_ZEPHYR_DIR)" ]; then \ + echo "❌ usp_zephyr not found at $(USP_ZEPHYR_DIR) — run 'west update usp_zephyr usp' first"; \ + exit 1; \ + fi + @echo "Applying usp_zephyr patches..." + @cd "$(USP_ZEPHYR_DIR)" && \ + for p in $(shell pwd)/patches/0001-feat-sx126x-add-external-RF-switch-GPIO-support-tx-r.patch \ + $(shell pwd)/patches/0002-fix-zephyr-4.3-remove-select-ZEPHYR_LORA_BASICS_MODE.patch \ + $(shell pwd)/patches/0003-fix-usp-main-2025-fix-LR_FHSS_SRC_PATH-for-flattened.patch; do \ + name=$$(basename $$p); \ + if git apply --check "$$p" 2>/dev/null; then \ + git apply "$$p" && echo "✓ Applied $$name"; \ + elif git apply --reverse --check "$$p" 2>/dev/null; then \ + echo "⚠ Already applied: $$name"; \ + else \ + echo "❌ Cannot apply $$name — check usp_zephyr revision"; exit 1; \ + fi; \ + done + ##@ Development .PHONY: pre-commit-install diff --git a/patches/0001-feat-sx126x-add-external-RF-switch-GPIO-support-tx-r.patch b/patches/0001-feat-sx126x-add-external-RF-switch-GPIO-support-tx-r.patch new file mode 100644 index 00000000..1a25b7b0 --- /dev/null +++ b/patches/0001-feat-sx126x-add-external-RF-switch-GPIO-support-tx-r.patch @@ -0,0 +1,251 @@ +From a23856a670226bb4e3e83acfc07106e47866c118 Mon Sep 17 00:00:00 2001 +From: Michael Pham <61564344+Mikefly123@users.noreply.github.com> +Date: Sat, 4 Jul 2026 09:59:41 -0700 +Subject: [PATCH 1/3] feat(sx126x): add external RF-switch GPIO support + (tx/rx-enable-gpios) + +The EBYTE E22-400M30S (SX1262) module drives its RF switch via two +dedicated GPIOs (TX-EN, RX-EN) rather than DIO2, so the existing +dio2-as-rf-switch mechanism is unusable on PROVES FCB v5e. + +Changes: +- dts/bindings/usp/semtech,sx126x-new-common.yaml: add optional + tx-enable-gpios and rx-enable-gpios phandle-array properties with + full description of operating-mode semantics. +- drivers/usp/sx126x/sx126x_hal_context.h: add tx_enable and + rx_enable gpio_dt_spec fields to sx126x_hal_context_cfg_t (both + zero-initialised / .port==NULL when absent in DT, so boards without + the properties compile and behave identically to before). +- drivers/usp/sx126x/sx126x_board.c: configure both pins as + OUTPUT_INACTIVE at init; wire them into SX126X_CONFIG via the + existing CONFIGURE_GPIO_IF_IN_DT helper. +- drivers/usp/sx126x/sx126x_hal.c: add sx126x_hal_update_rf_switch() + that intercepts the first byte of every sx126x_hal_write() command + buffer and drives the GPIOs before the SPI transaction: + TX-class (TX-EN=1, RX-EN=0): 0x83 SetTx, 0xD1 SetTxContinuousWave, + 0xD2 SetTxInfinitePreamble + RX-class (TX-EN=0, RX-EN=1): 0x82 SetRx, 0x94 SetRxDutyCycle, + 0xC5 SetCad + Inactive (TX-EN=0, RX-EN=0): 0x84 SetSleep, 0x80 SetStandby + All other opcodes leave switch state unchanged. + Deactivation of the leaving path always precedes activation of the + entering path to prevent simultaneous PA+LNA enable. + +Boards without tx-enable-gpios / rx-enable-gpios in DT are unaffected: +gpio_dt_spec.port is NULL and all branches are skipped at runtime. +Build verified: zephyr.elf + zephyr.uf2 compile clean on Zephyr 4.3 / +RP2350 with FLASH 104760 B / RAM 31564 B (ping_pong sample). + +Co-Authored-By: Claude Fable 5 +--- + drivers/usp/sx126x/sx126x_board.c | 22 +++++ + drivers/usp/sx126x/sx126x_hal.c | 87 +++++++++++++++++++ + drivers/usp/sx126x/sx126x_hal_context.h | 10 +++ + .../usp/semtech,sx126x-new-common.yaml | 26 +++++- + 4 files changed, 144 insertions(+), 1 deletion(-) + +diff --git a/drivers/usp/sx126x/sx126x_board.c b/drivers/usp/sx126x/sx126x_board.c +index 215801a..d119aba 100644 +--- a/drivers/usp/sx126x/sx126x_board.c ++++ b/drivers/usp/sx126x/sx126x_board.c +@@ -239,6 +239,26 @@ static int sx126x_init( const struct device* dev ) + } + } + ++ /* External RF-switch GPIOs — configure as outputs, inactive (both paths off) */ ++ if( config->tx_enable.port ) ++ { ++ ret = gpio_pin_configure_dt( &config->tx_enable, GPIO_OUTPUT_INACTIVE ); ++ if( ret < 0 ) ++ { ++ LOG_ERR( "Could not configure tx-enable gpio" ); ++ return ret; ++ } ++ } ++ if( config->rx_enable.port ) ++ { ++ ret = gpio_pin_configure_dt( &config->rx_enable, GPIO_OUTPUT_INACTIVE ); ++ if( ret < 0 ) ++ { ++ LOG_ERR( "Could not configure rx-enable gpio" ); ++ return ret; ++ } ++ } ++ + data->radio_status = RADIO_AWAKE; + data->tx_power_offset_db_current = config->tx_power_offset_db; + +@@ -366,6 +386,8 @@ static int sx126x_pm_action( const struct device* dev, enum pm_device_action act + CONFIGURE_GPIO_IF_IN_DT( node_id, dio1, dio1_gpios ) CONFIGURE_GPIO_IF_IN_DT( node_id, dio2, dio2_gpios ) \ + CONFIGURE_GPIO_IF_IN_DT( node_id, dio3, dio3_gpios ) \ + .dio2_as_rf_switch = DT_PROP( node_id, dio2_as_rf_switch ), \ ++ CONFIGURE_GPIO_IF_IN_DT( node_id, tx_enable, tx_enable_gpios ) \ ++ CONFIGURE_GPIO_IF_IN_DT( node_id, rx_enable, rx_enable_gpios ) \ + SX126X_CFG_TCXO( node_id ), .capa_xta = DT_PROP_OR( node_id, xtal_capacitor_value_xta, 0xFF ), \ + .capa_xtb = DT_PROP_OR( node_id, xtal_capacitor_value_xtb, 0xFF ), .reg_mode = DT_PROP( node_id, reg_mode ), \ + .tx_power_offset_db = DT_PROP_OR( node_id, tx_power_offset, 0 ), \ +diff --git a/drivers/usp/sx126x/sx126x_hal.c b/drivers/usp/sx126x/sx126x_hal.c +index a3215cc..1530077 100644 +--- a/drivers/usp/sx126x/sx126x_hal.c ++++ b/drivers/usp/sx126x/sx126x_hal.c +@@ -111,6 +111,86 @@ static void sx126x_hal_check_device_ready( const void* context ) + * --- PUBLIC FUNCTIONS DEFINITION --------------------------------------------- + */ + ++/* ++ * External RF-switch toggle helper. ++ * ++ * Called in sx126x_hal_write() before each SPI opcode transaction so the ++ * TX-EN / RX-EN lines track the radio mode without requiring a mode-callback ++ * hook (USP's SX126x HAL layer has none). ++ * ++ * Opcode table (SX126x datasheet §13.1): ++ * TX-class (TX-EN=1, RX-EN=0): ++ * 0x83 SetTx ++ * 0xD1 SetTxContinuousWave ++ * 0xD2 SetTxInfinitePreamble ++ * RX-class (TX-EN=0, RX-EN=1): ++ * 0x82 SetRx ++ * 0x94 SetRxDutyCycle ++ * 0xC5 SetCad ++ * Inactive (TX-EN=0, RX-EN=0): ++ * 0x84 SetSleep ++ * 0x80 SetStandby ++ * All other opcodes leave the switch state unchanged. ++ */ ++static void sx126x_hal_update_rf_switch( const struct sx126x_hal_context_cfg_t* config, uint8_t opcode ) ++{ ++ bool tx_active; ++ bool rx_active; ++ ++ switch( opcode ) ++ { ++ case 0x83: /* SetTx */ ++ case 0xD1: /* SetTxContinuousWave */ ++ case 0xD2: /* SetTxInfinitePreamble */ ++ tx_active = true; ++ rx_active = false; ++ break; ++ ++ case 0x82: /* SetRx */ ++ case 0x94: /* SetRxDutyCycle */ ++ case 0xC5: /* SetCad */ ++ tx_active = false; ++ rx_active = true; ++ break; ++ ++ case 0x84: /* SetSleep */ ++ case 0x80: /* SetStandby */ ++ tx_active = false; ++ rx_active = false; ++ break; ++ ++ default: ++ /* No switch change for config/status opcodes */ ++ return; ++ } ++ ++ /* Deassert the path we are leaving before asserting the new one to ++ * avoid momentarily enabling both PA and LNA simultaneously. ++ */ ++ if( config->tx_enable.port ) ++ { ++ if( !tx_active ) ++ { ++ gpio_pin_set_dt( &config->tx_enable, 0 ); ++ } ++ } ++ if( config->rx_enable.port ) ++ { ++ if( !rx_active ) ++ { ++ gpio_pin_set_dt( &config->rx_enable, 0 ); ++ } ++ } ++ if( config->tx_enable.port && tx_active ) ++ { ++ gpio_pin_set_dt( &config->tx_enable, 1 ); ++ } ++ if( config->rx_enable.port && rx_active ) ++ { ++ gpio_pin_set_dt( &config->rx_enable, 1 ); ++ } ++} ++ + sx126x_hal_status_t sx126x_hal_write( const void* context, const uint8_t* command, const uint16_t command_length, + const uint8_t* data, const uint16_t data_length ) + { +@@ -127,6 +207,13 @@ sx126x_hal_status_t sx126x_hal_write( const void* context, const uint8_t* comman + const struct spi_buf_set tx_buf_set = { tx_bufs, .count = ARRAY_SIZE( tx_bufs ) }; + + sx126x_hal_check_device_ready( context ); ++ ++ /* Toggle external RF switch before writing the mode-change opcode */ ++ if( command_length > 0 ) ++ { ++ sx126x_hal_update_rf_switch( config, command[0] ); ++ } ++ + ret = spi_write_dt( &config->spi, &tx_buf_set ); + if( ret ) + { +diff --git a/drivers/usp/sx126x/sx126x_hal_context.h b/drivers/usp/sx126x/sx126x_hal_context.h +index 228f883..0c56bef 100644 +--- a/drivers/usp/sx126x/sx126x_hal_context.h ++++ b/drivers/usp/sx126x/sx126x_hal_context.h +@@ -72,6 +72,16 @@ struct sx126x_hal_context_cfg_t + struct gpio_dt_spec dio3; /* DIO3 pin */ + + bool dio2_as_rf_switch; ++ ++ /* External RF-switch GPIOs (optional; absent when port == NULL). ++ * tx_enable is asserted during TX-class operations; rx_enable during RX. ++ * Both are deasserted on standby/sleep/init. ++ * These are mutually exclusive with dio2-as-rf-switch in hardware but ++ * the driver does not enforce that — user must not set both in DT. ++ */ ++ struct gpio_dt_spec tx_enable; /* TX-EN line, e.g. EBYTE E22-400M30S pin 12 */ ++ struct gpio_dt_spec rx_enable; /* RX-EN line, e.g. EBYTE E22-400M30S pin 11 */ ++ + struct sx126x_hal_context_tcxo_cfg_t tcxo_cfg; /* TCXO config, says if dio3-tcxo */ + uint8_t capa_xta; /* set to 0xFF if not configured*/ + uint8_t capa_xtb; /* set to 0xFF if not configured*/ +diff --git a/dts/bindings/usp/semtech,sx126x-new-common.yaml b/dts/bindings/usp/semtech,sx126x-new-common.yaml +index 72032bb..56a8e40 100644 +--- a/dts/bindings/usp/semtech,sx126x-new-common.yaml ++++ b/dts/bindings/usp/semtech,sx126x-new-common.yaml +@@ -128,6 +128,30 @@ properties: + required: false + enum: [0, 1, 2, 3, 4, 5, 6, 7] + description: | +- The ramp-up time for the radio PA, between 0x0 (10us) to 0x07 (3400us). ++ The ramp-up time for the radio PA, between 0x0 (10us) to 0x07 (3400us). + If not provided, the driver will use the default, recommended time (40us). + It is not recommended to modify this value. ++ ++ tx-enable-gpios: ++ type: phandle-array ++ required: false ++ description: | ++ External RF-switch TX-enable GPIO. ++ ++ When present, the driver asserts this pin active before any transmit-class ++ operation (SetTx / SetTxContinuousWave / SetTxInfinitePreamble) and ++ deasserts it on standby, sleep, and receive-class operations. Use this for ++ modules such as the EBYTE E22-400M30S (SX1262) that drive an external ++ RF switch with a dedicated TX-EN line instead of using DIO2. ++ ++ Must not be combined with dio2-as-rf-switch. ++ ++ rx-enable-gpios: ++ type: phandle-array ++ required: false ++ description: | ++ External RF-switch RX-enable GPIO. ++ ++ When present, the driver asserts this pin active before any receive-class ++ operation (SetRx / SetRxDutyCycle / SetCad) and deasserts it on standby, ++ sleep, and transmit-class operations. Pair with tx-enable-gpios. +-- +2.50.1 (Apple Git-155) + diff --git a/patches/0002-fix-zephyr-4.3-remove-select-ZEPHYR_LORA_BASICS_MODE.patch b/patches/0002-fix-zephyr-4.3-remove-select-ZEPHYR_LORA_BASICS_MODE.patch new file mode 100644 index 00000000..57e34fdd --- /dev/null +++ b/patches/0002-fix-zephyr-4.3-remove-select-ZEPHYR_LORA_BASICS_MODE.patch @@ -0,0 +1,36 @@ +From dcabc513f4c4d00be1370cbf986bca284eddff8d Mon Sep 17 00:00:00 2001 +From: Michael Pham <61564344+Mikefly123@users.noreply.github.com> +Date: Sat, 4 Jul 2026 14:30:01 -0700 +Subject: [PATCH 2/3] fix(zephyr-4.3): remove select + ZEPHYR_LORA_BASICS_MODEM_MODULE (internalized in 4.3) + +ZEPHYR_LORA_BASICS_MODEM_MODULE was an external-module auto-symbol in +Zephyr <=4.2. In 4.3 it became an internal Zephyr symbol and is not +exposed to external modules. Remove the select to avoid a fatal Kconfig +'direct dependencies 0' abort. + +Co-Authored-By: Claude Fable 5 +--- + drivers/usp/Kconfig | 6 +++++- + 1 file changed, 5 insertions(+), 1 deletion(-) + +diff --git a/drivers/usp/Kconfig b/drivers/usp/Kconfig +index 5518a25..cc7940f 100644 +--- a/drivers/usp/Kconfig ++++ b/drivers/usp/Kconfig +@@ -9,7 +9,11 @@ menuconfig LORA_BASICS_MODEM_DRIVERS + bool "LoRa drivers from the new LoRa Basics Modem stack [EXPERIMENTAL]" + select POLL + select EXPERIMENTAL +- select ZEPHYR_LORA_BASICS_MODEM_MODULE ++ # PATCH(zephyr-4.3): ZEPHYR_LORA_BASICS_MODEM_MODULE was an external-module ++ # auto-symbol in Zephyr <=4.2. In 4.3 it became an internal Zephyr symbol ++ # (zephyr/modules/lora-basics-modem/Kconfig) that is NOT exposed in the ++ # auto-generated Kconfig.modules for external builds. Remove the select to ++ # avoid a fatal Kconfig "direct dependencies 0" abort. + depends on !LORA + help + Include LoRa drivers from the new LoRa Basics Modem stack in the system configuration. +-- +2.50.1 (Apple Git-155) + diff --git a/patches/0003-fix-usp-main-2025-fix-LR_FHSS_SRC_PATH-for-flattened.patch b/patches/0003-fix-usp-main-2025-fix-LR_FHSS_SRC_PATH-for-flattened.patch new file mode 100644 index 00000000..2cb0baf3 --- /dev/null +++ b/patches/0003-fix-usp-main-2025-fix-LR_FHSS_SRC_PATH-for-flattened.patch @@ -0,0 +1,34 @@ +From 79f38c6669106d8018755dd3b2a509eb5f1bc924 Mon Sep 17 00:00:00 2001 +From: Michael Pham <61564344+Mikefly123@users.noreply.github.com> +Date: Sat, 4 Jul 2026 14:30:08 -0700 +Subject: [PATCH 3/3] fix(usp-main-2025): fix LR_FHSS_SRC_PATH for flattened + lr_fhss_driver layout + +Upstream usp removed lr_fhss_driver/src/; lr_fhss_mac.c now lives flat +in sx126x_driver/src (same dir as LBM_SX126X_LIB_DIR). Drop the now- +invalid subdirectory suffix to fix the cmake path. + +Co-Authored-By: Claude Fable 5 +--- + modules/usp_drivers/dev_env.cmake | 6 +++++- + 1 file changed, 5 insertions(+), 1 deletion(-) + +diff --git a/modules/usp_drivers/dev_env.cmake b/modules/usp_drivers/dev_env.cmake +index f92cafe..1e76230 100644 +--- a/modules/usp_drivers/dev_env.cmake ++++ b/modules/usp_drivers/dev_env.cmake +@@ -1,6 +1,10 @@ + # SPDX-License-Identifier: BSD-3-Clause-Clear + + if(SX126X_ENABLE_LR_FHSS) +- set(LR_FHSS_SRC_PATH "${LBM_SX126X_LIB_DIR}/lr_fhss_driver/src" ++ # PATCH(usp-main-2025): upstream usp repo removed the lr_fhss_driver/src ++ # subdirectory; lr_fhss_mac.c now lives flat in sx126x_driver/src/. ++ # Use the same directory as LBM_SX126X_LIB_DIR (already the default before ++ # dev_env.cmake was included). ++ set(LR_FHSS_SRC_PATH "${LBM_SX126X_LIB_DIR}" + CACHE PATH "Path to folder containing LR-FHSS driver" FORCE) + endif() +-- +2.50.1 (Apple Git-155) + diff --git a/west.yml b/west.yml index 051b91be..2d9333e8 100644 --- a/west.yml +++ b/west.yml @@ -88,6 +88,22 @@ manifest: revision: f4ead3bf4a6dab3a07d7b5f5315795c073db568d path: lib/zephyr-workspace/modules/fatfs + # USP radio stack (Semtech Unified Software Platform) — v5e+ boards only. + # usp_zephyr upstream is pinned at the commit immediately below our local + # RF-switch patch (commit a23856a in spikes/usp_zephyr). Two Zephyr-4.3 + # compat fixes are carried as patches (see patches/usp_zephyr-*.patch). + - name: usp_zephyr + url: https://github.com/Lora-net/usp_zephyr + revision: bfacd435f53935ebea1a0e95fb877f4ede119985 + path: lib/zephyr-workspace/modules/lib/usp_zephyr + + # USP core library (LBM + RAL) — pulled by usp_zephyr at runtime. + # Path mirrors the usp_zephyr west.yml convention (modules/lib/usp). + - name: usp + url: https://github.com/Lora-net/usp + revision: 351b2015350670eb4dfa3aec35eb04433e062654 + path: lib/zephyr-workspace/modules/lib/usp + self: path: . west-commands: west-commands.yml From 5c17352190c9f6275b6a744e0a141564f0084e93 Mon Sep 17 00:00:00 2001 From: Michael Pham <61564344+Mikefly123@users.noreply.github.com> Date: Sat, 4 Jul 2026 14:35:56 -0700 Subject: [PATCH 04/51] fix(board/v5e): remove nested C comment in DTS board file 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 --- .../proves_flight_control_board_v5e_rp2350a_m33.dts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/boards/bronco_space/proves_flight_control_board_v5e/proves_flight_control_board_v5e_rp2350a_m33.dts b/boards/bronco_space/proves_flight_control_board_v5e/proves_flight_control_board_v5e_rp2350a_m33.dts index 0ab5e0c8..a52bcec1 100644 --- a/boards/bronco_space/proves_flight_control_board_v5e/proves_flight_control_board_v5e_rp2350a_m33.dts +++ b/boards/bronco_space/proves_flight_control_board_v5e/proves_flight_control_board_v5e_rp2350a_m33.dts @@ -41,8 +41,8 @@ * claiming this device. LORA_BASICS_MODEM_DRIVERS * depends on !LORA so exactly one driver binds. * spi-max-frequency: kept at 125000 Hz from the GRC bring-up value; - * may be a board-level SPI signal-integrity workaround. - * /* kept from v5e bring-up; revisit */ + * may be a board-level SPI signal-integrity workaround + * (kept from v5e bring-up; revisit before production). * tx/rx-enable-gpios: USP upstream has no external RF-switch support; * carried via our patch (spikes/patches/0001-feat-…). * dio3-as-tcxo-control + tcxo-voltage: replaces dio3-tcxo-voltage in From 8782fb4c18b28f7ccd2935a2a0d7c1af196d2596 Mon Sep 17 00:00:00 2001 From: Michael Pham <61564344+Mikefly123@users.noreply.github.com> Date: Sat, 4 Jul 2026 15:57:06 -0700 Subject: [PATCH 05/51] test(phase2): wire LinkProfiles gtest into pcr unit-test CMakeLists 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 --- .../test/unit-tests/CMakeLists.txt | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/PROVESFlightControllerReference/test/unit-tests/CMakeLists.txt b/PROVESFlightControllerReference/test/unit-tests/CMakeLists.txt index 0a047550..aa7b09f6 100644 --- a/PROVESFlightControllerReference/test/unit-tests/CMakeLists.txt +++ b/PROVESFlightControllerReference/test/unit-tests/CMakeLists.txt @@ -5,6 +5,19 @@ enable_testing() add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/../../../lib/fprime/googletest googletest-build) +# --- UspRadio: LinkProfiles host-side test --- +# LinkProfiles.hpp is header-only and free of Zephyr/USP includes, so it +# compiles here with a minimal FPrimeBasicTypes stub provided inline in the test. +add_executable(test_LinkProfiles + ${CMAKE_CURRENT_SOURCE_DIR}/../../../lib/fprime-zephyr/fprime-zephyr/Drv/UspRadio/test/ut/test_LinkProfiles.cpp +) +target_include_directories(test_LinkProfiles PRIVATE + # Exposes #include "fprime-zephyr/Drv/UspRadio/LinkProfiles.hpp" + ${CMAKE_CURRENT_SOURCE_DIR}/../../../lib/fprime-zephyr +) +target_link_libraries(test_LinkProfiles gtest_main) +add_test(NAME test_LinkProfiles COMMAND test_LinkProfiles) + # --- Helper Libraries --- # DetumbleManager Magnetorquer From 1360ccb3cf4bc8c6667faa5bb9c61c97f75859a7 Mon Sep 17 00:00:00 2001 From: Michael Pham <61564344+Mikefly123@users.noreply.github.com> Date: Sat, 4 Jul 2026 16:12:51 -0700 Subject: [PATCH 06/51] test(phase3): wire ProfilePolicy gtest into pcr unit-test CMakeLists 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 --- .../test/unit-tests/CMakeLists.txt | 21 ++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/PROVESFlightControllerReference/test/unit-tests/CMakeLists.txt b/PROVESFlightControllerReference/test/unit-tests/CMakeLists.txt index aa7b09f6..02745fe8 100644 --- a/PROVESFlightControllerReference/test/unit-tests/CMakeLists.txt +++ b/PROVESFlightControllerReference/test/unit-tests/CMakeLists.txt @@ -5,7 +5,7 @@ enable_testing() add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/../../../lib/fprime/googletest googletest-build) -# --- UspRadio: LinkProfiles host-side test --- +# --- UspRadio: LinkProfiles host-side test (Phase 2) --- # LinkProfiles.hpp is header-only and free of Zephyr/USP includes, so it # compiles here with a minimal FPrimeBasicTypes stub provided inline in the test. add_executable(test_LinkProfiles @@ -18,6 +18,25 @@ target_include_directories(test_LinkProfiles PRIVATE target_link_libraries(test_LinkProfiles gtest_main) add_test(NAME test_LinkProfiles COMMAND test_LinkProfiles) +# --- UspRadio: ProfilePolicy host-side test (Phase 3) --- +# ProfilePolicy.cpp is free of F'/USP/Zephyr includes (host-compilable). +# LINK_PROFILES_USE_HOST_TYPES is defined inside the test source to activate +# cstdint typedefs in both ProfilePolicy.hpp and LinkProfiles.hpp. +add_executable(test_ProfilePolicy + ${CMAKE_CURRENT_SOURCE_DIR}/../../../lib/fprime-zephyr/fprime-zephyr/Drv/UspRadio/test/ut/test_ProfilePolicy.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/../../../lib/fprime-zephyr/fprime-zephyr/Drv/UspRadio/ProfilePolicy.cpp +) +target_include_directories(test_ProfilePolicy PRIVATE + # Exposes #include "fprime-zephyr/Drv/UspRadio/ProfilePolicy.hpp" + # and #include "fprime-zephyr/Drv/UspRadio/LinkProfiles.hpp" + ${CMAKE_CURRENT_SOURCE_DIR}/../../../lib/fprime-zephyr +) +# ProfilePolicy.cpp includes ProfilePolicy.hpp which includes LinkProfiles.hpp. +# Neither header pulls in F', Zephyr, or USP, so no additional link deps. +target_compile_definitions(test_ProfilePolicy PRIVATE LINK_PROFILES_USE_HOST_TYPES) +target_link_libraries(test_ProfilePolicy gtest_main) +add_test(NAME test_ProfilePolicy COMMAND test_ProfilePolicy) + # --- Helper Libraries --- # DetumbleManager Magnetorquer From ad10ef6fb5b0c050c12b1602ba8755d8bf9df4bc Mon Sep 17 00:00:00 2001 From: Michael Pham <61564344+Mikefly123@users.noreply.github.com> Date: Sat, 4 Jul 2026 16:19:41 -0700 Subject: [PATCH 07/51] chore(phase4): bump fprime-zephyr to eef9f4c 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 --- lib/fprime-zephyr | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/fprime-zephyr b/lib/fprime-zephyr index 31399714..eef9f4c0 160000 --- a/lib/fprime-zephyr +++ b/lib/fprime-zephyr @@ -1 +1 @@ -Subproject commit 313997144363ea930af66e86b52d43537cf7f489 +Subproject commit eef9f4c059a9b0a22008d39288eb0af49e353032 From 32a30ba1abdeaec6fada8e77d6b2fa363d28568e Mon Sep 17 00:00:00 2001 From: Michael Pham <61564344+Mikefly123@users.noreply.github.com> Date: Sat, 4 Jul 2026 16:24:54 -0700 Subject: [PATCH 08/51] chore(phase4): bump fprime-zephyr to 2aafb52 UspRadio constructor refactor: configure(RalSession&) injection pattern allows FPP autocoder to instantiate the component without USP headers. Co-Authored-By: Claude Fable 5 --- lib/fprime-zephyr | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/fprime-zephyr b/lib/fprime-zephyr index eef9f4c0..2aafb523 160000 --- a/lib/fprime-zephyr +++ b/lib/fprime-zephyr @@ -1 +1 @@ -Subproject commit eef9f4c059a9b0a22008d39288eb0af49e353032 +Subproject commit 2aafb52380f52aa084883edda6d0bd64a4eabb4d From 394d7391e25d867c0e3a2c95b402e1dae0193ac0 Mon Sep 17 00:00:00 2001 From: Michael Pham <61564344+Mikefly123@users.noreply.github.com> Date: Sat, 4 Jul 2026 16:25:31 -0700 Subject: [PATCH 09/51] =?UTF-8?q?feat(phase4):=20topology=20integration=20?= =?UTF-8?q?=E2=80=94=20UspRadio=20on=20v5e,=20LoRa=20on=20v5c/v5d?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- .../ReferenceDeployment/Main.cpp | 12 ++++ .../ReferenceDeployment/Top/CMakeLists.txt | 17 +++++- .../Top/RadioInstances_Lora.fpp | 14 +++++ .../Top/RadioInstances_Usp.fpp | 19 ++++++ .../Top/RadioTopology_Lora.fpp | 60 +++++++++++++++++++ .../Top/RadioTopology_Usp.fpp | 56 +++++++++++++++++ .../Top/ReferenceDeploymentTopology.cpp | 41 ++++++++++++- .../Top/ReferenceDeploymentTopologyDefs.hpp | 10 +++- .../ReferenceDeployment/Top/instances.fpp | 5 +- .../ReferenceDeployment/Top/topology.fpp | 41 ++----------- 10 files changed, 234 insertions(+), 41 deletions(-) create mode 100644 PROVESFlightControllerReference/ReferenceDeployment/Top/RadioInstances_Lora.fpp create mode 100644 PROVESFlightControllerReference/ReferenceDeployment/Top/RadioInstances_Usp.fpp create mode 100644 PROVESFlightControllerReference/ReferenceDeployment/Top/RadioTopology_Lora.fpp create mode 100644 PROVESFlightControllerReference/ReferenceDeployment/Top/RadioTopology_Usp.fpp diff --git a/PROVESFlightControllerReference/ReferenceDeployment/Main.cpp b/PROVESFlightControllerReference/ReferenceDeployment/Main.cpp index 20df9d57..713d4ad3 100644 --- a/PROVESFlightControllerReference/ReferenceDeployment/Main.cpp +++ b/PROVESFlightControllerReference/ReferenceDeployment/Main.cpp @@ -21,7 +21,12 @@ const struct device* ina219Sys = DEVICE_DT_GET(DT_NODELABEL(ina219_0)); const struct device* ina219Sol = DEVICE_DT_GET(DT_NODELABEL(ina219_1)); const struct device* serial = DEVICE_DT_GET(DT_NODELABEL(cdc_acm_uart0)); +// v5c/v5d: Zephyr LoRa driver device node. +// v5e: USP does not use a Zephyr lora device; RalSessionImpl acquires the +// radio handle via smtc_rac_get_radio() in init(). +#ifndef CONFIG_LORA_BASICS_MODEM_DRIVERS const struct device* lora = DEVICE_DT_GET(DT_NODELABEL(lora0)); +#endif // const struct device* spi0 = DEVICE_DT_GET(DT_NODELABEL(spi0)); const struct device* peripheral_uart = DEVICE_DT_GET(DT_NODELABEL(uart0)); const struct device* peripheral_uart1 = DEVICE_DT_GET(DT_NODELABEL(uart1)); @@ -76,7 +81,14 @@ int main(int argc, char* argv[]) { // Flight Control Board device bindings inputs.ina219SysDevice = ina219Sys; inputs.ina219SolDevice = ina219Sol; +#ifndef CONFIG_LORA_BASICS_MODEM_DRIVERS inputs.loraDevice = lora; +#else + // v5e USP path: freq/power passed instead of a device pointer. + // Constants match LoRaConfig values used by the legacy driver. + inputs.uspFreqHz = 915000000U; + inputs.uspTxPowerDbm = 14; +#endif inputs.uartDevice = serial; inputs.lsm6dsoDevice = lsm6dso; inputs.lis2mdlDevice = lis2mdl; diff --git a/PROVESFlightControllerReference/ReferenceDeployment/Top/CMakeLists.txt b/PROVESFlightControllerReference/ReferenceDeployment/Top/CMakeLists.txt index d84487ac..2c561a37 100644 --- a/PROVESFlightControllerReference/ReferenceDeployment/Top/CMakeLists.txt +++ b/PROVESFlightControllerReference/ReferenceDeployment/Top/CMakeLists.txt @@ -5,15 +5,30 @@ # AUTOCODER_INPUTS: list of files to be passed to the autocoders # DEPENDS: list of libraries that this module depends on # +# Per-board radio selection (Phase 4 — USP port): +# - v5e (CONFIG_LORA_BASICS_MODEM_DRIVERS=y): RadioInstances_Usp.fpp + +# RadioTopology_Usp.fpp → Zephyr::UspRadio active component +# - v5c / v5d (CONFIG_LORA_BASICS_MODEM_DRIVERS not set): RadioInstances_Lora.fpp + +# RadioTopology_Lora.fpp → Zephyr::LoRa passive driver (legacy path) +# # More information in the F´ CMake API documentation: # https://fprime.jpl.nasa.gov/latest/docs/reference/api/cmake/API/ -# #### +if(DEFINED CONFIG_LORA_BASICS_MODEM_DRIVERS) + set(RADIO_INSTANCES_FPP "${CMAKE_CURRENT_LIST_DIR}/RadioInstances_Usp.fpp") + set(RADIO_TOPOLOGY_FPP "${CMAKE_CURRENT_LIST_DIR}/RadioTopology_Usp.fpp") +else() + set(RADIO_INSTANCES_FPP "${CMAKE_CURRENT_LIST_DIR}/RadioInstances_Lora.fpp") + set(RADIO_TOPOLOGY_FPP "${CMAKE_CURRENT_LIST_DIR}/RadioTopology_Lora.fpp") +endif() + register_fprime_module( AUTOCODER_INPUTS "${CMAKE_CURRENT_LIST_DIR}/instances.fpp" + "${RADIO_INSTANCES_FPP}" "${CMAKE_CURRENT_LIST_DIR}/topology.fpp" + "${RADIO_TOPOLOGY_FPP}" "${CMAKE_CURRENT_LIST_DIR}/ReferenceDeploymentPackets.fppi" SOURCES "${CMAKE_CURRENT_LIST_DIR}/ReferenceDeploymentTopology.cpp" diff --git a/PROVESFlightControllerReference/ReferenceDeployment/Top/RadioInstances_Lora.fpp b/PROVESFlightControllerReference/ReferenceDeployment/Top/RadioInstances_Lora.fpp new file mode 100644 index 00000000..8d504014 --- /dev/null +++ b/PROVESFlightControllerReference/ReferenceDeployment/Top/RadioInstances_Lora.fpp @@ -0,0 +1,14 @@ +module ReferenceDeployment { + + # ---------------------------------------------------------------------- + # Radio instance: Zephyr::LoRa (v5c / v5d — legacy path) + # + # Selected by CMakeLists.txt when CONFIG_LORA_BASICS_MODEM_DRIVERS is not + # set (i.e. any board that uses the Zephyr in-tree LoRa driver). + # ---------------------------------------------------------------------- + + instance lora: Zephyr.LoRa base id 0x1001F000 + + instance loraRetry: Svc.ComRetry base id 0x10063000 + +} diff --git a/PROVESFlightControllerReference/ReferenceDeployment/Top/RadioInstances_Usp.fpp b/PROVESFlightControllerReference/ReferenceDeployment/Top/RadioInstances_Usp.fpp new file mode 100644 index 00000000..f2260195 --- /dev/null +++ b/PROVESFlightControllerReference/ReferenceDeployment/Top/RadioInstances_Usp.fpp @@ -0,0 +1,19 @@ +module ReferenceDeployment { + + # ---------------------------------------------------------------------- + # Radio instance: Zephyr::UspRadio (v5e+ — Semtech USP path) + # + # Selected by CMakeLists.txt when CONFIG_LORA_BASICS_MODEM_DRIVERS is set + # (i.e. boards with the SX1262 USP driver enabled in their defconfig). + # + # UspRadio is an ACTIVE component (deferred-handler SBand pattern). + # Queue / stack sizes match the component's expected message depth. + # Priority 11 (above rate groups, below sequencer). + # ---------------------------------------------------------------------- + + instance uspRadio: Zephyr.UspRadio base id 0x1001F000 \ + queue size Default.QUEUE_SIZE * 2 \ + stack size Default.STACK_SIZE \ + priority 11 + +} diff --git a/PROVESFlightControllerReference/ReferenceDeployment/Top/RadioTopology_Lora.fpp b/PROVESFlightControllerReference/ReferenceDeployment/Top/RadioTopology_Lora.fpp new file mode 100644 index 00000000..bc263e33 --- /dev/null +++ b/PROVESFlightControllerReference/ReferenceDeployment/Top/RadioTopology_Lora.fpp @@ -0,0 +1,60 @@ +module ReferenceDeployment { + + # ---------------------------------------------------------------------- + # Radio topology: Zephyr::LoRa (v5c / v5d — legacy path) + # + # Wires the `lora` passive driver to the ComCcsdsLora subtopology's + # Svc.Com interface (framer / frameAccumulator / commsBufferManager), + # with the ComRetry retry shim in the downlink path. + # Also registers per-board rate-group connections for lora's schedIn. + # + # Selected by CMakeLists.txt when CONFIG_LORA_BASICS_MODEM_DRIVERS is not + # set. + # ---------------------------------------------------------------------- + + topology ReferenceDeployment { + + instance lora + instance loraRetry + + connections CommunicationsRadio { + lora.allocate -> ComCcsdsLora.commsBufferManager.bufferGetCallee + lora.deallocate -> ComCcsdsLora.commsBufferManager.bufferSendIn + + # ComDriver <-> FrameAccumulator (Uplink) + lora.dataOut -> ComCcsdsLora.frameAccumulator.dataIn + ComCcsdsLora.frameAccumulator.dataReturnOut -> lora.dataReturnIn + + # ComStub <-> ComDriver (Downlink) with retry shim + ComCcsdsLora.framer.dataOut -> loraRetry.dataIn + loraRetry.dataOut -> lora.dataIn + + lora.dataReturnOut -> loraRetry.dataReturnIn + loraRetry.dataReturnOut -> ComCcsdsLora.framer.dataReturnIn + + lora.comStatusOut -> loraRetry.comStatusIn + loraRetry.comStatusOut -> downlinkDelay.comStatusIn + downlinkDelay.comStatusOut -> ComCcsdsLora.framer.comStatusIn + + # Startup sequence wiring (board-agnostic; placed here to avoid + # duplication; identical in the USP path) + startupManager.runSequence -> cmdSeq.seqRunIn + cmdSeq.seqStartOut -> startupManager.sequenceStarted + cmdSeq.seqDone -> startupManager.completeSequence + + modeManager.runSequence -> safeModeSeq.seqRunIn + safeModeSeq.seqDone -> modeManager.completeSequence + + # RTC time change cancels running sequences + rtcManager.cancelSequences[0] -> cmdSeq.seqCancelIn + rtcManager.cancelSequences[1] -> payloadSeq.seqCancelIn + rtcManager.cancelSequences[2] -> safeModeSeq.seqCancelIn + } + + # LoRa (passive) has no run port. + # Rate-group member slots used here must match RadioTopology_Usp.fpp. + # (No additional rate group connections needed for passive LoRa driver.) + + } + +} diff --git a/PROVESFlightControllerReference/ReferenceDeployment/Top/RadioTopology_Usp.fpp b/PROVESFlightControllerReference/ReferenceDeployment/Top/RadioTopology_Usp.fpp new file mode 100644 index 00000000..4096f06e --- /dev/null +++ b/PROVESFlightControllerReference/ReferenceDeployment/Top/RadioTopology_Usp.fpp @@ -0,0 +1,56 @@ +module ReferenceDeployment { + + # ---------------------------------------------------------------------- + # Radio topology: Zephyr::UspRadio (v5e+ — Semtech USP path) + # + # Wires the `uspRadio` active component to the ComCcsdsLora subtopology's + # Svc.Com interface (framer / frameAccumulator / commsBufferManager). + # No ComRetry shim: UspRadio handles its own back-pressure via the + # active-component queue. + # Also registers the run port on rateGroup1Hz (slot 20 — unused by LoRa path) + # and preserves startup-sequence and cancel-sequence wiring. + # + # Selected by CMakeLists.txt when CONFIG_LORA_BASICS_MODEM_DRIVERS is set. + # ---------------------------------------------------------------------- + + topology ReferenceDeployment { + + instance uspRadio + + connections CommunicationsRadio { + uspRadio.allocate -> ComCcsdsLora.commsBufferManager.bufferGetCallee + uspRadio.deallocate -> ComCcsdsLora.commsBufferManager.bufferSendIn + + # UspRadio <-> FrameAccumulator (Uplink) + uspRadio.dataOut -> ComCcsdsLora.frameAccumulator.dataIn + ComCcsdsLora.frameAccumulator.dataReturnOut -> uspRadio.dataReturnIn + + # UspRadio <-> Framer (Downlink — direct, no retry shim) + ComCcsdsLora.framer.dataOut -> uspRadio.dataIn + uspRadio.dataReturnOut -> ComCcsdsLora.framer.dataReturnIn + uspRadio.comStatusOut -> downlinkDelay.comStatusIn + downlinkDelay.comStatusOut -> ComCcsdsLora.framer.comStatusIn + + # Startup sequence wiring (identical to Lora path) + startupManager.runSequence -> cmdSeq.seqRunIn + cmdSeq.seqStartOut -> startupManager.sequenceStarted + cmdSeq.seqDone -> startupManager.completeSequence + + modeManager.runSequence -> safeModeSeq.seqRunIn + safeModeSeq.seqDone -> modeManager.completeSequence + + # RTC time change cancels running sequences + rtcManager.cancelSequences[0] -> cmdSeq.seqCancelIn + rtcManager.cancelSequences[1] -> payloadSeq.seqCancelIn + rtcManager.cancelSequences[2] -> safeModeSeq.seqCancelIn + } + + connections RadioRateGroup { + # UspRadio run port: revert-deadline tick + telemetry flush (1 Hz) + # Slot 20 is free in the legacy LoRa topology. + rateGroup1Hz.RateGroupMemberOut[20] -> uspRadio.run + } + + } + +} diff --git a/PROVESFlightControllerReference/ReferenceDeployment/Top/ReferenceDeploymentTopology.cpp b/PROVESFlightControllerReference/ReferenceDeployment/Top/ReferenceDeploymentTopology.cpp index e54eedff..a5475f1e 100644 --- a/PROVESFlightControllerReference/ReferenceDeployment/Top/ReferenceDeploymentTopology.cpp +++ b/PROVESFlightControllerReference/ReferenceDeployment/Top/ReferenceDeploymentTopology.cpp @@ -10,10 +10,25 @@ // Necessary project-specified types #include +#include #include #include +// Phase 4: per-board radio startup +#ifdef CONFIG_LORA_BASICS_MODEM_DRIVERS +// v5e USP path: RalSessionImpl + UspRadio +#include "fprime-zephyr/Drv/UspRadio/RalSessionImpl.hpp" +#include "fprime-zephyr/Drv/UspRadio/UspRadio.hpp" + +// Static RalSessionImpl instance (lives for the entire flight). +// Freq and power match LoRaCfg constants used by the legacy driver. +static Zephyr::RalSessionImpl s_ralSession( + 915000000U, // 915 MHz (matches LoRaConfig::FREQUENCY) + 14 // +14 dBm (matches LoRaConfig::TX_POWER) +); +#endif // CONFIG_LORA_BASICS_MODEM_DRIVERS + static const struct gpio_dt_spec ledGpio = GPIO_DT_SPEC_GET(DT_NODELABEL(led0), gpios); static const struct gpio_dt_spec burnwire0Gpio = GPIO_DT_SPEC_GET(DT_NODELABEL(burnwire0), gpios); static const struct gpio_dt_spec burnwire1Gpio = GPIO_DT_SPEC_GET(DT_NODELABEL(burnwire1), gpios); @@ -121,9 +136,31 @@ void setupTopology(const TopologyState& state) { // Autocoded task kick-off (active components). Function provided by autocoder. startTasks(state); - // We have a pipeline for both the LoRa and UART drive to allow for ground harness debugging an - // for over-the-air communications. + // We have a pipeline for both the radio and UART driver to allow for ground + // harness debugging and for over-the-air communications. + // + // Board selection (Phase 4 USP port): + // v5e (CONFIG_LORA_BASICS_MODEM_DRIVERS=y): UspRadio with RalSessionImpl + // v5c/v5d (legacy): Zephyr LoRa driver + // + // Both paths boot with TX DISABLED; the startup sequence enables TX after + // the mode manager permits it (identical gating to the legacy LoRa path). +#ifdef CONFIG_LORA_BASICS_MODEM_DRIVERS + // v5e USP path. + // 1. Inject the RalSessionImpl (constructed at file scope above) into + // the autocoded 'uspRadio' component via configure(). + // 2. UspRadio::start() calls session.init() which internally calls + // zephyr_usp_initialization_wait() + zephyr_smtc_rac_init() (see + // RalSessionImpl::init()), applies the P0 boot-default profile, and + // starts continuous RX. TX stays DISABLED until the startup-sequence + // sends a TRANSMIT(ENABLED) command (same gating as the legacy path). + uspRadio.configure(s_ralSession); + if (!uspRadio.start(Zephyr::UspTransmitState::DISABLED)) { + Fw::Logger::log("[Topology] UspRadio start() failed -- radio inactive\n"); + } +#else lora.start(state.loraDevice, Zephyr::TransmitState::DISABLED); +#endif comDriver.configure(state.uartDevice, state.baudRate); // static struct spi_cs_control cs_ctrl = { diff --git a/PROVESFlightControllerReference/ReferenceDeployment/Top/ReferenceDeploymentTopologyDefs.hpp b/PROVESFlightControllerReference/ReferenceDeployment/Top/ReferenceDeploymentTopologyDefs.hpp index 8d5be24a..b664850f 100644 --- a/PROVESFlightControllerReference/ReferenceDeployment/Top/ReferenceDeploymentTopologyDefs.hpp +++ b/PROVESFlightControllerReference/ReferenceDeployment/Top/ReferenceDeploymentTopologyDefs.hpp @@ -113,7 +113,15 @@ namespace ReferenceDeployment { struct TopologyState { const device* uartDevice; //!< UART device path for communication const device* spi0Device; //!< Spi device path for s-band LoRa module - const device* loraDevice; //!< LoRa device path for communication +#ifdef CONFIG_LORA_BASICS_MODEM_DRIVERS + // v5e (USP path): no Zephyr LoRa device; radio is initialised by + // RalSessionImpl via the USP/RAC API. Freq and power come from LoRaCfg + // constants re-exported below so callers don't need the Zephyr lora header. + uint32_t uspFreqHz; //!< Carrier frequency in Hz (e.g. 915000000) + int8_t uspTxPowerDbm; //!< TX power in dBm (e.g. 14) +#else + const device* loraDevice; //!< LoRa device path for communication (v5c/v5d) +#endif ComCcsdsLora::SubtopologyState comCcsdsLora; //!< Subtopology state for ComCcsdsLora // ComCcsdsSband::SubtopologyState comCcsdsSband; //!< Subtopology state for ComCcsdsSband U32 baudRate; //!< Baud rate for UART communication diff --git a/PROVESFlightControllerReference/ReferenceDeployment/Top/instances.fpp b/PROVESFlightControllerReference/ReferenceDeployment/Top/instances.fpp index c1a541eb..e8485dfe 100644 --- a/PROVESFlightControllerReference/ReferenceDeployment/Top/instances.fpp +++ b/PROVESFlightControllerReference/ReferenceDeployment/Top/instances.fpp @@ -103,7 +103,8 @@ module ReferenceDeployment { instance downlinkDelay: Components.ComDelay base id 0x1001E000 - instance lora: Zephyr.LoRa base id 0x1001F000 + # lora / uspRadio instance lives in RadioInstances_Lora.fpp or + # RadioInstances_Usp.fpp (selected by CMakeLists.txt per board). instance comSplitterEvents: Svc.ComSplitter base id 0x10020000 @@ -215,7 +216,7 @@ module ReferenceDeployment { instance fileUplinkCollector: Utilities.BufferCollector base id 0x10060000 instance telemetryDelay: Utilities.RateDelay base id 0x10061000 - instance loraRetry: Svc.ComRetry base id 0x10063000 + # loraRetry lives in RadioInstances_Lora.fpp (only on non-USP boards). instance downlinkRepeater: Utilities.BufferRepeater base id 0x10064000 diff --git a/PROVESFlightControllerReference/ReferenceDeployment/Top/topology.fpp b/PROVESFlightControllerReference/ReferenceDeployment/Top/topology.fpp index d7ab78d9..e379802c 100644 --- a/PROVESFlightControllerReference/ReferenceDeployment/Top/topology.fpp +++ b/PROVESFlightControllerReference/ReferenceDeployment/Top/topology.fpp @@ -30,8 +30,8 @@ module ReferenceDeployment { instance rateGroup1Hz instance rateGroupDriver instance timer - instance lora - instance loraRetry + # lora / uspRadio instance declared in RadioInstances_Lora.fpp or + # RadioInstances_Usp.fpp (CMakeLists.txt picks per board). instance gpioWatchdog instance gpioBurnwire0 instance gpioBurnwire1 @@ -191,39 +191,10 @@ module ReferenceDeployment { # comDelaySband.comStatusOut -> ComCcsdsSband.framer.comStatusIn #} - connections CommunicationsRadio { - lora.allocate -> ComCcsdsLora.commsBufferManager.bufferGetCallee - lora.deallocate -> ComCcsdsLora.commsBufferManager.bufferSendIn - - # ComDriver <-> FrameAccumulator (Uplink) - lora.dataOut -> ComCcsdsLora.frameAccumulator.dataIn - ComCcsdsLora.frameAccumulator.dataReturnOut -> lora.dataReturnIn - - # ComStub <-> ComDriver (Downlink) - ComCcsdsLora.framer.dataOut -> loraRetry.dataIn - loraRetry.dataOut -> lora.dataIn - - lora.dataReturnOut -> loraRetry.dataReturnIn - loraRetry.dataReturnOut -> ComCcsdsLora.framer.dataReturnIn - - lora.comStatusOut -> loraRetry.comStatusIn - loraRetry.comStatusOut -> downlinkDelay.comStatusIn - downlinkDelay.comStatusOut ->ComCcsdsLora.framer.comStatusIn - - startupManager.runSequence -> cmdSeq.seqRunIn - cmdSeq.seqStartOut -> startupManager.sequenceStarted - cmdSeq.seqDone -> startupManager.completeSequence - - modeManager.runSequence -> safeModeSeq.seqRunIn - safeModeSeq.seqDone -> modeManager.completeSequence - - # RTC time change cancels running sequences - rtcManager.cancelSequences[0] -> cmdSeq.seqCancelIn - rtcManager.cancelSequences[1] -> payloadSeq.seqCancelIn - rtcManager.cancelSequences[2] -> safeModeSeq.seqCancelIn - - - } + # CommunicationsRadio connections live in RadioTopology_Lora.fpp or + # RadioTopology_Usp.fpp (CMakeLists.txt picks per board). + # Those files also carry the startup-sequence and RTC cancel-sequence + # wiring (identical for both radio variants). connections CommunicationsUart { # ComDriver buffer allocations From 48f077541e4476ffb5bfa00fbd75ec0d900cebaa Mon Sep 17 00:00:00 2001 From: Michael Pham <61564344+Mikefly123@users.noreply.github.com> Date: Sat, 4 Jul 2026 16:55:49 -0700 Subject: [PATCH 10/51] =?UTF-8?q?feat(phase4):=20topology=20integration=20?= =?UTF-8?q?=E2=80=94=20v5e=20uses=20Zephyr::UspRadio,=20v5c/v5d=20keep=20Z?= =?UTF-8?q?ephyr::LoRa?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- .gitignore | 6 +++ .../ReferenceDeployment/Top/CMakeLists.txt | 38 ++++++++++++++----- .../Top/RadioInstances_Lora.fpp | 14 ------- .../Top/RadioInstances_Lora.fppi | 7 ++++ .../Top/RadioInstances_Usp.fpp | 19 ---------- .../Top/RadioInstances_Usp.fppi | 11 ++++++ .../Top/RadioPacketsBytesReceived_Lora.fppi | 1 + .../Top/RadioPacketsBytesReceived_Usp.fppi | 1 + .../Top/RadioPacketsRadio_Lora.fppi | 5 +++ .../Top/RadioPacketsRadio_Usp.fppi | 9 +++++ ...ology_Lora.fpp => RadioTopology_Lora.fppi} | 31 +++------------ ...opology_Usp.fpp => RadioTopology_Usp.fppi} | 32 +++++----------- .../Top/ReferenceDeploymentPackets.fppi | 14 +++---- .../Top/ReferenceDeploymentTopology.cpp | 6 +-- .../ReferenceDeployment/Top/instances.fpp | 8 ++-- .../ReferenceDeployment/Top/topology.fpp | 9 +++-- ...s_flight_control_board_v5e_rp2350a_m33.dts | 11 ++++++ ...ht_control_board_v5e_rp2350a_m33_defconfig | 3 ++ lib/fprime-zephyr | 2 +- 19 files changed, 116 insertions(+), 111 deletions(-) delete mode 100644 PROVESFlightControllerReference/ReferenceDeployment/Top/RadioInstances_Lora.fpp create mode 100644 PROVESFlightControllerReference/ReferenceDeployment/Top/RadioInstances_Lora.fppi delete mode 100644 PROVESFlightControllerReference/ReferenceDeployment/Top/RadioInstances_Usp.fpp create mode 100644 PROVESFlightControllerReference/ReferenceDeployment/Top/RadioInstances_Usp.fppi create mode 100644 PROVESFlightControllerReference/ReferenceDeployment/Top/RadioPacketsBytesReceived_Lora.fppi create mode 100644 PROVESFlightControllerReference/ReferenceDeployment/Top/RadioPacketsBytesReceived_Usp.fppi create mode 100644 PROVESFlightControllerReference/ReferenceDeployment/Top/RadioPacketsRadio_Lora.fppi create mode 100644 PROVESFlightControllerReference/ReferenceDeployment/Top/RadioPacketsRadio_Usp.fppi rename PROVESFlightControllerReference/ReferenceDeployment/Top/{RadioTopology_Lora.fpp => RadioTopology_Lora.fppi} (56%) rename PROVESFlightControllerReference/ReferenceDeployment/Top/{RadioTopology_Usp.fpp => RadioTopology_Usp.fppi} (61%) diff --git a/.gitignore b/.gitignore index 94fcbd03..df108bf5 100644 --- a/.gitignore +++ b/.gitignore @@ -58,3 +58,9 @@ yamcs/yamcs-runtime/ /circuit-python-passthrough/firmware.uf2 /circuit-python-passthrough/lib/ /circuit-python-passthrough/tools/ + +# Phase 4 radio config symlinks (generated by CMake at configure time) +PROVESFlightControllerReference/ReferenceDeployment/Top/RadioInstances.fppi +PROVESFlightControllerReference/ReferenceDeployment/Top/RadioTopology.fppi +PROVESFlightControllerReference/ReferenceDeployment/Top/RadioPacketsBytesReceived.fppi +PROVESFlightControllerReference/ReferenceDeployment/Top/RadioPacketsRadio.fppi diff --git a/PROVESFlightControllerReference/ReferenceDeployment/Top/CMakeLists.txt b/PROVESFlightControllerReference/ReferenceDeployment/Top/CMakeLists.txt index 2c561a37..0875ef4c 100644 --- a/PROVESFlightControllerReference/ReferenceDeployment/Top/CMakeLists.txt +++ b/PROVESFlightControllerReference/ReferenceDeployment/Top/CMakeLists.txt @@ -6,29 +6,47 @@ # DEPENDS: list of libraries that this module depends on # # Per-board radio selection (Phase 4 — USP port): -# - v5e (CONFIG_LORA_BASICS_MODEM_DRIVERS=y): RadioInstances_Usp.fpp + -# RadioTopology_Usp.fpp → Zephyr::UspRadio active component -# - v5c / v5d (CONFIG_LORA_BASICS_MODEM_DRIVERS not set): RadioInstances_Lora.fpp + -# RadioTopology_Lora.fpp → Zephyr::LoRa passive driver (legacy path) +# v5e (CONFIG_LORA_BASICS_MODEM_DRIVERS=y): +# RadioInstances.fppi -> RadioInstances_Usp.fppi (Zephyr::UspRadio) +# RadioTopology.fppi -> RadioTopology_Usp.fppi +# v5c / v5d (CONFIG_LORA_BASICS_MODEM_DRIVERS not set): +# RadioInstances.fppi -> RadioInstances_Lora.fppi (Zephyr::LoRa) +# RadioTopology.fppi -> RadioTopology_Lora.fppi +# +# FPP `include` directives resolve relative to the source file, so we +# create symbolic links in the source tree at configure time. The links +# are committed to .gitignore so they do not appear as untracked files. # # More information in the F´ CMake API documentation: # https://fprime.jpl.nasa.gov/latest/docs/reference/api/cmake/API/ #### if(DEFINED CONFIG_LORA_BASICS_MODEM_DRIVERS) - set(RADIO_INSTANCES_FPP "${CMAKE_CURRENT_LIST_DIR}/RadioInstances_Usp.fpp") - set(RADIO_TOPOLOGY_FPP "${CMAKE_CURRENT_LIST_DIR}/RadioTopology_Usp.fpp") + set(RADIO_SUFFIX "Usp") else() - set(RADIO_INSTANCES_FPP "${CMAKE_CURRENT_LIST_DIR}/RadioInstances_Lora.fpp") - set(RADIO_TOPOLOGY_FPP "${CMAKE_CURRENT_LIST_DIR}/RadioTopology_Lora.fpp") + set(RADIO_SUFFIX "Lora") endif() +# Generate per-board .fppi symlinks in the source dir. +# FPP `include` resolves relative to the source file, so symlinks live +# alongside the FPP files. CREATE_LINK SYMBOLIC COPY_ON_ERROR falls back +# to a file copy on platforms that don't support symlinks. +foreach(_kind "Instances" "Topology") + set(_src "${CMAKE_CURRENT_LIST_DIR}/Radio${_kind}_${RADIO_SUFFIX}.fppi") + set(_dst "${CMAKE_CURRENT_LIST_DIR}/Radio${_kind}.fppi") + file(CREATE_LINK "${_src}" "${_dst}" SYMBOLIC COPY_ON_ERROR) +endforeach() +# Packet snippet symlinks (telemetry packet definitions reference per-board instance names) +foreach(_kind "BytesReceived" "Radio") + set(_src "${CMAKE_CURRENT_LIST_DIR}/RadioPackets${_kind}_${RADIO_SUFFIX}.fppi") + set(_dst "${CMAKE_CURRENT_LIST_DIR}/RadioPackets${_kind}.fppi") + file(CREATE_LINK "${_src}" "${_dst}" SYMBOLIC COPY_ON_ERROR) +endforeach() + register_fprime_module( AUTOCODER_INPUTS "${CMAKE_CURRENT_LIST_DIR}/instances.fpp" - "${RADIO_INSTANCES_FPP}" "${CMAKE_CURRENT_LIST_DIR}/topology.fpp" - "${RADIO_TOPOLOGY_FPP}" "${CMAKE_CURRENT_LIST_DIR}/ReferenceDeploymentPackets.fppi" SOURCES "${CMAKE_CURRENT_LIST_DIR}/ReferenceDeploymentTopology.cpp" diff --git a/PROVESFlightControllerReference/ReferenceDeployment/Top/RadioInstances_Lora.fpp b/PROVESFlightControllerReference/ReferenceDeployment/Top/RadioInstances_Lora.fpp deleted file mode 100644 index 8d504014..00000000 --- a/PROVESFlightControllerReference/ReferenceDeployment/Top/RadioInstances_Lora.fpp +++ /dev/null @@ -1,14 +0,0 @@ -module ReferenceDeployment { - - # ---------------------------------------------------------------------- - # Radio instance: Zephyr::LoRa (v5c / v5d — legacy path) - # - # Selected by CMakeLists.txt when CONFIG_LORA_BASICS_MODEM_DRIVERS is not - # set (i.e. any board that uses the Zephyr in-tree LoRa driver). - # ---------------------------------------------------------------------- - - instance lora: Zephyr.LoRa base id 0x1001F000 - - instance loraRetry: Svc.ComRetry base id 0x10063000 - -} diff --git a/PROVESFlightControllerReference/ReferenceDeployment/Top/RadioInstances_Lora.fppi b/PROVESFlightControllerReference/ReferenceDeployment/Top/RadioInstances_Lora.fppi new file mode 100644 index 00000000..24ecdf8e --- /dev/null +++ b/PROVESFlightControllerReference/ReferenceDeployment/Top/RadioInstances_Lora.fppi @@ -0,0 +1,7 @@ +# Radio instance declarations for v5c / v5d (Zephyr LoRa legacy path). +# Included by instances.fpp inside module ReferenceDeployment { }. +# Selected by CMakeLists.txt when CONFIG_LORA_BASICS_MODEM_DRIVERS is not set. + + instance lora: Zephyr.LoRa base id 0x1001F000 + + instance loraRetry: Svc.ComRetry base id 0x10063000 diff --git a/PROVESFlightControllerReference/ReferenceDeployment/Top/RadioInstances_Usp.fpp b/PROVESFlightControllerReference/ReferenceDeployment/Top/RadioInstances_Usp.fpp deleted file mode 100644 index f2260195..00000000 --- a/PROVESFlightControllerReference/ReferenceDeployment/Top/RadioInstances_Usp.fpp +++ /dev/null @@ -1,19 +0,0 @@ -module ReferenceDeployment { - - # ---------------------------------------------------------------------- - # Radio instance: Zephyr::UspRadio (v5e+ — Semtech USP path) - # - # Selected by CMakeLists.txt when CONFIG_LORA_BASICS_MODEM_DRIVERS is set - # (i.e. boards with the SX1262 USP driver enabled in their defconfig). - # - # UspRadio is an ACTIVE component (deferred-handler SBand pattern). - # Queue / stack sizes match the component's expected message depth. - # Priority 11 (above rate groups, below sequencer). - # ---------------------------------------------------------------------- - - instance uspRadio: Zephyr.UspRadio base id 0x1001F000 \ - queue size Default.QUEUE_SIZE * 2 \ - stack size Default.STACK_SIZE \ - priority 11 - -} diff --git a/PROVESFlightControllerReference/ReferenceDeployment/Top/RadioInstances_Usp.fppi b/PROVESFlightControllerReference/ReferenceDeployment/Top/RadioInstances_Usp.fppi new file mode 100644 index 00000000..2bfda5b8 --- /dev/null +++ b/PROVESFlightControllerReference/ReferenceDeployment/Top/RadioInstances_Usp.fppi @@ -0,0 +1,11 @@ +# Radio instance declarations for v5e (Semtech USP path). +# Included by instances.fpp inside module ReferenceDeployment { }. +# Selected by CMakeLists.txt when CONFIG_LORA_BASICS_MODEM_DRIVERS is set. +# +# UspRadio is an ACTIVE component (deferred-handler SBand pattern). +# Queue = 20 (QUEUE_SIZE*2), stack = 4K, priority 11 (above rate groups). + + instance uspRadio: Zephyr.UspRadio base id 0x1001F000 \ + queue size Default.QUEUE_SIZE * 2 \ + stack size Default.STACK_SIZE \ + priority 11 diff --git a/PROVESFlightControllerReference/ReferenceDeployment/Top/RadioPacketsBytesReceived_Lora.fppi b/PROVESFlightControllerReference/ReferenceDeployment/Top/RadioPacketsBytesReceived_Lora.fppi new file mode 100644 index 00000000..c38ffbcf --- /dev/null +++ b/PROVESFlightControllerReference/ReferenceDeployment/Top/RadioPacketsBytesReceived_Lora.fppi @@ -0,0 +1 @@ + lora.BytesReceived diff --git a/PROVESFlightControllerReference/ReferenceDeployment/Top/RadioPacketsBytesReceived_Usp.fppi b/PROVESFlightControllerReference/ReferenceDeployment/Top/RadioPacketsBytesReceived_Usp.fppi new file mode 100644 index 00000000..a0ed55ee --- /dev/null +++ b/PROVESFlightControllerReference/ReferenceDeployment/Top/RadioPacketsBytesReceived_Usp.fppi @@ -0,0 +1 @@ + uspRadio.BytesReceived diff --git a/PROVESFlightControllerReference/ReferenceDeployment/Top/RadioPacketsRadio_Lora.fppi b/PROVESFlightControllerReference/ReferenceDeployment/Top/RadioPacketsRadio_Lora.fppi new file mode 100644 index 00000000..236e4f90 --- /dev/null +++ b/PROVESFlightControllerReference/ReferenceDeployment/Top/RadioPacketsRadio_Lora.fppi @@ -0,0 +1,5 @@ + packet Radio id 8 group 2 { + lora.LastRssi + lora.LastSnr + lora.BytesSent + } diff --git a/PROVESFlightControllerReference/ReferenceDeployment/Top/RadioPacketsRadio_Usp.fppi b/PROVESFlightControllerReference/ReferenceDeployment/Top/RadioPacketsRadio_Usp.fppi new file mode 100644 index 00000000..7b4935b0 --- /dev/null +++ b/PROVESFlightControllerReference/ReferenceDeployment/Top/RadioPacketsRadio_Usp.fppi @@ -0,0 +1,9 @@ + packet Radio id 8 group 2 { + uspRadio.LastRssi + uspRadio.LastSnr + uspRadio.BytesSent + uspRadio.TxProfile + uspRadio.RxProfile + uspRadio.ProfileTableVersion + uspRadio.RxReverts + } diff --git a/PROVESFlightControllerReference/ReferenceDeployment/Top/RadioTopology_Lora.fpp b/PROVESFlightControllerReference/ReferenceDeployment/Top/RadioTopology_Lora.fppi similarity index 56% rename from PROVESFlightControllerReference/ReferenceDeployment/Top/RadioTopology_Lora.fpp rename to PROVESFlightControllerReference/ReferenceDeployment/Top/RadioTopology_Lora.fppi index bc263e33..aa54e011 100644 --- a/PROVESFlightControllerReference/ReferenceDeployment/Top/RadioTopology_Lora.fpp +++ b/PROVESFlightControllerReference/ReferenceDeployment/Top/RadioTopology_Lora.fppi @@ -1,18 +1,6 @@ -module ReferenceDeployment { - - # ---------------------------------------------------------------------- - # Radio topology: Zephyr::LoRa (v5c / v5d — legacy path) - # - # Wires the `lora` passive driver to the ComCcsdsLora subtopology's - # Svc.Com interface (framer / frameAccumulator / commsBufferManager), - # with the ComRetry retry shim in the downlink path. - # Also registers per-board rate-group connections for lora's schedIn. - # - # Selected by CMakeLists.txt when CONFIG_LORA_BASICS_MODEM_DRIVERS is not - # set. - # ---------------------------------------------------------------------- - - topology ReferenceDeployment { +# Radio topology connections for v5c / v5d (Zephyr LoRa legacy path). +# Included by topology.fpp inside topology ReferenceDeployment { }. +# Selected by CMakeLists.txt when CONFIG_LORA_BASICS_MODEM_DRIVERS is not set. instance lora instance loraRetry @@ -25,7 +13,7 @@ module ReferenceDeployment { lora.dataOut -> ComCcsdsLora.frameAccumulator.dataIn ComCcsdsLora.frameAccumulator.dataReturnOut -> lora.dataReturnIn - # ComStub <-> ComDriver (Downlink) with retry shim + # ComStub <-> ComDriver (Downlink) with ComRetry shim ComCcsdsLora.framer.dataOut -> loraRetry.dataIn loraRetry.dataOut -> lora.dataIn @@ -36,8 +24,7 @@ module ReferenceDeployment { loraRetry.comStatusOut -> downlinkDelay.comStatusIn downlinkDelay.comStatusOut -> ComCcsdsLora.framer.comStatusIn - # Startup sequence wiring (board-agnostic; placed here to avoid - # duplication; identical in the USP path) + # Startup sequence wiring startupManager.runSequence -> cmdSeq.seqRunIn cmdSeq.seqStartOut -> startupManager.sequenceStarted cmdSeq.seqDone -> startupManager.completeSequence @@ -50,11 +37,3 @@ module ReferenceDeployment { rtcManager.cancelSequences[1] -> payloadSeq.seqCancelIn rtcManager.cancelSequences[2] -> safeModeSeq.seqCancelIn } - - # LoRa (passive) has no run port. - # Rate-group member slots used here must match RadioTopology_Usp.fpp. - # (No additional rate group connections needed for passive LoRa driver.) - - } - -} diff --git a/PROVESFlightControllerReference/ReferenceDeployment/Top/RadioTopology_Usp.fpp b/PROVESFlightControllerReference/ReferenceDeployment/Top/RadioTopology_Usp.fppi similarity index 61% rename from PROVESFlightControllerReference/ReferenceDeployment/Top/RadioTopology_Usp.fpp rename to PROVESFlightControllerReference/ReferenceDeployment/Top/RadioTopology_Usp.fppi index 4096f06e..a48a1278 100644 --- a/PROVESFlightControllerReference/ReferenceDeployment/Top/RadioTopology_Usp.fpp +++ b/PROVESFlightControllerReference/ReferenceDeployment/Top/RadioTopology_Usp.fppi @@ -1,19 +1,9 @@ -module ReferenceDeployment { - - # ---------------------------------------------------------------------- - # Radio topology: Zephyr::UspRadio (v5e+ — Semtech USP path) - # - # Wires the `uspRadio` active component to the ComCcsdsLora subtopology's - # Svc.Com interface (framer / frameAccumulator / commsBufferManager). - # No ComRetry shim: UspRadio handles its own back-pressure via the - # active-component queue. - # Also registers the run port on rateGroup1Hz (slot 20 — unused by LoRa path) - # and preserves startup-sequence and cancel-sequence wiring. - # - # Selected by CMakeLists.txt when CONFIG_LORA_BASICS_MODEM_DRIVERS is set. - # ---------------------------------------------------------------------- - - topology ReferenceDeployment { +# Radio topology connections for v5e (Semtech USP path). +# Included by topology.fpp inside topology ReferenceDeployment { }. +# Selected by CMakeLists.txt when CONFIG_LORA_BASICS_MODEM_DRIVERS is set. +# +# UspRadio is active; no ComRetry shim needed (active-component queue provides +# back-pressure). Direct framer -> uspRadio.dataIn path. instance uspRadio @@ -25,7 +15,7 @@ module ReferenceDeployment { uspRadio.dataOut -> ComCcsdsLora.frameAccumulator.dataIn ComCcsdsLora.frameAccumulator.dataReturnOut -> uspRadio.dataReturnIn - # UspRadio <-> Framer (Downlink — direct, no retry shim) + # UspRadio <-> Framer (Downlink, direct — no retry shim) ComCcsdsLora.framer.dataOut -> uspRadio.dataIn uspRadio.dataReturnOut -> ComCcsdsLora.framer.dataReturnIn uspRadio.comStatusOut -> downlinkDelay.comStatusIn @@ -46,11 +36,7 @@ module ReferenceDeployment { } connections RadioRateGroup { - # UspRadio run port: revert-deadline tick + telemetry flush (1 Hz) - # Slot 20 is free in the legacy LoRa topology. + # UspRadio run port: revert-deadline tick + telemetry flush at 1 Hz. + # Slot 20 is the first free slot after the legacy topology's highest used (19). rateGroup1Hz.RateGroupMemberOut[20] -> uspRadio.run } - - } - -} diff --git a/PROVESFlightControllerReference/ReferenceDeployment/Top/ReferenceDeploymentPackets.fppi b/PROVESFlightControllerReference/ReferenceDeployment/Top/ReferenceDeploymentPackets.fppi index f107909f..b3a80bea 100644 --- a/PROVESFlightControllerReference/ReferenceDeployment/Top/ReferenceDeploymentPackets.fppi +++ b/PROVESFlightControllerReference/ReferenceDeployment/Top/ReferenceDeploymentPackets.fppi @@ -29,7 +29,10 @@ telemetry packets ReferenceDeploymentPackets { ComCcsdsLora.authenticatelora.CurrentSequenceNumber ComCcsdsUart.authenticate.CurrentSequenceNumber - lora.BytesReceived + # Radio bytes received: per-board instance name. + # RadioPackets.fppi is a symlink to RadioPackets_{Lora,Usp}.fppi + # created by CMakeLists.txt at configure time. + include "RadioPacketsBytesReceived.fppi" } @@ -45,13 +48,8 @@ telemetry packets ReferenceDeploymentPackets { ReferenceDeployment.imuManager.MagnetometerSamplingFrequency } - packet Radio id 8 group 2 { - lora.LastRssi - lora.LastSnr - lora.BytesSent -# sband.LastRssi -# sband.LastSnr - } + # Radio packet: per-board instance name. + include "RadioPacketsRadio.fppi" packet PowerMonitor id 11 group 2 { ReferenceDeployment.ina219SysManager.Current diff --git a/PROVESFlightControllerReference/ReferenceDeployment/Top/ReferenceDeploymentTopology.cpp b/PROVESFlightControllerReference/ReferenceDeployment/Top/ReferenceDeploymentTopology.cpp index a5475f1e..ec86f91f 100644 --- a/PROVESFlightControllerReference/ReferenceDeployment/Top/ReferenceDeploymentTopology.cpp +++ b/PROVESFlightControllerReference/ReferenceDeployment/Top/ReferenceDeploymentTopology.cpp @@ -149,14 +149,14 @@ void setupTopology(const TopologyState& state) { // v5e USP path. // 1. Inject the RalSessionImpl (constructed at file scope above) into // the autocoded 'uspRadio' component via configure(). - // 2. UspRadio::start() calls session.init() which internally calls + // 2. UspRadio::startRadio() calls session.init() which internally calls // zephyr_usp_initialization_wait() + zephyr_smtc_rac_init() (see // RalSessionImpl::init()), applies the P0 boot-default profile, and // starts continuous RX. TX stays DISABLED until the startup-sequence // sends a TRANSMIT(ENABLED) command (same gating as the legacy path). uspRadio.configure(s_ralSession); - if (!uspRadio.start(Zephyr::UspTransmitState::DISABLED)) { - Fw::Logger::log("[Topology] UspRadio start() failed -- radio inactive\n"); + if (!uspRadio.startRadio(Zephyr::UspTransmitState::DISABLED)) { + Fw::Logger::log("[Topology] UspRadio startRadio() failed -- radio inactive\n"); } #else lora.start(state.loraDevice, Zephyr::TransmitState::DISABLED); diff --git a/PROVESFlightControllerReference/ReferenceDeployment/Top/instances.fpp b/PROVESFlightControllerReference/ReferenceDeployment/Top/instances.fpp index e8485dfe..73c4c42e 100644 --- a/PROVESFlightControllerReference/ReferenceDeployment/Top/instances.fpp +++ b/PROVESFlightControllerReference/ReferenceDeployment/Top/instances.fpp @@ -103,8 +103,10 @@ module ReferenceDeployment { instance downlinkDelay: Components.ComDelay base id 0x1001E000 - # lora / uspRadio instance lives in RadioInstances_Lora.fpp or - # RadioInstances_Usp.fpp (selected by CMakeLists.txt per board). + # lora / uspRadio instance: per-board variant selected by CMakeLists.txt. + # CMake writes RadioInstances.fppi -> RadioInstances_{Lora,Usp}.fppi + # before the FPP autocoder runs. + include "RadioInstances.fppi" instance comSplitterEvents: Svc.ComSplitter base id 0x10020000 @@ -216,7 +218,7 @@ module ReferenceDeployment { instance fileUplinkCollector: Utilities.BufferCollector base id 0x10060000 instance telemetryDelay: Utilities.RateDelay base id 0x10061000 - # loraRetry lives in RadioInstances_Lora.fpp (only on non-USP boards). + # loraRetry is included via RadioInstances.fppi (Lora path only). instance downlinkRepeater: Utilities.BufferRepeater base id 0x10064000 diff --git a/PROVESFlightControllerReference/ReferenceDeployment/Top/topology.fpp b/PROVESFlightControllerReference/ReferenceDeployment/Top/topology.fpp index e379802c..34e1484f 100644 --- a/PROVESFlightControllerReference/ReferenceDeployment/Top/topology.fpp +++ b/PROVESFlightControllerReference/ReferenceDeployment/Top/topology.fpp @@ -191,10 +191,11 @@ module ReferenceDeployment { # comDelaySband.comStatusOut -> ComCcsdsSband.framer.comStatusIn #} - # CommunicationsRadio connections live in RadioTopology_Lora.fpp or - # RadioTopology_Usp.fpp (CMakeLists.txt picks per board). - # Those files also carry the startup-sequence and RTC cancel-sequence - # wiring (identical for both radio variants). + # CommunicationsRadio connections: per-board variant selected by CMake. + # CMake writes RadioTopology.fppi -> RadioTopology_{Lora,Usp}.fppi + # before the FPP autocoder runs. That file also carries the + # startup-sequence and RTC cancel-sequence wiring (identical for both). + include "RadioTopology.fppi" connections CommunicationsUart { # ComDriver buffer allocations diff --git a/boards/bronco_space/proves_flight_control_board_v5e/proves_flight_control_board_v5e_rp2350a_m33.dts b/boards/bronco_space/proves_flight_control_board_v5e/proves_flight_control_board_v5e_rp2350a_m33.dts index a52bcec1..aa66ad2c 100644 --- a/boards/bronco_space/proves_flight_control_board_v5e/proves_flight_control_board_v5e_rp2350a_m33.dts +++ b/boards/bronco_space/proves_flight_control_board_v5e/proves_flight_control_board_v5e_rp2350a_m33.dts @@ -31,6 +31,17 @@ /* Remove the legacy sx1276 node inherited from v5.dtsi */ /delete-node/ &lora0; +/* + * Point the USP "chosen" transceiver handle at our sx1262 node. + * smtc_sw_platform_helper.c calls DEVICE_DT_GET(DT_CHOSEN(zephyr_lorawan_transceiver)) + * to obtain the ral_t* handle for the SX1262. + */ +/ { + chosen { + zephyr,lorawan-transceiver = &lora0_usp; + }; +}; + &spi1 { /* * USP SX1262 node — E22-400M30S module, SPI1 CS0 (same as v5 SX1276). diff --git a/boards/bronco_space/proves_flight_control_board_v5e/proves_flight_control_board_v5e_rp2350a_m33_defconfig b/boards/bronco_space/proves_flight_control_board_v5e/proves_flight_control_board_v5e_rp2350a_m33_defconfig index 62a79813..b29ed2c3 100644 --- a/boards/bronco_space/proves_flight_control_board_v5e/proves_flight_control_board_v5e_rp2350a_m33_defconfig +++ b/boards/bronco_space/proves_flight_control_board_v5e/proves_flight_control_board_v5e_rp2350a_m33_defconfig @@ -38,6 +38,9 @@ CONFIG_LORA_BASICS_MODEM_DRIVERS=y CONFIG_LORA_BASICS_MODEM_DRIVERS_RAL_RALF=y CONFIG_LORA_BASICS_MODEM_DRIVERS_EVENT_TRIGGER_GLOBAL_THREAD=y CONFIG_USP=y +# USP main thread: provides zephyr_usp_initialization_wait() + smtc_rac run loop. +# RalSessionImpl calls zephyr_usp_initialization_wait() before smtc_rac_open_radio(). +CONFIG_USP_MAIN_THREAD=y # RTC CONFIG_RTC=y diff --git a/lib/fprime-zephyr b/lib/fprime-zephyr index 2aafb523..61642b7c 160000 --- a/lib/fprime-zephyr +++ b/lib/fprime-zephyr @@ -1 +1 @@ -Subproject commit 2aafb52380f52aa084883edda6d0bd64a4eabb4d +Subproject commit 61642b7c1a639e29533d03f9c1f75a199dcdaf05 From aa264fd2406f4e449375cb6aa59465a1c1548a9e Mon Sep 17 00:00:00 2001 From: Michael Pham <61564344+Mikefly123@users.noreply.github.com> Date: Sat, 4 Jul 2026 20:25:44 -0700 Subject: [PATCH 11/51] fix(v5e): guard FlashWorker against CONFIG_IMG_MANAGER=n; add hwil-bench.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 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 --- .../Components/FlashWorker/FlashWorker.cpp | 19 ++++++++++++++++++- .../Components/FlashWorker/FlashWorker.hpp | 4 ++++ hwil-bench.conf | 16 ++++++++++++++++ 3 files changed, 38 insertions(+), 1 deletion(-) create mode 100644 hwil-bench.conf diff --git a/PROVESFlightControllerReference/Components/FlashWorker/FlashWorker.cpp b/PROVESFlightControllerReference/Components/FlashWorker/FlashWorker.cpp index ef9fb0a6..0065155f 100644 --- a/PROVESFlightControllerReference/Components/FlashWorker/FlashWorker.cpp +++ b/PROVESFlightControllerReference/Components/FlashWorker/FlashWorker.cpp @@ -8,8 +8,10 @@ #include "Os/File.hpp" #include "Os/Task.hpp" +#ifdef CONFIG_IMG_MANAGER #include #include +#endif namespace Components { // static_assert(FlashWorker::REGION_NUMBER == UPLOAD_FLASH_AREA_LABEL, @@ -27,6 +29,7 @@ FlashWorker ::~FlashWorker() {} // Flash helpers // ---------------------------------------------------------------------- +#ifdef CONFIG_IMG_MANAGER Update::UpdateStatus FlashWorker ::writeImage(const Fw::StringBase& file_name, Os::File& file, U32 expected_crc32) { const FwSizeType CHUNK = static_cast(sizeof(this->m_data)); FW_ASSERT(file.isOpen()); @@ -69,21 +72,29 @@ Update::UpdateStatus FlashWorker ::writeImage(const Fw::StringBase& file_name, O } return return_status; } +#endif // CONFIG_IMG_MANAGER // ---------------------------------------------------------------------- // Handler implementations for typed input ports // ---------------------------------------------------------------------- Update::UpdateStatus FlashWorker ::confirmImage_handler(FwIndexType portNum) { +#ifndef CONFIG_IMG_MANAGER + return Update::UpdateStatus::OP_OK; +#else int status = boot_write_img_confirmed(); if (status != 0) { this->log_WARNING_LO_ConfirmImageFailed(static_cast(-1 * status)); return Update::UpdateStatus::NEXT_BOOT_ERROR; } return Update::UpdateStatus::OP_OK; +#endif // CONFIG_IMG_MANAGER } Update::UpdateStatus FlashWorker ::nextBoot_handler(FwIndexType portNum, const Update::NextBootMode& mode) { +#ifndef CONFIG_IMG_MANAGER + return Update::UpdateStatus::OP_OK; +#else int permanent = (mode == Update::NextBootMode::PERMANENT) ? BOOT_UPGRADE_PERMANENT : BOOT_UPGRADE_TEST; int status = boot_request_upgrade(permanent); @@ -92,10 +103,12 @@ Update::UpdateStatus FlashWorker ::nextBoot_handler(FwIndexType portNum, const U return Update::UpdateStatus::NEXT_BOOT_ERROR; } return Update::UpdateStatus::OP_OK; +#endif // CONFIG_IMG_MANAGER } void FlashWorker ::prepareImage_handler(FwIndexType portNum) { Update::UpdateStatus return_status = Update::UpdateStatus::OP_OK; +#ifdef CONFIG_IMG_MANAGER int status = boot_erase_img_bank(FlashWorker::REGION_NUMBER); if (status != 0) { this->log_WARNING_LO_FlashEraseFailed(static_cast(-1 * status)); @@ -103,13 +116,16 @@ void FlashWorker ::prepareImage_handler(FwIndexType portNum) { } else { this->m_last_successful = PREPARE; } +#else + this->m_last_successful = PREPARE; +#endif // CONFIG_IMG_MANAGER this->prepareImageDone_out(0, return_status); } void FlashWorker ::updateImage_handler(FwIndexType portNum, const Fw::StringBase& file, U32 crc32) { Os::File image_file; Update::UpdateStatus return_status = Update::UpdateStatus::OP_OK; - +#ifdef CONFIG_IMG_MANAGER if (this->m_last_successful != PREPARE) { return_status = Update::UpdateStatus::UNPREPARED; this->m_last_successful = IDLE; @@ -125,6 +141,7 @@ void FlashWorker ::updateImage_handler(FwIndexType portNum, const Fw::StringBase this->log_WARNING_LO_ImageFileReadError(file, static_cast(file_status)); } } +#endif // CONFIG_IMG_MANAGER this->updateImageDone_out(0, return_status); } diff --git a/PROVESFlightControllerReference/Components/FlashWorker/FlashWorker.hpp b/PROVESFlightControllerReference/Components/FlashWorker/FlashWorker.hpp index 657fdca2..f78367cb 100644 --- a/PROVESFlightControllerReference/Components/FlashWorker/FlashWorker.hpp +++ b/PROVESFlightControllerReference/Components/FlashWorker/FlashWorker.hpp @@ -8,7 +8,9 @@ #define Update_FlashWorker_HPP #include "Os/File.hpp" #include "PROVESFlightControllerReference/Components/FlashWorker/FlashWorkerComponentAc.hpp" +#ifdef CONFIG_IMG_MANAGER #include +#endif namespace Components { class FlashWorker final : public FlashWorkerComponentBase { @@ -58,8 +60,10 @@ class FlashWorker final : public FlashWorkerComponentBase { private: Step m_last_successful; +#ifdef CONFIG_IMG_MANAGER U8 m_data[CONFIG_IMG_BLOCK_BUF_SIZE]; struct flash_img_context m_flash_context; +#endif }; } // namespace Components diff --git a/hwil-bench.conf b/hwil-bench.conf new file mode 100644 index 00000000..571aeb64 --- /dev/null +++ b/hwil-bench.conf @@ -0,0 +1,16 @@ +# HWIL bench overlay: disables MCUBoot for direct bare-metal flash at 0x10000000. +# Use only for bench sessions where the board does not have MCUBoot pre-installed. +# Revert: remove this file and regenerate with fprime-util generate -f. +# Do not commit to main. + +CONFIG_BOOTLOADER_MCUBOOT=n +# Without MCUBoot we do not need the boot image management config. +CONFIG_MCUBOOT_BOOTUTIL_LIB=n +CONFIG_MCUBOOT_BOOTLOADER_MODE_SWAP_USING_OFFSET=n +CONFIG_MCUBOOT_BOOTLOADER_NO_DOWNGRADE=n +CONFIG_ROM_END_OFFSET=0 +CONFIG_MCUBOOT_UPDATE_FOOTER_SIZE=0 +CONFIG_USE_DT_CODE_PARTITION=n +CONFIG_FLASH_MAP=n +CONFIG_STREAM_FLASH=n +CONFIG_IMG_MANAGER=n From 2b3b8f7bb005721ed48227ddc899373129a9364e Mon Sep 17 00:00:00 2001 From: Michael Pham <61564344+Mikefly123@users.noreply.github.com> Date: Sat, 4 Jul 2026 20:25:50 -0700 Subject: [PATCH 12/51] chore(phase5): bump fprime-zephyr to f7bf1f7 (RX ConfigurationFailed fix) Co-Authored-By: Claude Fable 5 --- lib/fprime-zephyr | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/fprime-zephyr b/lib/fprime-zephyr index 61642b7c..f7bf1f7d 160000 --- a/lib/fprime-zephyr +++ b/lib/fprime-zephyr @@ -1 +1 @@ -Subproject commit 61642b7c1a639e29533d03f9c1f75a199dcdaf05 +Subproject commit f7bf1f7dd1afd0e14622288bd5eeca962fd5992b From 871eee414f5895daad20d2fd3777db0394f95544 Mon Sep 17 00:00:00 2001 From: Michael Pham <61564344+Mikefly123@users.noreply.github.com> Date: Sun, 5 Jul 2026 02:11:13 -0700 Subject: [PATCH 13/51] chore(phase5): bump fprime-zephyr to e7b9e8f (onPostRx/onPostTx three-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 --- lib/fprime-zephyr | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/fprime-zephyr b/lib/fprime-zephyr index f7bf1f7d..e7b9e8f2 160000 --- a/lib/fprime-zephyr +++ b/lib/fprime-zephyr @@ -1 +1 @@ -Subproject commit f7bf1f7dd1afd0e14622288bd5eeca962fd5992b +Subproject commit e7b9e8f287b00250d5392a86585c5e0002980fcc From ee87a00948dcb3093018f28a3e40e1a9ece21228 Mon Sep 17 00:00:00 2001 From: Michael Pham <61564344+Mikefly123@users.noreply.github.com> Date: Sun, 5 Jul 2026 02:15:42 -0700 Subject: [PATCH 14/51] fix(phase5): carry Zephyr CDC-ACM poll-mode TX drain patch + zephyr-patches 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 --- Makefile | 21 +++++++++++++ ...m-poll-mode-tx-drain-on-class-enable.patch | 30 +++++++++++++++++++ 2 files changed, 51 insertions(+) create mode 100644 patches/0004-fix-usbd-cdc-acm-poll-mode-tx-drain-on-class-enable.patch diff --git a/Makefile b/Makefile index e5d75728..8e2e078e 100644 --- a/Makefile +++ b/Makefile @@ -84,6 +84,27 @@ usp-patches: ## Apply usp_zephyr patches (RF-switch GPIO + Zephyr 4.3 compat) fi; \ done +ZEPHYR_DIR ?= $(shell pwd)/lib/zephyr-workspace/zephyr + +.PHONY: zephyr-patches +zephyr-patches: ## Apply Zephyr tree patches (CDC-ACM poll-mode TX drain) + @if [ ! -d "$(ZEPHYR_DIR)" ]; then \ + echo "zephyr not found at $(ZEPHYR_DIR) — run 'west update' first"; \ + exit 1; \ + fi + @echo "Applying Zephyr patches..." + @cd "$(ZEPHYR_DIR)" && \ + for p in $(shell pwd)/patches/0004-fix-usbd-cdc-acm-poll-mode-tx-drain-on-class-enable.patch; do \ + name=$$(basename $$p); \ + if git apply --check "$$p" 2>/dev/null; then \ + git apply "$$p" && echo "OK Applied $$name"; \ + elif git apply --reverse --check "$$p" 2>/dev/null; then \ + echo "Already applied: $$name"; \ + else \ + echo "Cannot apply $$name — check Zephyr revision"; exit 1; \ + fi; \ + done + ##@ Development .PHONY: pre-commit-install diff --git a/patches/0004-fix-usbd-cdc-acm-poll-mode-tx-drain-on-class-enable.patch b/patches/0004-fix-usbd-cdc-acm-poll-mode-tx-drain-on-class-enable.patch new file mode 100644 index 00000000..5b1b8f3b --- /dev/null +++ b/patches/0004-fix-usbd-cdc-acm-poll-mode-tx-drain-on-class-enable.patch @@ -0,0 +1,30 @@ +From 0000000000000000000000000000000000000001 Mon Sep 17 00:00:00 2001 +From: Michael Pham +Date: Sat, 5 Jul 2026 00:00:00 -0700 +Subject: [PATCH] fix(usbd_cdc_acm): drain TX FIFO on class enable in poll mode + +When the USB CDC-ACM class is enabled and the driver is used in poll mode +(IRQ TX not enabled), any data written to the TX FIFO before the class was +enabled is silently lost. Fix: schedule tx_fifo_work in the else-if branch. + +Root cause on FCB v5e: F Prime ComCcsdsUart writes to FIFO during early boot +before USB host enumerates; class enables later; poll-mode never re-drains. + +Signed-off-by: Michael Pham +--- +diff --git a/subsys/usb/device_next/class/usbd_cdc_acm.c b/subsys/usb/device_next/class/usbd_cdc_acm.c +index d3921c0f146..a8241ad130e 100644 +--- a/subsys/usb/device_next/class/usbd_cdc_acm.c ++++ b/subsys/usb/device_next/class/usbd_cdc_acm.c +@@ -368,6 +368,9 @@ static void usbd_cdc_acm_enable(struct usbd_class_data *const c_data) + /* Queue pending TX data on IN endpoint */ + cdc_acm_work_schedule(&data->tx_fifo_work, K_NO_WAIT); + } ++ } else if (!ring_buf_is_empty(data->tx_fifo.rb)) { ++ /* Poll-mode TX: drain any data buffered before the class was enabled */ ++ cdc_acm_work_schedule(&data->tx_fifo_work, K_NO_WAIT); + } + } + +-- +2.39.3 (Apple Git-146) From 682d9b23a9e96618b5a005acf9292b62ec7dee3b Mon Sep 17 00:00:00 2001 From: Michael Pham <61564344+Mikefly123@users.noreply.github.com> Date: Sun, 5 Jul 2026 03:21:13 -0700 Subject: [PATCH 15/51] =?UTF-8?q?fix(phase5):=20add=20CDC-ACM=200005=20pat?= =?UTF-8?q?ch=20=E2=80=94=20clear=20BUSY=20on=20disable=20+=2010=20ms=20re?= =?UTF-8?q?try?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 Co-Authored-By: Claude Fable 5 --- Makefile | 6 +- ...ck-tx-fifo-busy-on-disable-and-retry.patch | 65 +++++++++++++++++++ 2 files changed, 69 insertions(+), 2 deletions(-) create mode 100644 patches/0005-fix-usbd-cdc-acm-stuck-tx-fifo-busy-on-disable-and-retry.patch diff --git a/Makefile b/Makefile index 8e2e078e..025629a0 100644 --- a/Makefile +++ b/Makefile @@ -87,14 +87,16 @@ usp-patches: ## Apply usp_zephyr patches (RF-switch GPIO + Zephyr 4.3 compat) ZEPHYR_DIR ?= $(shell pwd)/lib/zephyr-workspace/zephyr .PHONY: zephyr-patches -zephyr-patches: ## Apply Zephyr tree patches (CDC-ACM poll-mode TX drain) +zephyr-patches: ## Apply Zephyr tree patches (CDC-ACM TX fixes) @if [ ! -d "$(ZEPHYR_DIR)" ]; then \ echo "zephyr not found at $(ZEPHYR_DIR) — run 'west update' first"; \ exit 1; \ fi @echo "Applying Zephyr patches..." @cd "$(ZEPHYR_DIR)" && \ - for p in $(shell pwd)/patches/0004-fix-usbd-cdc-acm-poll-mode-tx-drain-on-class-enable.patch; do \ + for p in \ + $(shell pwd)/patches/0004-fix-usbd-cdc-acm-poll-mode-tx-drain-on-class-enable.patch \ + $(shell pwd)/patches/0005-fix-usbd-cdc-acm-stuck-tx-fifo-busy-on-disable-and-retry.patch; do \ name=$$(basename $$p); \ if git apply --check "$$p" 2>/dev/null; then \ git apply "$$p" && echo "OK Applied $$name"; \ diff --git a/patches/0005-fix-usbd-cdc-acm-stuck-tx-fifo-busy-on-disable-and-retry.patch b/patches/0005-fix-usbd-cdc-acm-stuck-tx-fifo-busy-on-disable-and-retry.patch new file mode 100644 index 00000000..6bf8764e --- /dev/null +++ b/patches/0005-fix-usbd-cdc-acm-stuck-tx-fifo-busy-on-disable-and-retry.patch @@ -0,0 +1,65 @@ +From 0000000000000000000000000000000000000002 Mon Sep 17 00:00:00 2001 +From: Michael Pham +Date: Sat, 5 Jul 2026 00:00:00 -0700 +Subject: [PATCH] fix(usbd_cdc_acm): clear TX_FIFO_BUSY on disable; retry when stuck + +Two related fixes for secondary silence on USB CDC-ACM TX path: + +1. Clear CDC_ACM_TX_FIFO_BUSY in usbd_cdc_acm_disable(). + If the host stops issuing IN tokens while a transfer is in flight, + TX_FIFO_BUSY stays set indefinitely. The error completion path + (ECONNABORTED) clears it on USB disconnect/cancel, but a passive stall + where the host driver stops polling without disconnecting does not fire + any completion. Clearing on disable ensures the flag is reset on the + next enable/reconnect cycle. + +2. Self-reschedule tx_fifo_work with 10ms delay when BUSY is already set + and ring buffer is non-empty. + Provides a periodic drain retry when the USB IN transfer stalls. When + BUSY clears normally (completion fires), the retry fires once and is a + no-op (ring_buf_is_empty after drain). When BUSY is stuck, the handler + retries every 10ms without burning CPU. + +Root cause on FCB v5e (RP2350, macOS host): after the initial burst of +~34 AOS frames is delivered, the macOS USB CDC driver pauses issuing IN +tokens while the GDS Python process processes the burst. TX_FIFO_BUSY +stays set. Subsequent uart_poll_out() calls schedule tx_fifo_work but the +handler returns early on every invocation. The 10ms retry loop ensures +the IN endpoint is re-armed as soon as the host resumes polling. + +Applies on top of: + 0004-fix-usbd-cdc-acm-poll-mode-tx-drain-on-class-enable.patch + +Signed-off-by: Michael Pham +--- +diff --git a/subsys/usb/device_next/class/usbd_cdc_acm.c b/subsys/usb/device_next/class/usbd_cdc_acm.c +index a8241ad130e..704b41a5a14 100644 +--- a/subsys/usb/device_next/class/usbd_cdc_acm.c ++++ b/subsys/usb/device_next/class/usbd_cdc_acm.c +@@ -378,6 +378,10 @@ static void usbd_cdc_acm_disable(struct usbd_class_data *const c_data) + + atomic_clear_bit(&data->state, CDC_ACM_CLASS_ENABLED); + atomic_clear_bit(&data->state, CDC_ACM_CLASS_SUSPENDED); ++ /* Clear TX_FIFO_BUSY on disable so a stuck IN transfer does not block ++ * the TX path after the host reconnects and re-enables the class. ++ */ ++ atomic_clear_bit(&data->state, CDC_ACM_TX_FIFO_BUSY); + LOG_INF("Configuration disabled"); + } + +@@ -648,6 +652,13 @@ static void cdc_acm_tx_fifo_handler(struct k_work *work) + + if (atomic_test_and_set_bit(&data->state, CDC_ACM_TX_FIFO_BUSY)) { + LOG_DBG("TX transfer already in progress"); ++ /* Reschedule if data is waiting - guards against a stuck IN transfer ++ * where the host stops issuing IN tokens (e.g. macOS flow-control). ++ * The retry is a no-op once the completion fires and clears BUSY. ++ */ ++ if (!ring_buf_is_empty(data->tx_fifo.rb)) { ++ cdc_acm_work_schedule(&data->tx_fifo_work, K_MSEC(10)); ++ } + return; + } + +-- +2.39.3 (Apple Git-146) From edc03f545efcf71536bbd188b4c857b5363d451f Mon Sep 17 00:00:00 2001 From: Michael Pham <61564344+Mikefly123@users.noreply.github.com> Date: Sun, 5 Jul 2026 03:34:45 -0700 Subject: [PATCH 16/51] chore(phase5): bump fprime-zephyr to 6e36691 (ProfilePolicy revert display 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 --- lib/fprime-zephyr | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/fprime-zephyr b/lib/fprime-zephyr index e7b9e8f2..6e36691a 160000 --- a/lib/fprime-zephyr +++ b/lib/fprime-zephyr @@ -1 +1 @@ -Subproject commit e7b9e8f287b00250d5392a86585c5e0002980fcc +Subproject commit 6e36691ac08a275cb7356acdd95eca3ba5c4a0eb From 65d96bde2750bf2128b351b72b6fb70a9f45b8eb Mon Sep 17 00:00:00 2001 From: Michael Pham <61564344+Mikefly123@users.noreply.github.com> Date: Sun, 5 Jul 2026 04:36:56 -0700 Subject: [PATCH 17/51] chore(phase5): bump fprime-zephyr to 401d926 (applyGfsk CRC seed/whitening 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 --- lib/fprime-zephyr | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/fprime-zephyr b/lib/fprime-zephyr index 6e36691a..401d926d 160000 --- a/lib/fprime-zephyr +++ b/lib/fprime-zephyr @@ -1 +1 @@ -Subproject commit 6e36691ac08a275cb7356acdd95eca3ba5c4a0eb +Subproject commit 401d926dd759ed49c861caeff81dbaf92a125809 From 8d40fd92b6ad6aaaf7d0241a4fb8668d2f0f8cc0 Mon Sep 17 00:00:00 2001 From: Michael Pham <61564344+Mikefly123@users.noreply.github.com> Date: Fri, 10 Jul 2026 19:11:08 -0700 Subject: [PATCH 18/51] fix(usp): carry SX126x wakeup-busy race fix as patch 0006 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 --- Makefile | 5 +- ...up-busy-race-add-t_woff-settle-delay.patch | 58 +++++++++++++++++++ 2 files changed, 61 insertions(+), 2 deletions(-) create mode 100644 patches/0006-fix-sx126x-wakeup-busy-race-add-t_woff-settle-delay.patch diff --git a/Makefile b/Makefile index 025629a0..300633ca 100644 --- a/Makefile +++ b/Makefile @@ -64,7 +64,7 @@ zephyr-setup: fprime-venv ## Set up Zephyr environment USP_ZEPHYR_DIR ?= $(shell pwd)/lib/zephyr-workspace/modules/lib/usp_zephyr .PHONY: usp-patches -usp-patches: ## Apply usp_zephyr patches (RF-switch GPIO + Zephyr 4.3 compat) +usp-patches: ## Apply usp_zephyr patches (RF-switch GPIO + Zephyr 4.3 compat + wakeup-busy race fix) @if [ ! -d "$(USP_ZEPHYR_DIR)" ]; then \ echo "❌ usp_zephyr not found at $(USP_ZEPHYR_DIR) — run 'west update usp_zephyr usp' first"; \ exit 1; \ @@ -73,7 +73,8 @@ usp-patches: ## Apply usp_zephyr patches (RF-switch GPIO + Zephyr 4.3 compat) @cd "$(USP_ZEPHYR_DIR)" && \ for p in $(shell pwd)/patches/0001-feat-sx126x-add-external-RF-switch-GPIO-support-tx-r.patch \ $(shell pwd)/patches/0002-fix-zephyr-4.3-remove-select-ZEPHYR_LORA_BASICS_MODE.patch \ - $(shell pwd)/patches/0003-fix-usp-main-2025-fix-LR_FHSS_SRC_PATH-for-flattened.patch; do \ + $(shell pwd)/patches/0003-fix-usp-main-2025-fix-LR_FHSS_SRC_PATH-for-flattened.patch \ + $(shell pwd)/patches/0006-fix-sx126x-wakeup-busy-race-add-t_woff-settle-delay.patch; do \ name=$$(basename $$p); \ if git apply --check "$$p" 2>/dev/null; then \ git apply "$$p" && echo "✓ Applied $$name"; \ diff --git a/patches/0006-fix-sx126x-wakeup-busy-race-add-t_woff-settle-delay.patch b/patches/0006-fix-sx126x-wakeup-busy-race-add-t_woff-settle-delay.patch new file mode 100644 index 00000000..ce56db0c --- /dev/null +++ b/patches/0006-fix-sx126x-wakeup-busy-race-add-t_woff-settle-delay.patch @@ -0,0 +1,58 @@ +From 0000000000000000000000000000000000000006 Mon Sep 17 00:00:00 2001 +From: Michael Pham +Date: Fri, 10 Jul 2026 00:00:00 -0700 +Subject: [PATCH] fix(sx126x_hal): add t_woff settle delay after wake-up NSS + glitch to close BUSY-poll race + +sx126x_hal_check_device_ready() wakes a sleeping SX126x with a glitch on +NSS and then immediately polls BUSY via sx126x_hal_wait_on_busy(). Per +the datasheet, the chip needs up to ~340us (t_woff, warm start) after +the wake-up NSS edge before it reliably asserts BUSY. Polling right away +can sample BUSY before the chip has driven it, so the caller believes +the radio is ready when it is still starting up; the very next SPI +command (frequently SET_FREQ) is then clocked into a device that isn't +listening yet and is silently dropped. This reproduces ~40% of the time +in release builds where the post-wake instruction path is fast enough +to win the race. + +Fix: insert a k_busy_wait(500) between the wake-up NSS toggle and the +BUSY poll to wait out the chip's startup window before trusting BUSY. + +Same root cause and fix as the loramac_node SX126xWakeup() path fixed +for the ground-radio-controller GRC firmware +(patches/zephyr-sx126x-wakeup-busy-delay.patch there); this patch is +the USP HAL (Semtech RAC-managed) equivalent for the flight stack. + +Preserves the existing external RF-switch GPIO patch (0001) in this +same file — this change only adds the delay inside +sx126x_hal_check_device_ready() and does not touch +sx126x_hal_update_rf_switch() or sx126x_hal_write(). + +Co-Authored-By: Claude Sonnet 5 +--- + drivers/usp/sx126x/sx126x_hal.c | 9 +++++++++ + 1 file changed, 9 insertions(+) + +diff --git a/drivers/usp/sx126x/sx126x_hal.c b/drivers/usp/sx126x/sx126x_hal.c +index 1530077..699d53b 100644 +--- a/drivers/usp/sx126x/sx126x_hal.c ++++ b/drivers/usp/sx126x/sx126x_hal.c +@@ -101,6 +101,15 @@ static void sx126x_hal_check_device_ready( const void* context ) + gpio_pin_set_dt( cs, 1 ); + k_usleep( 100 ); + gpio_pin_set_dt( cs, 0 ); ++ ++ /* The chip takes up to ~340us (datasheet t_woff, warm start) after ++ * the wake-up NSS edge before it is ready. Polling BUSY immediately ++ * can sample it before the chip has asserted it, in which case the ++ * next command is clocked into a device still starting up and is ++ * silently ignored. Wait out the startup window before polling. ++ */ ++ k_busy_wait( 500 ); ++ + sx126x_hal_wait_on_busy( context ); + data->radio_status = RADIO_AWAKE; + } +-- +2.50.1 (Apple Git-155) + From ef37630c7b24359d9fe46f53564cf29ac7335dbb Mon Sep 17 00:00:00 2001 From: Michael Pham <61564344+Mikefly123@users.noreply.github.com> Date: Fri, 10 Jul 2026 19:11:27 -0700 Subject: [PATCH 19/51] fix(zephyr): carry bounded cdc_acm_poll_out wait as patch 0007 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- Makefile | 3 +- ...acm-bound-poll-out-backpressure-wait.patch | 36 +++++++++++++++++++ 2 files changed, 38 insertions(+), 1 deletion(-) create mode 100644 patches/0007-fix-usbd-cdc-acm-bound-poll-out-backpressure-wait.patch diff --git a/Makefile b/Makefile index 300633ca..f2119dbe 100644 --- a/Makefile +++ b/Makefile @@ -97,7 +97,8 @@ zephyr-patches: ## Apply Zephyr tree patches (CDC-ACM TX fixes) @cd "$(ZEPHYR_DIR)" && \ for p in \ $(shell pwd)/patches/0004-fix-usbd-cdc-acm-poll-mode-tx-drain-on-class-enable.patch \ - $(shell pwd)/patches/0005-fix-usbd-cdc-acm-stuck-tx-fifo-busy-on-disable-and-retry.patch; do \ + $(shell pwd)/patches/0005-fix-usbd-cdc-acm-stuck-tx-fifo-busy-on-disable-and-retry.patch \ + $(shell pwd)/patches/0007-fix-usbd-cdc-acm-bound-poll-out-backpressure-wait.patch; do \ name=$$(basename $$p); \ if git apply --check "$$p" 2>/dev/null; then \ git apply "$$p" && echo "OK Applied $$name"; \ diff --git a/patches/0007-fix-usbd-cdc-acm-bound-poll-out-backpressure-wait.patch b/patches/0007-fix-usbd-cdc-acm-bound-poll-out-backpressure-wait.patch new file mode 100644 index 00000000..00137ffa --- /dev/null +++ b/patches/0007-fix-usbd-cdc-acm-bound-poll-out-backpressure-wait.patch @@ -0,0 +1,36 @@ +From: bench +Subject: [PATCH] fix(usbd-cdc-acm): bound poll_out sleep-retry to ~20ms, discard on sustained backpressure + +An attached-but-stalled host session (macOS ceases IN polling) makes the +unbounded 1ms sleep-retry loop take the full stall duration (minutes) per +console byte. Any logging thread then cascades into a com-stack livelock +(HWIL 2026-07-10). Bound the wait and fall back to the detached-case +discard behavior. +--- +--- a/subsys/usb/device_next/class/usbd_cdc_acm.c ++++ b/subsys/usb/device_next/class/usbd_cdc_acm.c +@@ -1007,6 +1007,7 @@ + struct cdc_acm_uart_data *const data = dev->data; + k_spinlock_key_t key; + uint32_t wrote; ++ int retries = 20; + + while (true) { + key = k_spin_lock(&data->lock); +@@ -1017,7 +1018,15 @@ + break; + } + +- if (k_is_in_isr() || !data->flow_ctrl) { ++ /* Bounded wait: with an attached-but-stalled host session (macOS ++ * ceases IN polling for minutes at a time), an unbounded sleep-retry ++ * here makes every console write take the full stall duration. Any ++ * thread that logs (event text loggers, assert reporting) then backs ++ * up its own queues and cascades into a system-wide com livelock. ++ * After ~20 ms of backpressure, treat the console as best-effort and ++ * discard, exactly like the detached (!flow_ctrl) case below. ++ */ ++ if (k_is_in_isr() || !data->flow_ctrl || retries-- <= 0) { + LOG_WRN_ONCE("Ring buffer full, discard data"); + break; + } From b1e80f9110742ebfe6286d0cc7f1d2eb110ea154 Mon Sep 17 00:00:00 2001 From: Michael Pham <61564344+Mikefly123@users.noreply.github.com> Date: Fri, 10 Jul 2026 19:11:43 -0700 Subject: [PATCH 20/51] fix(fsw): FatalHandler hard-reboots after stopping the watchdog MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- .../Components/FatalHandler/FatalHandler.cpp | 21 +++++++++++++++++++ .../Components/FatalHandler/FatalHandler.hpp | 4 ++++ 2 files changed, 25 insertions(+) diff --git a/PROVESFlightControllerReference/Components/FatalHandler/FatalHandler.cpp b/PROVESFlightControllerReference/Components/FatalHandler/FatalHandler.cpp index 8a46afb1..3de9097b 100644 --- a/PROVESFlightControllerReference/Components/FatalHandler/FatalHandler.cpp +++ b/PROVESFlightControllerReference/Components/FatalHandler/FatalHandler.cpp @@ -12,7 +12,9 @@ #include #include +#include #include +#include namespace Components { @@ -24,9 +26,28 @@ FatalHandler ::FatalHandler(const char* const compName) : FatalHandlerComponentB FatalHandler ::~FatalHandler() {} +void FatalHandler::reboot() { + // Use Zephyr to reboot the system. + // https://docs.zephyrproject.org/apidoc/latest/reboot_8h.html#a18abe5d5b8089e8429c25bafa5e76d3d + // Attempt a warm reboot first. + sys_reboot(SYS_REBOOT_WARM); + + // Attempt a cold reboot if the warm reboot somehow returns/fails. + sys_reboot(SYS_REBOOT_COLD); +} + void FatalHandler::FatalReceive_handler(const FwIndexType portNum, FwEventIdType Id) { Fw::Logger::log("FATAL %" PRI_FwEventIdType " handled.\n", Id); + // Stop petting the external hardware watchdog. This is kept as belt-and-braces: on + // this deployment there is no Zephyr software watchdog, so the external HW watchdog + // (Components.Watchdog, rateGroup1Hz member) is the nominal reset path once pets stop. this->stopWatchdog_out(0); + // Do not rely solely on the external watchdog to eventually starve and reset the board + // (it may be absent/disconnected on a bench, or its timeout may be long). Delay briefly + // to allow the FATAL log/event to drain, then force a reboot directly so a real reset is + // guaranteed regardless of the external watchdog's state. + Os::Task::delay(Fw::TimeInterval(0, 1000)); // Delay to allow log to be processed + this->reboot(); } } // namespace Components diff --git a/PROVESFlightControllerReference/Components/FatalHandler/FatalHandler.hpp b/PROVESFlightControllerReference/Components/FatalHandler/FatalHandler.hpp index 98ddac2e..a9552a1e 100644 --- a/PROVESFlightControllerReference/Components/FatalHandler/FatalHandler.hpp +++ b/PROVESFlightControllerReference/Components/FatalHandler/FatalHandler.hpp @@ -32,6 +32,10 @@ class FatalHandler final : public FatalHandlerComponentBase { //! ~FatalHandler(); + //! Reboot the device + //! + void reboot(); + private: // ---------------------------------------------------------------------- // Handler implementations for user-defined typed input ports From eaac48f0e066986f707bbebcc53b5ee9831c06b8 Mon Sep 17 00:00:00 2001 From: Michael Pham <61564344+Mikefly123@users.noreply.github.com> Date: Fri, 10 Jul 2026 19:11:44 -0700 Subject: [PATCH 21/51] chore: bump fprime-zephyr to da7d41e (UART flow control + stopRadio hardening) Picks up: - ZephyrUartDriver TX staging ring + writer thread + comStatus flow control - RalSessionImpl::stopRadio abort-failure propagation Co-Authored-By: Claude Fable 5 --- lib/fprime-zephyr | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/fprime-zephyr b/lib/fprime-zephyr index 401d926d..da7d41e4 160000 --- a/lib/fprime-zephyr +++ b/lib/fprime-zephyr @@ -1 +1 @@ -Subproject commit 401d926dd759ed49c861caeff81dbaf92a125809 +Subproject commit da7d41e45101d5c2619a28087687ec6721c4ecc2 From fc7bc14f6374afbe7b92430b57450c742e8adaad Mon Sep 17 00:00:00 2001 From: Michael Pham <61564344+Mikefly123@users.noreply.github.com> Date: Fri, 10 Jul 2026 19:37:21 -0700 Subject: [PATCH 22/51] fix(fsw): carry ComAggregator bounded-timeout patch (issue #432) + bump 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 --- Makefile | 11 ++++++ lib/fprime-zephyr | 2 +- patches/README.md | 10 ++++++ ...prime-com-aggregator-bounded-timeout.patch | 35 +++++++++++++++++++ 4 files changed, 57 insertions(+), 1 deletion(-) create mode 100644 patches/fprime-com-aggregator-bounded-timeout.patch diff --git a/Makefile b/Makefile index f2119dbe..054ee6c3 100644 --- a/Makefile +++ b/Makefile @@ -21,6 +21,17 @@ submodules: ## Initialize and update git submodules echo "❌ Error: Unable to apply patch. Run 'cd lib/fprime && git status' to check."; \ exit 1; \ fi + @echo "Applying fprime ComAggregator bounded-timeout patch (issue #432)..." + @cd lib/fprime && \ + if git apply --check ../../patches/fprime-com-aggregator-bounded-timeout.patch 2>/dev/null; then \ + git apply ../../patches/fprime-com-aggregator-bounded-timeout.patch && \ + echo "✓ Applied ComAggregator bounded-timeout patch"; \ + elif git apply --reverse --check ../../patches/fprime-com-aggregator-bounded-timeout.patch 2>/dev/null; then \ + echo "⚠ Patch already applied"; \ + else \ + echo "❌ Error: Unable to apply ComAggregator patch. Run 'cd lib/fprime && git status' to check."; \ + exit 1; \ + fi export VIRTUAL_ENV ?= $(shell pwd)/fprime-venv .PHONY: fprime-venv diff --git a/lib/fprime-zephyr b/lib/fprime-zephyr index da7d41e4..1b7b8c48 160000 --- a/lib/fprime-zephyr +++ b/lib/fprime-zephyr @@ -1 +1 @@ -Subproject commit da7d41e45101d5c2619a28087687ec6721c4ecc2 +Subproject commit 1b7b8c48aa77b996aec310888fa14006844fd0b5 diff --git a/patches/README.md b/patches/README.md index 09cbaf39..38da4827 100644 --- a/patches/README.md +++ b/patches/README.md @@ -15,3 +15,13 @@ The patch is automatically applied by the `make submodules` target to ensure ver **Application:** This patch is applied automatically when running `make submodules` (or `make` which includes that target). **Note:** After applying this patch, `git status` will show `lib/fprime` as modified. This is expected and should **not** be committed. The patched state is reapplied automatically on each `make submodules` run. + +## fprime-com-aggregator-bounded-timeout.patch + +Fixes issue #432: `Svc::ComAggregator`'s 10 Hz timeout signal FW_ASSERTs (queue FULL) whenever the component's dispatch thread stalls for longer than `queue_depth / timeout_rate` (~1.5 s at depth 15 / 10 Hz). + +Upstream's `m_allow_timeout` guard (fprime #4402) only suppresses timeout signals in the WAIT_STATUS state. While the state machine sits in FILL, a stalled dispatch thread (downstream backpressure, thread starvation from CDC-ACM host stalls) still lets rate-group ticks fill the queue and trip the autocoded assert in `aggregationMachine_sendSignalFinish`. + +The patch bounds timeout-signal queue occupancy in the hand-coded `timeout_handler`: the signal is only enqueued when the queue retains headroom for it plus the (flow-controlled, at most one each) in-flight `fill` and `status` signals. Timeout ticks are periodic and idempotent, so a skipped tick is retried on the next cycle — behavior is unchanged except that the queue can no longer overflow. + +**Application:** Applied automatically by `make submodules`, same mechanism as the fprime-gds version patch. Candidate for upstreaming to nasa/fprime. diff --git a/patches/fprime-com-aggregator-bounded-timeout.patch b/patches/fprime-com-aggregator-bounded-timeout.patch new file mode 100644 index 00000000..4215db86 --- /dev/null +++ b/patches/fprime-com-aggregator-bounded-timeout.patch @@ -0,0 +1,35 @@ +diff --git a/Svc/ComAggregator/ComAggregator.cpp b/Svc/ComAggregator/ComAggregator.cpp +index dc6dd130d..6cfeecd6c 100644 +--- a/Svc/ComAggregator/ComAggregator.cpp ++++ b/Svc/ComAggregator/ComAggregator.cpp +@@ -8,6 +8,12 @@ + + namespace Svc { + ++namespace { ++//! Queue slots that must remain free for a timeout signal to be enqueued: the timeout itself plus one ++//! in-flight 'fill' and one in-flight 'status' signal (each bounded to one message by the com protocol). ++constexpr FwSizeType TIMEOUT_QUEUE_HEADROOM = 3; ++} // namespace ++ + // ---------------------------------------------------------------------- + // Component construction and destruction + // ---------------------------------------------------------------------- +@@ -55,7 +61,16 @@ void ComAggregator ::timeout_handler(FwIndexType portNum, U32 context) { + // + // Behaviorally, this solution will work exactly like the naive implementation with an infinite queue depth, but + // prevents queue overflow when using finite queues. +- if (this->m_allow_timeout) { ++ // ++ // Even so, timeout remains the only signal source without flow control: 'fill' and 'status' are each bounded ++ // to one in-flight message by the com protocol, but the rate group keeps delivering ticks while this ++ // component's dispatch thread is stalled (downstream backpressure, thread starvation). In the FILL state ++ // (m_allow_timeout true) such a stall would still fill the queue with timeout signals and trip the queue-full ++ // assertion in the autocoded signal send. Ticks are periodic and idempotent, so additionally skip the signal ++ // unless the queue has headroom for it plus the in-flight flow-controlled signals; a skipped tick is simply ++ // retried on the next cycle. ++ if (this->m_allow_timeout && ++ (this->m_queue.getMessagesAvailable() + TIMEOUT_QUEUE_HEADROOM <= this->m_queue.getDepth())) { + this->aggregationMachine_sendSignal_timeout(); + } + } From 09b96139082baf8b40551aec6b8961dffd812a32 Mon Sep 17 00:00:00 2001 From: Michael Pham <61564344+Mikefly123@users.noreply.github.com> Date: Fri, 10 Jul 2026 20:07:57 -0700 Subject: [PATCH 23/51] fix(fsw): carry fprime sched-tick drop patch (issue #432 class) + stopRadio hardening bump MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 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 --- Makefile | 11 +++ lib/fprime-zephyr | 2 +- patches/README.md | 8 +++ patches/fprime-sched-tick-drop.patch | 104 +++++++++++++++++++++++++++ 4 files changed, 124 insertions(+), 1 deletion(-) create mode 100644 patches/fprime-sched-tick-drop.patch diff --git a/Makefile b/Makefile index 054ee6c3..626b5dbd 100644 --- a/Makefile +++ b/Makefile @@ -32,6 +32,17 @@ submodules: ## Initialize and update git submodules echo "❌ Error: Unable to apply ComAggregator patch. Run 'cd lib/fprime && git status' to check."; \ exit 1; \ fi + @echo "Applying fprime sched-tick drop patch (issue #432 class)..." + @cd lib/fprime && \ + if git apply --check ../../patches/fprime-sched-tick-drop.patch 2>/dev/null; then \ + git apply ../../patches/fprime-sched-tick-drop.patch && \ + echo "✓ Applied sched-tick drop patch"; \ + elif git apply --reverse --check ../../patches/fprime-sched-tick-drop.patch 2>/dev/null; then \ + echo "⚠ Patch already applied"; \ + else \ + echo "❌ Error: Unable to apply sched-tick drop patch. Run 'cd lib/fprime && git status' to check."; \ + exit 1; \ + fi export VIRTUAL_ENV ?= $(shell pwd)/fprime-venv .PHONY: fprime-venv diff --git a/lib/fprime-zephyr b/lib/fprime-zephyr index 1b7b8c48..e2c1078b 160000 --- a/lib/fprime-zephyr +++ b/lib/fprime-zephyr @@ -1 +1 @@ -Subproject commit 1b7b8c48aa77b996aec310888fa14006844fd0b5 +Subproject commit e2c1078b26d4bf02d8a45a41c04635bf56535a1e diff --git a/patches/README.md b/patches/README.md index 38da4827..a4b42ec9 100644 --- a/patches/README.md +++ b/patches/README.md @@ -25,3 +25,11 @@ Upstream's `m_allow_timeout` guard (fprime #4402) only suppresses timeout signal The patch bounds timeout-signal queue occupancy in the hand-coded `timeout_handler`: the signal is only enqueued when the queue retains headroom for it plus the (flow-controlled, at most one each) in-flight `fill` and `status` signals. Timeout ticks are periodic and idempotent, so a skipped tick is retried on the next cycle — behavior is unchanged except that the queue can no longer overflow. **Application:** Applied automatically by `make submodules`, same mechanism as the fprime-gds version patch. Candidate for upstreaming to nasa/fprime. + +## fprime-sched-tick-drop.patch + +Second instance of the issue-#432 defect class, captured by gdb tripwire during HWIL soak #5 (2026-07-10): `safeModeSeq` (Svc::CmdSequencer) hit the identical queue-full FW_ASSERT in its autocoded `schedIn_handlerBase` — rate-group sched ticks accumulate in any active component's queue whenever its dispatch thread stalls longer than `queue_depth / tick_rate`. + +The patch adds the `drop` queue-full annotation to the periodic `Svc.Sched` async inputs of all eight upstream Svc components that lacked it (CmdSequencer, CmdDispatcher, TlmChan, TlmPacketizer, FileDownlink, BufferLogger, DpManager, DpWriter). Dropping a periodic tick is safe by construction — the next tick retries — and upstream already uses `drop` for exactly this on `ComQueue.run` and `ActiveRateGroup.CycleIn`. + +**Application:** Applied automatically by `make submodules`. Candidate for upstreaming to nasa/fprime. diff --git a/patches/fprime-sched-tick-drop.patch b/patches/fprime-sched-tick-drop.patch new file mode 100644 index 00000000..94cba5dc --- /dev/null +++ b/patches/fprime-sched-tick-drop.patch @@ -0,0 +1,104 @@ +diff --git a/Svc/BufferLogger/BufferLogger.fpp b/Svc/BufferLogger/BufferLogger.fpp +index 6da2ffd69..79fae4cc7 100644 +--- a/Svc/BufferLogger/BufferLogger.fpp ++++ b/Svc/BufferLogger/BufferLogger.fpp +@@ -21,7 +21,7 @@ module Svc { + @ Ping output port + output port pingOut: Svc.Ping + +- async input port schedIn: Svc.Sched ++ async input port schedIn: Svc.Sched drop + + # ---------------------------------------------------------------------- + # Special ports +diff --git a/Svc/CmdDispatcher/CmdDispatcher.fpp b/Svc/CmdDispatcher/CmdDispatcher.fpp +index 66343b886..49b921372 100644 +--- a/Svc/CmdDispatcher/CmdDispatcher.fpp ++++ b/Svc/CmdDispatcher/CmdDispatcher.fpp +@@ -27,7 +27,7 @@ module Svc { + async input port pingIn: Svc.Ping + + @ Run port used to emit telemetry +- async input port run: Svc.Sched ++ async input port run: Svc.Sched drop + + @ Ping output port + output port pingOut: Svc.Ping +diff --git a/Svc/CmdSequencer/CmdSequencer.fpp b/Svc/CmdSequencer/CmdSequencer.fpp +index 1573fbe60..4e61d7272 100644 +--- a/Svc/CmdSequencer/CmdSequencer.fpp ++++ b/Svc/CmdSequencer/CmdSequencer.fpp +@@ -83,7 +83,7 @@ module Svc { + output port comCmdOut: Fw.Com + + @ Schedule in port +- async input port schedIn: Svc.Sched ++ async input port schedIn: Svc.Sched drop + + @ Notifies that a sequence has started running + output port seqStartOut: Svc.CmdSeqIn +diff --git a/Svc/DpManager/DpManager.fpp b/Svc/DpManager/DpManager.fpp +index ebbf5a46f..74574f97f 100644 +--- a/Svc/DpManager/DpManager.fpp ++++ b/Svc/DpManager/DpManager.fpp +@@ -8,7 +8,7 @@ module Svc { + # ---------------------------------------------------------------------- + + @ Schedule in port +- async input port schedIn: Svc.Sched ++ async input port schedIn: Svc.Sched drop + + # ---------------------------------------------------------------------- + # Ports for handling buffer requests +diff --git a/Svc/DpWriter/DpWriter.fpp b/Svc/DpWriter/DpWriter.fpp +index f24594f09..152be91ba 100644 +--- a/Svc/DpWriter/DpWriter.fpp ++++ b/Svc/DpWriter/DpWriter.fpp +@@ -8,7 +8,7 @@ module Svc { + # ---------------------------------------------------------------------- + + @ Schedule in port +- async input port schedIn: Svc.Sched ++ async input port schedIn: Svc.Sched drop + + # ---------------------------------------------------------------------- + # Ports for handling data products +diff --git a/Svc/FileDownlink/FileDownlink.fpp b/Svc/FileDownlink/FileDownlink.fpp +index eed9376aa..d1d94db1a 100644 +--- a/Svc/FileDownlink/FileDownlink.fpp ++++ b/Svc/FileDownlink/FileDownlink.fpp +@@ -8,7 +8,7 @@ module Svc { + # ---------------------------------------------------------------------- + + @ Run input port +- async input port Run: Svc.Sched ++ async input port Run: Svc.Sched drop + + @ Mutexed Sendfile input port + guarded input port SendFile: Svc.SendFileRequest +diff --git a/Svc/TlmChan/TlmChan.fpp b/Svc/TlmChan/TlmChan.fpp +index d15072ed0..8f737cdc7 100644 +--- a/Svc/TlmChan/TlmChan.fpp ++++ b/Svc/TlmChan/TlmChan.fpp +@@ -10,7 +10,7 @@ module Svc { + guarded input port TlmGet: Fw.TlmGet + + @ Run port for starting packet send cycle +- async input port Run: Svc.Sched ++ async input port Run: Svc.Sched drop + + @ Packet send port + output port PktSend: Fw.Com +diff --git a/Svc/TlmPacketizer/TlmPacketizer.fpp b/Svc/TlmPacketizer/TlmPacketizer.fpp +index f03dbafff..7ef46a7b2 100644 +--- a/Svc/TlmPacketizer/TlmPacketizer.fpp ++++ b/Svc/TlmPacketizer/TlmPacketizer.fpp +@@ -17,7 +17,7 @@ module Svc { + output port pingOut: Svc.Ping + + @ Run port for starting packet send cycle +- async input port Run: Svc.Sched ++ async input port Run: Svc.Sched drop + + @ Telemetry input port + sync input port TlmRecv: Fw.Tlm From 468db15bdef7f1a7e5876b914ca5853c971038c2 Mon Sep 17 00:00:00 2001 From: Michael Pham <61564344+Mikefly123@users.noreply.github.com> Date: Fri, 10 Jul 2026 20:28:08 -0700 Subject: [PATCH 24/51] fix(fsw): extend sched-tick drop patch to PingIn ports (3rd captured instance) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- patches/README.md | 2 + patches/fprime-sched-tick-drop.patch | 168 +++++++++++++++++++++++++-- 2 files changed, 159 insertions(+), 11 deletions(-) diff --git a/patches/README.md b/patches/README.md index a4b42ec9..93921c87 100644 --- a/patches/README.md +++ b/patches/README.md @@ -32,4 +32,6 @@ Second instance of the issue-#432 defect class, captured by gdb tripwire during The patch adds the `drop` queue-full annotation to the periodic `Svc.Sched` async inputs of all eight upstream Svc components that lacked it (CmdSequencer, CmdDispatcher, TlmChan, TlmPacketizer, FileDownlink, BufferLogger, DpManager, DpWriter). Dropping a periodic tick is safe by construction — the next tick retries — and upstream already uses `drop` for exactly this on `ComQueue.run` and `ActiveRateGroup.CycleIn`. +A third capture (same soak: `Svc::Health` 1 Hz ping → `rateGroup50Hz.PingIn_handlerBase`, identical queue-full assert) showed pings are another unbounded periodic producer, so the patch also adds `drop` to the 14 async `PingIn`/`pingIn` ports in Svc (including `ActiveRateGroup` and `FpySequencer`, which was explicitly `assert`). Dropping a ping is the *designed* failure path: Health's ping-timeout policy exists precisely to catch a component that stops responding — an assert on the ping enqueue kills the board through the very mechanism meant to detect stuck components gracefully. + **Application:** Applied automatically by `make submodules`. Candidate for upstreaming to nasa/fprime. diff --git a/patches/fprime-sched-tick-drop.patch b/patches/fprime-sched-tick-drop.patch index 94cba5dc..70dbf16e 100644 --- a/patches/fprime-sched-tick-drop.patch +++ b/patches/fprime-sched-tick-drop.patch @@ -1,8 +1,27 @@ +diff --git a/Svc/ActiveRateGroup/ActiveRateGroup.fpp b/Svc/ActiveRateGroup/ActiveRateGroup.fpp +index 3ee1488e3..c60ad6d0d 100644 +--- a/Svc/ActiveRateGroup/ActiveRateGroup.fpp ++++ b/Svc/ActiveRateGroup/ActiveRateGroup.fpp +@@ -15,7 +15,7 @@ module Svc { + output port RateGroupMemberOut: [ActiveRateGroupOutputPorts] Sched + + @ Ping input port for health +- async input port PingIn: Ping ++ async input port PingIn: Ping drop + + @ Ping output port for health + output port PingOut: Ping diff --git a/Svc/BufferLogger/BufferLogger.fpp b/Svc/BufferLogger/BufferLogger.fpp -index 6da2ffd69..79fae4cc7 100644 +index 6da2ffd69..e045b1856 100644 --- a/Svc/BufferLogger/BufferLogger.fpp +++ b/Svc/BufferLogger/BufferLogger.fpp -@@ -21,7 +21,7 @@ module Svc { +@@ -16,12 +16,12 @@ module Svc { + async input port comIn: Fw.Com + + @ Ping input port +- async input port pingIn: Svc.Ping ++ async input port pingIn: Svc.Ping drop + @ Ping output port output port pingOut: Svc.Ping @@ -12,11 +31,15 @@ index 6da2ffd69..79fae4cc7 100644 # ---------------------------------------------------------------------- # Special ports diff --git a/Svc/CmdDispatcher/CmdDispatcher.fpp b/Svc/CmdDispatcher/CmdDispatcher.fpp -index 66343b886..49b921372 100644 +index 66343b886..2f7732896 100644 --- a/Svc/CmdDispatcher/CmdDispatcher.fpp +++ b/Svc/CmdDispatcher/CmdDispatcher.fpp -@@ -27,7 +27,7 @@ module Svc { - async input port pingIn: Svc.Ping +@@ -24,10 +24,10 @@ module Svc { + async input port seqCmdBuff: [CmdDispatcherSequencePorts] Fw.Com hook + + @ Ping input port +- async input port pingIn: Svc.Ping ++ async input port pingIn: Svc.Ping drop @ Run port used to emit telemetry - async input port run: Svc.Sched @@ -25,9 +48,18 @@ index 66343b886..49b921372 100644 @ Ping output port output port pingOut: Svc.Ping diff --git a/Svc/CmdSequencer/CmdSequencer.fpp b/Svc/CmdSequencer/CmdSequencer.fpp -index 1573fbe60..4e61d7272 100644 +index 1573fbe60..614fefd5f 100644 --- a/Svc/CmdSequencer/CmdSequencer.fpp +++ b/Svc/CmdSequencer/CmdSequencer.fpp +@@ -68,7 +68,7 @@ module Svc { + async input port cmdResponseIn: Fw.CmdResponse + + @ Ping in port +- async input port pingIn: Svc.Ping ++ async input port pingIn: Svc.Ping drop + + @ Ping out port + output port pingOut: Svc.Ping @@ -83,7 +83,7 @@ module Svc { output port comCmdOut: Fw.Com @@ -37,6 +69,32 @@ index 1573fbe60..4e61d7272 100644 @ Notifies that a sequence has started running output port seqStartOut: Svc.CmdSeqIn +diff --git a/Svc/ComLogger/ComLogger.fpp b/Svc/ComLogger/ComLogger.fpp +index c4327b7dd..cf4bc2238 100644 +--- a/Svc/ComLogger/ComLogger.fpp ++++ b/Svc/ComLogger/ComLogger.fpp +@@ -11,7 +11,7 @@ module Svc { + async input port comIn: Fw.Com + + @ Ping input port +- async input port pingIn: Svc.Ping ++ async input port pingIn: Svc.Ping drop + + @ Ping output port + output port pingOut: Svc.Ping +diff --git a/Svc/DpCatalog/DpCatalog.fpp b/Svc/DpCatalog/DpCatalog.fpp +index e1fd468dd..59ffc363f 100644 +--- a/Svc/DpCatalog/DpCatalog.fpp ++++ b/Svc/DpCatalog/DpCatalog.fpp +@@ -32,7 +32,7 @@ module Svc { + # Component specific ports + + @ Ping input port +- async input port pingIn: Svc.Ping ++ async input port pingIn: Svc.Ping drop + + @ Ping output port + output port pingOut: Svc.Ping diff --git a/Svc/DpManager/DpManager.fpp b/Svc/DpManager/DpManager.fpp index ebbf5a46f..74574f97f 100644 --- a/Svc/DpManager/DpManager.fpp @@ -63,8 +121,21 @@ index f24594f09..152be91ba 100644 # ---------------------------------------------------------------------- # Ports for handling data products +diff --git a/Svc/EventManager/EventManager.fpp b/Svc/EventManager/EventManager.fpp +index b97a8d807..2ea88960a 100644 +--- a/Svc/EventManager/EventManager.fpp ++++ b/Svc/EventManager/EventManager.fpp +@@ -53,7 +53,7 @@ module Svc { + output port FatalAnnounce: Svc.FatalEvent + + @ Ping input port +- async input port pingIn: Svc.Ping ++ async input port pingIn: Svc.Ping drop + + @ Ping output port + output port pingOut: Svc.Ping diff --git a/Svc/FileDownlink/FileDownlink.fpp b/Svc/FileDownlink/FileDownlink.fpp -index eed9376aa..d1d94db1a 100644 +index eed9376aa..5e710a3f4 100644 --- a/Svc/FileDownlink/FileDownlink.fpp +++ b/Svc/FileDownlink/FileDownlink.fpp @@ -8,7 +8,7 @@ module Svc { @@ -76,11 +147,72 @@ index eed9376aa..d1d94db1a 100644 @ Mutexed Sendfile input port guarded input port SendFile: Svc.SendFileRequest +@@ -23,7 +23,7 @@ module Svc { + output port bufferSendOut: Fw.BufferSend + + @ Ping input port +- async input port pingIn: Svc.Ping ++ async input port pingIn: Svc.Ping drop + + @ Ping output port + output port pingOut: Svc.Ping +diff --git a/Svc/FileManager/FileManager.fpp b/Svc/FileManager/FileManager.fpp +index bef777c71..d85f42002 100644 +--- a/Svc/FileManager/FileManager.fpp ++++ b/Svc/FileManager/FileManager.fpp +@@ -8,7 +8,7 @@ module Svc { + # ---------------------------------------------------------------------- + + @ Ping input port +- async input port pingIn: Svc.Ping ++ async input port pingIn: Svc.Ping drop + + @ Scheduler input port for rate group operations + sync input port schedIn: Sched +diff --git a/Svc/FileUplink/FileUplink.fpp b/Svc/FileUplink/FileUplink.fpp +index 90ef4e9a8..b2b67bd5c 100644 +--- a/Svc/FileUplink/FileUplink.fpp ++++ b/Svc/FileUplink/FileUplink.fpp +@@ -14,7 +14,7 @@ module Svc { + output port bufferSendOut: Fw.BufferSend + + @ Ping in +- async input port pingIn: Svc.Ping ++ async input port pingIn: Svc.Ping drop + + @ Ping out + output port pingOut: Svc.Ping +diff --git a/Svc/FpySequencer/FpySequencer.fpp b/Svc/FpySequencer/FpySequencer.fpp +index 8af40d3d7..b9e80e1af 100644 +--- a/Svc/FpySequencer/FpySequencer.fpp ++++ b/Svc/FpySequencer/FpySequencer.fpp +@@ -38,7 +38,7 @@ module Svc { + + @ Ping in port + # TODO should ping have highest prio? or lowest? +- async input port pingIn: Svc.Ping priority 10 assert ++ async input port pingIn: Svc.Ping priority 10 drop + + @ port to trigger a wakeup or timeout check. increase frequency + @ to increase temporal resolution of sequencer +diff --git a/Svc/PrmDb/PrmDb.fpp b/Svc/PrmDb/PrmDb.fpp +index 66f850e30..9588336e3 100644 +--- a/Svc/PrmDb/PrmDb.fpp ++++ b/Svc/PrmDb/PrmDb.fpp +@@ -60,7 +60,7 @@ module Svc { + async input port setPrm: Fw.PrmSet + + @ Ping input port +- async input port pingIn: Svc.Ping ++ async input port pingIn: Svc.Ping drop + + @ Ping output port + output port pingOut: Svc.Ping diff --git a/Svc/TlmChan/TlmChan.fpp b/Svc/TlmChan/TlmChan.fpp -index d15072ed0..8f737cdc7 100644 +index d15072ed0..52b73b2d8 100644 --- a/Svc/TlmChan/TlmChan.fpp +++ b/Svc/TlmChan/TlmChan.fpp -@@ -10,7 +10,7 @@ module Svc { +@@ -10,13 +10,13 @@ module Svc { guarded input port TlmGet: Fw.TlmGet @ Run port for starting packet send cycle @@ -89,11 +221,25 @@ index d15072ed0..8f737cdc7 100644 @ Packet send port output port PktSend: Fw.Com + + @ Ping input port +- async input port pingIn: Svc.Ping ++ async input port pingIn: Svc.Ping drop + + @ Ping output port + output port pingOut: Svc.Ping diff --git a/Svc/TlmPacketizer/TlmPacketizer.fpp b/Svc/TlmPacketizer/TlmPacketizer.fpp -index f03dbafff..7ef46a7b2 100644 +index f03dbafff..91f45eb6f 100644 --- a/Svc/TlmPacketizer/TlmPacketizer.fpp +++ b/Svc/TlmPacketizer/TlmPacketizer.fpp -@@ -17,7 +17,7 @@ module Svc { +@@ -11,13 +11,13 @@ module Svc { + output port PktSend: Fw.Com + + @ Ping input port +- async input port pingIn: Svc.Ping ++ async input port pingIn: Svc.Ping drop + + @ Ping output port output port pingOut: Svc.Ping @ Run port for starting packet send cycle From 3381f423d39fec3720c731f6514cec72161a1a01 Mon Sep 17 00:00:00 2001 From: Michael Pham <61564344+Mikefly123@users.noreply.github.com> Date: Fri, 10 Jul 2026 22:38:33 -0700 Subject: [PATCH 25/51] chore(debug): add segger module to west allowlist for RTT capture builds 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 --- west.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/west.yml b/west.yml index 2d9333e8..4b689eac 100644 --- a/west.yml +++ b/west.yml @@ -32,6 +32,7 @@ manifest: - mcuboot # Bootloader support - fatfs # FatFS (file system) support - hal_st # Required for certain sensors + - segger # SEGGER RTT (debug capture; inert without CONFIG_USE_SEGGER_RTT) - name: loramac-node revision: fb00b383072518c918e2258b0916c996f2d4eebe From f5a0e3b5ddf984f99d0b1e84673ac565454fdaa9 Mon Sep 17 00:00:00 2001 From: Michael Pham <61564344+Mikefly123@users.noreply.github.com> Date: Sat, 11 Jul 2026 10:31:44 -0700 Subject: [PATCH 26/51] fix(usp): serialize RAC API calls + exempt UNLOCK from RP failsafe 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 --- Makefile | 20 ++++++++- ...tc-modem-hal-implement-rac-api-mutex.patch | 43 +++++++++++++++++++ ...-failsafe-exempt-unlock-radio-access.patch | 20 +++++++++ 3 files changed, 82 insertions(+), 1 deletion(-) create mode 100644 patches/0008-fix-smtc-modem-hal-implement-rac-api-mutex.patch create mode 100644 patches/0009-fix-radio-planner-failsafe-exempt-unlock-radio-access.patch diff --git a/Makefile b/Makefile index 626b5dbd..b40cc923 100644 --- a/Makefile +++ b/Makefile @@ -96,7 +96,8 @@ usp-patches: ## Apply usp_zephyr patches (RF-switch GPIO + Zephyr 4.3 compat + w for p in $(shell pwd)/patches/0001-feat-sx126x-add-external-RF-switch-GPIO-support-tx-r.patch \ $(shell pwd)/patches/0002-fix-zephyr-4.3-remove-select-ZEPHYR_LORA_BASICS_MODE.patch \ $(shell pwd)/patches/0003-fix-usp-main-2025-fix-LR_FHSS_SRC_PATH-for-flattened.patch \ - $(shell pwd)/patches/0006-fix-sx126x-wakeup-busy-race-add-t_woff-settle-delay.patch; do \ + $(shell pwd)/patches/0006-fix-sx126x-wakeup-busy-race-add-t_woff-settle-delay.patch \ + $(shell pwd)/patches/0008-fix-smtc-modem-hal-implement-rac-api-mutex.patch; do \ name=$$(basename $$p); \ if git apply --check "$$p" 2>/dev/null; then \ git apply "$$p" && echo "✓ Applied $$name"; \ @@ -107,6 +108,23 @@ usp-patches: ## Apply usp_zephyr patches (RF-switch GPIO + Zephyr 4.3 compat + w fi; \ done +# USP_DIR: the Semtech smtc_rac_lib west module (radio planner lives here). +USP_DIR ?= $(shell pwd)/lib/zephyr-workspace/modules/lib/usp + +.PHONY: usp-core-patches +usp-core-patches: ## Apply usp (smtc_rac_lib) patches (radio-planner failsafe unlock exemption) + @cd "$(USP_DIR)" && \ + for p in $(shell pwd)/patches/0009-fix-radio-planner-failsafe-exempt-unlock-radio-access.patch; do \ + name=$$(basename $$p); \ + if git apply --check "$$p" 2>/dev/null; then \ + git apply "$$p" && echo "✓ Applied $$name"; \ + elif git apply --reverse --check "$$p" 2>/dev/null; then \ + echo "⚠ Already applied: $$name"; \ + else \ + echo "❌ Cannot apply $$name — check usp revision"; exit 1; \ + fi; \ + done + ZEPHYR_DIR ?= $(shell pwd)/lib/zephyr-workspace/zephyr .PHONY: zephyr-patches diff --git a/patches/0008-fix-smtc-modem-hal-implement-rac-api-mutex.patch b/patches/0008-fix-smtc-modem-hal-implement-rac-api-mutex.patch new file mode 100644 index 00000000..ecbe1517 --- /dev/null +++ b/patches/0008-fix-smtc-modem-hal-implement-rac-api-mutex.patch @@ -0,0 +1,43 @@ +diff --git a/modules/smtc_modem_hal/smtc_modem_hal.c b/modules/smtc_modem_hal/smtc_modem_hal.c +index a08e7b9..a46c474 100644 +--- a/modules/smtc_modem_hal/smtc_modem_hal.c ++++ b/modules/smtc_modem_hal/smtc_modem_hal.c +@@ -176,14 +176,36 @@ struct k_sem* smtc_modem_hal_get_event_sem( void ) + return &lbm_main_loop_sem; + } + ++/* RAC API serialization (HWIL 2026-07-11): the RAC wraps every public entry ++ * point (engine pass included) in protect/unprotect and relies on it for ++ * mutual exclusion between the USP engine thread and API callers on other ++ * threads. The bare-metal stub provided none: a concurrent ++ * rp_task_enqueue()/abort against a running engine tears the radio planner's ++ * task structs (observed live on RP2350: RP_FAILSAFE panic on a ++ * LOCK_RADIO_ACCESS task whose type field read non-LOCK, and a TX launch ++ * taken through the LR-FHSS branch while on a LoRa profile). k_mutex allows ++ * recursive locking by the owner, which the RAC requires: post-transaction ++ * callbacks run inside the engine pass and may call ++ * smtc_rac_unlock_radio_access(), which re-enters protect. The radio/timer ++ * IRQ callbacks only set flags and never call protect, so ISR context is ++ * excluded by design; guard anyway rather than fault. ++ */ ++K_MUTEX_DEFINE( prv_rac_api_mutex ); ++ + void smtc_modem_hal_protect_api_call( void ) + { +- // Do nothing in case implementation is bare metal ++ if( !k_is_in_isr( ) ) ++ { ++ ( void ) k_mutex_lock( &prv_rac_api_mutex, K_FOREVER ); ++ } + } + + void smtc_modem_hal_unprotect_api_call( void ) + { +- // Do nothing in case implementation is bare metal ++ if( !k_is_in_isr( ) ) ++ { ++ ( void ) k_mutex_unlock( &prv_rac_api_mutex ); ++ } + } + + /* ------------ Timer management ------------ */ diff --git a/patches/0009-fix-radio-planner-failsafe-exempt-unlock-radio-access.patch b/patches/0009-fix-radio-planner-failsafe-exempt-unlock-radio-access.patch new file mode 100644 index 00000000..cc985ff2 --- /dev/null +++ b/patches/0009-fix-radio-planner-failsafe-exempt-unlock-radio-access.patch @@ -0,0 +1,20 @@ +diff --git a/smtc_rac_lib/radio_planner/src/radio_planner.c b/smtc_rac_lib/radio_planner/src/radio_planner.c +index 2c44c68..a4c99d6 100644 +--- a/smtc_rac_lib/radio_planner/src/radio_planner.c ++++ b/smtc_rac_lib/radio_planner/src/radio_planner.c +@@ -444,8 +444,15 @@ rp_stats_t rp_get_stats( const radio_planner_t* rp ) + + void rp_callback( radio_planner_t* rp ) + { ++ // UNLOCK_RADIO_ACCESS must be exempt like LOCK_RADIO_ACCESS: a lock task ++ // held open longer than the failsafe window (e.g. continuous RX under the ++ // raw RAC) keeps its original start_time_ms, and unlock_radio_access ++ // retypes the still-RUNNING task to UNLOCK before the engine processes ++ // it — the very next rp_callback would evaluate the failsafe against the ++ // stale start time and panic at the moment the client releases the lock. + if( ( rp->tasks[rp->radio_task_id].state == RP_TASK_STATE_RUNNING ) && + ( rp->tasks[rp->radio_task_id].type != RP_TASK_TYPE_LOCK_RADIO_ACCESS ) && ++ ( rp->tasks[rp->radio_task_id].type != RP_TASK_TYPE_UNLOCK_RADIO_ACCESS ) && + ( rp->disable_failsafe != RP_DISABLE_FAILSAFE_KEY ) && + ( ( int32_t ) ( rp->tasks[rp->radio_task_id].start_time_ms + 128000 - smtc_modem_hal_get_time_in_ms( ) ) < 0 ) ) + { From aaff8dc534c2c301abf6339b1d5139f1b03aa8e5 Mon Sep 17 00:00:00 2001 From: Michael Pham <61564344+Mikefly123@users.noreply.github.com> Date: Sat, 11 Jul 2026 10:31:57 -0700 Subject: [PATCH 27/51] chore(submodule): bump fprime-zephyr to bdfcb4a (radio fix stack, bench-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 --- lib/fprime-zephyr | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/fprime-zephyr b/lib/fprime-zephyr index e2c1078b..bdfcb4a2 160000 --- a/lib/fprime-zephyr +++ b/lib/fprime-zephyr @@ -1 +1 @@ -Subproject commit e2c1078b26d4bf02d8a45a41c04635bf56535a1e +Subproject commit bdfcb4a2c1c4370fd2e73bb510746594bf7bbd00 From 8078c2af4d6571d836224e366687368c5f94eb88 Mon Sep 17 00:00:00 2001 From: Michael Pham <61564344+Mikefly123@users.noreply.github.com> Date: Sat, 11 Jul 2026 11:29:25 -0700 Subject: [PATCH 28/51] =?UTF-8?q?feat(radio):=20RX=20ring=20+=20skip-re-ar?= =?UTF-8?q?m=20throughput=20bundle=20=E2=80=94=20submodule=20bump=20to=20f?= =?UTF-8?q?prime-zephyr=20320d8ed=20+=20RxDropped=20packet=20entry?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Fable 5 --- .../ReferenceDeployment/Top/RadioPacketsRadio_Usp.fppi | 1 + lib/fprime-zephyr | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/PROVESFlightControllerReference/ReferenceDeployment/Top/RadioPacketsRadio_Usp.fppi b/PROVESFlightControllerReference/ReferenceDeployment/Top/RadioPacketsRadio_Usp.fppi index 7b4935b0..50e8c5ae 100644 --- a/PROVESFlightControllerReference/ReferenceDeployment/Top/RadioPacketsRadio_Usp.fppi +++ b/PROVESFlightControllerReference/ReferenceDeployment/Top/RadioPacketsRadio_Usp.fppi @@ -6,4 +6,5 @@ uspRadio.RxProfile uspRadio.ProfileTableVersion uspRadio.RxReverts + uspRadio.RxDropped } diff --git a/lib/fprime-zephyr b/lib/fprime-zephyr index bdfcb4a2..320d8edb 160000 --- a/lib/fprime-zephyr +++ b/lib/fprime-zephyr @@ -1 +1 @@ -Subproject commit bdfcb4a2c1c4370fd2e73bb510746594bf7bbd00 +Subproject commit 320d8edbe379d0654e590e5423423a35c42025c0 From 071f78a4b412b451ac87461877a8f7515949b6e4 Mon Sep 17 00:00:00 2001 From: Michael Pham <61564344+Mikefly123@users.noreply.github.com> Date: Sat, 11 Jul 2026 11:47:43 -0700 Subject: [PATCH 29/51] chore(submodule): bump fprime-zephyr to f1b9265 (skip-re-arm v2) Co-Authored-By: Claude Fable 5 --- lib/fprime-zephyr | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/fprime-zephyr b/lib/fprime-zephyr index 320d8edb..f1b92659 160000 --- a/lib/fprime-zephyr +++ b/lib/fprime-zephyr @@ -1 +1 @@ -Subproject commit 320d8edbe379d0654e590e5423423a35c42025c0 +Subproject commit f1b9265996f9615cfb262ad6eef1433249b258ca From fc0a51471f2368d8378177f78ff74a8c30f7a322 Mon Sep 17 00:00:00 2001 From: Michael Pham <61564344+Mikefly123@users.noreply.github.com> Date: Sat, 11 Jul 2026 12:49:23 -0700 Subject: [PATCH 30/51] chore(submodule): bump fprime-zephyr to 0046fbc (true TX payload length per frame) Co-Authored-By: Claude Fable 5 --- lib/fprime-zephyr | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/fprime-zephyr b/lib/fprime-zephyr index f1b92659..0046fbc4 160000 --- a/lib/fprime-zephyr +++ b/lib/fprime-zephyr @@ -1 +1 @@ -Subproject commit f1b9265996f9615cfb262ad6eef1433249b258ca +Subproject commit 0046fbc4d762fe8606498d9d7cab6846f38c7795 From d7102b690fcbf14f641aaa640a13f51454b8a495 Mon Sep 17 00:00:00 2001 From: Michael Pham <61564344+Mikefly123@users.noreply.github.com> Date: Sat, 11 Jul 2026 17:18:15 -0700 Subject: [PATCH 31/51] fix(bench): move hwil-bench.conf out of repo root to docs/bench example MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit MCUBoot-off bench overlay was tracked at repo root despite its own 'do not commit' header — a config-control foot-gun for flight builds. Now an example under docs/bench/; local root copy is gitignored. Co-Authored-By: Claude Fable 5 --- .gitignore | 3 +++ docs/bench/hwil-bench.conf.example | 22 ++++++++++++++++++++++ hwil-bench.conf | 16 ---------------- 3 files changed, 25 insertions(+), 16 deletions(-) create mode 100644 docs/bench/hwil-bench.conf.example delete mode 100644 hwil-bench.conf diff --git a/.gitignore b/.gitignore index df108bf5..cc912897 100644 --- a/.gitignore +++ b/.gitignore @@ -59,6 +59,9 @@ yamcs/yamcs-runtime/ /circuit-python-passthrough/lib/ /circuit-python-passthrough/tools/ +# HWIL bench overlay — local copy only, never commit (use docs/bench/hwil-bench.conf.example as source) +/hwil-bench.conf + # Phase 4 radio config symlinks (generated by CMake at configure time) PROVESFlightControllerReference/ReferenceDeployment/Top/RadioInstances.fppi PROVESFlightControllerReference/ReferenceDeployment/Top/RadioTopology.fppi diff --git a/docs/bench/hwil-bench.conf.example b/docs/bench/hwil-bench.conf.example new file mode 100644 index 00000000..e3dd58fe --- /dev/null +++ b/docs/bench/hwil-bench.conf.example @@ -0,0 +1,22 @@ +# BENCH-ONLY EXAMPLE — do NOT pass to a flight build. +# This file disables MCUBoot so you can flash a bare-metal image directly +# to 0x10000000 on boards that do not have MCUBoot pre-installed. +# +# How to use: +# cp docs/bench/hwil-bench.conf.example hwil-bench.conf +# fprime-util generate -f -- -DEXTRA_CONF_FILE=hwil-bench.conf +# +# The local copy (hwil-bench.conf at repo root) is listed in .gitignore +# and must never be committed or used in a flight build. + +CONFIG_BOOTLOADER_MCUBOOT=n +# Without MCUBoot we do not need the boot image management config. +CONFIG_MCUBOOT_BOOTUTIL_LIB=n +CONFIG_MCUBOOT_BOOTLOADER_MODE_SWAP_USING_OFFSET=n +CONFIG_MCUBOOT_BOOTLOADER_NO_DOWNGRADE=n +CONFIG_ROM_END_OFFSET=0 +CONFIG_MCUBOOT_UPDATE_FOOTER_SIZE=0 +CONFIG_USE_DT_CODE_PARTITION=n +CONFIG_FLASH_MAP=n +CONFIG_STREAM_FLASH=n +CONFIG_IMG_MANAGER=n diff --git a/hwil-bench.conf b/hwil-bench.conf deleted file mode 100644 index 571aeb64..00000000 --- a/hwil-bench.conf +++ /dev/null @@ -1,16 +0,0 @@ -# HWIL bench overlay: disables MCUBoot for direct bare-metal flash at 0x10000000. -# Use only for bench sessions where the board does not have MCUBoot pre-installed. -# Revert: remove this file and regenerate with fprime-util generate -f. -# Do not commit to main. - -CONFIG_BOOTLOADER_MCUBOOT=n -# Without MCUBoot we do not need the boot image management config. -CONFIG_MCUBOOT_BOOTUTIL_LIB=n -CONFIG_MCUBOOT_BOOTLOADER_MODE_SWAP_USING_OFFSET=n -CONFIG_MCUBOOT_BOOTLOADER_NO_DOWNGRADE=n -CONFIG_ROM_END_OFFSET=0 -CONFIG_MCUBOOT_UPDATE_FOOTER_SIZE=0 -CONFIG_USE_DT_CODE_PARTITION=n -CONFIG_FLASH_MAP=n -CONFIG_STREAM_FLASH=n -CONFIG_IMG_MANAGER=n From 14a84ae03658c14453bd6cf7eee8906bf176caef Mon Sep 17 00:00:00 2001 From: Michael Pham <61564344+Mikefly123@users.noreply.github.com> Date: Sat, 11 Jul 2026 17:19:09 -0700 Subject: [PATCH 32/51] =?UTF-8?q?fix:=20review-board=20one-liners=20?= =?UTF-8?q?=E2=80=94=20reboot()=20private,=200008=20ISR=20assert,=20GFSK?= =?UTF-8?q?=20cap=20doc?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - FatalHandler::reboot() moved to private (remove uncommanded-reset surface) - patch 0008: replace silent ISR skip with __ASSERT + unconditional mutex - CONTEXT.md: GFSK cap corrected to <=75 kbps (Carson at 80 kbps = 130 kHz > 125 kHz IARU) - FatalHandler: clarify 1 ms delay units/intent in comment Co-Authored-By: Claude Fable 5 --- CONTEXT.md | 2 +- .../Components/FatalHandler/FatalHandler.cpp | 4 +++- .../Components/FatalHandler/FatalHandler.hpp | 8 +++---- ...tc-modem-hal-implement-rac-api-mutex.patch | 22 ++++++++----------- 4 files changed, 17 insertions(+), 19 deletions(-) diff --git a/CONTEXT.md b/CONTEXT.md index c2fc1394..44bad5ca 100644 --- a/CONTEXT.md +++ b/CONTEXT.md @@ -21,7 +21,7 @@ Glossary for the flight-software radio domain. Terms here are canonical; use the ## Modulations - **CW (Continuous Wave)** — unmodulated carrier transmission for beacons, range testing, and RF debug. A test mode, not a Link Profile. -- **GFSK** — Gaussian FSK packet modulation; the high-throughput downlink option on the USP Radio Path. Capped at ~75–80 kbps by the Band Constraint. +- **GFSK** — Gaussian FSK packet modulation; the high-throughput downlink option on the USP Radio Path. Capped at ≤75 kbps (with fdev = 25 kHz) by the Band Constraint. - **Band Constraint** — IARU coordination limits PROVES UHF emissions to ≤125 kHz occupied bandwidth. Every Link Profile must satisfy it. - **LR-FHSS** — long-range frequency-hopping modulation. SX126x can transmit but never receive it, so it is out of scope until gateway-grade or LR20xx receive hardware exists in the ground segment. diff --git a/PROVESFlightControllerReference/Components/FatalHandler/FatalHandler.cpp b/PROVESFlightControllerReference/Components/FatalHandler/FatalHandler.cpp index 3de9097b..20f2bfa6 100644 --- a/PROVESFlightControllerReference/Components/FatalHandler/FatalHandler.cpp +++ b/PROVESFlightControllerReference/Components/FatalHandler/FatalHandler.cpp @@ -14,6 +14,7 @@ #include #include #include + #include namespace Components { @@ -46,7 +47,8 @@ void FatalHandler::FatalReceive_handler(const FwIndexType portNum, FwEventIdType // (it may be absent/disconnected on a bench, or its timeout may be long). Delay briefly // to allow the FATAL log/event to drain, then force a reboot directly so a real reset is // guaranteed regardless of the external watchdog's state. - Os::Task::delay(Fw::TimeInterval(0, 1000)); // Delay to allow log to be processed + Os::Task::delay(Fw::TimeInterval( + 0, 1000)); // 1 ms (TimeInterval is seconds, microseconds); best-effort log drain before forced reboot this->reboot(); } diff --git a/PROVESFlightControllerReference/Components/FatalHandler/FatalHandler.hpp b/PROVESFlightControllerReference/Components/FatalHandler/FatalHandler.hpp index a9552a1e..22a59672 100644 --- a/PROVESFlightControllerReference/Components/FatalHandler/FatalHandler.hpp +++ b/PROVESFlightControllerReference/Components/FatalHandler/FatalHandler.hpp @@ -32,10 +32,6 @@ class FatalHandler final : public FatalHandlerComponentBase { //! ~FatalHandler(); - //! Reboot the device - //! - void reboot(); - private: // ---------------------------------------------------------------------- // Handler implementations for user-defined typed input ports @@ -46,6 +42,10 @@ class FatalHandler final : public FatalHandlerComponentBase { void FatalReceive_handler(const FwIndexType portNum, /*!< The port number*/ FwEventIdType Id /*!< The ID of the FATAL event*/ ); + + //! Reboot the device + //! + void reboot(); }; } // namespace Components diff --git a/patches/0008-fix-smtc-modem-hal-implement-rac-api-mutex.patch b/patches/0008-fix-smtc-modem-hal-implement-rac-api-mutex.patch index ecbe1517..04d66e5e 100644 --- a/patches/0008-fix-smtc-modem-hal-implement-rac-api-mutex.patch +++ b/patches/0008-fix-smtc-modem-hal-implement-rac-api-mutex.patch @@ -2,10 +2,10 @@ diff --git a/modules/smtc_modem_hal/smtc_modem_hal.c b/modules/smtc_modem_hal/sm index a08e7b9..a46c474 100644 --- a/modules/smtc_modem_hal/smtc_modem_hal.c +++ b/modules/smtc_modem_hal/smtc_modem_hal.c -@@ -176,14 +176,36 @@ struct k_sem* smtc_modem_hal_get_event_sem( void ) +@@ -176,14 +176,32 @@ struct k_sem* smtc_modem_hal_get_event_sem( void ) return &lbm_main_loop_sem; } - + +/* RAC API serialization (HWIL 2026-07-11): the RAC wraps every public entry + * point (engine pass included) in protect/unprotect and relies on it for + * mutual exclusion between the USP engine thread and API callers on other @@ -18,26 +18,22 @@ index a08e7b9..a46c474 100644 + * callbacks run inside the engine pass and may call + * smtc_rac_unlock_radio_access(), which re-enters protect. The radio/timer + * IRQ callbacks only set flags and never call protect, so ISR context is -+ * excluded by design; guard anyway rather than fault. ++ * excluded by design; assert if a caller violates this. + */ +K_MUTEX_DEFINE( prv_rac_api_mutex ); + void smtc_modem_hal_protect_api_call( void ) { - // Do nothing in case implementation is bare metal -+ if( !k_is_in_isr( ) ) -+ { -+ ( void ) k_mutex_lock( &prv_rac_api_mutex, K_FOREVER ); -+ } ++ __ASSERT( !k_is_in_isr( ), "smtc_modem_hal_protect_api_call from ISR" ); ++ ( void ) k_mutex_lock( &prv_rac_api_mutex, K_FOREVER ); } - + void smtc_modem_hal_unprotect_api_call( void ) { - // Do nothing in case implementation is bare metal -+ if( !k_is_in_isr( ) ) -+ { -+ ( void ) k_mutex_unlock( &prv_rac_api_mutex ); -+ } ++ __ASSERT( !k_is_in_isr( ), "smtc_modem_hal_unprotect_api_call from ISR" ); ++ ( void ) k_mutex_unlock( &prv_rac_api_mutex ); } - + /* ------------ Timer management ------------ */ From a7d0e7d22b6aede07387d0eca7d479d26ef6aed9 Mon Sep 17 00:00:00 2001 From: Michael Pham <61564344+Mikefly123@users.noreply.github.com> Date: Sun, 12 Jul 2026 10:49:15 -0700 Subject: [PATCH 33/51] fix(lint): satisfy make fmt (pre-existing failures, surfaced by CI now running on this branch) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - patches/*.patch: trailing whitespace + missing final newline (pre-commit hooks auto-fixed these; verified all patches still git-apply/--reverse cleanly against their submodule checkouts post-reformat — this project applies via `git apply`, not `git am`, so the mbox "-- " signature convention some hooks touched is not load-bearing here). - .codespell-ignore-words.txt: added "comman" — a codespell false positive on a git-generated diff hunk-header context snippet (auto-truncated mid-word from "command" by git's own context-line heuristic), not a real typo. - Main.cpp / ReferenceDeploymentTopologyDefs.hpp: clang-format alignment only, no semantic change. --- .codespell-ignore-words.txt | 1 + .../ReferenceDeployment/Main.cpp | 4 +- .../Top/ReferenceDeploymentTopologyDefs.hpp | 10 +-- ...external-RF-switch-GPIO-support-tx-r.patch | 15 ++-- ...emove-select-ZEPHYR_LORA_BASICS_MODE.patch | 3 +- ...5-fix-LR_FHSS_SRC_PATH-for-flattened.patch | 5 +- ...m-poll-mode-tx-drain-on-class-enable.patch | 4 +- ...up-busy-race-add-t_woff-settle-delay.patch | 1 - ...acm-bound-poll-out-backpressure-wait.patch | 4 +- ...-failsafe-exempt-unlock-radio-access.patch | 2 +- ...prime-com-aggregator-bounded-timeout.patch | 4 +- patches/fprime-sched-tick-drop.patch | 86 +++++++++---------- 12 files changed, 68 insertions(+), 71 deletions(-) diff --git a/.codespell-ignore-words.txt b/.codespell-ignore-words.txt index 9c69ee4e..5cc75138 100644 --- a/.codespell-ignore-words.txt +++ b/.codespell-ignore-words.txt @@ -2,6 +2,7 @@ ALS comIn bufferIn commandIn +comman Ines rsource ser diff --git a/PROVESFlightControllerReference/ReferenceDeployment/Main.cpp b/PROVESFlightControllerReference/ReferenceDeployment/Main.cpp index 713d4ad3..ec87cf63 100644 --- a/PROVESFlightControllerReference/ReferenceDeployment/Main.cpp +++ b/PROVESFlightControllerReference/ReferenceDeployment/Main.cpp @@ -86,8 +86,8 @@ int main(int argc, char* argv[]) { #else // v5e USP path: freq/power passed instead of a device pointer. // Constants match LoRaConfig values used by the legacy driver. - inputs.uspFreqHz = 915000000U; - inputs.uspTxPowerDbm = 14; + inputs.uspFreqHz = 915000000U; + inputs.uspTxPowerDbm = 14; #endif inputs.uartDevice = serial; inputs.lsm6dsoDevice = lsm6dso; diff --git a/PROVESFlightControllerReference/ReferenceDeployment/Top/ReferenceDeploymentTopologyDefs.hpp b/PROVESFlightControllerReference/ReferenceDeployment/Top/ReferenceDeploymentTopologyDefs.hpp index b664850f..7c309490 100644 --- a/PROVESFlightControllerReference/ReferenceDeployment/Top/ReferenceDeploymentTopologyDefs.hpp +++ b/PROVESFlightControllerReference/ReferenceDeployment/Top/ReferenceDeploymentTopologyDefs.hpp @@ -111,16 +111,16 @@ namespace ReferenceDeployment { * autocoder. The contents are entirely up to the definition of the project. This deployment uses subtopologies. */ struct TopologyState { - const device* uartDevice; //!< UART device path for communication - const device* spi0Device; //!< Spi device path for s-band LoRa module + const device* uartDevice; //!< UART device path for communication + const device* spi0Device; //!< Spi device path for s-band LoRa module #ifdef CONFIG_LORA_BASICS_MODEM_DRIVERS // v5e (USP path): no Zephyr LoRa device; radio is initialised by // RalSessionImpl via the USP/RAC API. Freq and power come from LoRaCfg // constants re-exported below so callers don't need the Zephyr lora header. - uint32_t uspFreqHz; //!< Carrier frequency in Hz (e.g. 915000000) - int8_t uspTxPowerDbm; //!< TX power in dBm (e.g. 14) + uint32_t uspFreqHz; //!< Carrier frequency in Hz (e.g. 915000000) + int8_t uspTxPowerDbm; //!< TX power in dBm (e.g. 14) #else - const device* loraDevice; //!< LoRa device path for communication (v5c/v5d) + const device* loraDevice; //!< LoRa device path for communication (v5c/v5d) #endif ComCcsdsLora::SubtopologyState comCcsdsLora; //!< Subtopology state for ComCcsdsLora // ComCcsdsSband::SubtopologyState comCcsdsSband; //!< Subtopology state for ComCcsdsSband diff --git a/patches/0001-feat-sx126x-add-external-RF-switch-GPIO-support-tx-r.patch b/patches/0001-feat-sx126x-add-external-RF-switch-GPIO-support-tx-r.patch index 1a25b7b0..11789c61 100644 --- a/patches/0001-feat-sx126x-add-external-RF-switch-GPIO-support-tx-r.patch +++ b/patches/0001-feat-sx126x-add-external-RF-switch-GPIO-support-tx-r.patch @@ -51,7 +51,7 @@ index 215801a..d119aba 100644 @@ -239,6 +239,26 @@ static int sx126x_init( const struct device* dev ) } } - + + /* External RF-switch GPIOs — configure as outputs, inactive (both paths off) */ + if( config->tx_enable.port ) + { @@ -74,7 +74,7 @@ index 215801a..d119aba 100644 + data->radio_status = RADIO_AWAKE; data->tx_power_offset_db_current = config->tx_power_offset_db; - + @@ -366,6 +386,8 @@ static int sx126x_pm_action( const struct device* dev, enum pm_device_action act CONFIGURE_GPIO_IF_IN_DT( node_id, dio1, dio1_gpios ) CONFIGURE_GPIO_IF_IN_DT( node_id, dio2, dio2_gpios ) \ CONFIGURE_GPIO_IF_IN_DT( node_id, dio3, dio3_gpios ) \ @@ -91,7 +91,7 @@ index a3215cc..1530077 100644 @@ -111,6 +111,86 @@ static void sx126x_hal_check_device_ready( const void* context ) * --- PUBLIC FUNCTIONS DEFINITION --------------------------------------------- */ - + +/* + * External RF-switch toggle helper. + * @@ -177,7 +177,7 @@ index a3215cc..1530077 100644 { @@ -127,6 +207,13 @@ sx126x_hal_status_t sx126x_hal_write( const void* context, const uint8_t* comman const struct spi_buf_set tx_buf_set = { tx_bufs, .count = ARRAY_SIZE( tx_bufs ) }; - + sx126x_hal_check_device_ready( context ); + + /* Toggle external RF switch before writing the mode-change opcode */ @@ -195,7 +195,7 @@ index 228f883..0c56bef 100644 +++ b/drivers/usp/sx126x/sx126x_hal_context.h @@ -72,6 +72,16 @@ struct sx126x_hal_context_cfg_t struct gpio_dt_spec dio3; /* DIO3 pin */ - + bool dio2_as_rf_switch; + + /* External RF-switch GPIOs (optional; absent when port == NULL). @@ -218,7 +218,7 @@ index 72032bb..56a8e40 100644 required: false enum: [0, 1, 2, 3, 4, 5, 6, 7] description: | -- The ramp-up time for the radio PA, between 0x0 (10us) to 0x07 (3400us). +- The ramp-up time for the radio PA, between 0x0 (10us) to 0x07 (3400us). + The ramp-up time for the radio PA, between 0x0 (10us) to 0x07 (3400us). If not provided, the driver will use the default, recommended time (40us). It is not recommended to modify this value. @@ -246,6 +246,5 @@ index 72032bb..56a8e40 100644 + When present, the driver asserts this pin active before any receive-class + operation (SetRx / SetRxDutyCycle / SetCad) and deasserts it on standby, + sleep, and transmit-class operations. Pair with tx-enable-gpios. --- +-- 2.50.1 (Apple Git-155) - diff --git a/patches/0002-fix-zephyr-4.3-remove-select-ZEPHYR_LORA_BASICS_MODE.patch b/patches/0002-fix-zephyr-4.3-remove-select-ZEPHYR_LORA_BASICS_MODE.patch index 57e34fdd..f98faeb7 100644 --- a/patches/0002-fix-zephyr-4.3-remove-select-ZEPHYR_LORA_BASICS_MODE.patch +++ b/patches/0002-fix-zephyr-4.3-remove-select-ZEPHYR_LORA_BASICS_MODE.patch @@ -31,6 +31,5 @@ index 5518a25..cc7940f 100644 depends on !LORA help Include LoRa drivers from the new LoRa Basics Modem stack in the system configuration. --- +-- 2.50.1 (Apple Git-155) - diff --git a/patches/0003-fix-usp-main-2025-fix-LR_FHSS_SRC_PATH-for-flattened.patch b/patches/0003-fix-usp-main-2025-fix-LR_FHSS_SRC_PATH-for-flattened.patch index 2cb0baf3..f1800369 100644 --- a/patches/0003-fix-usp-main-2025-fix-LR_FHSS_SRC_PATH-for-flattened.patch +++ b/patches/0003-fix-usp-main-2025-fix-LR_FHSS_SRC_PATH-for-flattened.patch @@ -19,7 +19,7 @@ index f92cafe..1e76230 100644 +++ b/modules/usp_drivers/dev_env.cmake @@ -1,6 +1,10 @@ # SPDX-License-Identifier: BSD-3-Clause-Clear - + if(SX126X_ENABLE_LR_FHSS) - set(LR_FHSS_SRC_PATH "${LBM_SX126X_LIB_DIR}/lr_fhss_driver/src" + # PATCH(usp-main-2025): upstream usp repo removed the lr_fhss_driver/src @@ -29,6 +29,5 @@ index f92cafe..1e76230 100644 + set(LR_FHSS_SRC_PATH "${LBM_SX126X_LIB_DIR}" CACHE PATH "Path to folder containing LR-FHSS driver" FORCE) endif() --- +-- 2.50.1 (Apple Git-155) - diff --git a/patches/0004-fix-usbd-cdc-acm-poll-mode-tx-drain-on-class-enable.patch b/patches/0004-fix-usbd-cdc-acm-poll-mode-tx-drain-on-class-enable.patch index 5b1b8f3b..0cad825a 100644 --- a/patches/0004-fix-usbd-cdc-acm-poll-mode-tx-drain-on-class-enable.patch +++ b/patches/0004-fix-usbd-cdc-acm-poll-mode-tx-drain-on-class-enable.patch @@ -25,6 +25,6 @@ index d3921c0f146..a8241ad130e 100644 + cdc_acm_work_schedule(&data->tx_fifo_work, K_NO_WAIT); } } - --- + +-- 2.39.3 (Apple Git-146) diff --git a/patches/0006-fix-sx126x-wakeup-busy-race-add-t_woff-settle-delay.patch b/patches/0006-fix-sx126x-wakeup-busy-race-add-t_woff-settle-delay.patch index ce56db0c..35618e37 100644 --- a/patches/0006-fix-sx126x-wakeup-busy-race-add-t_woff-settle-delay.patch +++ b/patches/0006-fix-sx126x-wakeup-busy-race-add-t_woff-settle-delay.patch @@ -55,4 +55,3 @@ index 1530077..699d53b 100644 } -- 2.50.1 (Apple Git-155) - diff --git a/patches/0007-fix-usbd-cdc-acm-bound-poll-out-backpressure-wait.patch b/patches/0007-fix-usbd-cdc-acm-bound-poll-out-backpressure-wait.patch index 00137ffa..763f614f 100644 --- a/patches/0007-fix-usbd-cdc-acm-bound-poll-out-backpressure-wait.patch +++ b/patches/0007-fix-usbd-cdc-acm-bound-poll-out-backpressure-wait.patch @@ -14,13 +14,13 @@ discard behavior. k_spinlock_key_t key; uint32_t wrote; + int retries = 20; - + while (true) { key = k_spin_lock(&data->lock); @@ -1017,7 +1018,15 @@ break; } - + - if (k_is_in_isr() || !data->flow_ctrl) { + /* Bounded wait: with an attached-but-stalled host session (macOS + * ceases IN polling for minutes at a time), an unbounded sleep-retry diff --git a/patches/0009-fix-radio-planner-failsafe-exempt-unlock-radio-access.patch b/patches/0009-fix-radio-planner-failsafe-exempt-unlock-radio-access.patch index cc985ff2..213f190c 100644 --- a/patches/0009-fix-radio-planner-failsafe-exempt-unlock-radio-access.patch +++ b/patches/0009-fix-radio-planner-failsafe-exempt-unlock-radio-access.patch @@ -3,7 +3,7 @@ index 2c44c68..a4c99d6 100644 --- a/smtc_rac_lib/radio_planner/src/radio_planner.c +++ b/smtc_rac_lib/radio_planner/src/radio_planner.c @@ -444,8 +444,15 @@ rp_stats_t rp_get_stats( const radio_planner_t* rp ) - + void rp_callback( radio_planner_t* rp ) { + // UNLOCK_RADIO_ACCESS must be exempt like LOCK_RADIO_ACCESS: a lock task diff --git a/patches/fprime-com-aggregator-bounded-timeout.patch b/patches/fprime-com-aggregator-bounded-timeout.patch index 4215db86..d3f8aa71 100644 --- a/patches/fprime-com-aggregator-bounded-timeout.patch +++ b/patches/fprime-com-aggregator-bounded-timeout.patch @@ -3,9 +3,9 @@ index dc6dd130d..6cfeecd6c 100644 --- a/Svc/ComAggregator/ComAggregator.cpp +++ b/Svc/ComAggregator/ComAggregator.cpp @@ -8,6 +8,12 @@ - + namespace Svc { - + +namespace { +//! Queue slots that must remain free for a timeout signal to be enqueued: the timeout itself plus one +//! in-flight 'fill' and one in-flight 'status' signal (each bounded to one message by the com protocol). diff --git a/patches/fprime-sched-tick-drop.patch b/patches/fprime-sched-tick-drop.patch index 70dbf16e..684dce9b 100644 --- a/patches/fprime-sched-tick-drop.patch +++ b/patches/fprime-sched-tick-drop.patch @@ -4,11 +4,11 @@ index 3ee1488e3..c60ad6d0d 100644 +++ b/Svc/ActiveRateGroup/ActiveRateGroup.fpp @@ -15,7 +15,7 @@ module Svc { output port RateGroupMemberOut: [ActiveRateGroupOutputPorts] Sched - + @ Ping input port for health - async input port PingIn: Ping + async input port PingIn: Ping drop - + @ Ping output port for health output port PingOut: Ping diff --git a/Svc/BufferLogger/BufferLogger.fpp b/Svc/BufferLogger/BufferLogger.fpp @@ -17,17 +17,17 @@ index 6da2ffd69..e045b1856 100644 +++ b/Svc/BufferLogger/BufferLogger.fpp @@ -16,12 +16,12 @@ module Svc { async input port comIn: Fw.Com - + @ Ping input port - async input port pingIn: Svc.Ping + async input port pingIn: Svc.Ping drop - + @ Ping output port output port pingOut: Svc.Ping - + - async input port schedIn: Svc.Sched + async input port schedIn: Svc.Sched drop - + # ---------------------------------------------------------------------- # Special ports diff --git a/Svc/CmdDispatcher/CmdDispatcher.fpp b/Svc/CmdDispatcher/CmdDispatcher.fpp @@ -36,15 +36,15 @@ index 66343b886..2f7732896 100644 +++ b/Svc/CmdDispatcher/CmdDispatcher.fpp @@ -24,10 +24,10 @@ module Svc { async input port seqCmdBuff: [CmdDispatcherSequencePorts] Fw.Com hook - + @ Ping input port - async input port pingIn: Svc.Ping + async input port pingIn: Svc.Ping drop - + @ Run port used to emit telemetry - async input port run: Svc.Sched + async input port run: Svc.Sched drop - + @ Ping output port output port pingOut: Svc.Ping diff --git a/Svc/CmdSequencer/CmdSequencer.fpp b/Svc/CmdSequencer/CmdSequencer.fpp @@ -53,20 +53,20 @@ index 1573fbe60..614fefd5f 100644 +++ b/Svc/CmdSequencer/CmdSequencer.fpp @@ -68,7 +68,7 @@ module Svc { async input port cmdResponseIn: Fw.CmdResponse - + @ Ping in port - async input port pingIn: Svc.Ping + async input port pingIn: Svc.Ping drop - + @ Ping out port output port pingOut: Svc.Ping @@ -83,7 +83,7 @@ module Svc { output port comCmdOut: Fw.Com - + @ Schedule in port - async input port schedIn: Svc.Sched + async input port schedIn: Svc.Sched drop - + @ Notifies that a sequence has started running output port seqStartOut: Svc.CmdSeqIn diff --git a/Svc/ComLogger/ComLogger.fpp b/Svc/ComLogger/ComLogger.fpp @@ -75,11 +75,11 @@ index c4327b7dd..cf4bc2238 100644 +++ b/Svc/ComLogger/ComLogger.fpp @@ -11,7 +11,7 @@ module Svc { async input port comIn: Fw.Com - + @ Ping input port - async input port pingIn: Svc.Ping + async input port pingIn: Svc.Ping drop - + @ Ping output port output port pingOut: Svc.Ping diff --git a/Svc/DpCatalog/DpCatalog.fpp b/Svc/DpCatalog/DpCatalog.fpp @@ -88,11 +88,11 @@ index e1fd468dd..59ffc363f 100644 +++ b/Svc/DpCatalog/DpCatalog.fpp @@ -32,7 +32,7 @@ module Svc { # Component specific ports - + @ Ping input port - async input port pingIn: Svc.Ping + async input port pingIn: Svc.Ping drop - + @ Ping output port output port pingOut: Svc.Ping diff --git a/Svc/DpManager/DpManager.fpp b/Svc/DpManager/DpManager.fpp @@ -101,11 +101,11 @@ index ebbf5a46f..74574f97f 100644 +++ b/Svc/DpManager/DpManager.fpp @@ -8,7 +8,7 @@ module Svc { # ---------------------------------------------------------------------- - + @ Schedule in port - async input port schedIn: Svc.Sched + async input port schedIn: Svc.Sched drop - + # ---------------------------------------------------------------------- # Ports for handling buffer requests diff --git a/Svc/DpWriter/DpWriter.fpp b/Svc/DpWriter/DpWriter.fpp @@ -114,11 +114,11 @@ index f24594f09..152be91ba 100644 +++ b/Svc/DpWriter/DpWriter.fpp @@ -8,7 +8,7 @@ module Svc { # ---------------------------------------------------------------------- - + @ Schedule in port - async input port schedIn: Svc.Sched + async input port schedIn: Svc.Sched drop - + # ---------------------------------------------------------------------- # Ports for handling data products diff --git a/Svc/EventManager/EventManager.fpp b/Svc/EventManager/EventManager.fpp @@ -127,11 +127,11 @@ index b97a8d807..2ea88960a 100644 +++ b/Svc/EventManager/EventManager.fpp @@ -53,7 +53,7 @@ module Svc { output port FatalAnnounce: Svc.FatalEvent - + @ Ping input port - async input port pingIn: Svc.Ping + async input port pingIn: Svc.Ping drop - + @ Ping output port output port pingOut: Svc.Ping diff --git a/Svc/FileDownlink/FileDownlink.fpp b/Svc/FileDownlink/FileDownlink.fpp @@ -140,20 +140,20 @@ index eed9376aa..5e710a3f4 100644 +++ b/Svc/FileDownlink/FileDownlink.fpp @@ -8,7 +8,7 @@ module Svc { # ---------------------------------------------------------------------- - + @ Run input port - async input port Run: Svc.Sched + async input port Run: Svc.Sched drop - + @ Mutexed Sendfile input port guarded input port SendFile: Svc.SendFileRequest @@ -23,7 +23,7 @@ module Svc { output port bufferSendOut: Fw.BufferSend - + @ Ping input port - async input port pingIn: Svc.Ping + async input port pingIn: Svc.Ping drop - + @ Ping output port output port pingOut: Svc.Ping diff --git a/Svc/FileManager/FileManager.fpp b/Svc/FileManager/FileManager.fpp @@ -162,11 +162,11 @@ index bef777c71..d85f42002 100644 +++ b/Svc/FileManager/FileManager.fpp @@ -8,7 +8,7 @@ module Svc { # ---------------------------------------------------------------------- - + @ Ping input port - async input port pingIn: Svc.Ping + async input port pingIn: Svc.Ping drop - + @ Scheduler input port for rate group operations sync input port schedIn: Sched diff --git a/Svc/FileUplink/FileUplink.fpp b/Svc/FileUplink/FileUplink.fpp @@ -175,11 +175,11 @@ index 90ef4e9a8..b2b67bd5c 100644 +++ b/Svc/FileUplink/FileUplink.fpp @@ -14,7 +14,7 @@ module Svc { output port bufferSendOut: Fw.BufferSend - + @ Ping in - async input port pingIn: Svc.Ping + async input port pingIn: Svc.Ping drop - + @ Ping out output port pingOut: Svc.Ping diff --git a/Svc/FpySequencer/FpySequencer.fpp b/Svc/FpySequencer/FpySequencer.fpp @@ -187,12 +187,12 @@ index 8af40d3d7..b9e80e1af 100644 --- a/Svc/FpySequencer/FpySequencer.fpp +++ b/Svc/FpySequencer/FpySequencer.fpp @@ -38,7 +38,7 @@ module Svc { - + @ Ping in port # TODO should ping have highest prio? or lowest? - async input port pingIn: Svc.Ping priority 10 assert + async input port pingIn: Svc.Ping priority 10 drop - + @ port to trigger a wakeup or timeout check. increase frequency @ to increase temporal resolution of sequencer diff --git a/Svc/PrmDb/PrmDb.fpp b/Svc/PrmDb/PrmDb.fpp @@ -201,11 +201,11 @@ index 66f850e30..9588336e3 100644 +++ b/Svc/PrmDb/PrmDb.fpp @@ -60,7 +60,7 @@ module Svc { async input port setPrm: Fw.PrmSet - + @ Ping input port - async input port pingIn: Svc.Ping + async input port pingIn: Svc.Ping drop - + @ Ping output port output port pingOut: Svc.Ping diff --git a/Svc/TlmChan/TlmChan.fpp b/Svc/TlmChan/TlmChan.fpp @@ -214,18 +214,18 @@ index d15072ed0..52b73b2d8 100644 +++ b/Svc/TlmChan/TlmChan.fpp @@ -10,13 +10,13 @@ module Svc { guarded input port TlmGet: Fw.TlmGet - + @ Run port for starting packet send cycle - async input port Run: Svc.Sched + async input port Run: Svc.Sched drop - + @ Packet send port output port PktSend: Fw.Com - + @ Ping input port - async input port pingIn: Svc.Ping + async input port pingIn: Svc.Ping drop - + @ Ping output port output port pingOut: Svc.Ping diff --git a/Svc/TlmPacketizer/TlmPacketizer.fpp b/Svc/TlmPacketizer/TlmPacketizer.fpp @@ -234,17 +234,17 @@ index f03dbafff..91f45eb6f 100644 +++ b/Svc/TlmPacketizer/TlmPacketizer.fpp @@ -11,13 +11,13 @@ module Svc { output port PktSend: Fw.Com - + @ Ping input port - async input port pingIn: Svc.Ping + async input port pingIn: Svc.Ping drop - + @ Ping output port output port pingOut: Svc.Ping - + @ Run port for starting packet send cycle - async input port Run: Svc.Sched + async input port Run: Svc.Sched drop - + @ Telemetry input port sync input port TlmRecv: Fw.Tlm From a6285911649d83867a3343f0cb1a2489c0ffea65 Mon Sep 17 00:00:00 2001 From: Michael Pham <61564344+Mikefly123@users.noreply.github.com> Date: Sun, 12 Jul 2026 11:04:02 -0700 Subject: [PATCH 34/51] finalize USP radio operating frequency (437.4 MHz / 10 dBm) + fix include order MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 437.4 MHz / 10 dBm (70cm amateur band) is the intended operating configuration, not a temporary bench override — confirmed with the user. Dropped the "BENCH OVERRIDE...TODO: revert to 915 MHz/14dBm" framing since it no longer applies; this value stays. (Main.cpp's separate TopologyState.uspFreqHz/uspTxPowerDbm fields are set but never read anywhere — dead code, left untouched.) Also fixes the #include order (clang-format) that was blocking CI lint. --- .../Top/ReferenceDeploymentTopology.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/PROVESFlightControllerReference/ReferenceDeployment/Top/ReferenceDeploymentTopology.cpp b/PROVESFlightControllerReference/ReferenceDeployment/Top/ReferenceDeploymentTopology.cpp index ec86f91f..96203a0a 100644 --- a/PROVESFlightControllerReference/ReferenceDeployment/Top/ReferenceDeploymentTopology.cpp +++ b/PROVESFlightControllerReference/ReferenceDeployment/Top/ReferenceDeploymentTopology.cpp @@ -9,8 +9,8 @@ // #include // Necessary project-specified types -#include #include +#include #include #include @@ -22,10 +22,10 @@ #include "fprime-zephyr/Drv/UspRadio/UspRadio.hpp" // Static RalSessionImpl instance (lives for the entire flight). -// Freq and power match LoRaCfg constants used by the legacy driver. -static Zephyr::RalSessionImpl s_ralSession( - 915000000U, // 915 MHz (matches LoRaConfig::FREQUENCY) - 14 // +14 dBm (matches LoRaConfig::TX_POWER) +// 437.4 MHz / 70cm amateur band, matches GRC LoRaCfg DEFAULT_FREQ. TX power +// capped at 10 dBm. +static Zephyr::RalSessionImpl s_ralSession(437400000U, // 437.4 MHz + 10 // +10 dBm ); #endif // CONFIG_LORA_BASICS_MODEM_DRIVERS From 11ec65902ca9750ba9a3b43f9ce35fd6c09c287d Mon Sep 17 00:00:00 2001 From: Michael Pham <61564344+Mikefly123@users.noreply.github.com> Date: Sun, 12 Jul 2026 11:04:30 -0700 Subject: [PATCH 35/51] ci: apply carried usp/zephyr patches before generate/build MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit make build's dependency chain (submodules -> zephyr -> generate-if-needed) never invoked usp-patches / usp-core-patches / zephyr-patches. west update fetches the vanilla usp_zephyr/usp/zephyr modules; the USP radio port needs the RF-switch GPIO, Zephyr 4.3 compat, wakeup-race, radio-planner failsafe, and CDC-ACM patches carried in patches/ applied on top. This gap predates the main merge (checked: this branch's own pre-merge ci.yaml had the same gap) — it just never got exercised because CI never built the v5e/USP config to completion before now. Confirmed locally: 'devicetree error: tx-enable-gpios ... not declared in properties' is exactly what CI hit, and it goes away once usp-patches (which adds that property to the semtech,sx1262-new binding) is applied first. --- .github/workflows/ci.yaml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 001ce992..13453ee3 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -59,6 +59,15 @@ jobs: run: | make zephyr-workspace + - name: Apply carried patches + # west update fetches vanilla usp_zephyr/usp/zephyr modules; the USP + # radio port needs the RF-switch GPIO, Zephyr 4.3 compat, wakeup-race, + # radio-planner failsafe, and CDC-ACM fixes carried in patches/. + run: | + make usp-patches + make usp-core-patches + make zephyr-patches + - name: Setup Zephyr SDK if: steps.cache-zephyr-sdk.outputs.cache-hit != 'true' run: | From 39bc6930aa023084230d4f857395a649bd436b2a Mon Sep 17 00:00:00 2001 From: Michael Pham <61564344+Mikefly123@users.noreply.github.com> Date: Sun, 12 Jul 2026 11:18:22 -0700 Subject: [PATCH 36/51] fix(patches): revert whitespace corruption from the earlier lint-fix commit MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit a7d0e7d's "trim trailing whitespace" pass stripped trailing whitespace from diff context/removed lines inside patches/*.patch — byte-exact content that git apply matches against the real upstream files, some of which legitimately have trailing whitespace. This broke `git apply` for several carried patches (caught by CI: "Cannot apply 0001-feat-sx126x-...patch — check usp_zephyr revision", reproduced locally against a clean clone of the pinned usp_zephyr revision). Restored all 9 affected patches to their pre-a7d0e7d byte-exact content and verified every one (usp_zephyr: 0001/0002/0003/0006/0008 sequential; usp: 0009; zephyr: 0004/0005/0007 sequential; fprime: fprime-gds-version/ fprime-com-aggregator-bounded-timeout/fprime-sched-tick-drop) applies cleanly against a truly clean checkout at its pinned revision — not just reverse-check against an already-patched local submodule, which was the flawed verification method that let the original regression through. Also excludes patches/ from the trailing-whitespace and end-of-file-fixer pre-commit hooks so this class of corruption can't recur — those hooks have no domain awareness that a .patch file's bytes are load-bearing. --- .pre-commit-config.yaml | 7 ++ ...external-RF-switch-GPIO-support-tx-r.patch | 15 ++-- ...emove-select-ZEPHYR_LORA_BASICS_MODE.patch | 3 +- ...5-fix-LR_FHSS_SRC_PATH-for-flattened.patch | 5 +- ...m-poll-mode-tx-drain-on-class-enable.patch | 4 +- ...up-busy-race-add-t_woff-settle-delay.patch | 1 + ...acm-bound-poll-out-backpressure-wait.patch | 4 +- ...-failsafe-exempt-unlock-radio-access.patch | 2 +- ...prime-com-aggregator-bounded-timeout.patch | 4 +- patches/fprime-sched-tick-drop.patch | 86 +++++++++---------- 10 files changed, 71 insertions(+), 60 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index a762d93b..7ad769a3 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -3,7 +3,14 @@ repos: rev: v5.0.0 hooks: - id: trailing-whitespace + # patches/*.patch are machine-generated diffs applied via `git apply`; + # their context/removed lines must byte-match the real upstream files, + # which can legitimately have trailing whitespace. Stripping it here + # silently breaks patch application (see: 0001-feat-sx126x patch vs + # usp_zephyr's semtech,sx126x-new-common.yaml). + exclude: ^patches/ - id: end-of-file-fixer + exclude: ^patches/ - id: check-yaml exclude: ^mkdocs\.yml$ - id: check-json diff --git a/patches/0001-feat-sx126x-add-external-RF-switch-GPIO-support-tx-r.patch b/patches/0001-feat-sx126x-add-external-RF-switch-GPIO-support-tx-r.patch index 11789c61..1a25b7b0 100644 --- a/patches/0001-feat-sx126x-add-external-RF-switch-GPIO-support-tx-r.patch +++ b/patches/0001-feat-sx126x-add-external-RF-switch-GPIO-support-tx-r.patch @@ -51,7 +51,7 @@ index 215801a..d119aba 100644 @@ -239,6 +239,26 @@ static int sx126x_init( const struct device* dev ) } } - + + /* External RF-switch GPIOs — configure as outputs, inactive (both paths off) */ + if( config->tx_enable.port ) + { @@ -74,7 +74,7 @@ index 215801a..d119aba 100644 + data->radio_status = RADIO_AWAKE; data->tx_power_offset_db_current = config->tx_power_offset_db; - + @@ -366,6 +386,8 @@ static int sx126x_pm_action( const struct device* dev, enum pm_device_action act CONFIGURE_GPIO_IF_IN_DT( node_id, dio1, dio1_gpios ) CONFIGURE_GPIO_IF_IN_DT( node_id, dio2, dio2_gpios ) \ CONFIGURE_GPIO_IF_IN_DT( node_id, dio3, dio3_gpios ) \ @@ -91,7 +91,7 @@ index a3215cc..1530077 100644 @@ -111,6 +111,86 @@ static void sx126x_hal_check_device_ready( const void* context ) * --- PUBLIC FUNCTIONS DEFINITION --------------------------------------------- */ - + +/* + * External RF-switch toggle helper. + * @@ -177,7 +177,7 @@ index a3215cc..1530077 100644 { @@ -127,6 +207,13 @@ sx126x_hal_status_t sx126x_hal_write( const void* context, const uint8_t* comman const struct spi_buf_set tx_buf_set = { tx_bufs, .count = ARRAY_SIZE( tx_bufs ) }; - + sx126x_hal_check_device_ready( context ); + + /* Toggle external RF switch before writing the mode-change opcode */ @@ -195,7 +195,7 @@ index 228f883..0c56bef 100644 +++ b/drivers/usp/sx126x/sx126x_hal_context.h @@ -72,6 +72,16 @@ struct sx126x_hal_context_cfg_t struct gpio_dt_spec dio3; /* DIO3 pin */ - + bool dio2_as_rf_switch; + + /* External RF-switch GPIOs (optional; absent when port == NULL). @@ -218,7 +218,7 @@ index 72032bb..56a8e40 100644 required: false enum: [0, 1, 2, 3, 4, 5, 6, 7] description: | -- The ramp-up time for the radio PA, between 0x0 (10us) to 0x07 (3400us). +- The ramp-up time for the radio PA, between 0x0 (10us) to 0x07 (3400us). + The ramp-up time for the radio PA, between 0x0 (10us) to 0x07 (3400us). If not provided, the driver will use the default, recommended time (40us). It is not recommended to modify this value. @@ -246,5 +246,6 @@ index 72032bb..56a8e40 100644 + When present, the driver asserts this pin active before any receive-class + operation (SetRx / SetRxDutyCycle / SetCad) and deasserts it on standby, + sleep, and transmit-class operations. Pair with tx-enable-gpios. --- +-- 2.50.1 (Apple Git-155) + diff --git a/patches/0002-fix-zephyr-4.3-remove-select-ZEPHYR_LORA_BASICS_MODE.patch b/patches/0002-fix-zephyr-4.3-remove-select-ZEPHYR_LORA_BASICS_MODE.patch index f98faeb7..57e34fdd 100644 --- a/patches/0002-fix-zephyr-4.3-remove-select-ZEPHYR_LORA_BASICS_MODE.patch +++ b/patches/0002-fix-zephyr-4.3-remove-select-ZEPHYR_LORA_BASICS_MODE.patch @@ -31,5 +31,6 @@ index 5518a25..cc7940f 100644 depends on !LORA help Include LoRa drivers from the new LoRa Basics Modem stack in the system configuration. --- +-- 2.50.1 (Apple Git-155) + diff --git a/patches/0003-fix-usp-main-2025-fix-LR_FHSS_SRC_PATH-for-flattened.patch b/patches/0003-fix-usp-main-2025-fix-LR_FHSS_SRC_PATH-for-flattened.patch index f1800369..2cb0baf3 100644 --- a/patches/0003-fix-usp-main-2025-fix-LR_FHSS_SRC_PATH-for-flattened.patch +++ b/patches/0003-fix-usp-main-2025-fix-LR_FHSS_SRC_PATH-for-flattened.patch @@ -19,7 +19,7 @@ index f92cafe..1e76230 100644 +++ b/modules/usp_drivers/dev_env.cmake @@ -1,6 +1,10 @@ # SPDX-License-Identifier: BSD-3-Clause-Clear - + if(SX126X_ENABLE_LR_FHSS) - set(LR_FHSS_SRC_PATH "${LBM_SX126X_LIB_DIR}/lr_fhss_driver/src" + # PATCH(usp-main-2025): upstream usp repo removed the lr_fhss_driver/src @@ -29,5 +29,6 @@ index f92cafe..1e76230 100644 + set(LR_FHSS_SRC_PATH "${LBM_SX126X_LIB_DIR}" CACHE PATH "Path to folder containing LR-FHSS driver" FORCE) endif() --- +-- 2.50.1 (Apple Git-155) + diff --git a/patches/0004-fix-usbd-cdc-acm-poll-mode-tx-drain-on-class-enable.patch b/patches/0004-fix-usbd-cdc-acm-poll-mode-tx-drain-on-class-enable.patch index 0cad825a..5b1b8f3b 100644 --- a/patches/0004-fix-usbd-cdc-acm-poll-mode-tx-drain-on-class-enable.patch +++ b/patches/0004-fix-usbd-cdc-acm-poll-mode-tx-drain-on-class-enable.patch @@ -25,6 +25,6 @@ index d3921c0f146..a8241ad130e 100644 + cdc_acm_work_schedule(&data->tx_fifo_work, K_NO_WAIT); } } - --- + +-- 2.39.3 (Apple Git-146) diff --git a/patches/0006-fix-sx126x-wakeup-busy-race-add-t_woff-settle-delay.patch b/patches/0006-fix-sx126x-wakeup-busy-race-add-t_woff-settle-delay.patch index 35618e37..ce56db0c 100644 --- a/patches/0006-fix-sx126x-wakeup-busy-race-add-t_woff-settle-delay.patch +++ b/patches/0006-fix-sx126x-wakeup-busy-race-add-t_woff-settle-delay.patch @@ -55,3 +55,4 @@ index 1530077..699d53b 100644 } -- 2.50.1 (Apple Git-155) + diff --git a/patches/0007-fix-usbd-cdc-acm-bound-poll-out-backpressure-wait.patch b/patches/0007-fix-usbd-cdc-acm-bound-poll-out-backpressure-wait.patch index 763f614f..00137ffa 100644 --- a/patches/0007-fix-usbd-cdc-acm-bound-poll-out-backpressure-wait.patch +++ b/patches/0007-fix-usbd-cdc-acm-bound-poll-out-backpressure-wait.patch @@ -14,13 +14,13 @@ discard behavior. k_spinlock_key_t key; uint32_t wrote; + int retries = 20; - + while (true) { key = k_spin_lock(&data->lock); @@ -1017,7 +1018,15 @@ break; } - + - if (k_is_in_isr() || !data->flow_ctrl) { + /* Bounded wait: with an attached-but-stalled host session (macOS + * ceases IN polling for minutes at a time), an unbounded sleep-retry diff --git a/patches/0009-fix-radio-planner-failsafe-exempt-unlock-radio-access.patch b/patches/0009-fix-radio-planner-failsafe-exempt-unlock-radio-access.patch index 213f190c..cc985ff2 100644 --- a/patches/0009-fix-radio-planner-failsafe-exempt-unlock-radio-access.patch +++ b/patches/0009-fix-radio-planner-failsafe-exempt-unlock-radio-access.patch @@ -3,7 +3,7 @@ index 2c44c68..a4c99d6 100644 --- a/smtc_rac_lib/radio_planner/src/radio_planner.c +++ b/smtc_rac_lib/radio_planner/src/radio_planner.c @@ -444,8 +444,15 @@ rp_stats_t rp_get_stats( const radio_planner_t* rp ) - + void rp_callback( radio_planner_t* rp ) { + // UNLOCK_RADIO_ACCESS must be exempt like LOCK_RADIO_ACCESS: a lock task diff --git a/patches/fprime-com-aggregator-bounded-timeout.patch b/patches/fprime-com-aggregator-bounded-timeout.patch index d3f8aa71..4215db86 100644 --- a/patches/fprime-com-aggregator-bounded-timeout.patch +++ b/patches/fprime-com-aggregator-bounded-timeout.patch @@ -3,9 +3,9 @@ index dc6dd130d..6cfeecd6c 100644 --- a/Svc/ComAggregator/ComAggregator.cpp +++ b/Svc/ComAggregator/ComAggregator.cpp @@ -8,6 +8,12 @@ - + namespace Svc { - + +namespace { +//! Queue slots that must remain free for a timeout signal to be enqueued: the timeout itself plus one +//! in-flight 'fill' and one in-flight 'status' signal (each bounded to one message by the com protocol). diff --git a/patches/fprime-sched-tick-drop.patch b/patches/fprime-sched-tick-drop.patch index 684dce9b..70dbf16e 100644 --- a/patches/fprime-sched-tick-drop.patch +++ b/patches/fprime-sched-tick-drop.patch @@ -4,11 +4,11 @@ index 3ee1488e3..c60ad6d0d 100644 +++ b/Svc/ActiveRateGroup/ActiveRateGroup.fpp @@ -15,7 +15,7 @@ module Svc { output port RateGroupMemberOut: [ActiveRateGroupOutputPorts] Sched - + @ Ping input port for health - async input port PingIn: Ping + async input port PingIn: Ping drop - + @ Ping output port for health output port PingOut: Ping diff --git a/Svc/BufferLogger/BufferLogger.fpp b/Svc/BufferLogger/BufferLogger.fpp @@ -17,17 +17,17 @@ index 6da2ffd69..e045b1856 100644 +++ b/Svc/BufferLogger/BufferLogger.fpp @@ -16,12 +16,12 @@ module Svc { async input port comIn: Fw.Com - + @ Ping input port - async input port pingIn: Svc.Ping + async input port pingIn: Svc.Ping drop - + @ Ping output port output port pingOut: Svc.Ping - + - async input port schedIn: Svc.Sched + async input port schedIn: Svc.Sched drop - + # ---------------------------------------------------------------------- # Special ports diff --git a/Svc/CmdDispatcher/CmdDispatcher.fpp b/Svc/CmdDispatcher/CmdDispatcher.fpp @@ -36,15 +36,15 @@ index 66343b886..2f7732896 100644 +++ b/Svc/CmdDispatcher/CmdDispatcher.fpp @@ -24,10 +24,10 @@ module Svc { async input port seqCmdBuff: [CmdDispatcherSequencePorts] Fw.Com hook - + @ Ping input port - async input port pingIn: Svc.Ping + async input port pingIn: Svc.Ping drop - + @ Run port used to emit telemetry - async input port run: Svc.Sched + async input port run: Svc.Sched drop - + @ Ping output port output port pingOut: Svc.Ping diff --git a/Svc/CmdSequencer/CmdSequencer.fpp b/Svc/CmdSequencer/CmdSequencer.fpp @@ -53,20 +53,20 @@ index 1573fbe60..614fefd5f 100644 +++ b/Svc/CmdSequencer/CmdSequencer.fpp @@ -68,7 +68,7 @@ module Svc { async input port cmdResponseIn: Fw.CmdResponse - + @ Ping in port - async input port pingIn: Svc.Ping + async input port pingIn: Svc.Ping drop - + @ Ping out port output port pingOut: Svc.Ping @@ -83,7 +83,7 @@ module Svc { output port comCmdOut: Fw.Com - + @ Schedule in port - async input port schedIn: Svc.Sched + async input port schedIn: Svc.Sched drop - + @ Notifies that a sequence has started running output port seqStartOut: Svc.CmdSeqIn diff --git a/Svc/ComLogger/ComLogger.fpp b/Svc/ComLogger/ComLogger.fpp @@ -75,11 +75,11 @@ index c4327b7dd..cf4bc2238 100644 +++ b/Svc/ComLogger/ComLogger.fpp @@ -11,7 +11,7 @@ module Svc { async input port comIn: Fw.Com - + @ Ping input port - async input port pingIn: Svc.Ping + async input port pingIn: Svc.Ping drop - + @ Ping output port output port pingOut: Svc.Ping diff --git a/Svc/DpCatalog/DpCatalog.fpp b/Svc/DpCatalog/DpCatalog.fpp @@ -88,11 +88,11 @@ index e1fd468dd..59ffc363f 100644 +++ b/Svc/DpCatalog/DpCatalog.fpp @@ -32,7 +32,7 @@ module Svc { # Component specific ports - + @ Ping input port - async input port pingIn: Svc.Ping + async input port pingIn: Svc.Ping drop - + @ Ping output port output port pingOut: Svc.Ping diff --git a/Svc/DpManager/DpManager.fpp b/Svc/DpManager/DpManager.fpp @@ -101,11 +101,11 @@ index ebbf5a46f..74574f97f 100644 +++ b/Svc/DpManager/DpManager.fpp @@ -8,7 +8,7 @@ module Svc { # ---------------------------------------------------------------------- - + @ Schedule in port - async input port schedIn: Svc.Sched + async input port schedIn: Svc.Sched drop - + # ---------------------------------------------------------------------- # Ports for handling buffer requests diff --git a/Svc/DpWriter/DpWriter.fpp b/Svc/DpWriter/DpWriter.fpp @@ -114,11 +114,11 @@ index f24594f09..152be91ba 100644 +++ b/Svc/DpWriter/DpWriter.fpp @@ -8,7 +8,7 @@ module Svc { # ---------------------------------------------------------------------- - + @ Schedule in port - async input port schedIn: Svc.Sched + async input port schedIn: Svc.Sched drop - + # ---------------------------------------------------------------------- # Ports for handling data products diff --git a/Svc/EventManager/EventManager.fpp b/Svc/EventManager/EventManager.fpp @@ -127,11 +127,11 @@ index b97a8d807..2ea88960a 100644 +++ b/Svc/EventManager/EventManager.fpp @@ -53,7 +53,7 @@ module Svc { output port FatalAnnounce: Svc.FatalEvent - + @ Ping input port - async input port pingIn: Svc.Ping + async input port pingIn: Svc.Ping drop - + @ Ping output port output port pingOut: Svc.Ping diff --git a/Svc/FileDownlink/FileDownlink.fpp b/Svc/FileDownlink/FileDownlink.fpp @@ -140,20 +140,20 @@ index eed9376aa..5e710a3f4 100644 +++ b/Svc/FileDownlink/FileDownlink.fpp @@ -8,7 +8,7 @@ module Svc { # ---------------------------------------------------------------------- - + @ Run input port - async input port Run: Svc.Sched + async input port Run: Svc.Sched drop - + @ Mutexed Sendfile input port guarded input port SendFile: Svc.SendFileRequest @@ -23,7 +23,7 @@ module Svc { output port bufferSendOut: Fw.BufferSend - + @ Ping input port - async input port pingIn: Svc.Ping + async input port pingIn: Svc.Ping drop - + @ Ping output port output port pingOut: Svc.Ping diff --git a/Svc/FileManager/FileManager.fpp b/Svc/FileManager/FileManager.fpp @@ -162,11 +162,11 @@ index bef777c71..d85f42002 100644 +++ b/Svc/FileManager/FileManager.fpp @@ -8,7 +8,7 @@ module Svc { # ---------------------------------------------------------------------- - + @ Ping input port - async input port pingIn: Svc.Ping + async input port pingIn: Svc.Ping drop - + @ Scheduler input port for rate group operations sync input port schedIn: Sched diff --git a/Svc/FileUplink/FileUplink.fpp b/Svc/FileUplink/FileUplink.fpp @@ -175,11 +175,11 @@ index 90ef4e9a8..b2b67bd5c 100644 +++ b/Svc/FileUplink/FileUplink.fpp @@ -14,7 +14,7 @@ module Svc { output port bufferSendOut: Fw.BufferSend - + @ Ping in - async input port pingIn: Svc.Ping + async input port pingIn: Svc.Ping drop - + @ Ping out output port pingOut: Svc.Ping diff --git a/Svc/FpySequencer/FpySequencer.fpp b/Svc/FpySequencer/FpySequencer.fpp @@ -187,12 +187,12 @@ index 8af40d3d7..b9e80e1af 100644 --- a/Svc/FpySequencer/FpySequencer.fpp +++ b/Svc/FpySequencer/FpySequencer.fpp @@ -38,7 +38,7 @@ module Svc { - + @ Ping in port # TODO should ping have highest prio? or lowest? - async input port pingIn: Svc.Ping priority 10 assert + async input port pingIn: Svc.Ping priority 10 drop - + @ port to trigger a wakeup or timeout check. increase frequency @ to increase temporal resolution of sequencer diff --git a/Svc/PrmDb/PrmDb.fpp b/Svc/PrmDb/PrmDb.fpp @@ -201,11 +201,11 @@ index 66f850e30..9588336e3 100644 +++ b/Svc/PrmDb/PrmDb.fpp @@ -60,7 +60,7 @@ module Svc { async input port setPrm: Fw.PrmSet - + @ Ping input port - async input port pingIn: Svc.Ping + async input port pingIn: Svc.Ping drop - + @ Ping output port output port pingOut: Svc.Ping diff --git a/Svc/TlmChan/TlmChan.fpp b/Svc/TlmChan/TlmChan.fpp @@ -214,18 +214,18 @@ index d15072ed0..52b73b2d8 100644 +++ b/Svc/TlmChan/TlmChan.fpp @@ -10,13 +10,13 @@ module Svc { guarded input port TlmGet: Fw.TlmGet - + @ Run port for starting packet send cycle - async input port Run: Svc.Sched + async input port Run: Svc.Sched drop - + @ Packet send port output port PktSend: Fw.Com - + @ Ping input port - async input port pingIn: Svc.Ping + async input port pingIn: Svc.Ping drop - + @ Ping output port output port pingOut: Svc.Ping diff --git a/Svc/TlmPacketizer/TlmPacketizer.fpp b/Svc/TlmPacketizer/TlmPacketizer.fpp @@ -234,17 +234,17 @@ index f03dbafff..91f45eb6f 100644 +++ b/Svc/TlmPacketizer/TlmPacketizer.fpp @@ -11,13 +11,13 @@ module Svc { output port PktSend: Fw.Com - + @ Ping input port - async input port pingIn: Svc.Ping + async input port pingIn: Svc.Ping drop - + @ Ping output port output port pingOut: Svc.Ping - + @ Run port for starting packet send cycle - async input port Run: Svc.Sched + async input port Run: Svc.Sched drop - + @ Telemetry input port sync input port TlmRecv: Fw.Tlm From 976d510c0dd62f4e4b6b6ee51197d51ec20b41f6 Mon Sep 17 00:00:00 2001 From: Michael Pham <61564344+Mikefly123@users.noreply.github.com> Date: Sun, 12 Jul 2026 11:29:26 -0700 Subject: [PATCH 37/51] fix(mcuboot): scope out the USP radio stack from the bootloader image MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CI's build-mcuboot failed: "undefined reference to z_impl_log_panic" linking modules/usp/.../smtc_modem_hal.c.obj into MCUboot. Root cause: the v5e board defconfig enables CONFIG_LORA_BASICS_MODEM_DRIVERS/ CONFIG_USP for the main application, but sysbuild's mcuboot child image shares the same board defconfig, so it inherits the radio stack too — and MCUboot's build has no CONFIG_LOG, so usp_zephyr's HAL (which calls log_panic()) fails to link. Adds bootloader/sysbuild/mcuboot.conf (sysbuild's per-image Kconfig fragment convention — confirmed against Zephyr's own samples/sysbuild/with_mcuboot/sysbuild/mcuboot.conf) disabling LORA_BASICS_MODEM_DRIVERS for the mcuboot image only; USP `depends on` it so this transitively disables USP too. MCUboot never needed the radio stack regardless of which driver backs it. Verified locally: `make build-mcuboot` now completes (mcuboot.elf/ mcuboot.uf2 produced, 86380 B / 8.27% flash — lean, no radio bloat), followed by `make build` and `make check-console-disabled`, matching CI's exact sequence. --- Makefile | 2 ++ bootloader/sysbuild/mcuboot.conf | 11 +++++++++++ 2 files changed, 13 insertions(+) create mode 100644 bootloader/sysbuild/mcuboot.conf diff --git a/Makefile b/Makefile index 2dee187b..45d00426 100644 --- a/Makefile +++ b/Makefile @@ -271,6 +271,8 @@ SYSBUILD_PATH ?= $(shell pwd)/lib/zephyr-workspace/zephyr/samples/sysbuild/with_ .PHONY: build-mcuboot build-mcuboot: submodules zephyr fprime-venv @cp $(shell pwd)/bootloader/sysbuild.conf $(SYSBUILD_PATH)/sysbuild.conf + @mkdir -p $(SYSBUILD_PATH)/sysbuild + @cp $(shell pwd)/bootloader/sysbuild/mcuboot.conf $(SYSBUILD_PATH)/sysbuild/mcuboot.conf $(UV_RUN) $(shell pwd)/tools/bin/build-with-proves $(SYSBUILD_PATH) --sysbuild mv $(shell pwd)/build/with_mcuboot/zephyr/zephyr.uf2 $(shell pwd)/mcuboot.uf2 diff --git a/bootloader/sysbuild/mcuboot.conf b/bootloader/sysbuild/mcuboot.conf new file mode 100644 index 00000000..e88a4c66 --- /dev/null +++ b/bootloader/sysbuild/mcuboot.conf @@ -0,0 +1,11 @@ +# MCUboot-image-specific Kconfig fragment (sysbuild convention: +# sysbuild/.conf). The v5e board defconfig enables the USP radio +# stack (CONFIG_LORA_BASICS_MODEM_DRIVERS / CONFIG_USP), which is correct +# for the main application image but leaks into MCUboot too since both +# images share the same board defconfig. MCUboot never needs the radio +# and its build has no CONFIG_LOG, so usp_zephyr's smtc_modem_hal.c +# (which calls log_panic()) fails to link: "undefined reference to +# z_impl_log_panic". Disable the radio stack for the bootloader image +# only; disabling LORA_BASICS_MODEM_DRIVERS transitively disables USP +# (USP `depends on LORA_BASICS_MODEM_DRIVERS`). +CONFIG_LORA_BASICS_MODEM_DRIVERS=n From d83cd91064baefdf1cab0112c08732f174c688cb Mon Sep 17 00:00:00 2001 From: Michael Pham <61564344+Mikefly123@users.noreply.github.com> Date: Sun, 12 Jul 2026 11:45:56 -0700 Subject: [PATCH 38/51] fix(test): update integration tests for the USP radio component name MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit main's PR #385 added radio_test.py and conftest.py radio helpers using ReferenceDeployment.lora.* — the legacy component name. v5e now always builds Zephyr::UspRadio (ReferenceDeployment.uspRadio), so those commands don't exist in the dictionary; the GDS calls raised inside a broad except-Exception retry loop, so the failure surfaced 30s later as an opaque "assert gds_working" / "assert False" rather than the real KeyError (visible in the *_uart job's non-fixture failure: "ReferenceDeployment.lora.TRANSMIT wasn't in the dictionary"). TRANSMIT and the SendFailed/ConfigurationFailed/AllocationFailed event names are kept verbatim on UspRadio.fpp (confirmed against the FPP source), so this is a pure component-name substitution, not a behavior change. lora_passthrough_test.py's own `lora` reference is left as-is — that test is unconditionally @pytest.mark.skip (debug-only) and never executes. --- .../test/int/conftest.py | 4 ++-- .../test/int/radio_test.py | 18 ++++++++++-------- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/PROVESFlightControllerReference/test/int/conftest.py b/PROVESFlightControllerReference/test/int/conftest.py index 550a9562..3febbc1b 100644 --- a/PROVESFlightControllerReference/test/int/conftest.py +++ b/PROVESFlightControllerReference/test/int/conftest.py @@ -147,7 +147,7 @@ def _enable_radio(fprime_test_api: IntegrationTestAPI) -> None: args=[20], ) fprime_test_api.send_command( - command="ReferenceDeployment.lora.TRANSMIT", args=["ENABLED"] + command="ReferenceDeployment.uspRadio.TRANSMIT", args=["ENABLED"] ) @@ -211,7 +211,7 @@ def stop_radio( return fprime_test_api_session.send_command( - command="ReferenceDeployment.lora.TRANSMIT", args=["DISABLED"] + command="ReferenceDeployment.uspRadio.TRANSMIT", args=["DISABLED"] ) diff --git a/PROVESFlightControllerReference/test/int/radio_test.py b/PROVESFlightControllerReference/test/int/radio_test.py index b5a86ed3..8ca3071d 100644 --- a/PROVESFlightControllerReference/test/int/radio_test.py +++ b/PROVESFlightControllerReference/test/int/radio_test.py @@ -15,9 +15,11 @@ pytestmark = [pytest.mark.uart_only] downlinkDelay = "ReferenceDeployment.downlinkDelay" -lora = "ReferenceDeployment.lora" +# v5e builds Zephyr::UspRadio (not the legacy Zephyr::LoRa component); TRANSMIT +# and the error/warning event names are kept verbatim on UspRadio.fpp. +radio = "ReferenceDeployment.uspRadio" -LORA_ERROR_EVENTS = ("SendFailed", "ConfigurationFailed", "AllocationFailed") +RADIO_ERROR_EVENTS = ("SendFailed", "ConfigurationFailed", "AllocationFailed") @pytest.fixture(autouse=True) @@ -31,25 +33,25 @@ def setup_test(fprime_test_api: IntegrationTestAPI, start_gds): yield proves_send_and_assert_command( fprime_test_api, - f"{lora}.TRANSMIT", + f"{radio}.TRANSMIT", ["DISABLED"], ) def test_01_transmit_enabled(fprime_test_api: IntegrationTestAPI, start_gds): - """Enabling transmit must not produce any LoRa error/warning events.""" + """Enabling transmit must not produce any radio error/warning events.""" start: TimeType = TimeType().set_datetime( datetime.now(), time_base=TimeType.TimeBase("TB_DONT_CARE") ) proves_send_and_assert_command( fprime_test_api, - f"{lora}.TRANSMIT", + f"{radio}.TRANSMIT", ["ENABLED"], ) time.sleep(10) - for evt in LORA_ERROR_EVENTS: - result = fprime_test_api.await_event(f"{lora}.{evt}", start=start, timeout=0) - assert result is None, f"Unexpected {lora}.{evt}: {result}" + for evt in RADIO_ERROR_EVENTS: + result = fprime_test_api.await_event(f"{radio}.{evt}", start=start, timeout=0) + assert result is None, f"Unexpected {radio}.{evt}: {result}" From 78390619447ea71725aff2ec3da39d651ffe52a8 Mon Sep 17 00:00:00 2001 From: Michael Pham <61564344+Mikefly123@users.noreply.github.com> Date: Sun, 12 Jul 2026 22:23:57 -0700 Subject: [PATCH 39/51] feat(radio): bump fprime-zephyr for UspRadio RADIOHEAD_COMPAT shim MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Pulls fprime-zephyr 05b3de5: UspRadio RADIOHEAD_COMPAT parameter (default true) restoring the 4-byte RadioHead header on the air for LoRa profiles — the missing legacy-interop shim that broke the integration-radio CI job against the adafruit_rfm9x passthrough board. Also: register test_RadioHeadShim in the host-UT target and add the RadioHead Header glossary entry to CONTEXT.md. Verified: make test-unit 7/7 suites green (incl. test_RadioHeadShim); full v5e make build green; dictionary carries uspRadio.RADIOHEAD_COMPAT (bool, default True) + PRM_SET/PRM_SAVE. Co-Authored-By: Claude Fable 5 --- CONTEXT.md | 1 + .../test/unit-tests/CMakeLists.txt | 13 +++++++++++++ lib/fprime-zephyr | 2 +- 3 files changed, 15 insertions(+), 1 deletion(-) diff --git a/CONTEXT.md b/CONTEXT.md index 44bad5ca..ded91985 100644 --- a/CONTEXT.md +++ b/CONTEXT.md @@ -28,3 +28,4 @@ Glossary for the flight-software radio domain. Terms here are canonical; use the ## Ground segment - **GRC (Ground Radio Controller)** — the station-local Zephyr radio box (SX1262) that terminates the RF link. Must consume the same Profile Table as flight. +- **RadioHead Header** — the 4-byte `[destination, source, identifier, flags]` prefix the RadioHead LoRa ecosystem (adafruit_rfm9x, legacy `Zephyr::LoRa`) puts on every LoRa packet. The USP Radio Path radiates raw F´ frames; the `RADIOHEAD_COMPAT` parameter on `UspRadio` (default: enabled) prepends/strips this header so USP boards interoperate with RadioHead peers such as the CI CircuitPython passthrough board. Both ends of a link must agree; GFSK profiles are always raw. diff --git a/PROVESFlightControllerReference/test/unit-tests/CMakeLists.txt b/PROVESFlightControllerReference/test/unit-tests/CMakeLists.txt index 02745fe8..86d1524c 100644 --- a/PROVESFlightControllerReference/test/unit-tests/CMakeLists.txt +++ b/PROVESFlightControllerReference/test/unit-tests/CMakeLists.txt @@ -37,6 +37,19 @@ target_compile_definitions(test_ProfilePolicy PRIVATE LINK_PROFILES_USE_HOST_TYP target_link_libraries(test_ProfilePolicy gtest_main) add_test(NAME test_ProfilePolicy COMMAND test_ProfilePolicy) +# --- UspRadio: RadioHeadShim host-side test (RadioHead-compat toggle) --- +# RadioHeadShim.hpp is header-only and free of F'/USP/Zephyr includes. +add_executable(test_RadioHeadShim + ${CMAKE_CURRENT_SOURCE_DIR}/../../../lib/fprime-zephyr/fprime-zephyr/Drv/UspRadio/test/ut/test_RadioHeadShim.cpp +) +target_include_directories(test_RadioHeadShim PRIVATE + # Exposes #include "fprime-zephyr/Drv/UspRadio/RadioHeadShim.hpp" + ${CMAKE_CURRENT_SOURCE_DIR}/../../../lib/fprime-zephyr +) +target_compile_definitions(test_RadioHeadShim PRIVATE LINK_PROFILES_USE_HOST_TYPES) +target_link_libraries(test_RadioHeadShim gtest_main) +add_test(NAME test_RadioHeadShim COMMAND test_RadioHeadShim) + # --- Helper Libraries --- # DetumbleManager Magnetorquer diff --git a/lib/fprime-zephyr b/lib/fprime-zephyr index 0046fbc4..05b3de53 160000 --- a/lib/fprime-zephyr +++ b/lib/fprime-zephyr @@ -1 +1 @@ -Subproject commit 0046fbc4d762fe8606498d9d7cab6846f38c7795 +Subproject commit 05b3de53147d9805625b6fc859f0bfb46ecc3ff6 From 2cb083c97fc4ebd408daacc62cd8e3f70778d2c5 Mon Sep 17 00:00:00 2001 From: Michael Pham <61564344+Mikefly123@users.noreply.github.com> Date: Sun, 19 Jul 2026 18:19:12 -0700 Subject: [PATCH 40/51] fix(radio): bump fprime-zephyr for RalSessionImpl -Wreorder fix Co-Authored-By: Claude Sonnet 5 --- lib/fprime-zephyr | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/fprime-zephyr b/lib/fprime-zephyr index 05b3de53..38cc5401 160000 --- a/lib/fprime-zephyr +++ b/lib/fprime-zephyr @@ -1 +1 @@ -Subproject commit 05b3de53147d9805625b6fc859f0bfb46ecc3ff6 +Subproject commit 38cc54019812d6a860ae188ba7ca892b725ac9cd From 33424fa226a355de95e970bc5305d730b1f2cd4f Mon Sep 17 00:00:00 2001 From: Michael Pham <61564344+Mikefly123@users.noreply.github.com> Date: Sun, 19 Jul 2026 18:35:14 -0700 Subject: [PATCH 41/51] revert(flash): drop CONFIG_IMG_MANAGER=n bench workaround, unrelated to radio work Reverts aa264fd's FlashWorker CONFIG_IMG_MANAGER guards and removes the now-unusable docs/bench/hwil-bench.conf.example (its whole purpose was to exercise that guard). Bench boards should be flashed with MCUBoot like flight boards rather than special-cased with a bare-metal flash workaround, and these changes were unrelated to the USP radio work in this branch. Verified: default v5e build (CONFIG_IMG_MANAGER=y) still compiles clean. Co-Authored-By: Claude Sonnet 5 --- .gitignore | 3 --- .../Components/FlashWorker/FlashWorker.cpp | 19 +--------------- .../Components/FlashWorker/FlashWorker.hpp | 4 ---- docs/bench/hwil-bench.conf.example | 22 ------------------- 4 files changed, 1 insertion(+), 47 deletions(-) delete mode 100644 docs/bench/hwil-bench.conf.example diff --git a/.gitignore b/.gitignore index b7ba6a4a..a61c1430 100644 --- a/.gitignore +++ b/.gitignore @@ -60,9 +60,6 @@ yamcs/yamcs-runtime/ /circuit-python-passthrough/lib/ /circuit-python-passthrough/tools/ -# HWIL bench overlay — local copy only, never commit (use docs/bench/hwil-bench.conf.example as source) -/hwil-bench.conf - # Phase 4 radio config symlinks (generated by CMake at configure time) PROVESFlightControllerReference/ReferenceDeployment/Top/RadioInstances.fppi PROVESFlightControllerReference/ReferenceDeployment/Top/RadioTopology.fppi diff --git a/PROVESFlightControllerReference/Components/FlashWorker/FlashWorker.cpp b/PROVESFlightControllerReference/Components/FlashWorker/FlashWorker.cpp index 0065155f..ef9fb0a6 100644 --- a/PROVESFlightControllerReference/Components/FlashWorker/FlashWorker.cpp +++ b/PROVESFlightControllerReference/Components/FlashWorker/FlashWorker.cpp @@ -8,10 +8,8 @@ #include "Os/File.hpp" #include "Os/Task.hpp" -#ifdef CONFIG_IMG_MANAGER #include #include -#endif namespace Components { // static_assert(FlashWorker::REGION_NUMBER == UPLOAD_FLASH_AREA_LABEL, @@ -29,7 +27,6 @@ FlashWorker ::~FlashWorker() {} // Flash helpers // ---------------------------------------------------------------------- -#ifdef CONFIG_IMG_MANAGER Update::UpdateStatus FlashWorker ::writeImage(const Fw::StringBase& file_name, Os::File& file, U32 expected_crc32) { const FwSizeType CHUNK = static_cast(sizeof(this->m_data)); FW_ASSERT(file.isOpen()); @@ -72,29 +69,21 @@ Update::UpdateStatus FlashWorker ::writeImage(const Fw::StringBase& file_name, O } return return_status; } -#endif // CONFIG_IMG_MANAGER // ---------------------------------------------------------------------- // Handler implementations for typed input ports // ---------------------------------------------------------------------- Update::UpdateStatus FlashWorker ::confirmImage_handler(FwIndexType portNum) { -#ifndef CONFIG_IMG_MANAGER - return Update::UpdateStatus::OP_OK; -#else int status = boot_write_img_confirmed(); if (status != 0) { this->log_WARNING_LO_ConfirmImageFailed(static_cast(-1 * status)); return Update::UpdateStatus::NEXT_BOOT_ERROR; } return Update::UpdateStatus::OP_OK; -#endif // CONFIG_IMG_MANAGER } Update::UpdateStatus FlashWorker ::nextBoot_handler(FwIndexType portNum, const Update::NextBootMode& mode) { -#ifndef CONFIG_IMG_MANAGER - return Update::UpdateStatus::OP_OK; -#else int permanent = (mode == Update::NextBootMode::PERMANENT) ? BOOT_UPGRADE_PERMANENT : BOOT_UPGRADE_TEST; int status = boot_request_upgrade(permanent); @@ -103,12 +92,10 @@ Update::UpdateStatus FlashWorker ::nextBoot_handler(FwIndexType portNum, const U return Update::UpdateStatus::NEXT_BOOT_ERROR; } return Update::UpdateStatus::OP_OK; -#endif // CONFIG_IMG_MANAGER } void FlashWorker ::prepareImage_handler(FwIndexType portNum) { Update::UpdateStatus return_status = Update::UpdateStatus::OP_OK; -#ifdef CONFIG_IMG_MANAGER int status = boot_erase_img_bank(FlashWorker::REGION_NUMBER); if (status != 0) { this->log_WARNING_LO_FlashEraseFailed(static_cast(-1 * status)); @@ -116,16 +103,13 @@ void FlashWorker ::prepareImage_handler(FwIndexType portNum) { } else { this->m_last_successful = PREPARE; } -#else - this->m_last_successful = PREPARE; -#endif // CONFIG_IMG_MANAGER this->prepareImageDone_out(0, return_status); } void FlashWorker ::updateImage_handler(FwIndexType portNum, const Fw::StringBase& file, U32 crc32) { Os::File image_file; Update::UpdateStatus return_status = Update::UpdateStatus::OP_OK; -#ifdef CONFIG_IMG_MANAGER + if (this->m_last_successful != PREPARE) { return_status = Update::UpdateStatus::UNPREPARED; this->m_last_successful = IDLE; @@ -141,7 +125,6 @@ void FlashWorker ::updateImage_handler(FwIndexType portNum, const Fw::StringBase this->log_WARNING_LO_ImageFileReadError(file, static_cast(file_status)); } } -#endif // CONFIG_IMG_MANAGER this->updateImageDone_out(0, return_status); } diff --git a/PROVESFlightControllerReference/Components/FlashWorker/FlashWorker.hpp b/PROVESFlightControllerReference/Components/FlashWorker/FlashWorker.hpp index f78367cb..657fdca2 100644 --- a/PROVESFlightControllerReference/Components/FlashWorker/FlashWorker.hpp +++ b/PROVESFlightControllerReference/Components/FlashWorker/FlashWorker.hpp @@ -8,9 +8,7 @@ #define Update_FlashWorker_HPP #include "Os/File.hpp" #include "PROVESFlightControllerReference/Components/FlashWorker/FlashWorkerComponentAc.hpp" -#ifdef CONFIG_IMG_MANAGER #include -#endif namespace Components { class FlashWorker final : public FlashWorkerComponentBase { @@ -60,10 +58,8 @@ class FlashWorker final : public FlashWorkerComponentBase { private: Step m_last_successful; -#ifdef CONFIG_IMG_MANAGER U8 m_data[CONFIG_IMG_BLOCK_BUF_SIZE]; struct flash_img_context m_flash_context; -#endif }; } // namespace Components diff --git a/docs/bench/hwil-bench.conf.example b/docs/bench/hwil-bench.conf.example deleted file mode 100644 index e3dd58fe..00000000 --- a/docs/bench/hwil-bench.conf.example +++ /dev/null @@ -1,22 +0,0 @@ -# BENCH-ONLY EXAMPLE — do NOT pass to a flight build. -# This file disables MCUBoot so you can flash a bare-metal image directly -# to 0x10000000 on boards that do not have MCUBoot pre-installed. -# -# How to use: -# cp docs/bench/hwil-bench.conf.example hwil-bench.conf -# fprime-util generate -f -- -DEXTRA_CONF_FILE=hwil-bench.conf -# -# The local copy (hwil-bench.conf at repo root) is listed in .gitignore -# and must never be committed or used in a flight build. - -CONFIG_BOOTLOADER_MCUBOOT=n -# Without MCUBoot we do not need the boot image management config. -CONFIG_MCUBOOT_BOOTUTIL_LIB=n -CONFIG_MCUBOOT_BOOTLOADER_MODE_SWAP_USING_OFFSET=n -CONFIG_MCUBOOT_BOOTLOADER_NO_DOWNGRADE=n -CONFIG_ROM_END_OFFSET=0 -CONFIG_MCUBOOT_UPDATE_FOOTER_SIZE=0 -CONFIG_USE_DT_CODE_PARTITION=n -CONFIG_FLASH_MAP=n -CONFIG_STREAM_FLASH=n -CONFIG_IMG_MANAGER=n From dcd0b612f727f0bf9a11b8a3bf741bde9b055e3e Mon Sep 17 00:00:00 2001 From: Michael Pham <61564344+Mikefly123@users.noreply.github.com> Date: Tue, 21 Jul 2026 00:28:47 -0700 Subject: [PATCH 42/51] fix(patches): refresh fprime-sched-tick-drop.patch context for fprime v4.2.2 TlmPacketizer/CmdSequencer/PrmDb hunks had stale context after the v4.2.2 upgrade (#398); regenerated with identical semantic edits (drop qualifiers). Co-Authored-By: Claude Fable 5 --- patches/fprime-sched-tick-drop.patch | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/patches/fprime-sched-tick-drop.patch b/patches/fprime-sched-tick-drop.patch index 70dbf16e..587de513 100644 --- a/patches/fprime-sched-tick-drop.patch +++ b/patches/fprime-sched-tick-drop.patch @@ -48,7 +48,7 @@ index 66343b886..2f7732896 100644 @ Ping output port output port pingOut: Svc.Ping diff --git a/Svc/CmdSequencer/CmdSequencer.fpp b/Svc/CmdSequencer/CmdSequencer.fpp -index 1573fbe60..614fefd5f 100644 +index f1c401278..ebc381653 100644 --- a/Svc/CmdSequencer/CmdSequencer.fpp +++ b/Svc/CmdSequencer/CmdSequencer.fpp @@ -68,7 +68,7 @@ module Svc { @@ -60,7 +60,7 @@ index 1573fbe60..614fefd5f 100644 @ Ping out port output port pingOut: Svc.Ping -@@ -83,7 +83,7 @@ module Svc { +@@ -86,7 +86,7 @@ module Svc { output port comCmdOut: Fw.Com @ Schedule in port @@ -196,10 +196,10 @@ index 8af40d3d7..b9e80e1af 100644 @ port to trigger a wakeup or timeout check. increase frequency @ to increase temporal resolution of sequencer diff --git a/Svc/PrmDb/PrmDb.fpp b/Svc/PrmDb/PrmDb.fpp -index 66f850e30..9588336e3 100644 +index bca4f172f..b2a4b95b1 100644 --- a/Svc/PrmDb/PrmDb.fpp +++ b/Svc/PrmDb/PrmDb.fpp -@@ -60,7 +60,7 @@ module Svc { +@@ -69,7 +69,7 @@ module Svc { async input port setPrm: Fw.PrmSet @ Ping input port @@ -229,11 +229,11 @@ index d15072ed0..52b73b2d8 100644 @ Ping output port output port pingOut: Svc.Ping diff --git a/Svc/TlmPacketizer/TlmPacketizer.fpp b/Svc/TlmPacketizer/TlmPacketizer.fpp -index f03dbafff..91f45eb6f 100644 +index 1655c3e2e..4125fdac9 100644 --- a/Svc/TlmPacketizer/TlmPacketizer.fpp +++ b/Svc/TlmPacketizer/TlmPacketizer.fpp -@@ -11,13 +11,13 @@ module Svc { - output port PktSend: Fw.Com +@@ -28,13 +28,13 @@ module Svc { + async input port controlIn: EnableSection @ Ping input port - async input port pingIn: Svc.Ping @@ -246,5 +246,5 @@ index f03dbafff..91f45eb6f 100644 - async input port Run: Svc.Sched + async input port Run: Svc.Sched drop - @ Telemetry input port - sync input port TlmRecv: Fw.Tlm + @ Input configuration port + async input port configureSectionGroupRate: ConfigureGroupRate From f46d5334437129c56670b69539818dce814c127d Mon Sep 17 00:00:00 2001 From: Michael Pham <61564344+Mikefly123@users.noreply.github.com> Date: Tue, 21 Jul 2026 00:30:02 -0700 Subject: [PATCH 43/51] chore(zephyr-patches): drop obsolete 0004 CDC-ACM enable-drain patch Zephyr 4.4.1 (merged from main, #421) drains a non-empty tx_fifo on class enable unconditionally, superseding the poll-mode drain patch. 0005/0007 still apply cleanly and remain. Co-Authored-By: Claude Fable 5 --- Makefile | 1 - ...m-poll-mode-tx-drain-on-class-enable.patch | 30 ------------------- 2 files changed, 31 deletions(-) delete mode 100644 patches/0004-fix-usbd-cdc-acm-poll-mode-tx-drain-on-class-enable.patch diff --git a/Makefile b/Makefile index 41c9b03b..05df403b 100644 --- a/Makefile +++ b/Makefile @@ -130,7 +130,6 @@ zephyr-patches: ## Apply Zephyr tree patches (CDC-ACM TX fixes) @echo "Applying Zephyr patches..." @cd "$(ZEPHYR_DIR)" && \ for p in \ - $(shell pwd)/patches/0004-fix-usbd-cdc-acm-poll-mode-tx-drain-on-class-enable.patch \ $(shell pwd)/patches/0005-fix-usbd-cdc-acm-stuck-tx-fifo-busy-on-disable-and-retry.patch \ $(shell pwd)/patches/0007-fix-usbd-cdc-acm-bound-poll-out-backpressure-wait.patch; do \ name=$$(basename $$p); \ diff --git a/patches/0004-fix-usbd-cdc-acm-poll-mode-tx-drain-on-class-enable.patch b/patches/0004-fix-usbd-cdc-acm-poll-mode-tx-drain-on-class-enable.patch deleted file mode 100644 index 5b1b8f3b..00000000 --- a/patches/0004-fix-usbd-cdc-acm-poll-mode-tx-drain-on-class-enable.patch +++ /dev/null @@ -1,30 +0,0 @@ -From 0000000000000000000000000000000000000001 Mon Sep 17 00:00:00 2001 -From: Michael Pham -Date: Sat, 5 Jul 2026 00:00:00 -0700 -Subject: [PATCH] fix(usbd_cdc_acm): drain TX FIFO on class enable in poll mode - -When the USB CDC-ACM class is enabled and the driver is used in poll mode -(IRQ TX not enabled), any data written to the TX FIFO before the class was -enabled is silently lost. Fix: schedule tx_fifo_work in the else-if branch. - -Root cause on FCB v5e: F Prime ComCcsdsUart writes to FIFO during early boot -before USB host enumerates; class enables later; poll-mode never re-drains. - -Signed-off-by: Michael Pham ---- -diff --git a/subsys/usb/device_next/class/usbd_cdc_acm.c b/subsys/usb/device_next/class/usbd_cdc_acm.c -index d3921c0f146..a8241ad130e 100644 ---- a/subsys/usb/device_next/class/usbd_cdc_acm.c -+++ b/subsys/usb/device_next/class/usbd_cdc_acm.c -@@ -368,6 +368,9 @@ static void usbd_cdc_acm_enable(struct usbd_class_data *const c_data) - /* Queue pending TX data on IN endpoint */ - cdc_acm_work_schedule(&data->tx_fifo_work, K_NO_WAIT); - } -+ } else if (!ring_buf_is_empty(data->tx_fifo.rb)) { -+ /* Poll-mode TX: drain any data buffered before the class was enabled */ -+ cdc_acm_work_schedule(&data->tx_fifo_work, K_NO_WAIT); - } - } - --- -2.39.3 (Apple Git-146) From c896de2703d17b7097d41da85f8c62faf0f12234 Mon Sep 17 00:00:00 2001 From: Michael Pham <61564344+Mikefly123@users.noreply.github.com> Date: Tue, 21 Jul 2026 00:40:37 -0700 Subject: [PATCH 44/51] fix(usp-patches): carry board.yml full_name fix for Zephyr 4.4 schema Zephyr 4.4.1 (from main's #421) rejects usp_zephyr's vendored xiao_nrf54l15 board.yml (missing full_name), which breaks board discovery for every board including v5e. Added carried patch 0010 and wired it into make usp-patches. Co-Authored-By: Claude Fable 5 --- Makefile | 3 ++- ...nrf54l15-full_name-zephyr-4.4-schema.patch | 24 +++++++++++++++++++ 2 files changed, 26 insertions(+), 1 deletion(-) create mode 100644 patches/0010-fix-boards-xiao-nrf54l15-full_name-zephyr-4.4-schema.patch diff --git a/Makefile b/Makefile index 05df403b..f17f88c7 100644 --- a/Makefile +++ b/Makefile @@ -91,7 +91,8 @@ usp-patches: ## Apply usp_zephyr patches (RF-switch GPIO + Zephyr 4.3 compat + w $(shell pwd)/patches/0002-fix-zephyr-4.3-remove-select-ZEPHYR_LORA_BASICS_MODE.patch \ $(shell pwd)/patches/0003-fix-usp-main-2025-fix-LR_FHSS_SRC_PATH-for-flattened.patch \ $(shell pwd)/patches/0006-fix-sx126x-wakeup-busy-race-add-t_woff-settle-delay.patch \ - $(shell pwd)/patches/0008-fix-smtc-modem-hal-implement-rac-api-mutex.patch; do \ + $(shell pwd)/patches/0008-fix-smtc-modem-hal-implement-rac-api-mutex.patch \ + $(shell pwd)/patches/0010-fix-boards-xiao-nrf54l15-full_name-zephyr-4.4-schema.patch; do \ name=$$(basename $$p); \ if git apply --check "$$p" 2>/dev/null; then \ git apply "$$p" && echo "✓ Applied $$name"; \ diff --git a/patches/0010-fix-boards-xiao-nrf54l15-full_name-zephyr-4.4-schema.patch b/patches/0010-fix-boards-xiao-nrf54l15-full_name-zephyr-4.4-schema.patch new file mode 100644 index 00000000..5b69c3c0 --- /dev/null +++ b/patches/0010-fix-boards-xiao-nrf54l15-full_name-zephyr-4.4-schema.patch @@ -0,0 +1,24 @@ +From 0000000000000000000000000000000000000010 Mon Sep 17 00:00:00 2001 +From: Michael Pham +Date: Tue, 21 Jul 2026 00:00:00 -0700 +Subject: [PATCH] fix(boards): add full_name to xiao_nrf54l15 board.yml for Zephyr 4.4 schema + +Zephyr 4.4 board-schema.yaml requires name+full_name (or extend). The +vendored xiao_nrf54l15 board.yml predates this; matches the in-tree +Zephyr 4.4.1 board metadata. + +Signed-off-by: Michael Pham +--- +diff --git a/boards/seeed/xiao_nrf54l15/board.yml b/boards/seeed/xiao_nrf54l15/board.yml +index 9ffc64f..8641807 100644 +--- a/boards/seeed/xiao_nrf54l15/board.yml ++++ b/boards/seeed/xiao_nrf54l15/board.yml +@@ -1,5 +1,6 @@ + board: + name: xiao_nrf54l15 ++ full_name: XIAO NRF54L15 + vendor: seeed + socs: + - name: nrf54l15 +-- +2.39.3 (Apple Git-146) From 220d357d618aa8e8ba76d03c7cad8f8e21c1d728 Mon Sep 17 00:00:00 2001 From: Michael Pham <61564344+Mikefly123@users.noreply.github.com> Date: Tue, 21 Jul 2026 00:47:44 -0700 Subject: [PATCH 45/51] chore: bump fprime-zephyr to main-merged feat/usp-radio (fprime v4.2.2 compat) --- lib/fprime-zephyr | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/fprime-zephyr b/lib/fprime-zephyr index 38cc5401..e1d430ea 160000 --- a/lib/fprime-zephyr +++ b/lib/fprime-zephyr @@ -1 +1 @@ -Subproject commit 38cc54019812d6a860ae188ba7ca892b725ac9cd +Subproject commit e1d430eac1c07543e4e4323c93a844e091c88fe1 From 2b997f8dc22c8b149c8508a6c7c373f3161ba297 Mon Sep 17 00:00:00 2001 From: Michael Pham <61564344+Mikefly123@users.noreply.github.com> Date: Tue, 21 Jul 2026 09:50:23 -0700 Subject: [PATCH 46/51] fix(boot): shrink mbedTLS static heap 32K->16K to un-starve libc malloc arena DEFINITE FIX for CI run 29840066916 (integration-uart + integration-radio total command silence). Root cause, HWIL-verified on bench v5e: After merging main (fprime v4.2.2 / Zephyr 4.4.1 / TcSecurityDeframer), startup heap demand exceeded the libc malloc arena by ~1.8 KiB. The last BufferManager to configure, payloadBufferManager.setup (mgrId=1, 8384 B), got NULL from Fw::MallocAllocator -> FW_ASSERT at Svc/BufferManager/BufferManagerComponentImpl.cpp:163 -> FATAL AF_ASSERT_5 (0x01005005) -> FatalHandler::reboot() -> ~4 s boot loop (MCUboot banner spam, one 16-byte TM burst per cycle, zero commanding). Heap forensics at the assert: arena 156,888 B total, 150,280 used, 6,608 free vs 8,384 requested. Freeing 16 KiB of static mbedTLS heap (HMAC-SHA256 needs nowhere near 32 KiB) restores comfortable margin. Verified on bench v5e (CI-identical mcuboot.elf + bootable.signed.hex flash): pre-fix reproduces the CI loop exactly; post-fix boots stable, 0 resets in 45 s, continuous TM downlink. Co-Authored-By: Claude Fable 5 --- prj.conf | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/prj.conf b/prj.conf index b70bc5c5..0fa94842 100644 --- a/prj.conf +++ b/prj.conf @@ -84,7 +84,12 @@ CONFIG_MBEDTLS=y CONFIG_MBEDTLS_BUILTIN=y CONFIG_MBEDTLS_PSA_CRYPTO_C=y CONFIG_MBEDTLS_ENABLE_HEAP=y -CONFIG_MBEDTLS_HEAP_SIZE=32767 +# 16 KiB is ample for PSA HMAC-SHA256 (TcSecurityDeframer); the previous +# 32 KiB static heap left the libc malloc arena ~2 KiB short after the +# fprime v4.2.2 / Zephyr 4.4.1 merge, so payloadBufferManager.setup() +# FW_ASSERTed at boot (BufferManagerComponentImpl.cpp:163, allocator NULL) +# and FatalHandler reboot-looped the board (CI run 29840066916). +CONFIG_MBEDTLS_HEAP_SIZE=16384 CONFIG_MBEDTLS_SSL_MAX_CONTENT_LEN=4096 # HMAC-SHA256 for TcSecurityDeframer packet authentication CONFIG_PSA_WANT_KEY_TYPE_HMAC=y From 71ceaf350122d4b1240b49088ad1db1f36c86f03 Mon Sep 17 00:00:00 2001 From: Michael Pham <61564344+Mikefly123@users.noreply.github.com> Date: Sun, 26 Jul 2026 16:52:22 -0700 Subject: [PATCH 47/51] chore(radio): collapse carried patches into OSSF fork integration pins (#477) --- .github/workflows/ci.yaml | 9 - .gitmodules | 4 +- .pre-commit-config.yaml | 5 +- Makefile | 92 +------ lib/fprime | 2 +- lib/fprime-zephyr | 2 +- lib/zephyr-workspace/zephyr | 2 +- ...external-RF-switch-GPIO-support-tx-r.patch | 251 ------------------ ...emove-select-ZEPHYR_LORA_BASICS_MODE.patch | 36 --- ...5-fix-LR_FHSS_SRC_PATH-for-flattened.patch | 34 --- ...ck-tx-fifo-busy-on-disable-and-retry.patch | 65 ----- ...up-busy-race-add-t_woff-settle-delay.patch | 58 ---- ...acm-bound-poll-out-backpressure-wait.patch | 36 --- ...tc-modem-hal-implement-rac-api-mutex.patch | 39 --- ...-failsafe-exempt-unlock-radio-access.patch | 20 -- ...nrf54l15-full_name-zephyr-4.4-schema.patch | 24 -- patches/README.md | 61 ++--- ...prime-com-aggregator-bounded-timeout.patch | 35 --- patches/fprime-sched-tick-drop.patch | 250 ----------------- west.yml | 24 +- 20 files changed, 51 insertions(+), 998 deletions(-) delete mode 100644 patches/0001-feat-sx126x-add-external-RF-switch-GPIO-support-tx-r.patch delete mode 100644 patches/0002-fix-zephyr-4.3-remove-select-ZEPHYR_LORA_BASICS_MODE.patch delete mode 100644 patches/0003-fix-usp-main-2025-fix-LR_FHSS_SRC_PATH-for-flattened.patch delete mode 100644 patches/0005-fix-usbd-cdc-acm-stuck-tx-fifo-busy-on-disable-and-retry.patch delete mode 100644 patches/0006-fix-sx126x-wakeup-busy-race-add-t_woff-settle-delay.patch delete mode 100644 patches/0007-fix-usbd-cdc-acm-bound-poll-out-backpressure-wait.patch delete mode 100644 patches/0008-fix-smtc-modem-hal-implement-rac-api-mutex.patch delete mode 100644 patches/0009-fix-radio-planner-failsafe-exempt-unlock-radio-access.patch delete mode 100644 patches/0010-fix-boards-xiao-nrf54l15-full_name-zephyr-4.4-schema.patch delete mode 100644 patches/fprime-com-aggregator-bounded-timeout.patch delete mode 100644 patches/fprime-sched-tick-drop.patch diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 46b5cec9..335bd032 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -64,15 +64,6 @@ jobs: run: | make zephyr-workspace - - name: Apply carried patches - # west update fetches vanilla usp_zephyr/usp/zephyr modules; the USP - # radio port needs the RF-switch GPIO, Zephyr 4.3 compat, wakeup-race, - # radio-planner failsafe, and CDC-ACM fixes carried in patches/. - run: | - make usp-patches - make usp-core-patches - make zephyr-patches - - name: Setup Zephyr SDK if: steps.cache-zephyr-sdk.outputs.cache-hit != 'true' run: | diff --git a/.gitmodules b/.gitmodules index 5ea7e8a5..f3ae0fef 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,9 +1,9 @@ [submodule "lib/fprime"] path = lib/fprime - url = https://github.com/nasa/fprime.git + 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 [submodule "lib/fprime-zephyr"] path = lib/fprime-zephyr url = https://github.com/Open-Source-Space-Foundation/fprime-zephyr.git diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 3e51e6d9..a405304c 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -3,11 +3,10 @@ repos: rev: v5.0.0 hooks: - id: trailing-whitespace - # patches/*.patch are machine-generated diffs applied via `git apply`; + # patches/*.patch are machine-generated diffs applied via patch tooling; # their context/removed lines must byte-match the real upstream files, # which can legitimately have trailing whitespace. Stripping it here - # silently breaks patch application (see: 0001-feat-sx126x patch vs - # usp_zephyr's semtech,sx126x-new-common.yaml). + # silently breaks patch application. exclude: ^patches/ - id: end-of-file-fixer exclude: ^patches/ diff --git a/Makefile b/Makefile index f17f88c7..790f982e 100644 --- a/Makefile +++ b/Makefile @@ -11,28 +11,6 @@ help: ## Display this help. submodules: ## Initialize and update git submodules @git submodule foreach --recursive 'git checkout -- . && git clean -fd' || true @git submodule update --init --recursive - @echo "Applying fprime ComAggregator bounded-timeout patch (issue #432)..." - @cd lib/fprime && \ - if git apply --check ../../patches/fprime-com-aggregator-bounded-timeout.patch 2>/dev/null; then \ - git apply ../../patches/fprime-com-aggregator-bounded-timeout.patch && \ - echo "✓ Applied ComAggregator bounded-timeout patch"; \ - elif git apply --reverse --check ../../patches/fprime-com-aggregator-bounded-timeout.patch 2>/dev/null; then \ - echo "⚠ Patch already applied"; \ - else \ - echo "❌ Error: Unable to apply ComAggregator patch. Run 'cd lib/fprime && git status' to check."; \ - exit 1; \ - fi - @echo "Applying fprime sched-tick drop patch (issue #432 class)..." - @cd lib/fprime && \ - if git apply --check ../../patches/fprime-sched-tick-drop.patch 2>/dev/null; then \ - git apply ../../patches/fprime-sched-tick-drop.patch && \ - echo "✓ Applied sched-tick drop patch"; \ - elif git apply --reverse --check ../../patches/fprime-sched-tick-drop.patch 2>/dev/null; then \ - echo "⚠ Patch already applied"; \ - else \ - echo "❌ Error: Unable to apply sched-tick drop patch. Run 'cd lib/fprime && git status' to check."; \ - exit 1; \ - fi export VIRTUAL_ENV ?= $(shell pwd)/fprime-venv .PHONY: fprime-venv @@ -76,72 +54,10 @@ zephyr-setup: fprime-venv ## Set up Zephyr environment $(UV) pip install --prerelease=allow -r lib/zephyr-workspace/bootloader/mcuboot/zephyr/requirements.txt; \ } -# USP_ZEPHYR_DIR: west places usp_zephyr at this path (see west.yml). -USP_ZEPHYR_DIR ?= $(shell pwd)/lib/zephyr-workspace/modules/lib/usp_zephyr - -.PHONY: usp-patches -usp-patches: ## Apply usp_zephyr patches (RF-switch GPIO + Zephyr 4.3 compat + wakeup-busy race fix) - @if [ ! -d "$(USP_ZEPHYR_DIR)" ]; then \ - echo "❌ usp_zephyr not found at $(USP_ZEPHYR_DIR) — run 'west update usp_zephyr usp' first"; \ - exit 1; \ - fi - @echo "Applying usp_zephyr patches..." - @cd "$(USP_ZEPHYR_DIR)" && \ - for p in $(shell pwd)/patches/0001-feat-sx126x-add-external-RF-switch-GPIO-support-tx-r.patch \ - $(shell pwd)/patches/0002-fix-zephyr-4.3-remove-select-ZEPHYR_LORA_BASICS_MODE.patch \ - $(shell pwd)/patches/0003-fix-usp-main-2025-fix-LR_FHSS_SRC_PATH-for-flattened.patch \ - $(shell pwd)/patches/0006-fix-sx126x-wakeup-busy-race-add-t_woff-settle-delay.patch \ - $(shell pwd)/patches/0008-fix-smtc-modem-hal-implement-rac-api-mutex.patch \ - $(shell pwd)/patches/0010-fix-boards-xiao-nrf54l15-full_name-zephyr-4.4-schema.patch; do \ - name=$$(basename $$p); \ - if git apply --check "$$p" 2>/dev/null; then \ - git apply "$$p" && echo "✓ Applied $$name"; \ - elif git apply --reverse --check "$$p" 2>/dev/null; then \ - echo "⚠ Already applied: $$name"; \ - else \ - echo "❌ Cannot apply $$name — check usp_zephyr revision"; exit 1; \ - fi; \ - done - -# USP_DIR: the Semtech smtc_rac_lib west module (radio planner lives here). -USP_DIR ?= $(shell pwd)/lib/zephyr-workspace/modules/lib/usp - -.PHONY: usp-core-patches -usp-core-patches: ## Apply usp (smtc_rac_lib) patches (radio-planner failsafe unlock exemption) - @cd "$(USP_DIR)" && \ - for p in $(shell pwd)/patches/0009-fix-radio-planner-failsafe-exempt-unlock-radio-access.patch; do \ - name=$$(basename $$p); \ - if git apply --check "$$p" 2>/dev/null; then \ - git apply "$$p" && echo "✓ Applied $$name"; \ - elif git apply --reverse --check "$$p" 2>/dev/null; then \ - echo "⚠ Already applied: $$name"; \ - else \ - echo "❌ Cannot apply $$name — check usp revision"; exit 1; \ - fi; \ - done - -ZEPHYR_DIR ?= $(shell pwd)/lib/zephyr-workspace/zephyr - -.PHONY: zephyr-patches -zephyr-patches: ## Apply Zephyr tree patches (CDC-ACM TX fixes) - @if [ ! -d "$(ZEPHYR_DIR)" ]; then \ - echo "zephyr not found at $(ZEPHYR_DIR) — run 'west update' first"; \ - exit 1; \ - fi - @echo "Applying Zephyr patches..." - @cd "$(ZEPHYR_DIR)" && \ - for p in \ - $(shell pwd)/patches/0005-fix-usbd-cdc-acm-stuck-tx-fifo-busy-on-disable-and-retry.patch \ - $(shell pwd)/patches/0007-fix-usbd-cdc-acm-bound-poll-out-backpressure-wait.patch; do \ - name=$$(basename $$p); \ - if git apply --check "$$p" 2>/dev/null; then \ - git apply "$$p" && echo "OK Applied $$name"; \ - elif git apply --reverse --check "$$p" 2>/dev/null; then \ - echo "Already applied: $$name"; \ - else \ - echo "Cannot apply $$name — check Zephyr revision"; exit 1; \ - fi; \ - done +# Carried module patches (usp-patches / usp-core-patches / zephyr-patches and +# the fprime patch steps in `submodules`) were removed 2026-07-26: all module +# fixes now live on the Open-Source-Space-Foundation fork integration branches +# (feat/proves-usp-radio) pinned in west.yml / .gitmodules. See patches/README.md. ##@ Development diff --git a/lib/fprime b/lib/fprime index 8a62e455..baf163f3 160000 --- a/lib/fprime +++ b/lib/fprime @@ -1 +1 @@ -Subproject commit 8a62e455a90b6d4f498c332d45d65a2a819988d8 +Subproject commit baf163f3ba52ecfabaa39b4aa5847a3cecfb2ae6 diff --git a/lib/fprime-zephyr b/lib/fprime-zephyr index c997272b..c81485ff 160000 --- a/lib/fprime-zephyr +++ b/lib/fprime-zephyr @@ -1 +1 @@ -Subproject commit c997272b4a1e404cb7a44071a17fb6dff1b535f4 +Subproject commit c81485ff8c73a76772ae85f3b388a2d57d0ec9fc diff --git a/lib/zephyr-workspace/zephyr b/lib/zephyr-workspace/zephyr index 1f6485ec..3838a280 160000 --- a/lib/zephyr-workspace/zephyr +++ b/lib/zephyr-workspace/zephyr @@ -1 +1 @@ -Subproject commit 1f6485eca25431b5ff27ce9a754218c9e559bbbb +Subproject commit 3838a2802c916accdfa671de77633ee45b69441c diff --git a/patches/0001-feat-sx126x-add-external-RF-switch-GPIO-support-tx-r.patch b/patches/0001-feat-sx126x-add-external-RF-switch-GPIO-support-tx-r.patch deleted file mode 100644 index 1a25b7b0..00000000 --- a/patches/0001-feat-sx126x-add-external-RF-switch-GPIO-support-tx-r.patch +++ /dev/null @@ -1,251 +0,0 @@ -From a23856a670226bb4e3e83acfc07106e47866c118 Mon Sep 17 00:00:00 2001 -From: Michael Pham <61564344+Mikefly123@users.noreply.github.com> -Date: Sat, 4 Jul 2026 09:59:41 -0700 -Subject: [PATCH 1/3] feat(sx126x): add external RF-switch GPIO support - (tx/rx-enable-gpios) - -The EBYTE E22-400M30S (SX1262) module drives its RF switch via two -dedicated GPIOs (TX-EN, RX-EN) rather than DIO2, so the existing -dio2-as-rf-switch mechanism is unusable on PROVES FCB v5e. - -Changes: -- dts/bindings/usp/semtech,sx126x-new-common.yaml: add optional - tx-enable-gpios and rx-enable-gpios phandle-array properties with - full description of operating-mode semantics. -- drivers/usp/sx126x/sx126x_hal_context.h: add tx_enable and - rx_enable gpio_dt_spec fields to sx126x_hal_context_cfg_t (both - zero-initialised / .port==NULL when absent in DT, so boards without - the properties compile and behave identically to before). -- drivers/usp/sx126x/sx126x_board.c: configure both pins as - OUTPUT_INACTIVE at init; wire them into SX126X_CONFIG via the - existing CONFIGURE_GPIO_IF_IN_DT helper. -- drivers/usp/sx126x/sx126x_hal.c: add sx126x_hal_update_rf_switch() - that intercepts the first byte of every sx126x_hal_write() command - buffer and drives the GPIOs before the SPI transaction: - TX-class (TX-EN=1, RX-EN=0): 0x83 SetTx, 0xD1 SetTxContinuousWave, - 0xD2 SetTxInfinitePreamble - RX-class (TX-EN=0, RX-EN=1): 0x82 SetRx, 0x94 SetRxDutyCycle, - 0xC5 SetCad - Inactive (TX-EN=0, RX-EN=0): 0x84 SetSleep, 0x80 SetStandby - All other opcodes leave switch state unchanged. - Deactivation of the leaving path always precedes activation of the - entering path to prevent simultaneous PA+LNA enable. - -Boards without tx-enable-gpios / rx-enable-gpios in DT are unaffected: -gpio_dt_spec.port is NULL and all branches are skipped at runtime. -Build verified: zephyr.elf + zephyr.uf2 compile clean on Zephyr 4.3 / -RP2350 with FLASH 104760 B / RAM 31564 B (ping_pong sample). - -Co-Authored-By: Claude Fable 5 ---- - drivers/usp/sx126x/sx126x_board.c | 22 +++++ - drivers/usp/sx126x/sx126x_hal.c | 87 +++++++++++++++++++ - drivers/usp/sx126x/sx126x_hal_context.h | 10 +++ - .../usp/semtech,sx126x-new-common.yaml | 26 +++++- - 4 files changed, 144 insertions(+), 1 deletion(-) - -diff --git a/drivers/usp/sx126x/sx126x_board.c b/drivers/usp/sx126x/sx126x_board.c -index 215801a..d119aba 100644 ---- a/drivers/usp/sx126x/sx126x_board.c -+++ b/drivers/usp/sx126x/sx126x_board.c -@@ -239,6 +239,26 @@ static int sx126x_init( const struct device* dev ) - } - } - -+ /* External RF-switch GPIOs — configure as outputs, inactive (both paths off) */ -+ if( config->tx_enable.port ) -+ { -+ ret = gpio_pin_configure_dt( &config->tx_enable, GPIO_OUTPUT_INACTIVE ); -+ if( ret < 0 ) -+ { -+ LOG_ERR( "Could not configure tx-enable gpio" ); -+ return ret; -+ } -+ } -+ if( config->rx_enable.port ) -+ { -+ ret = gpio_pin_configure_dt( &config->rx_enable, GPIO_OUTPUT_INACTIVE ); -+ if( ret < 0 ) -+ { -+ LOG_ERR( "Could not configure rx-enable gpio" ); -+ return ret; -+ } -+ } -+ - data->radio_status = RADIO_AWAKE; - data->tx_power_offset_db_current = config->tx_power_offset_db; - -@@ -366,6 +386,8 @@ static int sx126x_pm_action( const struct device* dev, enum pm_device_action act - CONFIGURE_GPIO_IF_IN_DT( node_id, dio1, dio1_gpios ) CONFIGURE_GPIO_IF_IN_DT( node_id, dio2, dio2_gpios ) \ - CONFIGURE_GPIO_IF_IN_DT( node_id, dio3, dio3_gpios ) \ - .dio2_as_rf_switch = DT_PROP( node_id, dio2_as_rf_switch ), \ -+ CONFIGURE_GPIO_IF_IN_DT( node_id, tx_enable, tx_enable_gpios ) \ -+ CONFIGURE_GPIO_IF_IN_DT( node_id, rx_enable, rx_enable_gpios ) \ - SX126X_CFG_TCXO( node_id ), .capa_xta = DT_PROP_OR( node_id, xtal_capacitor_value_xta, 0xFF ), \ - .capa_xtb = DT_PROP_OR( node_id, xtal_capacitor_value_xtb, 0xFF ), .reg_mode = DT_PROP( node_id, reg_mode ), \ - .tx_power_offset_db = DT_PROP_OR( node_id, tx_power_offset, 0 ), \ -diff --git a/drivers/usp/sx126x/sx126x_hal.c b/drivers/usp/sx126x/sx126x_hal.c -index a3215cc..1530077 100644 ---- a/drivers/usp/sx126x/sx126x_hal.c -+++ b/drivers/usp/sx126x/sx126x_hal.c -@@ -111,6 +111,86 @@ static void sx126x_hal_check_device_ready( const void* context ) - * --- PUBLIC FUNCTIONS DEFINITION --------------------------------------------- - */ - -+/* -+ * External RF-switch toggle helper. -+ * -+ * Called in sx126x_hal_write() before each SPI opcode transaction so the -+ * TX-EN / RX-EN lines track the radio mode without requiring a mode-callback -+ * hook (USP's SX126x HAL layer has none). -+ * -+ * Opcode table (SX126x datasheet §13.1): -+ * TX-class (TX-EN=1, RX-EN=0): -+ * 0x83 SetTx -+ * 0xD1 SetTxContinuousWave -+ * 0xD2 SetTxInfinitePreamble -+ * RX-class (TX-EN=0, RX-EN=1): -+ * 0x82 SetRx -+ * 0x94 SetRxDutyCycle -+ * 0xC5 SetCad -+ * Inactive (TX-EN=0, RX-EN=0): -+ * 0x84 SetSleep -+ * 0x80 SetStandby -+ * All other opcodes leave the switch state unchanged. -+ */ -+static void sx126x_hal_update_rf_switch( const struct sx126x_hal_context_cfg_t* config, uint8_t opcode ) -+{ -+ bool tx_active; -+ bool rx_active; -+ -+ switch( opcode ) -+ { -+ case 0x83: /* SetTx */ -+ case 0xD1: /* SetTxContinuousWave */ -+ case 0xD2: /* SetTxInfinitePreamble */ -+ tx_active = true; -+ rx_active = false; -+ break; -+ -+ case 0x82: /* SetRx */ -+ case 0x94: /* SetRxDutyCycle */ -+ case 0xC5: /* SetCad */ -+ tx_active = false; -+ rx_active = true; -+ break; -+ -+ case 0x84: /* SetSleep */ -+ case 0x80: /* SetStandby */ -+ tx_active = false; -+ rx_active = false; -+ break; -+ -+ default: -+ /* No switch change for config/status opcodes */ -+ return; -+ } -+ -+ /* Deassert the path we are leaving before asserting the new one to -+ * avoid momentarily enabling both PA and LNA simultaneously. -+ */ -+ if( config->tx_enable.port ) -+ { -+ if( !tx_active ) -+ { -+ gpio_pin_set_dt( &config->tx_enable, 0 ); -+ } -+ } -+ if( config->rx_enable.port ) -+ { -+ if( !rx_active ) -+ { -+ gpio_pin_set_dt( &config->rx_enable, 0 ); -+ } -+ } -+ if( config->tx_enable.port && tx_active ) -+ { -+ gpio_pin_set_dt( &config->tx_enable, 1 ); -+ } -+ if( config->rx_enable.port && rx_active ) -+ { -+ gpio_pin_set_dt( &config->rx_enable, 1 ); -+ } -+} -+ - sx126x_hal_status_t sx126x_hal_write( const void* context, const uint8_t* command, const uint16_t command_length, - const uint8_t* data, const uint16_t data_length ) - { -@@ -127,6 +207,13 @@ sx126x_hal_status_t sx126x_hal_write( const void* context, const uint8_t* comman - const struct spi_buf_set tx_buf_set = { tx_bufs, .count = ARRAY_SIZE( tx_bufs ) }; - - sx126x_hal_check_device_ready( context ); -+ -+ /* Toggle external RF switch before writing the mode-change opcode */ -+ if( command_length > 0 ) -+ { -+ sx126x_hal_update_rf_switch( config, command[0] ); -+ } -+ - ret = spi_write_dt( &config->spi, &tx_buf_set ); - if( ret ) - { -diff --git a/drivers/usp/sx126x/sx126x_hal_context.h b/drivers/usp/sx126x/sx126x_hal_context.h -index 228f883..0c56bef 100644 ---- a/drivers/usp/sx126x/sx126x_hal_context.h -+++ b/drivers/usp/sx126x/sx126x_hal_context.h -@@ -72,6 +72,16 @@ struct sx126x_hal_context_cfg_t - struct gpio_dt_spec dio3; /* DIO3 pin */ - - bool dio2_as_rf_switch; -+ -+ /* External RF-switch GPIOs (optional; absent when port == NULL). -+ * tx_enable is asserted during TX-class operations; rx_enable during RX. -+ * Both are deasserted on standby/sleep/init. -+ * These are mutually exclusive with dio2-as-rf-switch in hardware but -+ * the driver does not enforce that — user must not set both in DT. -+ */ -+ struct gpio_dt_spec tx_enable; /* TX-EN line, e.g. EBYTE E22-400M30S pin 12 */ -+ struct gpio_dt_spec rx_enable; /* RX-EN line, e.g. EBYTE E22-400M30S pin 11 */ -+ - struct sx126x_hal_context_tcxo_cfg_t tcxo_cfg; /* TCXO config, says if dio3-tcxo */ - uint8_t capa_xta; /* set to 0xFF if not configured*/ - uint8_t capa_xtb; /* set to 0xFF if not configured*/ -diff --git a/dts/bindings/usp/semtech,sx126x-new-common.yaml b/dts/bindings/usp/semtech,sx126x-new-common.yaml -index 72032bb..56a8e40 100644 ---- a/dts/bindings/usp/semtech,sx126x-new-common.yaml -+++ b/dts/bindings/usp/semtech,sx126x-new-common.yaml -@@ -128,6 +128,30 @@ properties: - required: false - enum: [0, 1, 2, 3, 4, 5, 6, 7] - description: | -- The ramp-up time for the radio PA, between 0x0 (10us) to 0x07 (3400us). -+ The ramp-up time for the radio PA, between 0x0 (10us) to 0x07 (3400us). - If not provided, the driver will use the default, recommended time (40us). - It is not recommended to modify this value. -+ -+ tx-enable-gpios: -+ type: phandle-array -+ required: false -+ description: | -+ External RF-switch TX-enable GPIO. -+ -+ When present, the driver asserts this pin active before any transmit-class -+ operation (SetTx / SetTxContinuousWave / SetTxInfinitePreamble) and -+ deasserts it on standby, sleep, and receive-class operations. Use this for -+ modules such as the EBYTE E22-400M30S (SX1262) that drive an external -+ RF switch with a dedicated TX-EN line instead of using DIO2. -+ -+ Must not be combined with dio2-as-rf-switch. -+ -+ rx-enable-gpios: -+ type: phandle-array -+ required: false -+ description: | -+ External RF-switch RX-enable GPIO. -+ -+ When present, the driver asserts this pin active before any receive-class -+ operation (SetRx / SetRxDutyCycle / SetCad) and deasserts it on standby, -+ sleep, and transmit-class operations. Pair with tx-enable-gpios. --- -2.50.1 (Apple Git-155) - diff --git a/patches/0002-fix-zephyr-4.3-remove-select-ZEPHYR_LORA_BASICS_MODE.patch b/patches/0002-fix-zephyr-4.3-remove-select-ZEPHYR_LORA_BASICS_MODE.patch deleted file mode 100644 index 57e34fdd..00000000 --- a/patches/0002-fix-zephyr-4.3-remove-select-ZEPHYR_LORA_BASICS_MODE.patch +++ /dev/null @@ -1,36 +0,0 @@ -From dcabc513f4c4d00be1370cbf986bca284eddff8d Mon Sep 17 00:00:00 2001 -From: Michael Pham <61564344+Mikefly123@users.noreply.github.com> -Date: Sat, 4 Jul 2026 14:30:01 -0700 -Subject: [PATCH 2/3] fix(zephyr-4.3): remove select - ZEPHYR_LORA_BASICS_MODEM_MODULE (internalized in 4.3) - -ZEPHYR_LORA_BASICS_MODEM_MODULE was an external-module auto-symbol in -Zephyr <=4.2. In 4.3 it became an internal Zephyr symbol and is not -exposed to external modules. Remove the select to avoid a fatal Kconfig -'direct dependencies 0' abort. - -Co-Authored-By: Claude Fable 5 ---- - drivers/usp/Kconfig | 6 +++++- - 1 file changed, 5 insertions(+), 1 deletion(-) - -diff --git a/drivers/usp/Kconfig b/drivers/usp/Kconfig -index 5518a25..cc7940f 100644 ---- a/drivers/usp/Kconfig -+++ b/drivers/usp/Kconfig -@@ -9,7 +9,11 @@ menuconfig LORA_BASICS_MODEM_DRIVERS - bool "LoRa drivers from the new LoRa Basics Modem stack [EXPERIMENTAL]" - select POLL - select EXPERIMENTAL -- select ZEPHYR_LORA_BASICS_MODEM_MODULE -+ # PATCH(zephyr-4.3): ZEPHYR_LORA_BASICS_MODEM_MODULE was an external-module -+ # auto-symbol in Zephyr <=4.2. In 4.3 it became an internal Zephyr symbol -+ # (zephyr/modules/lora-basics-modem/Kconfig) that is NOT exposed in the -+ # auto-generated Kconfig.modules for external builds. Remove the select to -+ # avoid a fatal Kconfig "direct dependencies 0" abort. - depends on !LORA - help - Include LoRa drivers from the new LoRa Basics Modem stack in the system configuration. --- -2.50.1 (Apple Git-155) - diff --git a/patches/0003-fix-usp-main-2025-fix-LR_FHSS_SRC_PATH-for-flattened.patch b/patches/0003-fix-usp-main-2025-fix-LR_FHSS_SRC_PATH-for-flattened.patch deleted file mode 100644 index 2cb0baf3..00000000 --- a/patches/0003-fix-usp-main-2025-fix-LR_FHSS_SRC_PATH-for-flattened.patch +++ /dev/null @@ -1,34 +0,0 @@ -From 79f38c6669106d8018755dd3b2a509eb5f1bc924 Mon Sep 17 00:00:00 2001 -From: Michael Pham <61564344+Mikefly123@users.noreply.github.com> -Date: Sat, 4 Jul 2026 14:30:08 -0700 -Subject: [PATCH 3/3] fix(usp-main-2025): fix LR_FHSS_SRC_PATH for flattened - lr_fhss_driver layout - -Upstream usp removed lr_fhss_driver/src/; lr_fhss_mac.c now lives flat -in sx126x_driver/src (same dir as LBM_SX126X_LIB_DIR). Drop the now- -invalid subdirectory suffix to fix the cmake path. - -Co-Authored-By: Claude Fable 5 ---- - modules/usp_drivers/dev_env.cmake | 6 +++++- - 1 file changed, 5 insertions(+), 1 deletion(-) - -diff --git a/modules/usp_drivers/dev_env.cmake b/modules/usp_drivers/dev_env.cmake -index f92cafe..1e76230 100644 ---- a/modules/usp_drivers/dev_env.cmake -+++ b/modules/usp_drivers/dev_env.cmake -@@ -1,6 +1,10 @@ - # SPDX-License-Identifier: BSD-3-Clause-Clear - - if(SX126X_ENABLE_LR_FHSS) -- set(LR_FHSS_SRC_PATH "${LBM_SX126X_LIB_DIR}/lr_fhss_driver/src" -+ # PATCH(usp-main-2025): upstream usp repo removed the lr_fhss_driver/src -+ # subdirectory; lr_fhss_mac.c now lives flat in sx126x_driver/src/. -+ # Use the same directory as LBM_SX126X_LIB_DIR (already the default before -+ # dev_env.cmake was included). -+ set(LR_FHSS_SRC_PATH "${LBM_SX126X_LIB_DIR}" - CACHE PATH "Path to folder containing LR-FHSS driver" FORCE) - endif() --- -2.50.1 (Apple Git-155) - diff --git a/patches/0005-fix-usbd-cdc-acm-stuck-tx-fifo-busy-on-disable-and-retry.patch b/patches/0005-fix-usbd-cdc-acm-stuck-tx-fifo-busy-on-disable-and-retry.patch deleted file mode 100644 index 6bf8764e..00000000 --- a/patches/0005-fix-usbd-cdc-acm-stuck-tx-fifo-busy-on-disable-and-retry.patch +++ /dev/null @@ -1,65 +0,0 @@ -From 0000000000000000000000000000000000000002 Mon Sep 17 00:00:00 2001 -From: Michael Pham -Date: Sat, 5 Jul 2026 00:00:00 -0700 -Subject: [PATCH] fix(usbd_cdc_acm): clear TX_FIFO_BUSY on disable; retry when stuck - -Two related fixes for secondary silence on USB CDC-ACM TX path: - -1. Clear CDC_ACM_TX_FIFO_BUSY in usbd_cdc_acm_disable(). - If the host stops issuing IN tokens while a transfer is in flight, - TX_FIFO_BUSY stays set indefinitely. The error completion path - (ECONNABORTED) clears it on USB disconnect/cancel, but a passive stall - where the host driver stops polling without disconnecting does not fire - any completion. Clearing on disable ensures the flag is reset on the - next enable/reconnect cycle. - -2. Self-reschedule tx_fifo_work with 10ms delay when BUSY is already set - and ring buffer is non-empty. - Provides a periodic drain retry when the USB IN transfer stalls. When - BUSY clears normally (completion fires), the retry fires once and is a - no-op (ring_buf_is_empty after drain). When BUSY is stuck, the handler - retries every 10ms without burning CPU. - -Root cause on FCB v5e (RP2350, macOS host): after the initial burst of -~34 AOS frames is delivered, the macOS USB CDC driver pauses issuing IN -tokens while the GDS Python process processes the burst. TX_FIFO_BUSY -stays set. Subsequent uart_poll_out() calls schedule tx_fifo_work but the -handler returns early on every invocation. The 10ms retry loop ensures -the IN endpoint is re-armed as soon as the host resumes polling. - -Applies on top of: - 0004-fix-usbd-cdc-acm-poll-mode-tx-drain-on-class-enable.patch - -Signed-off-by: Michael Pham ---- -diff --git a/subsys/usb/device_next/class/usbd_cdc_acm.c b/subsys/usb/device_next/class/usbd_cdc_acm.c -index a8241ad130e..704b41a5a14 100644 ---- a/subsys/usb/device_next/class/usbd_cdc_acm.c -+++ b/subsys/usb/device_next/class/usbd_cdc_acm.c -@@ -378,6 +378,10 @@ static void usbd_cdc_acm_disable(struct usbd_class_data *const c_data) - - atomic_clear_bit(&data->state, CDC_ACM_CLASS_ENABLED); - atomic_clear_bit(&data->state, CDC_ACM_CLASS_SUSPENDED); -+ /* Clear TX_FIFO_BUSY on disable so a stuck IN transfer does not block -+ * the TX path after the host reconnects and re-enables the class. -+ */ -+ atomic_clear_bit(&data->state, CDC_ACM_TX_FIFO_BUSY); - LOG_INF("Configuration disabled"); - } - -@@ -648,6 +652,13 @@ static void cdc_acm_tx_fifo_handler(struct k_work *work) - - if (atomic_test_and_set_bit(&data->state, CDC_ACM_TX_FIFO_BUSY)) { - LOG_DBG("TX transfer already in progress"); -+ /* Reschedule if data is waiting - guards against a stuck IN transfer -+ * where the host stops issuing IN tokens (e.g. macOS flow-control). -+ * The retry is a no-op once the completion fires and clears BUSY. -+ */ -+ if (!ring_buf_is_empty(data->tx_fifo.rb)) { -+ cdc_acm_work_schedule(&data->tx_fifo_work, K_MSEC(10)); -+ } - return; - } - --- -2.39.3 (Apple Git-146) diff --git a/patches/0006-fix-sx126x-wakeup-busy-race-add-t_woff-settle-delay.patch b/patches/0006-fix-sx126x-wakeup-busy-race-add-t_woff-settle-delay.patch deleted file mode 100644 index ce56db0c..00000000 --- a/patches/0006-fix-sx126x-wakeup-busy-race-add-t_woff-settle-delay.patch +++ /dev/null @@ -1,58 +0,0 @@ -From 0000000000000000000000000000000000000006 Mon Sep 17 00:00:00 2001 -From: Michael Pham -Date: Fri, 10 Jul 2026 00:00:00 -0700 -Subject: [PATCH] fix(sx126x_hal): add t_woff settle delay after wake-up NSS - glitch to close BUSY-poll race - -sx126x_hal_check_device_ready() wakes a sleeping SX126x with a glitch on -NSS and then immediately polls BUSY via sx126x_hal_wait_on_busy(). Per -the datasheet, the chip needs up to ~340us (t_woff, warm start) after -the wake-up NSS edge before it reliably asserts BUSY. Polling right away -can sample BUSY before the chip has driven it, so the caller believes -the radio is ready when it is still starting up; the very next SPI -command (frequently SET_FREQ) is then clocked into a device that isn't -listening yet and is silently dropped. This reproduces ~40% of the time -in release builds where the post-wake instruction path is fast enough -to win the race. - -Fix: insert a k_busy_wait(500) between the wake-up NSS toggle and the -BUSY poll to wait out the chip's startup window before trusting BUSY. - -Same root cause and fix as the loramac_node SX126xWakeup() path fixed -for the ground-radio-controller GRC firmware -(patches/zephyr-sx126x-wakeup-busy-delay.patch there); this patch is -the USP HAL (Semtech RAC-managed) equivalent for the flight stack. - -Preserves the existing external RF-switch GPIO patch (0001) in this -same file — this change only adds the delay inside -sx126x_hal_check_device_ready() and does not touch -sx126x_hal_update_rf_switch() or sx126x_hal_write(). - -Co-Authored-By: Claude Sonnet 5 ---- - drivers/usp/sx126x/sx126x_hal.c | 9 +++++++++ - 1 file changed, 9 insertions(+) - -diff --git a/drivers/usp/sx126x/sx126x_hal.c b/drivers/usp/sx126x/sx126x_hal.c -index 1530077..699d53b 100644 ---- a/drivers/usp/sx126x/sx126x_hal.c -+++ b/drivers/usp/sx126x/sx126x_hal.c -@@ -101,6 +101,15 @@ static void sx126x_hal_check_device_ready( const void* context ) - gpio_pin_set_dt( cs, 1 ); - k_usleep( 100 ); - gpio_pin_set_dt( cs, 0 ); -+ -+ /* The chip takes up to ~340us (datasheet t_woff, warm start) after -+ * the wake-up NSS edge before it is ready. Polling BUSY immediately -+ * can sample it before the chip has asserted it, in which case the -+ * next command is clocked into a device still starting up and is -+ * silently ignored. Wait out the startup window before polling. -+ */ -+ k_busy_wait( 500 ); -+ - sx126x_hal_wait_on_busy( context ); - data->radio_status = RADIO_AWAKE; - } --- -2.50.1 (Apple Git-155) - diff --git a/patches/0007-fix-usbd-cdc-acm-bound-poll-out-backpressure-wait.patch b/patches/0007-fix-usbd-cdc-acm-bound-poll-out-backpressure-wait.patch deleted file mode 100644 index 00137ffa..00000000 --- a/patches/0007-fix-usbd-cdc-acm-bound-poll-out-backpressure-wait.patch +++ /dev/null @@ -1,36 +0,0 @@ -From: bench -Subject: [PATCH] fix(usbd-cdc-acm): bound poll_out sleep-retry to ~20ms, discard on sustained backpressure - -An attached-but-stalled host session (macOS ceases IN polling) makes the -unbounded 1ms sleep-retry loop take the full stall duration (minutes) per -console byte. Any logging thread then cascades into a com-stack livelock -(HWIL 2026-07-10). Bound the wait and fall back to the detached-case -discard behavior. ---- ---- a/subsys/usb/device_next/class/usbd_cdc_acm.c -+++ b/subsys/usb/device_next/class/usbd_cdc_acm.c -@@ -1007,6 +1007,7 @@ - struct cdc_acm_uart_data *const data = dev->data; - k_spinlock_key_t key; - uint32_t wrote; -+ int retries = 20; - - while (true) { - key = k_spin_lock(&data->lock); -@@ -1017,7 +1018,15 @@ - break; - } - -- if (k_is_in_isr() || !data->flow_ctrl) { -+ /* Bounded wait: with an attached-but-stalled host session (macOS -+ * ceases IN polling for minutes at a time), an unbounded sleep-retry -+ * here makes every console write take the full stall duration. Any -+ * thread that logs (event text loggers, assert reporting) then backs -+ * up its own queues and cascades into a system-wide com livelock. -+ * After ~20 ms of backpressure, treat the console as best-effort and -+ * discard, exactly like the detached (!flow_ctrl) case below. -+ */ -+ if (k_is_in_isr() || !data->flow_ctrl || retries-- <= 0) { - LOG_WRN_ONCE("Ring buffer full, discard data"); - break; - } diff --git a/patches/0008-fix-smtc-modem-hal-implement-rac-api-mutex.patch b/patches/0008-fix-smtc-modem-hal-implement-rac-api-mutex.patch deleted file mode 100644 index 04d66e5e..00000000 --- a/patches/0008-fix-smtc-modem-hal-implement-rac-api-mutex.patch +++ /dev/null @@ -1,39 +0,0 @@ -diff --git a/modules/smtc_modem_hal/smtc_modem_hal.c b/modules/smtc_modem_hal/smtc_modem_hal.c -index a08e7b9..a46c474 100644 ---- a/modules/smtc_modem_hal/smtc_modem_hal.c -+++ b/modules/smtc_modem_hal/smtc_modem_hal.c -@@ -176,14 +176,32 @@ struct k_sem* smtc_modem_hal_get_event_sem( void ) - return &lbm_main_loop_sem; - } - -+/* RAC API serialization (HWIL 2026-07-11): the RAC wraps every public entry -+ * point (engine pass included) in protect/unprotect and relies on it for -+ * mutual exclusion between the USP engine thread and API callers on other -+ * threads. The bare-metal stub provided none: a concurrent -+ * rp_task_enqueue()/abort against a running engine tears the radio planner's -+ * task structs (observed live on RP2350: RP_FAILSAFE panic on a -+ * LOCK_RADIO_ACCESS task whose type field read non-LOCK, and a TX launch -+ * taken through the LR-FHSS branch while on a LoRa profile). k_mutex allows -+ * recursive locking by the owner, which the RAC requires: post-transaction -+ * callbacks run inside the engine pass and may call -+ * smtc_rac_unlock_radio_access(), which re-enters protect. The radio/timer -+ * IRQ callbacks only set flags and never call protect, so ISR context is -+ * excluded by design; assert if a caller violates this. -+ */ -+K_MUTEX_DEFINE( prv_rac_api_mutex ); -+ - void smtc_modem_hal_protect_api_call( void ) - { -- // Do nothing in case implementation is bare metal -+ __ASSERT( !k_is_in_isr( ), "smtc_modem_hal_protect_api_call from ISR" ); -+ ( void ) k_mutex_lock( &prv_rac_api_mutex, K_FOREVER ); - } - - void smtc_modem_hal_unprotect_api_call( void ) - { -- // Do nothing in case implementation is bare metal -+ __ASSERT( !k_is_in_isr( ), "smtc_modem_hal_unprotect_api_call from ISR" ); -+ ( void ) k_mutex_unlock( &prv_rac_api_mutex ); - } - - /* ------------ Timer management ------------ */ diff --git a/patches/0009-fix-radio-planner-failsafe-exempt-unlock-radio-access.patch b/patches/0009-fix-radio-planner-failsafe-exempt-unlock-radio-access.patch deleted file mode 100644 index cc985ff2..00000000 --- a/patches/0009-fix-radio-planner-failsafe-exempt-unlock-radio-access.patch +++ /dev/null @@ -1,20 +0,0 @@ -diff --git a/smtc_rac_lib/radio_planner/src/radio_planner.c b/smtc_rac_lib/radio_planner/src/radio_planner.c -index 2c44c68..a4c99d6 100644 ---- a/smtc_rac_lib/radio_planner/src/radio_planner.c -+++ b/smtc_rac_lib/radio_planner/src/radio_planner.c -@@ -444,8 +444,15 @@ rp_stats_t rp_get_stats( const radio_planner_t* rp ) - - void rp_callback( radio_planner_t* rp ) - { -+ // UNLOCK_RADIO_ACCESS must be exempt like LOCK_RADIO_ACCESS: a lock task -+ // held open longer than the failsafe window (e.g. continuous RX under the -+ // raw RAC) keeps its original start_time_ms, and unlock_radio_access -+ // retypes the still-RUNNING task to UNLOCK before the engine processes -+ // it — the very next rp_callback would evaluate the failsafe against the -+ // stale start time and panic at the moment the client releases the lock. - if( ( rp->tasks[rp->radio_task_id].state == RP_TASK_STATE_RUNNING ) && - ( rp->tasks[rp->radio_task_id].type != RP_TASK_TYPE_LOCK_RADIO_ACCESS ) && -+ ( rp->tasks[rp->radio_task_id].type != RP_TASK_TYPE_UNLOCK_RADIO_ACCESS ) && - ( rp->disable_failsafe != RP_DISABLE_FAILSAFE_KEY ) && - ( ( int32_t ) ( rp->tasks[rp->radio_task_id].start_time_ms + 128000 - smtc_modem_hal_get_time_in_ms( ) ) < 0 ) ) - { diff --git a/patches/0010-fix-boards-xiao-nrf54l15-full_name-zephyr-4.4-schema.patch b/patches/0010-fix-boards-xiao-nrf54l15-full_name-zephyr-4.4-schema.patch deleted file mode 100644 index 5b69c3c0..00000000 --- a/patches/0010-fix-boards-xiao-nrf54l15-full_name-zephyr-4.4-schema.patch +++ /dev/null @@ -1,24 +0,0 @@ -From 0000000000000000000000000000000000000010 Mon Sep 17 00:00:00 2001 -From: Michael Pham -Date: Tue, 21 Jul 2026 00:00:00 -0700 -Subject: [PATCH] fix(boards): add full_name to xiao_nrf54l15 board.yml for Zephyr 4.4 schema - -Zephyr 4.4 board-schema.yaml requires name+full_name (or extend). The -vendored xiao_nrf54l15 board.yml predates this; matches the in-tree -Zephyr 4.4.1 board metadata. - -Signed-off-by: Michael Pham ---- -diff --git a/boards/seeed/xiao_nrf54l15/board.yml b/boards/seeed/xiao_nrf54l15/board.yml -index 9ffc64f..8641807 100644 ---- a/boards/seeed/xiao_nrf54l15/board.yml -+++ b/boards/seeed/xiao_nrf54l15/board.yml -@@ -1,5 +1,6 @@ - board: - name: xiao_nrf54l15 -+ full_name: XIAO NRF54L15 - vendor: seeed - socs: - - name: nrf54l15 --- -2.39.3 (Apple Git-146) diff --git a/patches/README.md b/patches/README.md index 93921c87..fd774704 100644 --- a/patches/README.md +++ b/patches/README.md @@ -1,37 +1,28 @@ # Patches Directory -This directory contains patches that are automatically applied to git submodules during the build process. - -## fprime-gds-version.patch - -This patch updates the `fprime-gds` version requirement in `lib/fprime/requirements.txt` from 4.1.0 to 4.1.1a2. - -**Why:** The project requires fprime-gds 4.1.1a2 for specific features: -- file-uplink-cooldown argument -- file-uplink-chunk-size argument - -The patch is automatically applied by the `make submodules` target to ensure version consistency and eliminate the version mismatch warning. - -**Application:** This patch is applied automatically when running `make submodules` (or `make` which includes that target). - -**Note:** After applying this patch, `git status` will show `lib/fprime` as modified. This is expected and should **not** be committed. The patched state is reapplied automatically on each `make submodules` run. - -## fprime-com-aggregator-bounded-timeout.patch - -Fixes issue #432: `Svc::ComAggregator`'s 10 Hz timeout signal FW_ASSERTs (queue FULL) whenever the component's dispatch thread stalls for longer than `queue_depth / timeout_rate` (~1.5 s at depth 15 / 10 Hz). - -Upstream's `m_allow_timeout` guard (fprime #4402) only suppresses timeout signals in the WAIT_STATUS state. While the state machine sits in FILL, a stalled dispatch thread (downstream backpressure, thread starvation from CDC-ACM host stalls) still lets rate-group ticks fill the queue and trip the autocoded assert in `aggregationMachine_sendSignalFinish`. - -The patch bounds timeout-signal queue occupancy in the hand-coded `timeout_handler`: the signal is only enqueued when the queue retains headroom for it plus the (flow-controlled, at most one each) in-flight `fill` and `status` signals. Timeout ticks are periodic and idempotent, so a skipped tick is retried on the next cycle — behavior is unchanged except that the queue can no longer overflow. - -**Application:** Applied automatically by `make submodules`, same mechanism as the fprime-gds version patch. Candidate for upstreaming to nasa/fprime. - -## fprime-sched-tick-drop.patch - -Second instance of the issue-#432 defect class, captured by gdb tripwire during HWIL soak #5 (2026-07-10): `safeModeSeq` (Svc::CmdSequencer) hit the identical queue-full FW_ASSERT in its autocoded `schedIn_handlerBase` — rate-group sched ticks accumulate in any active component's queue whenever its dispatch thread stalls longer than `queue_depth / tick_rate`. - -The patch adds the `drop` queue-full annotation to the periodic `Svc.Sched` async inputs of all eight upstream Svc components that lacked it (CmdSequencer, CmdDispatcher, TlmChan, TlmPacketizer, FileDownlink, BufferLogger, DpManager, DpWriter). Dropping a periodic tick is safe by construction — the next tick retries — and upstream already uses `drop` for exactly this on `ComQueue.run` and `ActiveRateGroup.CycleIn`. - -A third capture (same soak: `Svc::Health` 1 Hz ping → `rateGroup50Hz.PingIn_handlerBase`, identical queue-full assert) showed pings are another unbounded periodic producer, so the patch also adds `drop` to the 14 async `PingIn`/`pingIn` ports in Svc (including `ActiveRateGroup` and `FpySequencer`, which was explicitly `assert`). Dropping a ping is the *designed* failure path: Health's ping-timeout policy exists precisely to catch a component that stops responding — an assert on the ping enqueue kills the board through the very mechanism meant to detect stuck components gracefully. - -**Application:** Applied automatically by `make submodules`. Candidate for upstreaming to nasa/fprime. +This directory once carried a stack of module patches (usp_zephyr, usp, zephyr, +fprime) applied at build time. As of 2026-07-26 all of those fixes have been +migrated to the `Open-Source-Space-Foundation` fork integration branches +(`feat/proves-usp-radio`), which are pinned directly in `west.yml` and +`.gitmodules`. The former patch-apply Makefile targets (`usp-patches`, +`usp-core-patches`, `zephyr-patches`, and the fprime steps in `submodules`) +were removed with them. + +Where the removed patches live now (integration PRs, each linking its +constituent PRs): + +| Former patch | Module | Integration PR | +|---|---|---| +| 0001 RF-switch GPIO, 0002 Zephyr-4.3 Kconfig, 0003 LR_FHSS path, 0006 wakeup settle, 0008 RAC mutex, 0010 board.yml schema | usp_zephyr | Open-Source-Space-Foundation/usp_zephyr#7 | +| 0009 radio-planner failsafe unlock exemption | usp | Open-Source-Space-Foundation/usp#3 | +| 0005 + 0007 CDC-ACM TX fixes | zephyr | Open-Source-Space-Foundation/zephyr#3 | +| fprime-com-aggregator-bounded-timeout, fprime-sched-tick-drop | fprime | Open-Source-Space-Foundation/fprime#5 | + +## fprime-yamcs-noapp-path.patch (the one remaining patch) + +Patches the *pip-installed* `fprime-yamcs` package (not a git submodule), so it +cannot move to a fork pin and remains a carried patch. It fixes the `--no-app` +path handling in `fprime_yamcs/__main__.py`. + +**Application:** applied automatically by `make fprime-venv` (and therefore by +`make`), alongside the scripted fprime-yamcs fixes in `tools/`. diff --git a/patches/fprime-com-aggregator-bounded-timeout.patch b/patches/fprime-com-aggregator-bounded-timeout.patch deleted file mode 100644 index 4215db86..00000000 --- a/patches/fprime-com-aggregator-bounded-timeout.patch +++ /dev/null @@ -1,35 +0,0 @@ -diff --git a/Svc/ComAggregator/ComAggregator.cpp b/Svc/ComAggregator/ComAggregator.cpp -index dc6dd130d..6cfeecd6c 100644 ---- a/Svc/ComAggregator/ComAggregator.cpp -+++ b/Svc/ComAggregator/ComAggregator.cpp -@@ -8,6 +8,12 @@ - - namespace Svc { - -+namespace { -+//! Queue slots that must remain free for a timeout signal to be enqueued: the timeout itself plus one -+//! in-flight 'fill' and one in-flight 'status' signal (each bounded to one message by the com protocol). -+constexpr FwSizeType TIMEOUT_QUEUE_HEADROOM = 3; -+} // namespace -+ - // ---------------------------------------------------------------------- - // Component construction and destruction - // ---------------------------------------------------------------------- -@@ -55,7 +61,16 @@ void ComAggregator ::timeout_handler(FwIndexType portNum, U32 context) { - // - // Behaviorally, this solution will work exactly like the naive implementation with an infinite queue depth, but - // prevents queue overflow when using finite queues. -- if (this->m_allow_timeout) { -+ // -+ // Even so, timeout remains the only signal source without flow control: 'fill' and 'status' are each bounded -+ // to one in-flight message by the com protocol, but the rate group keeps delivering ticks while this -+ // component's dispatch thread is stalled (downstream backpressure, thread starvation). In the FILL state -+ // (m_allow_timeout true) such a stall would still fill the queue with timeout signals and trip the queue-full -+ // assertion in the autocoded signal send. Ticks are periodic and idempotent, so additionally skip the signal -+ // unless the queue has headroom for it plus the in-flight flow-controlled signals; a skipped tick is simply -+ // retried on the next cycle. -+ if (this->m_allow_timeout && -+ (this->m_queue.getMessagesAvailable() + TIMEOUT_QUEUE_HEADROOM <= this->m_queue.getDepth())) { - this->aggregationMachine_sendSignal_timeout(); - } - } diff --git a/patches/fprime-sched-tick-drop.patch b/patches/fprime-sched-tick-drop.patch deleted file mode 100644 index 587de513..00000000 --- a/patches/fprime-sched-tick-drop.patch +++ /dev/null @@ -1,250 +0,0 @@ -diff --git a/Svc/ActiveRateGroup/ActiveRateGroup.fpp b/Svc/ActiveRateGroup/ActiveRateGroup.fpp -index 3ee1488e3..c60ad6d0d 100644 ---- a/Svc/ActiveRateGroup/ActiveRateGroup.fpp -+++ b/Svc/ActiveRateGroup/ActiveRateGroup.fpp -@@ -15,7 +15,7 @@ module Svc { - output port RateGroupMemberOut: [ActiveRateGroupOutputPorts] Sched - - @ Ping input port for health -- async input port PingIn: Ping -+ async input port PingIn: Ping drop - - @ Ping output port for health - output port PingOut: Ping -diff --git a/Svc/BufferLogger/BufferLogger.fpp b/Svc/BufferLogger/BufferLogger.fpp -index 6da2ffd69..e045b1856 100644 ---- a/Svc/BufferLogger/BufferLogger.fpp -+++ b/Svc/BufferLogger/BufferLogger.fpp -@@ -16,12 +16,12 @@ module Svc { - async input port comIn: Fw.Com - - @ Ping input port -- async input port pingIn: Svc.Ping -+ async input port pingIn: Svc.Ping drop - - @ Ping output port - output port pingOut: Svc.Ping - -- async input port schedIn: Svc.Sched -+ async input port schedIn: Svc.Sched drop - - # ---------------------------------------------------------------------- - # Special ports -diff --git a/Svc/CmdDispatcher/CmdDispatcher.fpp b/Svc/CmdDispatcher/CmdDispatcher.fpp -index 66343b886..2f7732896 100644 ---- a/Svc/CmdDispatcher/CmdDispatcher.fpp -+++ b/Svc/CmdDispatcher/CmdDispatcher.fpp -@@ -24,10 +24,10 @@ module Svc { - async input port seqCmdBuff: [CmdDispatcherSequencePorts] Fw.Com hook - - @ Ping input port -- async input port pingIn: Svc.Ping -+ async input port pingIn: Svc.Ping drop - - @ Run port used to emit telemetry -- async input port run: Svc.Sched -+ async input port run: Svc.Sched drop - - @ Ping output port - output port pingOut: Svc.Ping -diff --git a/Svc/CmdSequencer/CmdSequencer.fpp b/Svc/CmdSequencer/CmdSequencer.fpp -index f1c401278..ebc381653 100644 ---- a/Svc/CmdSequencer/CmdSequencer.fpp -+++ b/Svc/CmdSequencer/CmdSequencer.fpp -@@ -68,7 +68,7 @@ module Svc { - async input port cmdResponseIn: Fw.CmdResponse - - @ Ping in port -- async input port pingIn: Svc.Ping -+ async input port pingIn: Svc.Ping drop - - @ Ping out port - output port pingOut: Svc.Ping -@@ -86,7 +86,7 @@ module Svc { - output port comCmdOut: Fw.Com - - @ Schedule in port -- async input port schedIn: Svc.Sched -+ async input port schedIn: Svc.Sched drop - - @ Notifies that a sequence has started running - output port seqStartOut: Svc.CmdSeqIn -diff --git a/Svc/ComLogger/ComLogger.fpp b/Svc/ComLogger/ComLogger.fpp -index c4327b7dd..cf4bc2238 100644 ---- a/Svc/ComLogger/ComLogger.fpp -+++ b/Svc/ComLogger/ComLogger.fpp -@@ -11,7 +11,7 @@ module Svc { - async input port comIn: Fw.Com - - @ Ping input port -- async input port pingIn: Svc.Ping -+ async input port pingIn: Svc.Ping drop - - @ Ping output port - output port pingOut: Svc.Ping -diff --git a/Svc/DpCatalog/DpCatalog.fpp b/Svc/DpCatalog/DpCatalog.fpp -index e1fd468dd..59ffc363f 100644 ---- a/Svc/DpCatalog/DpCatalog.fpp -+++ b/Svc/DpCatalog/DpCatalog.fpp -@@ -32,7 +32,7 @@ module Svc { - # Component specific ports - - @ Ping input port -- async input port pingIn: Svc.Ping -+ async input port pingIn: Svc.Ping drop - - @ Ping output port - output port pingOut: Svc.Ping -diff --git a/Svc/DpManager/DpManager.fpp b/Svc/DpManager/DpManager.fpp -index ebbf5a46f..74574f97f 100644 ---- a/Svc/DpManager/DpManager.fpp -+++ b/Svc/DpManager/DpManager.fpp -@@ -8,7 +8,7 @@ module Svc { - # ---------------------------------------------------------------------- - - @ Schedule in port -- async input port schedIn: Svc.Sched -+ async input port schedIn: Svc.Sched drop - - # ---------------------------------------------------------------------- - # Ports for handling buffer requests -diff --git a/Svc/DpWriter/DpWriter.fpp b/Svc/DpWriter/DpWriter.fpp -index f24594f09..152be91ba 100644 ---- a/Svc/DpWriter/DpWriter.fpp -+++ b/Svc/DpWriter/DpWriter.fpp -@@ -8,7 +8,7 @@ module Svc { - # ---------------------------------------------------------------------- - - @ Schedule in port -- async input port schedIn: Svc.Sched -+ async input port schedIn: Svc.Sched drop - - # ---------------------------------------------------------------------- - # Ports for handling data products -diff --git a/Svc/EventManager/EventManager.fpp b/Svc/EventManager/EventManager.fpp -index b97a8d807..2ea88960a 100644 ---- a/Svc/EventManager/EventManager.fpp -+++ b/Svc/EventManager/EventManager.fpp -@@ -53,7 +53,7 @@ module Svc { - output port FatalAnnounce: Svc.FatalEvent - - @ Ping input port -- async input port pingIn: Svc.Ping -+ async input port pingIn: Svc.Ping drop - - @ Ping output port - output port pingOut: Svc.Ping -diff --git a/Svc/FileDownlink/FileDownlink.fpp b/Svc/FileDownlink/FileDownlink.fpp -index eed9376aa..5e710a3f4 100644 ---- a/Svc/FileDownlink/FileDownlink.fpp -+++ b/Svc/FileDownlink/FileDownlink.fpp -@@ -8,7 +8,7 @@ module Svc { - # ---------------------------------------------------------------------- - - @ Run input port -- async input port Run: Svc.Sched -+ async input port Run: Svc.Sched drop - - @ Mutexed Sendfile input port - guarded input port SendFile: Svc.SendFileRequest -@@ -23,7 +23,7 @@ module Svc { - output port bufferSendOut: Fw.BufferSend - - @ Ping input port -- async input port pingIn: Svc.Ping -+ async input port pingIn: Svc.Ping drop - - @ Ping output port - output port pingOut: Svc.Ping -diff --git a/Svc/FileManager/FileManager.fpp b/Svc/FileManager/FileManager.fpp -index bef777c71..d85f42002 100644 ---- a/Svc/FileManager/FileManager.fpp -+++ b/Svc/FileManager/FileManager.fpp -@@ -8,7 +8,7 @@ module Svc { - # ---------------------------------------------------------------------- - - @ Ping input port -- async input port pingIn: Svc.Ping -+ async input port pingIn: Svc.Ping drop - - @ Scheduler input port for rate group operations - sync input port schedIn: Sched -diff --git a/Svc/FileUplink/FileUplink.fpp b/Svc/FileUplink/FileUplink.fpp -index 90ef4e9a8..b2b67bd5c 100644 ---- a/Svc/FileUplink/FileUplink.fpp -+++ b/Svc/FileUplink/FileUplink.fpp -@@ -14,7 +14,7 @@ module Svc { - output port bufferSendOut: Fw.BufferSend - - @ Ping in -- async input port pingIn: Svc.Ping -+ async input port pingIn: Svc.Ping drop - - @ Ping out - output port pingOut: Svc.Ping -diff --git a/Svc/FpySequencer/FpySequencer.fpp b/Svc/FpySequencer/FpySequencer.fpp -index 8af40d3d7..b9e80e1af 100644 ---- a/Svc/FpySequencer/FpySequencer.fpp -+++ b/Svc/FpySequencer/FpySequencer.fpp -@@ -38,7 +38,7 @@ module Svc { - - @ Ping in port - # TODO should ping have highest prio? or lowest? -- async input port pingIn: Svc.Ping priority 10 assert -+ async input port pingIn: Svc.Ping priority 10 drop - - @ port to trigger a wakeup or timeout check. increase frequency - @ to increase temporal resolution of sequencer -diff --git a/Svc/PrmDb/PrmDb.fpp b/Svc/PrmDb/PrmDb.fpp -index bca4f172f..b2a4b95b1 100644 ---- a/Svc/PrmDb/PrmDb.fpp -+++ b/Svc/PrmDb/PrmDb.fpp -@@ -69,7 +69,7 @@ module Svc { - async input port setPrm: Fw.PrmSet - - @ Ping input port -- async input port pingIn: Svc.Ping -+ async input port pingIn: Svc.Ping drop - - @ Ping output port - output port pingOut: Svc.Ping -diff --git a/Svc/TlmChan/TlmChan.fpp b/Svc/TlmChan/TlmChan.fpp -index d15072ed0..52b73b2d8 100644 ---- a/Svc/TlmChan/TlmChan.fpp -+++ b/Svc/TlmChan/TlmChan.fpp -@@ -10,13 +10,13 @@ module Svc { - guarded input port TlmGet: Fw.TlmGet - - @ Run port for starting packet send cycle -- async input port Run: Svc.Sched -+ async input port Run: Svc.Sched drop - - @ Packet send port - output port PktSend: Fw.Com - - @ Ping input port -- async input port pingIn: Svc.Ping -+ async input port pingIn: Svc.Ping drop - - @ Ping output port - output port pingOut: Svc.Ping -diff --git a/Svc/TlmPacketizer/TlmPacketizer.fpp b/Svc/TlmPacketizer/TlmPacketizer.fpp -index 1655c3e2e..4125fdac9 100644 ---- a/Svc/TlmPacketizer/TlmPacketizer.fpp -+++ b/Svc/TlmPacketizer/TlmPacketizer.fpp -@@ -28,13 +28,13 @@ module Svc { - async input port controlIn: EnableSection - - @ Ping input port -- async input port pingIn: Svc.Ping -+ async input port pingIn: Svc.Ping drop - - @ Ping output port - output port pingOut: Svc.Ping - - @ Run port for starting packet send cycle -- async input port Run: Svc.Sched -+ async input port Run: Svc.Sched drop - - @ Input configuration port - async input port configureSectionGroupRate: ConfigureGroupRate diff --git a/west.yml b/west.yml index 412cfb85..3eb4c708 100644 --- a/west.yml +++ b/west.yml @@ -14,10 +14,12 @@ manifest: group-filter: [-babblesim, -optional] projects: - # Zephyr RTOS core + # Zephyr RTOS core — Open-Source-Space-Foundation fork, integration branch + # feat/proves-usp-radio (v4.4.1 + CDC-ACM fixes, formerly patches/0005+0007; + # constituent PRs tracked in OSSF/zephyr#3). - name: zephyr - repo-path: zephyr - revision: v4.4.1 + url: https://github.com/Open-Source-Space-Foundation/zephyr + revision: 3838a2802c916accdfa671de77633ee45b69441c path: lib/zephyr-workspace/zephyr west-commands: scripts/west-commands.yml import: @@ -89,19 +91,21 @@ manifest: path: lib/zephyr-workspace/modules/fatfs # USP radio stack (Semtech Unified Software Platform) — v5e+ boards only. - # usp_zephyr upstream is pinned at the commit immediately below our local - # RF-switch patch (commit a23856a in spikes/usp_zephyr). Two Zephyr-4.3 - # compat fixes are carried as patches (see patches/usp_zephyr-*.patch). + # Open-Source-Space-Foundation fork, integration branch feat/proves-usp-radio + # (RF-switch GPIO, Zephyr 4.3/4.4 compat, wakeup settle, RAC mutex — formerly + # patches/0001-0003,0006,0008,0010; constituent PRs tracked in OSSF/usp_zephyr#7). - name: usp_zephyr - url: https://github.com/Lora-net/usp_zephyr - revision: bfacd435f53935ebea1a0e95fb877f4ede119985 + url: https://github.com/Open-Source-Space-Foundation/usp_zephyr + revision: 0bf3e208124d43c55fcb7f91bae6a9ac9812ee71 path: lib/zephyr-workspace/modules/lib/usp_zephyr # USP core library (LBM + RAL) — pulled by usp_zephyr at runtime. # Path mirrors the usp_zephyr west.yml convention (modules/lib/usp). + # OSSF fork, integration branch feat/proves-usp-radio (radio-planner failsafe + # unlock exemption, formerly patches/0009; tracked in OSSF/usp#3). - name: usp - url: https://github.com/Lora-net/usp - revision: 351b2015350670eb4dfa3aec35eb04433e062654 + url: https://github.com/Open-Source-Space-Foundation/usp + revision: 8a60e4a16092f1bfa030b18494da267d8ca6a3ba path: lib/zephyr-workspace/modules/lib/usp self: From d704fd3b93d5ab4862706c85f4b0a7dc275b81de Mon Sep 17 00:00:00 2001 From: Michael Pham <61564344+Mikefly123@users.noreply.github.com> Date: Sun, 26 Jul 2026 18:40:27 -0700 Subject: [PATCH 48/51] test(int): RF mode/profile matrix integration tests (HIL rung 6) (#474) * 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 * 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 --------- Co-authored-by: Claude Fable 5 --- .../test/int/rf_profile_matrix_test.py | 448 ++++++++++++++++++ pytest.ini | 1 + 2 files changed, 449 insertions(+) create mode 100644 PROVESFlightControllerReference/test/int/rf_profile_matrix_test.py diff --git a/PROVESFlightControllerReference/test/int/rf_profile_matrix_test.py b/PROVESFlightControllerReference/test/int/rf_profile_matrix_test.py new file mode 100644 index 00000000..0372cac1 --- /dev/null +++ b/PROVESFlightControllerReference/test/int/rf_profile_matrix_test.py @@ -0,0 +1,448 @@ +""" +rf_profile_matrix_test.py: + +Integration tests for the RF mode/profile matrix (HIL regression plan rung 6). + +Codifies the Phase A bench evidence (logs/HIL-REGRESSION-REPORT-phaseA.md): + 1. SET_TX_PROFILE / SET_RX_PROFILE sweep across the profile set and back, + asserting each switch completes and the radio stays commandable. + 2. CONTINUOUS_WAVE with clean restore to RX (complements the issue-#207 + regression test in radio_test.py by proving the RX path re-arms — + a profile switch after CW exercises stop→reconfig→re-arm). + 3. Profile-switch-as-first-post-wake command — the exact SX126x wakeup-race + shape fixed by the busy-race settle delay (carried patch 0006): idle, + then issue a profile switch as the first command, repeated N times. + 4. Post-idle (>=90 s) profile switch sanity. + 5. Two-board profile-pairing tests (marked two_board_rf): bidirectional + frame delivery at each TX/RX profile pairing against a ground radio + running GRC-USP firmware. Skipped unless the ground-side environment + hooks are configured (see below). + + 6. GFSK/GMSK first-TX-after-switch wedge kill recipe (regression for the + Phase B anomaly-B wedge, fixed in fprime-zephyr PR #21): healthy P0 + radio traffic → brief idle → P0→P4/P5 switch → immediate TX. Pre-fix, + the first TX after switching into a (G)FSK profile hit an ACTIVE RX and + hung the SX126x in TX (SendFailed -116 → COMSTATUS FAILURE latch). + +Runtime knobs (env vars; defaults are CI-sane, raise them for hammer runs): + RF_PROFILE_HAMMER_CYCLES post-wake switch cycles (default 5; bench hammer 100) + RF_WEDGE_KILL_CYCLES kill-recipe repetitions per profile (default 2; + bench matrix used 4+ reps x idle sweep) + RF_WEDGE_IDLE_S idle seconds before the kill-recipe switch (default 1) + RF_PROFILE_WAKE_IDLE_S idle seconds before each post-wake switch (default 3) + RF_PROFILE_LONG_IDLE_S idle seconds for the post-idle sanity test (default 90) + USP_GROUND_DATA_TTY ground radio data-CDC device; downlinked RF frames + appear here as raw bytes (e.g. /dev/cu.usbmodem21103) + USP_GROUND_CMD shell command template to set the ground radio RX + profile; "{profile}" is replaced with the numeric + LinkProfileId (e.g. a script wrapping fprime-cli + against the ground GDS) + USP_GROUND_UPLINK_CMD shell command that makes the ground radio transmit + at least one RF frame at the profile whose numeric + LinkProfileId replaces "{profile}" (e.g. a script + that sets the ground TX profile then sends a bypass + NO_OP through a GDS attached to the ground data CDC) +""" + +import os +import subprocess +import time +from datetime import datetime + +import pytest +from common import cmdDispatch, proves_send_and_assert_command +from fprime_gds.common.models.serialize.time_type import TimeType +from fprime_gds.common.testing_fw.api import IntegrationTestAPI + +# Profile switches sever any in-flight RF link (stop→reconfig→re-arm), so this +# whole module must only run when the GDS is connected via UART. +pytestmark = [pytest.mark.uart_only] + +downlinkDelay = "ReferenceDeployment.downlinkDelay" +radio = "ReferenceDeployment.uspRadio" +tlmSend = "CdhCore.tlmSend" + +# TlmPacketizer packet id used to force an immediate downlink frame (and thus +# an immediate radio TX while TRANSMIT is ENABLED): Health, id 2. +HEALTH_PACKET_ID = 2 + +# Events that indicate the radio rejected or failed a reconfiguration. +PROFILE_ERROR_EVENTS = ("ConfigurationFailed", "InvalidProfile") +RADIO_ERROR_EVENTS = ("SendFailed", "ConfigurationFailed", "AllocationFailed") + +# LinkProfileId sweep order (P0 is the boot default, so the sweep ends by +# restoring it). Matches the Phase A rung-6 pairing order, extended with +# P4/P5 (validated on the bench in Phase B: throughput ladder + wedge-fix +# matrix; profile table v2). +PROFILE_SWEEP = [ + "P1_LORA_SF10", + "P2_LORA_SF5", + "P3_GFSK_38K", + "P4_GFSK_75K", + "P5_GMSK_83K", + "P0_LORA_SF8", +] +BOOT_PROFILE = "P0_LORA_SF8" + +# Numeric LinkProfileId values for the ground-side command template. +PROFILE_IDS = { + "P0_LORA_SF8": 0, + "P1_LORA_SF10": 1, + "P2_LORA_SF5": 2, + "P3_GFSK_38K": 3, + "P4_GFSK_75K": 4, + "P5_GMSK_83K": 5, +} + +# Continuous-wave burst duration (seconds); kept short so the command stays +# within the GDS command-completion timeout (same rationale as radio_test.py). +CW_SECONDS = 5 + +# RX auto-revert is disabled (revert_s=0) for all single-board switches — the +# tests restore P0 explicitly, and an auto-revert mid-test would race the +# ProfileChanged assertions. +NO_REVERT = 0 + +HAMMER_CYCLES = int(os.environ.get("RF_PROFILE_HAMMER_CYCLES", "5")) +WEDGE_KILL_CYCLES = int(os.environ.get("RF_WEDGE_KILL_CYCLES", "2")) +WEDGE_IDLE_S = float(os.environ.get("RF_WEDGE_IDLE_S", "1")) +WAKE_IDLE_S = float(os.environ.get("RF_PROFILE_WAKE_IDLE_S", "3")) +LONG_IDLE_S = float(os.environ.get("RF_PROFILE_LONG_IDLE_S", "90")) + +GROUND_DATA_TTY = os.environ.get("USP_GROUND_DATA_TTY") +GROUND_CMD = os.environ.get("USP_GROUND_CMD") +GROUND_UPLINK_CMD = os.environ.get("USP_GROUND_UPLINK_CMD") + +GROUND_READ_WINDOW_S = 20.0 + + +def _now_start() -> TimeType: + return TimeType().set_datetime( + datetime.now(), time_base=TimeType.TimeBase("TB_DONT_CARE") + ) + + +def _assert_no_profile_errors( + fprime_test_api: IntegrationTestAPI, start: TimeType, context: str +) -> None: + for evt in PROFILE_ERROR_EVENTS: + result = fprime_test_api.await_event(f"{radio}.{evt}", start=start, timeout=0) + assert result is None, f"Unexpected {radio}.{evt} {context}: {result}" + + +def _switch_profile( + fprime_test_api: IntegrationTestAPI, direction: str, profile: str +) -> None: + """Issue a SET_TX_PROFILE/SET_RX_PROFILE and assert the deferred apply + completed cleanly (ProfileChanged emitted, no error events).""" + start = _now_start() + if direction == "TX": + proves_send_and_assert_command( + fprime_test_api, f"{radio}.SET_TX_PROFILE", [profile] + ) + else: + proves_send_and_assert_command( + fprime_test_api, f"{radio}.SET_RX_PROFILE", [profile, NO_REVERT] + ) + # The command handler defers the apply to the component thread; the switch + # is only complete once ProfileChanged is emitted. + result = fprime_test_api.await_event( + f"{radio}.ProfileChanged", start=start, timeout=10 + ) + assert result is not None, ( + f"No {radio}.ProfileChanged after SET_{direction}_PROFILE({profile})" + ) + _assert_no_profile_errors( + fprime_test_api, start, f"after SET_{direction}_PROFILE({profile})" + ) + + +@pytest.fixture(autouse=True) +def setup_test(fprime_test_api: IntegrationTestAPI, start_gds): + """Set the downlink divider before each test; after each test disable + transmit and restore both profiles to the boot default so no test (or the + RF link of a subsequent session) inherits an off-default configuration.""" + proves_send_and_assert_command( + fprime_test_api, + f"{downlinkDelay}.DIVIDER_PRM_SET", + [20], + ) + yield + proves_send_and_assert_command( + fprime_test_api, + f"{radio}.TRANSMIT", + ["DISABLED"], + ) + proves_send_and_assert_command( + fprime_test_api, f"{radio}.SET_TX_PROFILE", [BOOT_PROFILE] + ) + proves_send_and_assert_command( + fprime_test_api, f"{radio}.SET_RX_PROFILE", [BOOT_PROFILE, NO_REVERT] + ) + + +def test_01_tx_profile_sweep(fprime_test_api: IntegrationTestAPI, start_gds): + """SET_TX_PROFILE sweep P0→P1→P2→P3 and back to P0: every switch must + complete with ProfileChanged and no error events, and the radio must stay + commandable throughout (rung-6 single-board half).""" + for profile in PROFILE_SWEEP: + _switch_profile(fprime_test_api, "TX", profile) + # Radio (and command path) still alive after the switch. + proves_send_and_assert_command(fprime_test_api, f"{cmdDispatch}.CMD_NO_OP") + + +def test_02_rx_profile_sweep(fprime_test_api: IntegrationTestAPI, start_gds): + """SET_RX_PROFILE sweep P0→P1→P2→P3 and back to P0. Each switch is a full + stop→reconfig→re-arm of the receiver; all must complete cleanly with the + board still commandable.""" + for profile in PROFILE_SWEEP: + _switch_profile(fprime_test_api, "RX", profile) + proves_send_and_assert_command(fprime_test_api, f"{cmdDispatch}.CMD_NO_OP") + + +def test_03_continuous_wave_restore_to_rx( + fprime_test_api: IntegrationTestAPI, start_gds +): + """CONTINUOUS_WAVE on/off with clean restore to RX. + + Complements radio_test.py::test_02_continuous_wave_repeated (issue #207, + which proves repeated CW and a TRANSMIT afterwards): here the post-CW + assertion is that the *receive* chain is healthy — an RX profile switch + (stop→reconfig→re-arm of the receiver) must succeed after the CW teardown, + which fails if CW left the modem wedged or the RX path un-armed. + """ + proves_send_and_assert_command( + fprime_test_api, + f"{radio}.CONTINUOUS_WAVE", + [CW_SECONDS], + ) + # Wait out the CW duration so the asynchronous teardown (restore to RX) + # completes before probing the receiver. + time.sleep(CW_SECONDS + 2) + + # Receiver must be reconfigurable post-CW: exercise a full RX + # stop→reconfig→re-arm cycle away from and back to the boot profile. + _switch_profile(fprime_test_api, "RX", "P2_LORA_SF5") + _switch_profile(fprime_test_api, "RX", BOOT_PROFILE) + + # And the TX path must be intact too (no wedge): enabling transmit must + # produce no radio error events. + start = _now_start() + proves_send_and_assert_command(fprime_test_api, f"{radio}.TRANSMIT", ["ENABLED"]) + time.sleep(10) + for evt in RADIO_ERROR_EVENTS: + result = fprime_test_api.await_event(f"{radio}.{evt}", start=start, timeout=0) + assert result is None, f"Unexpected {radio}.{evt} after CW restore: {result}" + + +def test_04_post_wake_profile_switch_hammer( + fprime_test_api: IntegrationTestAPI, start_gds +): + """Profile switch as the first post-wake command (SX126x wakeup-race shape, + carried patch 0006). Idle long enough for the radio to sleep, then issue a + profile switch as the first command; repeat RF_PROFILE_HAMMER_CYCLES times + alternating P2↔P0 (the Phase A rung-4 hammer pattern, which scored 100/100 + on the fixed build vs ~40% first-command drops before the fix). + + Default cycle count is small for CI; set RF_PROFILE_HAMMER_CYCLES=100 (and + optionally RF_PROFILE_WAKE_IDLE_S) for a bench hammer run. + """ + for cycle in range(HAMMER_CYCLES): + # Idle window: no commands, letting the modem reach its sleep state so + # the switch below is the first post-wake SPI command sequence. + time.sleep(WAKE_IDLE_S) + profile = "P2_LORA_SF5" if cycle % 2 == 0 else BOOT_PROFILE + try: + _switch_profile(fprime_test_api, "RX", profile) + except AssertionError as exc: + raise AssertionError( + f"Post-wake profile switch failed on cycle {cycle + 1}/" + f"{HAMMER_CYCLES}: {exc}" + ) from exc + + +def test_05_post_idle_profile_switch(fprime_test_api: IntegrationTestAPI, start_gds): + """Post-idle profile-switch sanity (Phase A rung-6 idle check): with + transmit disabled, stay command-idle for RF_PROFILE_LONG_IDLE_S (default + 90 s), then issue a profile switch as the first command and verify the + board is fully commandable afterwards.""" + proves_send_and_assert_command(fprime_test_api, f"{radio}.TRANSMIT", ["DISABLED"]) + time.sleep(LONG_IDLE_S) + _switch_profile(fprime_test_api, "RX", "P1_LORA_SF10") + _switch_profile(fprime_test_api, "RX", BOOT_PROFILE) + proves_send_and_assert_command(fprime_test_api, f"{cmdDispatch}.CMD_NO_OP") + + +def _force_tx_and_await_advance( + fprime_test_api: IntegrationTestAPI, + floor: int | None, + context: str, + timeout: float = 45.0, +) -> int: + """Force an immediate radio TX (SEND_PKT with TRANSMIT ENABLED) and wait + for uspRadio.BytesSent to advance past ``floor``. + + Channel movement is the robust TX-health signal here (per the bench flake + ledger): BytesSent increments only when a radio transmission actually + completes, so a delta both proves the TX went out and avoids the + short-timeout event-window flakiness of bare assert_event checks. + """ + proves_send_and_assert_command( + fprime_test_api, f"{tlmSend}.SEND_PKT", [HEALTH_PACKET_ID, "REALTIME"] + ) + deadline = time.monotonic() + timeout + last_seen = floor + while time.monotonic() < deadline: + result = fprime_test_api.await_telemetry(f"{radio}.BytesSent", timeout=5) + if result is not None: + val = int(result.get_val()) + last_seen = val + if floor is None or val > floor: + return val + raise AssertionError( + f"uspRadio.BytesSent did not advance past {floor} within {timeout}s " + f"{context} (last seen: {last_seen})" + ) + + +@pytest.mark.parametrize("target_profile", ["P4_GFSK_75K", "P5_GMSK_83K"]) +def test_08_gfsk_wedge_kill_recipe( + fprime_test_api: IntegrationTestAPI, start_gds, target_profile +): + """Regression for the Phase B anomaly-B TX wedge (fixed in fprime-zephyr + PR #21, fix/usp-radio-gfsk-rx-tx-wedge). + + Pre-fix kill recipe (deterministic within <=3 cycles on the bench): healthy + P0 radio traffic → brief idle → P0→P4/P5 profile switch → the FIRST TX + after switching into a (G)FSK profile aborted the freshly armed continuous + RX without quiescing the chip, so SetTx hit an ACTIVE GFSK/GMSK RX and hung + the SX126x in TX forever (SendFailed -116 + XOSC_START_ERR, then the + COMSTATUS FAILURE latch parked ComQueue → downlink dead until reboot). + LoRa RX tolerates the same abuse, which is why P0 was clean for months. + + Post-fix, every first-TX-after-switch must complete: no SendFailed / + ConfigurationFailed, and uspRadio.BytesSent must keep advancing (channel + movement, not just event silence) at the target profile AND after the + return to P0. Repeat RF_WEDGE_KILL_CYCLES times (default small for CI; + the bench validation matrix ran 4 reps x idle {0,0.25,1,5}s x {P4,P5}). + """ + proves_send_and_assert_command(fprime_test_api, f"{radio}.TRANSMIT", ["ENABLED"]) + try: + for cycle in range(WEDGE_KILL_CYCLES): + ctx = f"(cycle {cycle + 1}/{WEDGE_KILL_CYCLES}, {target_profile})" + + # 1. Healthy P0 traffic: prove the radio TX path is moving before + # the switch so a post-switch stall is unambiguous. + baseline = _force_tx_and_await_advance( + fprime_test_api, None, f"at P0 baseline {ctx}" + ) + + # 2. Brief command-idle window before the switch (the pre-fix + # wedge fired across idle lengths 0-5 s; default 1 s). + time.sleep(WEDGE_IDLE_S) + + # 3. P0 -> target profile switch... + start = _now_start() + _switch_profile(fprime_test_api, "TX", target_profile) + + # 4. ...then IMMEDIATE TX: the first TX after switching into + # GFSK/GMSK was the exact pre-fix kill moment. + baseline = _force_tx_and_await_advance( + fprime_test_api, baseline, f"on first TX after P0->{ctx}" + ) + for evt in RADIO_ERROR_EVENTS: + result = fprime_test_api.await_event( + f"{radio}.{evt}", start=start, timeout=0 + ) + assert result is None, ( + f"Unexpected {radio}.{evt} after switch to {target_profile} " + f"{ctx}: {result}" + ) + + # 5. Return to P0 and prove TX still advances (the pre-fix latch + # survived a P4->P5 switch; any wedge must show up here too). + start = _now_start() + _switch_profile(fprime_test_api, "TX", BOOT_PROFILE) + _force_tx_and_await_advance( + fprime_test_api, baseline, f"after return to P0 {ctx}" + ) + for evt in RADIO_ERROR_EVENTS: + result = fprime_test_api.await_event( + f"{radio}.{evt}", start=start, timeout=0 + ) + assert result is None, ( + f"Unexpected {radio}.{evt} after return to P0 {ctx}: {result}" + ) + finally: + proves_send_and_assert_command( + fprime_test_api, f"{radio}.TRANSMIT", ["DISABLED"] + ) + + +# --------------------------------------------------------------------------- +# Two-board tests: require a ground radio (GRC-USP firmware) on the bench. +# --------------------------------------------------------------------------- + + +def _require_ground(*env_vars: str) -> None: + missing = [v for v in env_vars if not os.environ.get(v)] + if missing: + pytest.skip("two-board ground radio not configured: set " + ", ".join(missing)) + + +def _set_ground_rx_profile(profile: str) -> None: + cmd = GROUND_CMD.format(profile=PROFILE_IDS[profile]) + subprocess.run(cmd, shell=True, check=True, timeout=60) + + +@pytest.mark.two_board_rf +def test_06_two_board_pairing_downlink(fprime_test_api: IntegrationTestAPI, start_gds): + """Flight→ground frame delivery at each profile pairing: for each profile, + set the ground radio RX profile and the flight TX profile to match, enable + transmit, and assert RF frames reach the ground radio (raw bytes on its + data CDC). Requires USP_GROUND_CMD and USP_GROUND_DATA_TTY.""" + _require_ground("USP_GROUND_CMD", "USP_GROUND_DATA_TTY") + import serial + + for profile in PROFILE_SWEEP: + _set_ground_rx_profile(profile) + _switch_profile(fprime_test_api, "TX", profile) + proves_send_and_assert_command( + fprime_test_api, f"{radio}.TRANSMIT", ["ENABLED"] + ) + try: + with serial.Serial(GROUND_DATA_TTY, baudrate=115200, timeout=1.0) as ser: + ser.reset_input_buffer() + deadline = time.monotonic() + GROUND_READ_WINDOW_S + rx = bytearray() + while time.monotonic() < deadline and len(rx) == 0: + chunk = ser.read(256) + if chunk: + rx.extend(chunk) + finally: + proves_send_and_assert_command( + fprime_test_api, f"{radio}.TRANSMIT", ["DISABLED"] + ) + assert len(rx) > 0, ( + f"No RF bytes reached the ground radio at profile pairing {profile}" + ) + + +@pytest.mark.two_board_rf +def test_07_two_board_pairing_uplink(fprime_test_api: IntegrationTestAPI, start_gds): + """Ground→flight frame delivery at each profile pairing: for each profile, + match the flight RX profile to the ground TX profile, trigger a ground + transmission (USP_GROUND_UPLINK_CMD), and assert the flight radio saw the + frame (uspRadio.LastRssi update — set on every received frame).""" + _require_ground("USP_GROUND_UPLINK_CMD") + + for profile in PROFILE_SWEEP: + _switch_profile(fprime_test_api, "RX", profile) + fprime_test_api.clear_histories() + cmd = GROUND_UPLINK_CMD.format(profile=PROFILE_IDS[profile]) + subprocess.run(cmd, shell=True, check=True, timeout=120) + result = fprime_test_api.await_telemetry(f"{radio}.LastRssi", timeout=30) + assert result is not None, ( + f"Flight radio saw no RF frame (no LastRssi update) at profile " + f"pairing {profile}" + ) diff --git a/pytest.ini b/pytest.ini index 07ca9309..a6c34627 100644 --- a/pytest.ini +++ b/pytest.ini @@ -7,6 +7,7 @@ markers = requires_antenna: marks tests that require the antenna board to be plugged in and the burnwire capacitor installed; skip on a bare flight controller requires_battery: marks tests that require the battery board connected with power flowing from the battery terminals; skip on a bare flight controller requires_watchdog_jumper: marks tests that require the JP6 watchdog jumper to be bridged so the watchdog can reset the MCU; skip when JP6 is open + two_board_rf: marks tests that require a second (ground) USP radio board on the bench; they self-skip unless the USP_GROUND_* environment hooks are configured filterwarnings = ignore::DeprecationWarning:yamcs\..* ignore::DeprecationWarning:google\.protobuf\..* From c6df588f527892088694983f5738416e6c153d47 Mon Sep 17 00:00:00 2001 From: Michael Pham <61564344+Mikefly123@users.noreply.github.com> Date: Thu, 30 Jul 2026 19:57:19 -0700 Subject: [PATCH 49/51] chore(fprime-zephyr): bump pin to bc3b6b6 (integration branch + GFSK wedge-fix host UTs) (#480) --- .../test/unit-tests/CMakeLists.txt | 12 ++++++++++++ lib/fprime-zephyr | 2 +- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/PROVESFlightControllerReference/test/unit-tests/CMakeLists.txt b/PROVESFlightControllerReference/test/unit-tests/CMakeLists.txt index 1221e718..1e7dbf2d 100644 --- a/PROVESFlightControllerReference/test/unit-tests/CMakeLists.txt +++ b/PROVESFlightControllerReference/test/unit-tests/CMakeLists.txt @@ -50,6 +50,18 @@ target_compile_definitions(test_RadioHeadShim PRIVATE LINK_PROFILES_USE_HOST_TYP target_link_libraries(test_RadioHeadShim gtest_main) add_test(NAME test_RadioHeadShim COMMAND test_RadioHeadShim) +# --- UspRadio: TxOutcomePolicy host-side test (GFSK wedge-fix item 3) --- +# TxOutcomePolicy.hpp is header-only and free of F'/USP/Zephyr includes. +add_executable(test_TxOutcomePolicy + ${CMAKE_CURRENT_SOURCE_DIR}/../../../lib/fprime-zephyr/fprime-zephyr/Drv/UspRadio/test/ut/test_TxOutcomePolicy.cpp +) +target_include_directories(test_TxOutcomePolicy PRIVATE + # Exposes #include "fprime-zephyr/Drv/UspRadio/TxOutcomePolicy.hpp" + ${CMAKE_CURRENT_SOURCE_DIR}/../../../lib/fprime-zephyr +) +target_link_libraries(test_TxOutcomePolicy gtest_main) +add_test(NAME test_TxOutcomePolicy COMMAND test_TxOutcomePolicy) + # --- Helper Libraries --- # DetumbleManager BDot diff --git a/lib/fprime-zephyr b/lib/fprime-zephyr index c81485ff..bc3b6b65 160000 --- a/lib/fprime-zephyr +++ b/lib/fprime-zephyr @@ -1 +1 @@ -Subproject commit c81485ff8c73a76772ae85f3b388a2d57d0ec9fc +Subproject commit bc3b6b65c942dba10345796f3969d7cb07206d88 From 60eb4856dcf5156d41300b354e87d1deefc0390e Mon Sep 17 00:00:00 2001 From: Michael Pham <61564344+Mikefly123@users.noreply.github.com> Date: Sun, 2 Aug 2026 16:31:27 -0700 Subject: [PATCH 50/51] chore(pins): re-pin zephyr + fprime-zephyr onto trimmed integration branches (#487) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- lib/fprime-zephyr | 2 +- west.yml | 11 +++++++---- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/lib/fprime-zephyr b/lib/fprime-zephyr index bc3b6b65..3da24b8e 160000 --- a/lib/fprime-zephyr +++ b/lib/fprime-zephyr @@ -1 +1 @@ -Subproject commit bc3b6b65c942dba10345796f3969d7cb07206d88 +Subproject commit 3da24b8e631327cad09ac55503a082c088f7e00a diff --git a/west.yml b/west.yml index 3eb4c708..3e8da381 100644 --- a/west.yml +++ b/west.yml @@ -15,11 +15,14 @@ manifest: projects: # Zephyr RTOS core — Open-Source-Space-Foundation fork, integration branch - # feat/proves-usp-radio (v4.4.1 + CDC-ACM fixes, formerly patches/0005+0007; - # constituent PRs tracked in OSSF/zephyr#3). + # feat/proves-usp-radio-trimmed (v4.4.1 + the CDC-ACM TX-FIFO drain fix, + # formerly patches/0005; constituent PR tracked in OSSF/zephyr#1). + # The poll_out bounded-wait layer (OSSF/zephyr#2) was pruned 2026-08-01: + # its added bound sits behind data->flow_ctrl, which is false on the v5e + # from both devicetree and the explicit UART_CFG_FLOW_CTRL_NONE configure, + # so the code was unreachable in the flight image. - name: zephyr - url: https://github.com/Open-Source-Space-Foundation/zephyr - revision: 3838a2802c916accdfa671de77633ee45b69441c + revision: 1f6485eca25431b5ff27ce9a754218c9e559bbbb path: lib/zephyr-workspace/zephyr west-commands: scripts/west-commands.yml import: From 1560dce1ecd715f5d322f6785bf06e50f5a7c3bd Mon Sep 17 00:00:00 2001 From: Michael Pham <61564344+Mikefly123@users.noreply.github.com> Date: Sun, 20 Sep 2026 11:38:06 -0700 Subject: [PATCH 51/51] Trim PR comments and remove dead radio state from the USP port (#518) Cleanup pass over PR #439 (feat/usp-radio): rewrite every comment and doc paragraph the PR added to state only what the code does or which constraint it protects, and remove dead/inconsistent debug scaffolding. - Remove TopologyState::uspFreqHz, uspTxPowerDbm and the Main.cpp assignments: set, never read. The topology constructs Zephyr::RalSessionImpl(437400000U, 10) directly. - Restore west.yml's zephyr project entry (repo-path/revision v4.4.1) to match origin/main; the pinned SHA is that tag's commit. - Remove "comman" from .codespell-ignore-words.txt (no match outside lib/). - Fix comments naming files that don't exist (RadioInstances_*.fpp, RadioPackets.fppi) to their real names (.fppi, RadioPacketsBytesReceived.fppi). - Fix the unit-test CMakeLists.txt note on where LINK_PROFILES_USE_HOST_TYPES is defined: CMake injects it for test_ProfilePolicy; only test_LinkProfiles.cpp defines it in-source. - Fix rf_profile_matrix_test.py docstrings describing a four-profile sweep; PROFILE_SWEEP has six entries. - Fix ADR 0002: no table-version telemetry channel exists in UspRadio.fpp; only the active TX and RX profile indices are telemetered. No behavior changes, no renames. Co-authored-by: Claude Sonnet 5 --- .codespell-ignore-words.txt | 1 - .gitignore | 2 +- .pre-commit-config.yaml | 6 +- CONTEXT.md | 34 +-- Makefile | 5 - .../Components/FatalHandler/FatalHandler.cpp | 18 +- .../Components/FatalHandler/FatalHandler.hpp | 3 +- .../ReferenceDeployment/Main.cpp | 9 +- .../ReferenceDeployment/Top/CMakeLists.txt | 27 +-- .../Top/RadioInstances_Lora.fppi | 4 +- .../Top/RadioInstances_Usp.fppi | 7 +- .../Top/RadioTopology_Lora.fppi | 6 +- .../Top/RadioTopology_Usp.fppi | 18 +- .../Top/ReferenceDeploymentPackets.fppi | 5 +- .../Top/ReferenceDeploymentTopology.cpp | 30 +-- .../Top/ReferenceDeploymentTopologyDefs.hpp | 10 +- .../ReferenceDeployment/Top/instances.fpp | 6 +- .../ReferenceDeployment/Top/topology.fpp | 9 +- .../test/int/radio_test.py | 2 - .../test/int/rf_profile_matrix_test.py | 219 +++++++----------- .../test/unit-tests/CMakeLists.txt | 27 +-- ...s_flight_control_board_v5e_rp2350a_m33.dts | 58 ++--- ...ht_control_board_v5e_rp2350a_m33_defconfig | 8 +- bootloader/sysbuild/mcuboot.conf | 13 +- .../0001-semtech-usp-for-sx126x-radio-path.md | 29 ++- docs/adr/0002-link-profile-table.md | 27 ++- patches/README.md | 34 ++- pytest.ini | 2 +- west.yml | 26 +-- 29 files changed, 227 insertions(+), 418 deletions(-) diff --git a/.codespell-ignore-words.txt b/.codespell-ignore-words.txt index 5cc75138..9c69ee4e 100644 --- a/.codespell-ignore-words.txt +++ b/.codespell-ignore-words.txt @@ -2,7 +2,6 @@ ALS comIn bufferIn commandIn -comman Ines rsource ser diff --git a/.gitignore b/.gitignore index d845a93b..29643194 100644 --- a/.gitignore +++ b/.gitignore @@ -59,7 +59,7 @@ yamcs/yamcs-runtime/ /circuit-python-passthrough/lib/ /circuit-python-passthrough/tools/ -# Phase 4 radio config symlinks (generated by CMake at configure time) +# Radio config symlinks (generated by CMake at configure time) PROVESFlightControllerReference/ReferenceDeployment/Top/RadioInstances.fppi PROVESFlightControllerReference/ReferenceDeployment/Top/RadioTopology.fppi PROVESFlightControllerReference/ReferenceDeployment/Top/RadioPacketsBytesReceived.fppi diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index a405304c..51e5dae8 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -3,10 +3,8 @@ repos: rev: v5.0.0 hooks: - id: trailing-whitespace - # patches/*.patch are machine-generated diffs applied via patch tooling; - # their context/removed lines must byte-match the real upstream files, - # which can legitimately have trailing whitespace. Stripping it here - # silently breaks patch application. + # patches/*.patch must byte-match upstream, including trailing + # whitespace, so whitespace hooks must not touch them. exclude: ^patches/ - id: end-of-file-fixer exclude: ^patches/ diff --git a/CONTEXT.md b/CONTEXT.md index ded91985..18bf5c83 100644 --- a/CONTEXT.md +++ b/CONTEXT.md @@ -1,31 +1,31 @@ -# CONTEXT — PROVES Flight Radio +# CONTEXT: PROVES Flight Radio Glossary for the flight-software radio domain. Terms here are canonical; use them in code, docs, commands, and telemetry names. ## Radio paths -- **USP Radio Path** — the radio stack built on Semtech's Unified Software Platform (USP). Applies to SX126x-class boards (FCB v5e onward). Multi-modulation capable. -- **Legacy Radio Path** — the existing loramac-node-backed Zephyr `drivers/lora` stack wrapped by the `Zephyr::LoRa` component. Applies to SX127x-class boards (FCB v5/v5c/v5d). LoRa modulation only. Kept buildable, not extended. -- **USP (Unified Software Platform)** — Semtech's radio software platform (radio drivers + RAL + radio access arbitration + LoRa Basics Modem). Not to be confused with a Zephyr-project product; `usp_zephyr` is its Zephyr integration module. -- **LBM (LoRa Basics Modem)** — Semtech's modem library bundled inside USP; its LoRaWAN stack is unused by PROVES (we fly raw CCSDS point-to-point). -- **RAL (Radio Abstraction Layer)** — USP's chip-agnostic radio API. The seam the flight component talks to (and the seam mocked in unit tests). +- **USP Radio Path**: the radio stack built on Semtech's Unified Software Platform (USP). Applies to SX126x-class boards (FCB v5e onward). Multi-modulation capable. +- **Legacy Radio Path**: the existing loramac-node-backed Zephyr `drivers/lora` stack wrapped by the `Zephyr::LoRa` component. Applies to SX127x-class boards (FCB v5/v5c/v5d). LoRa modulation only. +- **USP (Unified Software Platform)**: Semtech's radio software platform (radio drivers + RAL + radio access arbitration + LoRa Basics Modem). `usp_zephyr` is its Zephyr integration module. +- **LBM (LoRa Basics Modem)**: Semtech's modem library bundled inside USP. Its LoRaWAN stack is unused; PROVES flies raw CCSDS point-to-point. +- **RAL (Radio Abstraction Layer)**: USP's chip-agnostic radio API. The seam the flight component talks to, and the seam mocked in unit tests. ## Link configuration -- **Link Profile** — a complete, named radio configuration: modulation plus every parameter needed for two radios to interoperate (e.g. for GFSK: bitrate, deviation, BT, sync word, CRC, preamble). Identified by index into the Profile Table. Profiles are switched atomically; individual RF parameters are never commanded piecemeal in operations. -- **Profile Table** — the versioned, checked-in list of Link Profiles shared verbatim by flight and ground builds. Both ends must be built from the same table version for a profile index to mean the same thing. -- **TX Profile / RX Profile** — the Link Profile currently applied to the transmit and receive directions independently. The link is asymmetric by design (e.g. robust LoRa uplink, high-rate GFSK downlink). -- **Boot-Default Profile** — the profile each direction starts in at boot, and the profile RX Auto-Revert falls back to. Chosen for maximum link robustness, not throughput. -- **RX Auto-Revert** — safety mechanism: after an RX Profile change, if no valid frame is received within the commanded revert window, the RX Profile reverts to the Boot-Default Profile. Receiving a valid frame on the new profile confirms it. +- **Link Profile**: a complete, named radio configuration: modulation plus every parameter needed for two radios to interoperate (e.g. for GFSK: bitrate, deviation, BT, sync word, CRC, preamble). Identified by index into the Profile Table. Profiles are switched atomically; individual RF parameters are never commanded piecemeal in operations. +- **Profile Table**: the versioned, checked-in list of Link Profiles shared verbatim by flight and ground builds. Both ends must be built from the same table version for a profile index to mean the same thing. +- **TX Profile / RX Profile**: the Link Profile currently applied to the transmit and receive directions independently. The link is asymmetric by design (e.g. robust LoRa uplink, high-rate GFSK downlink). +- **Boot-Default Profile**: the profile each direction starts in at boot, and the profile RX Auto-Revert falls back to. Chosen for maximum link robustness, not throughput. +- **RX Auto-Revert**: safety mechanism: after an RX Profile change, if no valid frame is received within the commanded revert window, the RX Profile reverts to the Boot-Default Profile. Receiving a valid frame on the new profile confirms it. ## Modulations -- **CW (Continuous Wave)** — unmodulated carrier transmission for beacons, range testing, and RF debug. A test mode, not a Link Profile. -- **GFSK** — Gaussian FSK packet modulation; the high-throughput downlink option on the USP Radio Path. Capped at ≤75 kbps (with fdev = 25 kHz) by the Band Constraint. -- **Band Constraint** — IARU coordination limits PROVES UHF emissions to ≤125 kHz occupied bandwidth. Every Link Profile must satisfy it. -- **LR-FHSS** — long-range frequency-hopping modulation. SX126x can transmit but never receive it, so it is out of scope until gateway-grade or LR20xx receive hardware exists in the ground segment. +- **CW (Continuous Wave)**: unmodulated carrier transmission for beacons, range testing, and RF debug. A test mode, not a Link Profile. +- **GFSK**: Gaussian FSK packet modulation; the high-throughput downlink option on the USP Radio Path. Capped at 75 kbps, with fdev of 25 kHz, by the Band Constraint. +- **Band Constraint**: IARU coordination limits PROVES UHF emissions to 125 kHz occupied bandwidth or less. Every Link Profile must satisfy it. +- **LR-FHSS**: long-range frequency-hopping modulation. SX126x can transmit but never receive it, so it is out of scope until gateway-grade or LR20xx receive hardware exists in the ground segment. ## Ground segment -- **GRC (Ground Radio Controller)** — the station-local Zephyr radio box (SX1262) that terminates the RF link. Must consume the same Profile Table as flight. -- **RadioHead Header** — the 4-byte `[destination, source, identifier, flags]` prefix the RadioHead LoRa ecosystem (adafruit_rfm9x, legacy `Zephyr::LoRa`) puts on every LoRa packet. The USP Radio Path radiates raw F´ frames; the `RADIOHEAD_COMPAT` parameter on `UspRadio` (default: enabled) prepends/strips this header so USP boards interoperate with RadioHead peers such as the CI CircuitPython passthrough board. Both ends of a link must agree; GFSK profiles are always raw. +- **GRC (Ground Radio Controller)**: the station-local Zephyr radio box (SX1262) that terminates the RF link. Must consume the same Profile Table as flight. +- **RadioHead Header**: the 4-byte `[destination, source, identifier, flags]` prefix the RadioHead LoRa ecosystem (adafruit_rfm9x, legacy `Zephyr::LoRa`) puts on every LoRa packet. The USP Radio Path radiates raw F´ frames; the `RADIOHEAD_COMPAT` parameter on `UspRadio` (default: enabled) prepends/strips this header so USP boards interoperate with RadioHead peers such as the CI CircuitPython passthrough board. Both ends of a link must agree; GFSK profiles are always raw. diff --git a/Makefile b/Makefile index 790f982e..8c398e48 100644 --- a/Makefile +++ b/Makefile @@ -54,11 +54,6 @@ zephyr-setup: fprime-venv ## Set up Zephyr environment $(UV) pip install --prerelease=allow -r lib/zephyr-workspace/bootloader/mcuboot/zephyr/requirements.txt; \ } -# Carried module patches (usp-patches / usp-core-patches / zephyr-patches and -# the fprime patch steps in `submodules`) were removed 2026-07-26: all module -# fixes now live on the Open-Source-Space-Foundation fork integration branches -# (feat/proves-usp-radio) pinned in west.yml / .gitmodules. See patches/README.md. - ##@ Development .PHONY: pre-commit-install diff --git a/PROVESFlightControllerReference/Components/FatalHandler/FatalHandler.cpp b/PROVESFlightControllerReference/Components/FatalHandler/FatalHandler.cpp index 20f2bfa6..a5d62351 100644 --- a/PROVESFlightControllerReference/Components/FatalHandler/FatalHandler.cpp +++ b/PROVESFlightControllerReference/Components/FatalHandler/FatalHandler.cpp @@ -28,27 +28,19 @@ FatalHandler ::FatalHandler(const char* const compName) : FatalHandlerComponentB FatalHandler ::~FatalHandler() {} void FatalHandler::reboot() { - // Use Zephyr to reboot the system. - // https://docs.zephyrproject.org/apidoc/latest/reboot_8h.html#a18abe5d5b8089e8429c25bafa5e76d3d - // Attempt a warm reboot first. sys_reboot(SYS_REBOOT_WARM); - // Attempt a cold reboot if the warm reboot somehow returns/fails. + // Only reached if the warm reboot returns. sys_reboot(SYS_REBOOT_COLD); } void FatalHandler::FatalReceive_handler(const FwIndexType portNum, FwEventIdType Id) { Fw::Logger::log("FATAL %" PRI_FwEventIdType " handled.\n", Id); - // Stop petting the external hardware watchdog. This is kept as belt-and-braces: on - // this deployment there is no Zephyr software watchdog, so the external HW watchdog - // (Components.Watchdog, rateGroup1Hz member) is the nominal reset path once pets stop. + // Stop petting the external watchdog. this->stopWatchdog_out(0); - // Do not rely solely on the external watchdog to eventually starve and reset the board - // (it may be absent/disconnected on a bench, or its timeout may be long). Delay briefly - // to allow the FATAL log/event to drain, then force a reboot directly so a real reset is - // guaranteed regardless of the external watchdog's state. - Os::Task::delay(Fw::TimeInterval( - 0, 1000)); // 1 ms (TimeInterval is seconds, microseconds); best-effort log drain before forced reboot + // Short delay so the FATAL log can drain, then force a reboot so the reset + // does not depend on the external watchdog. + Os::Task::delay(Fw::TimeInterval(0, 1000)); // 1 ms this->reboot(); } diff --git a/PROVESFlightControllerReference/Components/FatalHandler/FatalHandler.hpp b/PROVESFlightControllerReference/Components/FatalHandler/FatalHandler.hpp index 22a59672..4d69b58f 100644 --- a/PROVESFlightControllerReference/Components/FatalHandler/FatalHandler.hpp +++ b/PROVESFlightControllerReference/Components/FatalHandler/FatalHandler.hpp @@ -43,8 +43,7 @@ class FatalHandler final : public FatalHandlerComponentBase { FwEventIdType Id /*!< The ID of the FATAL event*/ ); - //! Reboot the device - //! + //! Reboot the device via Zephyr sys_reboot() void reboot(); }; diff --git a/PROVESFlightControllerReference/ReferenceDeployment/Main.cpp b/PROVESFlightControllerReference/ReferenceDeployment/Main.cpp index 3b67e806..18357fb9 100644 --- a/PROVESFlightControllerReference/ReferenceDeployment/Main.cpp +++ b/PROVESFlightControllerReference/ReferenceDeployment/Main.cpp @@ -21,9 +21,7 @@ const struct device* ina219Sys = DEVICE_DT_GET(DT_NODELABEL(ina219_0)); const struct device* ina219Sol = DEVICE_DT_GET(DT_NODELABEL(ina219_1)); const struct device* serial = DEVICE_DT_GET(DT_NODELABEL(cdc_acm_uart0)); -// v5c/v5d: Zephyr LoRa driver device node. -// v5e: USP does not use a Zephyr lora device; RalSessionImpl acquires the -// radio handle via smtc_rac_get_radio() in init(). +// The USP build (v5e) has no Zephyr lora device node; the radio is opened by RalSessionImpl. #ifndef CONFIG_LORA_BASICS_MODEM_DRIVERS const struct device* lora = DEVICE_DT_GET(DT_NODELABEL(lora0)); #endif @@ -83,11 +81,6 @@ int main(int argc, char* argv[]) { inputs.ina219SolDevice = ina219Sol; #ifndef CONFIG_LORA_BASICS_MODEM_DRIVERS inputs.loraDevice = lora; -#else - // v5e USP path: freq/power passed instead of a device pointer. - // Constants match LoRaConfig values used by the legacy driver. - inputs.uspFreqHz = 915000000U; - inputs.uspTxPowerDbm = 14; #endif inputs.uartDevice = serial; inputs.lsm6dsoDevice = lsm6dso; diff --git a/PROVESFlightControllerReference/ReferenceDeployment/Top/CMakeLists.txt b/PROVESFlightControllerReference/ReferenceDeployment/Top/CMakeLists.txt index 040a88fa..3aaf5b58 100644 --- a/PROVESFlightControllerReference/ReferenceDeployment/Top/CMakeLists.txt +++ b/PROVESFlightControllerReference/ReferenceDeployment/Top/CMakeLists.txt @@ -5,20 +5,12 @@ # AUTOCODER_INPUTS: list of files to be passed to the autocoders # DEPENDS: list of libraries that this module depends on # -# Per-board radio selection (Phase 4 — USP port): -# v5e (CONFIG_LORA_BASICS_MODEM_DRIVERS=y): -# RadioInstances.fppi -> RadioInstances_Usp.fppi (Zephyr::UspRadio) -# RadioTopology.fppi -> RadioTopology_Usp.fppi -# v5c / v5d (CONFIG_LORA_BASICS_MODEM_DRIVERS not set): -# RadioInstances.fppi -> RadioInstances_Lora.fppi (Zephyr::LoRa) -# RadioTopology.fppi -> RadioTopology_Lora.fppi -# -# FPP `include` directives resolve relative to the source file, so we -# create symbolic links in the source tree at configure time. The links -# are committed to .gitignore so they do not appear as untracked files. -# -# More information in the F´ CMake API documentation: -# https://fprime.jpl.nasa.gov/latest/docs/reference/api/cmake/API/ +# Per-board radio selection: +# CONFIG_LORA_BASICS_MODEM_DRIVERS set (v5e): Radio*.fppi -> Radio*_Usp.fppi (Zephyr::UspRadio) +# CONFIG_LORA_BASICS_MODEM_DRIVERS unset (v5c/v5d): Radio*.fppi -> Radio*_Lora.fppi (Zephyr::LoRa) +# FPP `include` resolves relative to the including file, so the links are +# created in the source tree at configure time. COPY_ON_ERROR falls back to a +# file copy where symlinks are not supported. The links are gitignored. #### if(DEFINED CONFIG_LORA_BASICS_MODEM_DRIVERS) @@ -27,16 +19,13 @@ else() set(RADIO_SUFFIX "Lora") endif() -# Generate per-board .fppi symlinks in the source dir. -# FPP `include` resolves relative to the source file, so symlinks live -# alongside the FPP files. CREATE_LINK SYMBOLIC COPY_ON_ERROR falls back -# to a file copy on platforms that don't support symlinks. +# Instance and topology snippets. foreach(_kind "Instances" "Topology") set(_src "${CMAKE_CURRENT_LIST_DIR}/Radio${_kind}_${RADIO_SUFFIX}.fppi") set(_dst "${CMAKE_CURRENT_LIST_DIR}/Radio${_kind}.fppi") file(CREATE_LINK "${_src}" "${_dst}" SYMBOLIC COPY_ON_ERROR) endforeach() -# Packet snippet symlinks (telemetry packet definitions reference per-board instance names) +# Packet snippets (they reference the per-board instance names). foreach(_kind "BytesReceived" "Radio") set(_src "${CMAKE_CURRENT_LIST_DIR}/RadioPackets${_kind}_${RADIO_SUFFIX}.fppi") set(_dst "${CMAKE_CURRENT_LIST_DIR}/RadioPackets${_kind}.fppi") diff --git a/PROVESFlightControllerReference/ReferenceDeployment/Top/RadioInstances_Lora.fppi b/PROVESFlightControllerReference/ReferenceDeployment/Top/RadioInstances_Lora.fppi index 24ecdf8e..8a13b0f4 100644 --- a/PROVESFlightControllerReference/ReferenceDeployment/Top/RadioInstances_Lora.fppi +++ b/PROVESFlightControllerReference/ReferenceDeployment/Top/RadioInstances_Lora.fppi @@ -1,6 +1,6 @@ -# Radio instance declarations for v5c / v5d (Zephyr LoRa legacy path). +# Radio instances for v5c / v5d (Zephyr LoRa driver). # Included by instances.fpp inside module ReferenceDeployment { }. -# Selected by CMakeLists.txt when CONFIG_LORA_BASICS_MODEM_DRIVERS is not set. +# Selected by Top/CMakeLists.txt per board. instance lora: Zephyr.LoRa base id 0x1001F000 diff --git a/PROVESFlightControllerReference/ReferenceDeployment/Top/RadioInstances_Usp.fppi b/PROVESFlightControllerReference/ReferenceDeployment/Top/RadioInstances_Usp.fppi index 2bfda5b8..dbbb151f 100644 --- a/PROVESFlightControllerReference/ReferenceDeployment/Top/RadioInstances_Usp.fppi +++ b/PROVESFlightControllerReference/ReferenceDeployment/Top/RadioInstances_Usp.fppi @@ -1,9 +1,8 @@ -# Radio instance declarations for v5e (Semtech USP path). +# Radio instances for v5e (Semtech USP driver). # Included by instances.fpp inside module ReferenceDeployment { }. -# Selected by CMakeLists.txt when CONFIG_LORA_BASICS_MODEM_DRIVERS is set. +# Selected by Top/CMakeLists.txt per board. # -# UspRadio is an ACTIVE component (deferred-handler SBand pattern). -# Queue = 20 (QUEUE_SIZE*2), stack = 4K, priority 11 (above rate groups). +# UspRadio is an active component. Priority 11 is above the rate groups. instance uspRadio: Zephyr.UspRadio base id 0x1001F000 \ queue size Default.QUEUE_SIZE * 2 \ diff --git a/PROVESFlightControllerReference/ReferenceDeployment/Top/RadioTopology_Lora.fppi b/PROVESFlightControllerReference/ReferenceDeployment/Top/RadioTopology_Lora.fppi index 26ec3b1e..601d2807 100644 --- a/PROVESFlightControllerReference/ReferenceDeployment/Top/RadioTopology_Lora.fppi +++ b/PROVESFlightControllerReference/ReferenceDeployment/Top/RadioTopology_Lora.fppi @@ -1,6 +1,6 @@ -# Radio topology connections for v5c / v5d (Zephyr LoRa legacy path). +# Radio connections for v5c / v5d (Zephyr LoRa driver). # Included by topology.fpp inside topology ReferenceDeployment { }. -# Selected by CMakeLists.txt when CONFIG_LORA_BASICS_MODEM_DRIVERS is not set. +# Selected by Top/CMakeLists.txt per board. instance lora instance loraRetry @@ -24,7 +24,7 @@ loraRetry.comStatusOut -> downlinkDelay.comStatusIn downlinkDelay.comStatusOut -> ComCcsdsLora.framer.comStatusIn - # --- Startup / sequence wiring (identical for both radio variants) --- + # Startup and sequence wiring (same in RadioTopology_Usp.fppi) startupManager.runSequence -> cmdSeq.seqRunIn # StartupManager receives sequence status from CmdSeq diff --git a/PROVESFlightControllerReference/ReferenceDeployment/Top/RadioTopology_Usp.fppi b/PROVESFlightControllerReference/ReferenceDeployment/Top/RadioTopology_Usp.fppi index 13737d07..8c05a5e9 100644 --- a/PROVESFlightControllerReference/ReferenceDeployment/Top/RadioTopology_Usp.fppi +++ b/PROVESFlightControllerReference/ReferenceDeployment/Top/RadioTopology_Usp.fppi @@ -1,9 +1,8 @@ -# Radio topology connections for v5e (Semtech USP path). +# Radio connections for v5e (Semtech USP driver). # Included by topology.fpp inside topology ReferenceDeployment { }. -# Selected by CMakeLists.txt when CONFIG_LORA_BASICS_MODEM_DRIVERS is set. +# Selected by Top/CMakeLists.txt per board. # -# UspRadio is active; no ComRetry shim needed (active-component queue provides -# back-pressure). Direct framer -> uspRadio.dataIn path. +# UspRadio is active, so its queue provides back-pressure and no ComRetry shim is needed. instance uspRadio @@ -15,13 +14,13 @@ uspRadio.dataOut -> ComCcsdsLora.frameAccumulator.dataIn ComCcsdsLora.frameAccumulator.dataReturnOut -> uspRadio.dataReturnIn - # UspRadio <-> Framer (Downlink, direct — no retry shim) + # UspRadio <-> Framer (Downlink) ComCcsdsLora.framer.dataOut -> uspRadio.dataIn uspRadio.dataReturnOut -> ComCcsdsLora.framer.dataReturnIn uspRadio.comStatusOut -> downlinkDelay.comStatusIn downlinkDelay.comStatusOut -> ComCcsdsLora.framer.comStatusIn - # --- Startup / sequence wiring (identical for both radio variants) --- + # Startup and sequence wiring (same in RadioTopology_Lora.fppi) startupManager.runSequence -> cmdSeq.seqRunIn # StartupManager receives sequence status from CmdSeq @@ -40,9 +39,7 @@ startupManager.enableTransmit -> uspRadio.enableTransmit startupManager.disableTransmit -> uspRadio.disableTransmit - # --- Radio ever enabled this boot? --- - # UspRadio names its first-start signal radioFirstStart (radio-agnostic); - # both ends are Fw.Signal so it binds to startupManager.loraFirstStart. + # Both ports are Fw.Signal, so the names may differ. uspRadio.radioFirstStart -> startupManager.loraFirstStart modeManager.runSequence -> safeModeSeq.seqRunIn @@ -57,7 +54,6 @@ connections RadioRateGroup { - # UspRadio run port: revert-deadline tick + telemetry flush at 1 Hz. - # Slot 20 is the first free slot after the legacy topology's highest used (19). + # 1 Hz tick for the revert deadline and telemetry flush. Slot 20 is the first unused rate-group slot. rateGroup1Hz.RateGroupMemberOut[20] -> uspRadio.run } diff --git a/PROVESFlightControllerReference/ReferenceDeployment/Top/ReferenceDeploymentPackets.fppi b/PROVESFlightControllerReference/ReferenceDeployment/Top/ReferenceDeploymentPackets.fppi index 538a80da..44733d25 100644 --- a/PROVESFlightControllerReference/ReferenceDeployment/Top/ReferenceDeploymentPackets.fppi +++ b/PROVESFlightControllerReference/ReferenceDeployment/Top/ReferenceDeploymentPackets.fppi @@ -30,8 +30,7 @@ telemetry packets ReferenceDeploymentPackets { ComCcsdsUart.tcSecurityDeframer.CurrentSequenceNumber # Radio bytes received: per-board instance name. - # RadioPackets.fppi is a symlink to RadioPackets_{Lora,Usp}.fppi - # created by CMakeLists.txt at configure time. + # Radio BytesReceived channel. Selected by Top/CMakeLists.txt per board. include "RadioPacketsBytesReceived.fppi" } @@ -48,7 +47,7 @@ telemetry packets ReferenceDeploymentPackets { imuManager.MagnetometerSamplingFrequency } - # Radio packet: per-board instance name. + # Radio packet. Selected by Top/CMakeLists.txt per board. include "RadioPacketsRadio.fppi" packet PowerMonitor id 11 group 2 { diff --git a/PROVESFlightControllerReference/ReferenceDeployment/Top/ReferenceDeploymentTopology.cpp b/PROVESFlightControllerReference/ReferenceDeployment/Top/ReferenceDeploymentTopology.cpp index 96203a0a..1b8670d1 100644 --- a/PROVESFlightControllerReference/ReferenceDeployment/Top/ReferenceDeploymentTopology.cpp +++ b/PROVESFlightControllerReference/ReferenceDeployment/Top/ReferenceDeploymentTopology.cpp @@ -15,18 +15,12 @@ #include #include -// Phase 4: per-board radio startup #ifdef CONFIG_LORA_BASICS_MODEM_DRIVERS -// v5e USP path: RalSessionImpl + UspRadio #include "fprime-zephyr/Drv/UspRadio/RalSessionImpl.hpp" #include "fprime-zephyr/Drv/UspRadio/UspRadio.hpp" -// Static RalSessionImpl instance (lives for the entire flight). -// 437.4 MHz / 70cm amateur band, matches GRC LoRaCfg DEFAULT_FREQ. TX power -// capped at 10 dBm. -static Zephyr::RalSessionImpl s_ralSession(437400000U, // 437.4 MHz - 10 // +10 dBm -); +// USP radio session: carrier frequency in Hz, TX power in dBm. +static Zephyr::RalSessionImpl s_ralSession(437400000U, 10); #endif // CONFIG_LORA_BASICS_MODEM_DRIVERS static const struct gpio_dt_spec ledGpio = GPIO_DT_SPEC_GET(DT_NODELABEL(led0), gpios); @@ -136,24 +130,10 @@ void setupTopology(const TopologyState& state) { // Autocoded task kick-off (active components). Function provided by autocoder. startTasks(state); - // We have a pipeline for both the radio and UART driver to allow for ground - // harness debugging and for over-the-air communications. - // - // Board selection (Phase 4 USP port): - // v5e (CONFIG_LORA_BASICS_MODEM_DRIVERS=y): UspRadio with RalSessionImpl - // v5c/v5d (legacy): Zephyr LoRa driver - // - // Both paths boot with TX DISABLED; the startup sequence enables TX after - // the mode manager permits it (identical gating to the legacy LoRa path). + // The radio instance (uspRadio or lora) is selected by Top/CMakeLists.txt per board. + // Both radios boot with TX disabled; the startup sequence enables TX later. #ifdef CONFIG_LORA_BASICS_MODEM_DRIVERS - // v5e USP path. - // 1. Inject the RalSessionImpl (constructed at file scope above) into - // the autocoded 'uspRadio' component via configure(). - // 2. UspRadio::startRadio() calls session.init() which internally calls - // zephyr_usp_initialization_wait() + zephyr_smtc_rac_init() (see - // RalSessionImpl::init()), applies the P0 boot-default profile, and - // starts continuous RX. TX stays DISABLED until the startup-sequence - // sends a TRANSMIT(ENABLED) command (same gating as the legacy path). + // startRadio() initialises the injected session, applies the boot profile and starts RX. uspRadio.configure(s_ralSession); if (!uspRadio.startRadio(Zephyr::UspTransmitState::DISABLED)) { Fw::Logger::log("[Topology] UspRadio startRadio() failed -- radio inactive\n"); diff --git a/PROVESFlightControllerReference/ReferenceDeployment/Top/ReferenceDeploymentTopologyDefs.hpp b/PROVESFlightControllerReference/ReferenceDeployment/Top/ReferenceDeploymentTopologyDefs.hpp index 7c309490..7ef06580 100644 --- a/PROVESFlightControllerReference/ReferenceDeployment/Top/ReferenceDeploymentTopologyDefs.hpp +++ b/PROVESFlightControllerReference/ReferenceDeployment/Top/ReferenceDeploymentTopologyDefs.hpp @@ -113,14 +113,8 @@ namespace ReferenceDeployment { struct TopologyState { const device* uartDevice; //!< UART device path for communication const device* spi0Device; //!< Spi device path for s-band LoRa module -#ifdef CONFIG_LORA_BASICS_MODEM_DRIVERS - // v5e (USP path): no Zephyr LoRa device; radio is initialised by - // RalSessionImpl via the USP/RAC API. Freq and power come from LoRaCfg - // constants re-exported below so callers don't need the Zephyr lora header. - uint32_t uspFreqHz; //!< Carrier frequency in Hz (e.g. 915000000) - int8_t uspTxPowerDbm; //!< TX power in dBm (e.g. 14) -#else - const device* loraDevice; //!< LoRa device path for communication (v5c/v5d) +#ifndef CONFIG_LORA_BASICS_MODEM_DRIVERS + const device* loraDevice; //!< LoRa device path for communication (not present on the USP build) #endif ComCcsdsLora::SubtopologyState comCcsdsLora; //!< Subtopology state for ComCcsdsLora // ComCcsdsSband::SubtopologyState comCcsdsSband; //!< Subtopology state for ComCcsdsSband diff --git a/PROVESFlightControllerReference/ReferenceDeployment/Top/instances.fpp b/PROVESFlightControllerReference/ReferenceDeployment/Top/instances.fpp index 73c4c42e..0f46fec3 100644 --- a/PROVESFlightControllerReference/ReferenceDeployment/Top/instances.fpp +++ b/PROVESFlightControllerReference/ReferenceDeployment/Top/instances.fpp @@ -103,9 +103,7 @@ module ReferenceDeployment { instance downlinkDelay: Components.ComDelay base id 0x1001E000 - # lora / uspRadio instance: per-board variant selected by CMakeLists.txt. - # CMake writes RadioInstances.fppi -> RadioInstances_{Lora,Usp}.fppi - # before the FPP autocoder runs. + # Radio instances (lora + loraRetry, or uspRadio). Selected by Top/CMakeLists.txt per board. include "RadioInstances.fppi" instance comSplitterEvents: Svc.ComSplitter base id 0x10020000 @@ -218,7 +216,7 @@ module ReferenceDeployment { instance fileUplinkCollector: Utilities.BufferCollector base id 0x10060000 instance telemetryDelay: Utilities.RateDelay base id 0x10061000 - # loraRetry is included via RadioInstances.fppi (Lora path only). + # loraRetry is declared in RadioInstances.fppi (LoRa path only). instance downlinkRepeater: Utilities.BufferRepeater base id 0x10064000 diff --git a/PROVESFlightControllerReference/ReferenceDeployment/Top/topology.fpp b/PROVESFlightControllerReference/ReferenceDeployment/Top/topology.fpp index b8c8cbe8..60aaf0b7 100644 --- a/PROVESFlightControllerReference/ReferenceDeployment/Top/topology.fpp +++ b/PROVESFlightControllerReference/ReferenceDeployment/Top/topology.fpp @@ -30,8 +30,7 @@ module ReferenceDeployment { instance rateGroup1Hz instance rateGroupDriver instance timer - # lora / uspRadio instance declared in RadioInstances_Lora.fpp or - # RadioInstances_Usp.fpp (CMakeLists.txt picks per board). + # Radio instances are listed in RadioTopology.fppi. instance gpioWatchdog instance gpioBurnwire0 instance gpioBurnwire1 @@ -191,10 +190,8 @@ module ReferenceDeployment { # comDelaySband.comStatusOut -> ComCcsdsSband.framer.comStatusIn #} - # CommunicationsRadio connections: per-board variant selected by CMake. - # CMake writes RadioTopology.fppi -> RadioTopology_{Lora,Usp}.fppi - # before the FPP autocoder runs. That file also carries the - # startup-sequence and RTC cancel-sequence wiring (identical for both). + # Radio instances, CommunicationsRadio connections, and the startup-sequence + # and RTC cancel-sequence wiring. Selected by Top/CMakeLists.txt per board. include "RadioTopology.fppi" connections CommunicationsUart { diff --git a/PROVESFlightControllerReference/test/int/radio_test.py b/PROVESFlightControllerReference/test/int/radio_test.py index 7cb477a5..fa2c36af 100644 --- a/PROVESFlightControllerReference/test/int/radio_test.py +++ b/PROVESFlightControllerReference/test/int/radio_test.py @@ -15,8 +15,6 @@ pytestmark = [pytest.mark.uart_only] downlinkDelay = "ReferenceDeployment.downlinkDelay" -# v5e builds Zephyr::UspRadio (not the legacy Zephyr::LoRa component); TRANSMIT -# and the error/warning event names are kept verbatim on UspRadio.fpp. radio = "ReferenceDeployment.uspRadio" RADIO_ERROR_EVENTS = ("SendFailed", "ConfigurationFailed", "AllocationFailed") diff --git a/PROVESFlightControllerReference/test/int/rf_profile_matrix_test.py b/PROVESFlightControllerReference/test/int/rf_profile_matrix_test.py index 0372cac1..3d7a8461 100644 --- a/PROVESFlightControllerReference/test/int/rf_profile_matrix_test.py +++ b/PROVESFlightControllerReference/test/int/rf_profile_matrix_test.py @@ -1,47 +1,34 @@ """ rf_profile_matrix_test.py: -Integration tests for the RF mode/profile matrix (HIL regression plan rung 6). - -Codifies the Phase A bench evidence (logs/HIL-REGRESSION-REPORT-phaseA.md): - 1. SET_TX_PROFILE / SET_RX_PROFILE sweep across the profile set and back, - asserting each switch completes and the radio stays commandable. - 2. CONTINUOUS_WAVE with clean restore to RX (complements the issue-#207 - regression test in radio_test.py by proving the RX path re-arms — - a profile switch after CW exercises stop→reconfig→re-arm). - 3. Profile-switch-as-first-post-wake command — the exact SX126x wakeup-race - shape fixed by the busy-race settle delay (carried patch 0006): idle, - then issue a profile switch as the first command, repeated N times. - 4. Post-idle (>=90 s) profile switch sanity. - 5. Two-board profile-pairing tests (marked two_board_rf): bidirectional - frame delivery at each TX/RX profile pairing against a ground radio - running GRC-USP firmware. Skipped unless the ground-side environment - hooks are configured (see below). - - 6. GFSK/GMSK first-TX-after-switch wedge kill recipe (regression for the - Phase B anomaly-B wedge, fixed in fprime-zephyr PR #21): healthy P0 - radio traffic → brief idle → P0→P4/P5 switch → immediate TX. Pre-fix, - the first TX after switching into a (G)FSK profile hit an ACTIVE RX and - hung the SX126x in TX (SendFailed -116 → COMSTATUS FAILURE latch). - -Runtime knobs (env vars; defaults are CI-sane, raise them for hammer runs): - RF_PROFILE_HAMMER_CYCLES post-wake switch cycles (default 5; bench hammer 100) - RF_WEDGE_KILL_CYCLES kill-recipe repetitions per profile (default 2; - bench matrix used 4+ reps x idle sweep) - RF_WEDGE_IDLE_S idle seconds before the kill-recipe switch (default 1) +Integration tests for the USP radio link-profile commands: SET_TX_PROFILE, +SET_RX_PROFILE, CONTINUOUS_WAVE, and the first TX after a profile switch. + +Test groups: + - TX and RX profile sweeps through every LinkProfileId and back to P0. + - CONTINUOUS_WAVE with restore to RX. + - Profile switch as the first command after an idle window. + - Profile switch after a long idle window. + - GFSK/GMSK first TX after a profile switch. + - Two-board profile pairing against a ground radio (marked two_board_rf; + skipped unless the USP_GROUND_* env vars below are set). + +Env vars: + RF_PROFILE_HAMMER_CYCLES post-wake switch cycles (default 5) + RF_WEDGE_KILL_CYCLES first-TX-after-switch repetitions per profile + (default 2) + RF_WEDGE_IDLE_S idle seconds before the first-TX-after-switch + profile switch (default 1) RF_PROFILE_WAKE_IDLE_S idle seconds before each post-wake switch (default 3) - RF_PROFILE_LONG_IDLE_S idle seconds for the post-idle sanity test (default 90) + RF_PROFILE_LONG_IDLE_S idle seconds for the post-idle test (default 90) USP_GROUND_DATA_TTY ground radio data-CDC device; downlinked RF frames - appear here as raw bytes (e.g. /dev/cu.usbmodem21103) - USP_GROUND_CMD shell command template to set the ground radio RX + appear here as raw bytes + USP_GROUND_CMD shell command template that sets the ground radio RX profile; "{profile}" is replaced with the numeric - LinkProfileId (e.g. a script wrapping fprime-cli - against the ground GDS) - USP_GROUND_UPLINK_CMD shell command that makes the ground radio transmit - at least one RF frame at the profile whose numeric - LinkProfileId replaces "{profile}" (e.g. a script - that sets the ground TX profile then sends a bypass - NO_OP through a GDS attached to the ground data CDC) + LinkProfileId + USP_GROUND_UPLINK_CMD shell command template that makes the ground radio + transmit at least one RF frame; "{profile}" is + replaced with the numeric LinkProfileId """ import os @@ -54,26 +41,24 @@ from fprime_gds.common.models.serialize.time_type import TimeType from fprime_gds.common.testing_fw.api import IntegrationTestAPI -# Profile switches sever any in-flight RF link (stop→reconfig→re-arm), so this -# whole module must only run when the GDS is connected via UART. +# A profile switch drops any in-flight RF link, so this module only runs when +# the GDS is connected over UART. pytestmark = [pytest.mark.uart_only] downlinkDelay = "ReferenceDeployment.downlinkDelay" radio = "ReferenceDeployment.uspRadio" tlmSend = "CdhCore.tlmSend" -# TlmPacketizer packet id used to force an immediate downlink frame (and thus -# an immediate radio TX while TRANSMIT is ENABLED): Health, id 2. +# TlmPacketizer packet id (Health). SEND_PKT with this id forces a downlink +# frame, and so a radio TX while TRANSMIT is ENABLED. HEALTH_PACKET_ID = 2 # Events that indicate the radio rejected or failed a reconfiguration. PROFILE_ERROR_EVENTS = ("ConfigurationFailed", "InvalidProfile") RADIO_ERROR_EVENTS = ("SendFailed", "ConfigurationFailed", "AllocationFailed") -# LinkProfileId sweep order (P0 is the boot default, so the sweep ends by -# restoring it). Matches the Phase A rung-6 pairing order, extended with -# P4/P5 (validated on the bench in Phase B: throughput ladder + wedge-fix -# matrix; profile table v2). +# LinkProfileId sweep order. P0 is the boot default, so the sweep ends by +# restoring it. PROFILE_SWEEP = [ "P1_LORA_SF10", "P2_LORA_SF5", @@ -94,12 +79,12 @@ "P5_GMSK_83K": 5, } -# Continuous-wave burst duration (seconds); kept short so the command stays -# within the GDS command-completion timeout (same rationale as radio_test.py). +# Continuous-wave burst duration (seconds). Kept short so the command completes +# within the GDS command-completion timeout. CW_SECONDS = 5 -# RX auto-revert is disabled (revert_s=0) for all single-board switches — the -# tests restore P0 explicitly, and an auto-revert mid-test would race the +# RX auto-revert is disabled (revert_s=0) for all single-board switches. The +# tests restore P0 explicitly; an auto-revert mid-test would race the # ProfileChanged assertions. NO_REVERT = 0 @@ -133,8 +118,8 @@ def _assert_no_profile_errors( def _switch_profile( fprime_test_api: IntegrationTestAPI, direction: str, profile: str ) -> None: - """Issue a SET_TX_PROFILE/SET_RX_PROFILE and assert the deferred apply - completed cleanly (ProfileChanged emitted, no error events).""" + """Send SET_TX_PROFILE or SET_RX_PROFILE and assert the deferred apply + completed: ProfileChanged is emitted and no profile error events follow.""" start = _now_start() if direction == "TX": proves_send_and_assert_command( @@ -144,8 +129,8 @@ def _switch_profile( proves_send_and_assert_command( fprime_test_api, f"{radio}.SET_RX_PROFILE", [profile, NO_REVERT] ) - # The command handler defers the apply to the component thread; the switch - # is only complete once ProfileChanged is emitted. + # The command handler defers the apply to the component thread. The switch + # is complete only once ProfileChanged is emitted. result = fprime_test_api.await_event( f"{radio}.ProfileChanged", start=start, timeout=10 ) @@ -159,9 +144,8 @@ def _switch_profile( @pytest.fixture(autouse=True) def setup_test(fprime_test_api: IntegrationTestAPI, start_gds): - """Set the downlink divider before each test; after each test disable - transmit and restore both profiles to the boot default so no test (or the - RF link of a subsequent session) inherits an off-default configuration.""" + """Set the downlink divider before each test. After each test, disable + transmit and restore both profiles to the boot default.""" proves_send_and_assert_command( fprime_test_api, f"{downlinkDelay}.DIVIDER_PRM_SET", @@ -182,19 +166,19 @@ def setup_test(fprime_test_api: IntegrationTestAPI, start_gds): def test_01_tx_profile_sweep(fprime_test_api: IntegrationTestAPI, start_gds): - """SET_TX_PROFILE sweep P0→P1→P2→P3 and back to P0: every switch must - complete with ProfileChanged and no error events, and the radio must stay - commandable throughout (rung-6 single-board half).""" + """Sweep SET_TX_PROFILE through every profile in PROFILE_SWEEP, ending at P0. + Assert each switch emits ProfileChanged with no error events and the board + stays commandable.""" for profile in PROFILE_SWEEP: _switch_profile(fprime_test_api, "TX", profile) - # Radio (and command path) still alive after the switch. + # The command path is still alive after the switch. proves_send_and_assert_command(fprime_test_api, f"{cmdDispatch}.CMD_NO_OP") def test_02_rx_profile_sweep(fprime_test_api: IntegrationTestAPI, start_gds): - """SET_RX_PROFILE sweep P0→P1→P2→P3 and back to P0. Each switch is a full - stop→reconfig→re-arm of the receiver; all must complete cleanly with the - board still commandable.""" + """Sweep SET_RX_PROFILE through every profile in PROFILE_SWEEP, ending at P0. + Assert each switch emits ProfileChanged with no error events and the board + stays commandable.""" for profile in PROFILE_SWEEP: _switch_profile(fprime_test_api, "RX", profile) proves_send_and_assert_command(fprime_test_api, f"{cmdDispatch}.CMD_NO_OP") @@ -203,30 +187,22 @@ def test_02_rx_profile_sweep(fprime_test_api: IntegrationTestAPI, start_gds): def test_03_continuous_wave_restore_to_rx( fprime_test_api: IntegrationTestAPI, start_gds ): - """CONTINUOUS_WAVE on/off with clean restore to RX. - - Complements radio_test.py::test_02_continuous_wave_repeated (issue #207, - which proves repeated CW and a TRANSMIT afterwards): here the post-CW - assertion is that the *receive* chain is healthy — an RX profile switch - (stop→reconfig→re-arm of the receiver) must succeed after the CW teardown, - which fails if CW left the modem wedged or the RX path un-armed. - """ + """Run CONTINUOUS_WAVE, then assert the receiver can still be reconfigured + (RX profile switch away from P0 and back) and that TRANSMIT ENABLED + produces no radio error events.""" proves_send_and_assert_command( fprime_test_api, f"{radio}.CONTINUOUS_WAVE", [CW_SECONDS], ) - # Wait out the CW duration so the asynchronous teardown (restore to RX) - # completes before probing the receiver. + # Wait out the CW duration so the asynchronous restore to RX completes. time.sleep(CW_SECONDS + 2) - # Receiver must be reconfigurable post-CW: exercise a full RX - # stop→reconfig→re-arm cycle away from and back to the boot profile. + # The receiver must be reconfigurable after CW. _switch_profile(fprime_test_api, "RX", "P2_LORA_SF5") _switch_profile(fprime_test_api, "RX", BOOT_PROFILE) - # And the TX path must be intact too (no wedge): enabling transmit must - # produce no radio error events. + # The TX path must also be intact: enabling transmit must produce no radio error events. start = _now_start() proves_send_and_assert_command(fprime_test_api, f"{radio}.TRANSMIT", ["ENABLED"]) time.sleep(10) @@ -238,18 +214,11 @@ def test_03_continuous_wave_restore_to_rx( def test_04_post_wake_profile_switch_hammer( fprime_test_api: IntegrationTestAPI, start_gds ): - """Profile switch as the first post-wake command (SX126x wakeup-race shape, - carried patch 0006). Idle long enough for the radio to sleep, then issue a - profile switch as the first command; repeat RF_PROFILE_HAMMER_CYCLES times - alternating P2↔P0 (the Phase A rung-4 hammer pattern, which scored 100/100 - on the fixed build vs ~40% first-command drops before the fix). - - Default cycle count is small for CI; set RF_PROFILE_HAMMER_CYCLES=100 (and - optionally RF_PROFILE_WAKE_IDLE_S) for a bench hammer run. - """ + """Idle for RF_PROFILE_WAKE_IDLE_S, then send an RX profile switch as the + first command; repeat RF_PROFILE_HAMMER_CYCLES times alternating P2 and P0. + Assert every switch completes with ProfileChanged and no error events.""" for cycle in range(HAMMER_CYCLES): - # Idle window: no commands, letting the modem reach its sleep state so - # the switch below is the first post-wake SPI command sequence. + # No commands during the idle window, so the switch below is the first SPI command sequence after the modem sleeps. time.sleep(WAKE_IDLE_S) profile = "P2_LORA_SF5" if cycle % 2 == 0 else BOOT_PROFILE try: @@ -262,10 +231,9 @@ def test_04_post_wake_profile_switch_hammer( def test_05_post_idle_profile_switch(fprime_test_api: IntegrationTestAPI, start_gds): - """Post-idle profile-switch sanity (Phase A rung-6 idle check): with - transmit disabled, stay command-idle for RF_PROFILE_LONG_IDLE_S (default - 90 s), then issue a profile switch as the first command and verify the - board is fully commandable afterwards.""" + """With transmit disabled, idle for RF_PROFILE_LONG_IDLE_S, then send an RX + profile switch as the first command. Assert the switch completes and the + board is commandable afterwards.""" proves_send_and_assert_command(fprime_test_api, f"{radio}.TRANSMIT", ["DISABLED"]) time.sleep(LONG_IDLE_S) _switch_profile(fprime_test_api, "RX", "P1_LORA_SF10") @@ -279,14 +247,9 @@ def _force_tx_and_await_advance( context: str, timeout: float = 45.0, ) -> int: - """Force an immediate radio TX (SEND_PKT with TRANSMIT ENABLED) and wait - for uspRadio.BytesSent to advance past ``floor``. - - Channel movement is the robust TX-health signal here (per the bench flake - ledger): BytesSent increments only when a radio transmission actually - completes, so a delta both proves the TX went out and avoids the - short-timeout event-window flakiness of bare assert_event checks. - """ + """Send SEND_PKT to force a downlink frame, wait for uspRadio.BytesSent to + pass ``floor``, and return the new value. BytesSent increments only when a + radio transmission completes, so an increase proves the TX went out.""" proves_send_and_assert_command( fprime_test_api, f"{tlmSend}.SEND_PKT", [HEALTH_PACKET_ID, "REALTIME"] ) @@ -309,44 +272,29 @@ def _force_tx_and_await_advance( def test_08_gfsk_wedge_kill_recipe( fprime_test_api: IntegrationTestAPI, start_gds, target_profile ): - """Regression for the Phase B anomaly-B TX wedge (fixed in fprime-zephyr - PR #21, fix/usp-radio-gfsk-rx-tx-wedge). - - Pre-fix kill recipe (deterministic within <=3 cycles on the bench): healthy - P0 radio traffic → brief idle → P0→P4/P5 profile switch → the FIRST TX - after switching into a (G)FSK profile aborted the freshly armed continuous - RX without quiescing the chip, so SetTx hit an ACTIVE GFSK/GMSK RX and hung - the SX126x in TX forever (SendFailed -116 + XOSC_START_ERR, then the - COMSTATUS FAILURE latch parked ComQueue → downlink dead until reboot). - LoRa RX tolerates the same abuse, which is why P0 was clean for months. - - Post-fix, every first-TX-after-switch must complete: no SendFailed / - ConfigurationFailed, and uspRadio.BytesSent must keep advancing (channel - movement, not just event silence) at the target profile AND after the - return to P0. Repeat RF_WEDGE_KILL_CYCLES times (default small for CI; - the bench validation matrix ran 4 reps x idle {0,0.25,1,5}s x {P4,P5}). - """ + """Force a TX at P0, idle for RF_WEDGE_IDLE_S, switch the TX profile to a + GFSK/GMSK profile, force a TX immediately, then switch back to P0 and + force a TX again; repeat RF_WEDGE_KILL_CYCLES times. Assert + uspRadio.BytesSent advances after every forced TX and no radio error + events are logged.""" proves_send_and_assert_command(fprime_test_api, f"{radio}.TRANSMIT", ["ENABLED"]) try: for cycle in range(WEDGE_KILL_CYCLES): ctx = f"(cycle {cycle + 1}/{WEDGE_KILL_CYCLES}, {target_profile})" - # 1. Healthy P0 traffic: prove the radio TX path is moving before - # the switch so a post-switch stall is unambiguous. + # Force a TX at P0 to establish the BytesSent baseline. baseline = _force_tx_and_await_advance( fprime_test_api, None, f"at P0 baseline {ctx}" ) - # 2. Brief command-idle window before the switch (the pre-fix - # wedge fired across idle lengths 0-5 s; default 1 s). + # Idle before the switch. time.sleep(WEDGE_IDLE_S) - # 3. P0 -> target profile switch... + # Switch the TX profile from P0 to the target profile. start = _now_start() _switch_profile(fprime_test_api, "TX", target_profile) - # 4. ...then IMMEDIATE TX: the first TX after switching into - # GFSK/GMSK was the exact pre-fix kill moment. + # Force the first TX after the switch. baseline = _force_tx_and_await_advance( fprime_test_api, baseline, f"on first TX after P0->{ctx}" ) @@ -359,8 +307,7 @@ def test_08_gfsk_wedge_kill_recipe( f"{ctx}: {result}" ) - # 5. Return to P0 and prove TX still advances (the pre-fix latch - # survived a P4->P5 switch; any wedge must show up here too). + # Return to P0 and force a TX again. start = _now_start() _switch_profile(fprime_test_api, "TX", BOOT_PROFILE) _force_tx_and_await_advance( @@ -380,7 +327,8 @@ def test_08_gfsk_wedge_kill_recipe( # --------------------------------------------------------------------------- -# Two-board tests: require a ground radio (GRC-USP firmware) on the bench. +# Two-board tests: require a ground radio. Skipped unless the USP_GROUND_* env +# vars are set. # --------------------------------------------------------------------------- @@ -397,10 +345,9 @@ def _set_ground_rx_profile(profile: str) -> None: @pytest.mark.two_board_rf def test_06_two_board_pairing_downlink(fprime_test_api: IntegrationTestAPI, start_gds): - """Flight→ground frame delivery at each profile pairing: for each profile, - set the ground radio RX profile and the flight TX profile to match, enable - transmit, and assert RF frames reach the ground radio (raw bytes on its - data CDC). Requires USP_GROUND_CMD and USP_GROUND_DATA_TTY.""" + """For each profile, set the ground radio RX profile and the flight TX + profile to match, enable transmit, and assert raw RF bytes arrive on the + ground radio data CDC. Requires USP_GROUND_CMD and USP_GROUND_DATA_TTY.""" _require_ground("USP_GROUND_CMD", "USP_GROUND_DATA_TTY") import serial @@ -430,10 +377,10 @@ def test_06_two_board_pairing_downlink(fprime_test_api: IntegrationTestAPI, star @pytest.mark.two_board_rf def test_07_two_board_pairing_uplink(fprime_test_api: IntegrationTestAPI, start_gds): - """Ground→flight frame delivery at each profile pairing: for each profile, - match the flight RX profile to the ground TX profile, trigger a ground - transmission (USP_GROUND_UPLINK_CMD), and assert the flight radio saw the - frame (uspRadio.LastRssi update — set on every received frame).""" + """For each profile, set the flight RX profile to match the ground TX + profile, trigger a ground transmission with USP_GROUND_UPLINK_CMD, and + assert uspRadio.LastRssi updates (it is set on every received frame). + Requires USP_GROUND_UPLINK_CMD.""" _require_ground("USP_GROUND_UPLINK_CMD") for profile in PROFILE_SWEEP: diff --git a/PROVESFlightControllerReference/test/unit-tests/CMakeLists.txt b/PROVESFlightControllerReference/test/unit-tests/CMakeLists.txt index 1e7dbf2d..5f229426 100644 --- a/PROVESFlightControllerReference/test/unit-tests/CMakeLists.txt +++ b/PROVESFlightControllerReference/test/unit-tests/CMakeLists.txt @@ -5,58 +5,49 @@ enable_testing() add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/../../../lib/fprime/googletest googletest-build) -# --- UspRadio: LinkProfiles host-side test (Phase 2) --- -# LinkProfiles.hpp is header-only and free of Zephyr/USP includes, so it -# compiles here with a minimal FPrimeBasicTypes stub provided inline in the test. +# --- UspRadio host-side tests --- +# These headers have no F', Zephyr or USP dependencies. LINK_PROFILES_USE_HOST_TYPES +# makes LinkProfiles.hpp, ProfilePolicy.hpp and RadioHeadShim.hpp use +# types instead of F' types. test_LinkProfiles defines it in its own source. + +# LinkProfiles.hpp (header-only). add_executable(test_LinkProfiles ${CMAKE_CURRENT_SOURCE_DIR}/../../../lib/fprime-zephyr/fprime-zephyr/Drv/UspRadio/test/ut/test_LinkProfiles.cpp ) target_include_directories(test_LinkProfiles PRIVATE - # Exposes #include "fprime-zephyr/Drv/UspRadio/LinkProfiles.hpp" ${CMAKE_CURRENT_SOURCE_DIR}/../../../lib/fprime-zephyr ) target_link_libraries(test_LinkProfiles gtest_main) add_test(NAME test_LinkProfiles COMMAND test_LinkProfiles) -# --- UspRadio: ProfilePolicy host-side test (Phase 3) --- -# ProfilePolicy.cpp is free of F'/USP/Zephyr includes (host-compilable). -# LINK_PROFILES_USE_HOST_TYPES is defined inside the test source to activate -# cstdint typedefs in both ProfilePolicy.hpp and LinkProfiles.hpp. +# ProfilePolicy.cpp plus its test. The define applies to both translation units. add_executable(test_ProfilePolicy ${CMAKE_CURRENT_SOURCE_DIR}/../../../lib/fprime-zephyr/fprime-zephyr/Drv/UspRadio/test/ut/test_ProfilePolicy.cpp ${CMAKE_CURRENT_SOURCE_DIR}/../../../lib/fprime-zephyr/fprime-zephyr/Drv/UspRadio/ProfilePolicy.cpp ) target_include_directories(test_ProfilePolicy PRIVATE - # Exposes #include "fprime-zephyr/Drv/UspRadio/ProfilePolicy.hpp" - # and #include "fprime-zephyr/Drv/UspRadio/LinkProfiles.hpp" ${CMAKE_CURRENT_SOURCE_DIR}/../../../lib/fprime-zephyr ) -# ProfilePolicy.cpp includes ProfilePolicy.hpp which includes LinkProfiles.hpp. -# Neither header pulls in F', Zephyr, or USP, so no additional link deps. target_compile_definitions(test_ProfilePolicy PRIVATE LINK_PROFILES_USE_HOST_TYPES) target_link_libraries(test_ProfilePolicy gtest_main) add_test(NAME test_ProfilePolicy COMMAND test_ProfilePolicy) -# --- UspRadio: RadioHeadShim host-side test (RadioHead-compat toggle) --- -# RadioHeadShim.hpp is header-only and free of F'/USP/Zephyr includes. +# RadioHeadShim.hpp (header-only). add_executable(test_RadioHeadShim ${CMAKE_CURRENT_SOURCE_DIR}/../../../lib/fprime-zephyr/fprime-zephyr/Drv/UspRadio/test/ut/test_RadioHeadShim.cpp ) target_include_directories(test_RadioHeadShim PRIVATE - # Exposes #include "fprime-zephyr/Drv/UspRadio/RadioHeadShim.hpp" ${CMAKE_CURRENT_SOURCE_DIR}/../../../lib/fprime-zephyr ) target_compile_definitions(test_RadioHeadShim PRIVATE LINK_PROFILES_USE_HOST_TYPES) target_link_libraries(test_RadioHeadShim gtest_main) add_test(NAME test_RadioHeadShim COMMAND test_RadioHeadShim) -# --- UspRadio: TxOutcomePolicy host-side test (GFSK wedge-fix item 3) --- -# TxOutcomePolicy.hpp is header-only and free of F'/USP/Zephyr includes. +# TxOutcomePolicy.hpp (header-only, plain int/bool types, no define needed). add_executable(test_TxOutcomePolicy ${CMAKE_CURRENT_SOURCE_DIR}/../../../lib/fprime-zephyr/fprime-zephyr/Drv/UspRadio/test/ut/test_TxOutcomePolicy.cpp ) target_include_directories(test_TxOutcomePolicy PRIVATE - # Exposes #include "fprime-zephyr/Drv/UspRadio/TxOutcomePolicy.hpp" ${CMAKE_CURRENT_SOURCE_DIR}/../../../lib/fprime-zephyr ) target_link_libraries(test_TxOutcomePolicy gtest_main) diff --git a/boards/bronco_space/proves_flight_control_board_v5e/proves_flight_control_board_v5e_rp2350a_m33.dts b/boards/bronco_space/proves_flight_control_board_v5e/proves_flight_control_board_v5e_rp2350a_m33.dts index bbdec36d..fd7d71a1 100644 --- a/boards/bronco_space/proves_flight_control_board_v5e/proves_flight_control_board_v5e_rp2350a_m33.dts +++ b/boards/bronco_space/proves_flight_control_board_v5e/proves_flight_control_board_v5e_rp2350a_m33.dts @@ -3,18 +3,10 @@ * * SPDX-License-Identifier: Apache-2.0 * - * PROVES Flight Control Board v5e — SX1262 (E22-400M30S) via USP driver. + * PROVES Flight Control Board v5e: SX1262 (E22-400M30S) on SPI1 via the USP driver. * - * Same SoC + board base as v5d (inherits all v5 hardware: I2C sensors, - * UART, SPI0/SD-card, flash partitions, MCUboot layout), but the radio - * node on SPI1 is replaced with a USP-binding SX1262 node. Also carries - * the v5e hardware-bringup deltas from #385 (MCP23017 i2c1->i2c0 move, - * SBAND enable lines onto the MCP) merged in alongside the radio swap. - * - * Flash partition layout is inherited from proves_flight_control_board_v5.dtsi - * (16 MiB: boot 1M / slot0 1M / slot1 1M / test 1M / storage 12M — see dtsi). - * smtc_modem_hal_storage.c requires storage_partition; that node is defined - * in the shared dtsi at 0x400000 so no extra include is needed here. + * Everything else is inherited from proves_flight_control_board_v5.dtsi, + * including storage_partition, which smtc_modem_hal_storage.c requires. */ /dts-v1/; @@ -24,22 +16,12 @@ #include "../proves_flight_control_board_v5/proves_flight_control_board_v5.dtsi" -/* - * Replace the v5 SX1276 radio node with the USP SX1262 node. - * The v5.dtsi enables &spi1 with cs-gpios, pinctrl, and lora0 (sx1276@0). - * Delete the SX1276 node and add the USP-compatible SX1262 node at the - * same SPI1 CS0 slot. - */ #include -/* Remove the legacy sx1276 node inherited from v5.dtsi */ +/* Remove the v5 SX1276 node; the USP SX1262 node below takes its SPI1 CS0 slot. */ /delete-node/ &lora0; -/* - * Point the USP "chosen" transceiver handle at our sx1262 node. - * smtc_sw_platform_helper.c calls DEVICE_DT_GET(DT_CHOSEN(zephyr_lorawan_transceiver)) - * to obtain the ral_t* handle for the SX1262. - */ +/* USP (smtc_sw_platform_helper.c) gets its transceiver from DT_CHOSEN(zephyr_lorawan_transceiver). */ / { chosen { zephyr,lorawan-transceiver = &lora0_usp; @@ -48,27 +30,20 @@ &spi1 { /* - * USP SX1262 node — E22-400M30S module, SPI1 CS0 (same as v5 SX1276). + * USP SX1262 node for the E22-400M30S module. * - * Property notes (see REPORT-devicetree.md for mapping table): - * compatible: "semtech,sx1262-new" — USP binding; prevents the - * in-tree Zephyr lora driver (semtech,sx1262) from - * claiming this device. LORA_BASICS_MODEM_DRIVERS - * depends on !LORA so exactly one driver binds. - * spi-max-frequency: kept at 125000 Hz from the GRC bring-up value; - * may be a board-level SPI signal-integrity workaround - * (kept from v5e bring-up; revisit before production). - * tx/rx-enable-gpios: USP upstream has no external RF-switch support; - * carried via our patch (spikes/patches/0001-feat-…). - * dio3-as-tcxo-control + tcxo-voltage: replaces dio3-tcxo-voltage in - * the in-tree binding. - * tcxo-wakeup-time: replaces tcxo-power-startup-delay-ms. - * reg-mode: LDO required on E22-400M30S (no DC-DC inductor). + * compatible "semtech,sx1262-new" is the USP binding. It keeps the + * in-tree Zephyr lora driver (semtech,sx1262) from binding this node. + * tx/rx-enable-gpios drive the module RF switch. They need the + * RF-switch support in the Open-Source-Space-Foundation usp_zephyr fork. + * The TCXO is on DIO3 at 1.8 V (dio3-as-tcxo-control, tcxo-voltage) + * with a 10 ms wakeup time. reg-mode is LDO: the E22-400M30S has no + * DC-DC inductor. */ lora0_usp: sx1262_usp@0 { compatible = "semtech,sx1262-new"; reg = <0>; - spi-max-frequency = <125000>; /* kept from v5e bring-up; revisit */ + spi-max-frequency = <125000>; reset-gpios = <&gpio0 6 GPIO_ACTIVE_LOW>; busy-gpios = <&gpio0 13 GPIO_ACTIVE_HIGH>; dio1-gpios = <&gpio0 14 (GPIO_ACTIVE_HIGH | GPIO_PULL_DOWN)>; @@ -82,11 +57,6 @@ }; }; -/* - * v5e hardware bring-up (from #385, merged in alongside the USP radio swap - * above — independent of which radio driver binds). - */ - /* v5e moves the MCP23017 from i2c1 to i2c0. */ &i2c1 { /delete-node/ mcp23017@20; diff --git a/boards/bronco_space/proves_flight_control_board_v5e/proves_flight_control_board_v5e_rp2350a_m33_defconfig b/boards/bronco_space/proves_flight_control_board_v5e/proves_flight_control_board_v5e_rp2350a_m33_defconfig index 6c481e65..d48c820b 100644 --- a/boards/bronco_space/proves_flight_control_board_v5e/proves_flight_control_board_v5e_rp2350a_m33_defconfig +++ b/boards/bronco_space/proves_flight_control_board_v5e/proves_flight_control_board_v5e_rp2350a_m33_defconfig @@ -36,15 +36,13 @@ CONFIG_INA219=y CONFIG_TMP112=y CONFIG_VEML6031=y -# Radio — USP path (SX1262, E22-400M30S) -# CONFIG_LORA is intentionally omitted: USP uses "semtech,sx1262-new" binding -# and LORA_BASICS_MODEM_DRIVERS depends on !LORA. Exactly one driver binds. +# Radio: USP path (SX1262, E22-400M30S). +# CONFIG_LORA stays off: LORA_BASICS_MODEM_DRIVERS depends on !LORA. CONFIG_LORA_BASICS_MODEM_DRIVERS=y CONFIG_LORA_BASICS_MODEM_DRIVERS_RAL_RALF=y CONFIG_LORA_BASICS_MODEM_DRIVERS_EVENT_TRIGGER_GLOBAL_THREAD=y CONFIG_USP=y -# USP main thread: provides zephyr_usp_initialization_wait() + smtc_rac run loop. -# RalSessionImpl calls zephyr_usp_initialization_wait() before smtc_rac_open_radio(). +# Provides zephyr_usp_initialization_wait() and the smtc_rac run loop. CONFIG_USP_MAIN_THREAD=y # RTC diff --git a/bootloader/sysbuild/mcuboot.conf b/bootloader/sysbuild/mcuboot.conf index e88a4c66..9628cc01 100644 --- a/bootloader/sysbuild/mcuboot.conf +++ b/bootloader/sysbuild/mcuboot.conf @@ -1,11 +1,4 @@ -# MCUboot-image-specific Kconfig fragment (sysbuild convention: -# sysbuild/.conf). The v5e board defconfig enables the USP radio -# stack (CONFIG_LORA_BASICS_MODEM_DRIVERS / CONFIG_USP), which is correct -# for the main application image but leaks into MCUboot too since both -# images share the same board defconfig. MCUboot never needs the radio -# and its build has no CONFIG_LOG, so usp_zephyr's smtc_modem_hal.c -# (which calls log_panic()) fails to link: "undefined reference to -# z_impl_log_panic". Disable the radio stack for the bootloader image -# only; disabling LORA_BASICS_MODEM_DRIVERS transitively disables USP -# (USP `depends on LORA_BASICS_MODEM_DRIVERS`). +# Sysbuild per-image Kconfig fragment (sysbuild/.conf) for MCUboot. +# MCUboot shares the board defconfig but has no CONFIG_LOG, so usp_zephyr does +# not link. Disabling LORA_BASICS_MODEM_DRIVERS also disables USP. CONFIG_LORA_BASICS_MODEM_DRIVERS=n diff --git a/docs/adr/0001-semtech-usp-for-sx126x-radio-path.md b/docs/adr/0001-semtech-usp-for-sx126x-radio-path.md index b03a0049..439f1d83 100644 --- a/docs/adr/0001-semtech-usp-for-sx126x-radio-path.md +++ b/docs/adr/0001-semtech-usp-for-sx126x-radio-path.md @@ -1,4 +1,4 @@ -# 0001 — Adopt Semtech USP (RAL layer) for the SX126x radio path; retain loramac-node for SX127x +# 0001: Adopt Semtech USP (RAL layer) for the SX126x radio path; retain loramac-node for SX127x Date: 2026-07-04 Status: Accepted @@ -13,17 +13,16 @@ downlink) and a clean CW path. Three integration options existed: 1. **Semtech `usp_zephyr` module, component talks to RAL/RAC directly.** Full - modulation menu (LoRa/GFSK/LR-FHSS/CW), future LR20xx path. Supports SX126x/LR11xx/ - LR20xx only — no SX127x, no SX128x. Validated on Zephyr 4.2 (we run 4.3). + modulation menu (LoRa/GFSK/LR-FHSS/CW). Supports SX126x/LR11xx/LR20xx only, no + SX127x, no SX128x. Validated on Zephyr 4.2. 2. **In-tree Zephyr 4.3 LBM backend** (`drivers/lora/lora_basics_modem`, needs the - `lora-basics-modem` west module). Zero component change, covers SX126x *and* SX127x — + `lora-basics-modem` west module). Zero component change, covers SX126x *and* SX127x, but sits behind the standard `lora_modem_config` API, so no new modulations. 3. **Two-phase**: option 2 first for all boards, option 1 later. Touches every board - twice, roughly doubles schedule. + twice. -Hardware reality: FCB v5/v5c/v5d carry SX1276 (SX127x — unsupported by USP forever; -Semtech has ended new SX127x software). FCB v5e carries SX1262. The S-band component -(SX1280 via RadioLib) is unsupported by USP until LR2021-class hardware exists. +FCB v5/v5c/v5d carry SX1276 (SX127x), unsupported by USP. FCB v5e carries SX1262. +The S-band component (SX1280 via RadioLib) is unsupported by USP. ## Decision @@ -36,18 +35,16 @@ Semtech has ended new SX127x software). FCB v5e carries SX1262. The S-band compo - The new component keeps the existing Svc.Com + Svc.BufferAllocation port surface and the `TRANSMIT` / `CONTINUOUS_WAVE` command names and arguments verbatim, adding profile commands on top (see ADR 0002). -- S-band stays on RadioLib; LR2021 is noted as the future convergence path. +- S-band stays on RadioLib. - Component is **active (queued)** using the SBand deferred-handler pattern: all - RAL/SPI work runs on the component thread, callbacks only enqueue. (The SX126x - post-sleep first-SPI-command drop we diagnosed is the class of bug this prevents.) + RAL/SPI work runs on the component thread, callbacks only enqueue. ## Consequences -- Modulation features land only on v5e+; v5c/v5d get no new radio capability, ever. -- Two radio components coexist in lib/fprime-zephyr indefinitely; ground dictionaries - differ per board revision. -- We take on Zephyr 4.3-vs-4.2 validation risk for usp_zephyr ourselves (Phase 0 spike - gates the plan). +- Modulation features land only on v5e+; v5c/v5d get no new radio capability. +- Two radio components coexist in lib/fprime-zephyr; ground dictionaries differ per + board revision. +- We take on Zephyr 4.3-vs-4.2 validation risk for usp_zephyr ourselves. - Devicetree must ensure exactly one driver binds the `semtech,sx1262` node when both the in-tree Zephyr driver and USP's driver are present in the tree. - The standard Zephyr LoRa shell/API tooling does not see the USP radio; all operations diff --git a/docs/adr/0002-link-profile-table.md b/docs/adr/0002-link-profile-table.md index 6fd9aebe..df22d335 100644 --- a/docs/adr/0002-link-profile-table.md +++ b/docs/adr/0002-link-profile-table.md @@ -1,4 +1,4 @@ -# 0002 — Versioned Link Profile table, split TX/RX selection, RX auto-revert +# 0002: Versioned Link Profile table, split TX/RX selection, RX auto-revert Date: 2026-07-04 Status: Accepted @@ -6,36 +6,35 @@ Status: Accepted ## Context GFSK introduces many coupled RF parameters (bitrate, deviation, BT, sync word, CRC, -preamble). A single mismatched field between spacecraft and ground silently kills the -link, and a bad RX configuration on the spacecraft strands it (deaf to commands). +preamble). A single mismatched field between spacecraft and ground kills the link, +and a bad RX configuration on the spacecraft strands it (deaf to commands). Free-form per-parameter F´ params (the current `CODING_RATE`/`DATA_RATE`/`BANDWIDTH_*` approach, extended) would make mismatches easy and atomic switches impossible. The dominant operational use case is asymmetric: keep the uplink (spacecraft RX) on robust LoRa while switching only the downlink (spacecraft TX) to GFSK for bulk data. -The existing component already has separate TX/RX bandwidth params — the asymmetry -precedent exists. +The existing component already has separate TX/RX bandwidth params. ## Decision - Radio configuration is selected only by **Link Profile index** into a **versioned Profile Table** checked into one shared artifact consumed by both the flight build - and the GRC build. The table version is downlinked as telemetry. -- Selection is **per direction**: `SET_TX_PROFILE(idx)` and `SET_RX_PROFILE(idx, - revert_s)`. -- `SET_TX_PROFILE` is unguarded — worst case is a lost downlink until the next command. + and the GRC build. +- Selection is **per direction**: `SET_TX_PROFILE(profile)` and + `SET_RX_PROFILE(profile, revert_s)`. +- `SET_TX_PROFILE` is unguarded: worst case is a lost downlink until the next command. - `SET_RX_PROFILE` is guarded by **RX Auto-Revert**: if no valid frame is received within `revert_s`, RX reverts to the Boot-Default Profile. A valid frame received on the new profile confirms it. There is no separate confirm command. -- Individual RF parameters are not commandable in operations; effective parameters are - visible read-only via telemetry. (A lab-only raw-config path was considered and - deferred — bench experiments can rebuild the table instead.) +- Individual RF parameters are not commandable in operations. The active TX and RX + profile indices are telemetered. A lab-only raw-config path was considered and + deferred: experiments rebuild the table instead. ## Consequences - Adding or changing a profile is a coordinated flight+ground release (table version - bump), not an on-orbit parameter tweak. This is deliberate friction. -- The uplink can be experimented with safely; a failed RX experiment self-heals within + bump), not an on-orbit parameter tweak. +- The uplink can be experimented with safely; a failed RX experiment reverts within the revert window. - The legacy `Zephyr::LoRa` params remain only on SX127x boards; the new component does not carry them. diff --git a/patches/README.md b/patches/README.md index fd774704..b5cbdaa1 100644 --- a/patches/README.md +++ b/patches/README.md @@ -1,15 +1,22 @@ # Patches Directory -This directory once carried a stack of module patches (usp_zephyr, usp, zephyr, -fprime) applied at build time. As of 2026-07-26 all of those fixes have been -migrated to the `Open-Source-Space-Foundation` fork integration branches -(`feat/proves-usp-radio`), which are pinned directly in `west.yml` and -`.gitmodules`. The former patch-apply Makefile targets (`usp-patches`, -`usp-core-patches`, `zephyr-patches`, and the fprime steps in `submodules`) -were removed with them. +This directory holds one patch: `fprime-yamcs-noapp-path.patch`. -Where the removed patches live now (integration PRs, each linking its -constituent PRs): +It patches the pip-installed `fprime-yamcs` package's `fprime_yamcs/__main__.py`, +fixing `--no-app` path handling: `parsed_args.dictionary` is passed as a `Path` +where the callee expects one, and the venv `bin` directory is prepended to `PATH` +so the Java subprocesses can find the fprime-yamcs console scripts. + +**Application:** applied automatically by `make fprime-venv` (and therefore by +`make`); skipped if already applied. + +`*.patch` files keep trailing whitespace: pre-commit excludes `patches/` because +the patch context must byte-match the file it patches. + +## Former patches + +The following module patches were migrated to `Open-Source-Space-Foundation` fork +integration branches pinned in `west.yml` and `.gitmodules`. | Former patch | Module | Integration PR | |---|---|---| @@ -17,12 +24,3 @@ constituent PRs): | 0009 radio-planner failsafe unlock exemption | usp | Open-Source-Space-Foundation/usp#3 | | 0005 + 0007 CDC-ACM TX fixes | zephyr | Open-Source-Space-Foundation/zephyr#3 | | fprime-com-aggregator-bounded-timeout, fprime-sched-tick-drop | fprime | Open-Source-Space-Foundation/fprime#5 | - -## fprime-yamcs-noapp-path.patch (the one remaining patch) - -Patches the *pip-installed* `fprime-yamcs` package (not a git submodule), so it -cannot move to a fork pin and remains a carried patch. It fixes the `--no-app` -path handling in `fprime_yamcs/__main__.py`. - -**Application:** applied automatically by `make fprime-venv` (and therefore by -`make`), alongside the scripted fprime-yamcs fixes in `tools/`. diff --git a/pytest.ini b/pytest.ini index a6c34627..5e0b868b 100644 --- a/pytest.ini +++ b/pytest.ini @@ -7,7 +7,7 @@ markers = requires_antenna: marks tests that require the antenna board to be plugged in and the burnwire capacitor installed; skip on a bare flight controller requires_battery: marks tests that require the battery board connected with power flowing from the battery terminals; skip on a bare flight controller requires_watchdog_jumper: marks tests that require the JP6 watchdog jumper to be bridged so the watchdog can reset the MCU; skip when JP6 is open - two_board_rf: marks tests that require a second (ground) USP radio board on the bench; they self-skip unless the USP_GROUND_* environment hooks are configured + two_board_rf: marks tests that require a second (ground) USP radio board; skip unless the USP_GROUND_* environment variables are set filterwarnings = ignore::DeprecationWarning:yamcs\..* ignore::DeprecationWarning:google\.protobuf\..* diff --git a/west.yml b/west.yml index eb1ce496..23f1bbda 100644 --- a/west.yml +++ b/west.yml @@ -14,17 +14,10 @@ manifest: group-filter: [-babblesim, -optional] projects: - # Zephyr RTOS core — stock upstream zephyrproject-rtos/zephyr v4.4.1. - # No OSSF fork and no carried patches: the 2026-08-01 trim eliminated both - # Zephyr changes we used to carry, so this now tracks upstream verbatim. - # - CDC-ACM TX-FIFO drain fix (formerly patches/0005, OSSF/zephyr#1) is - # no longer required by the flight image. - # - poll_out bounded-wait layer (OSSF/zephyr#2) was pruned: its added - # bound sits behind data->flow_ctrl, which is false on the v5e from - # both devicetree and the explicit UART_CFG_FLOW_CTRL_NONE configure, - # so the code was unreachable in the flight image. + # Zephyr RTOS core - name: zephyr - revision: 1f6485eca25431b5ff27ce9a754218c9e559bbbb + repo-path: zephyr + revision: v4.4.1 path: lib/zephyr-workspace/zephyr west-commands: scripts/west-commands.yml import: @@ -95,19 +88,16 @@ manifest: revision: f4ead3bf4a6dab3a07d7b5f5315795c073db568d path: lib/zephyr-workspace/modules/fatfs - # USP radio stack (Semtech Unified Software Platform) — v5e+ boards only. - # Open-Source-Space-Foundation fork, integration branch feat/proves-usp-radio - # (RF-switch GPIO, Zephyr 4.3/4.4 compat, wakeup settle, RAC mutex — formerly - # patches/0001-0003,0006,0008,0010; constituent PRs tracked in OSSF/usp_zephyr#7). + # USP radio stack (Semtech Unified Software Platform) Zephyr module, v5e boards. + # Open-Source-Space-Foundation fork, integration branch feat/proves-usp-radio. - name: usp_zephyr url: https://github.com/Open-Source-Space-Foundation/usp_zephyr revision: 0bf3e208124d43c55fcb7f91bae6a9ac9812ee71 path: lib/zephyr-workspace/modules/lib/usp_zephyr - # USP core library (LBM + RAL) — pulled by usp_zephyr at runtime. - # Path mirrors the usp_zephyr west.yml convention (modules/lib/usp). - # OSSF fork, integration branch feat/proves-usp-radio (radio-planner failsafe - # unlock exemption, formerly patches/0009; tracked in OSSF/usp#3). + # USP core library (LBM + RAL), used by usp_zephyr. Path follows the + # usp_zephyr west.yml convention (modules/lib/usp). + # Open-Source-Space-Foundation fork, integration branch feat/proves-usp-radio. - name: usp url: https://github.com/Open-Source-Space-Foundation/usp revision: 8a60e4a16092f1bfa030b18494da267d8ca6a3ba