diff --git a/.github/workflows/build-app.yml b/.github/workflows/build-app.yml index 7775c01..192e639 100644 --- a/.github/workflows/build-app.yml +++ b/.github/workflows/build-app.yml @@ -3,6 +3,24 @@ name: Build app on: workflow_call: inputs: + board: + description: West board target, e.g. nrf54l15dk/nrf54l15/cpuapp. + type: string + required: true + board-id: + description: >- + Board identifier used in artifact/release names, - + (e.g. nrf54l15dk-nrf54l15). + type: string + required: true + sysbuild: + description: >- + Build with sysbuild (multi-image) instead of a single plain image. + Required for nRF54H20, whose Bluetooth controller runs on the radio + core (cpurad) as a separate image reached over HCI/IPC. + type: boolean + required: false + default: false version: description: >- Release version (vX.Y.Z[-suffix]). When set, app/VERSION is @@ -12,11 +30,6 @@ on: type: string required: false default: "" - artifact-name: - description: Name of the uploaded hex+elf artifact. - type: string - required: false - default: quickstart-bluetooth-nrf54l15dk secrets: MEMFAULT_PROJECT_KEY: description: Memfault project key baked into the build via CONFIG_MEMFAULT_NCS_PROJECT_KEY. @@ -67,26 +80,69 @@ jobs: - name: West zephyr-export run: west zephyr-export - - name: Build for nrf54l15dk/nrf54l15/cpuapp - # --no-sysbuild: plain app image, no MCUboot/sysbuild. + - name: Build for ${{ inputs.board }} + # L-series (nrf54l15/nrf54lm20) are single-core: --no-sysbuild yields one + # plain image. nRF54H20 needs sysbuild so the ipc_radio controller image + # (cpurad) and radio loader are built alongside the app image. run: | - west build -b nrf54l15dk/nrf54l15/cpuapp --no-sysbuild \ + set -euo pipefail + if [ "${{ inputs.sysbuild }}" = "true" ]; then + SYSBUILD_FLAG=--sysbuild + else + SYSBUILD_FLAG=--no-sysbuild + fi + west build -b ${{ inputs.board }} $SYSBUILD_FLAG \ project/app \ -- \ -DCONFIG_MEMFAULT_NCS_PROJECT_KEY=\"${{ secrets.MEMFAULT_PROJECT_KEY }}\" + - name: Resolve the app image build directory + id: appdir + # With sysbuild the primary app image lands in build/app; a plain build + # puts it directly in build. The .elf/.config we care about (Memfault + # instrumentation lives in the app image) come from here. + run: | + if [ "${{ inputs.sysbuild }}" = "true" ]; then + echo "dir=build/app" >> "$GITHUB_OUTPUT" + else + echo "dir=build" >> "$GITHUB_OUTPUT" + fi + - name: Read Memfault software version/type from the build config id: fwver # The exact strings the firmware reports, so Memfault symbol uploads link # to the right software version (no guessing the Zephyr format). run: | - CONF=build/zephyr/.config + CONF=${{ steps.appdir.outputs.dir }}/zephyr/.config VER=$(sed -nE 's/^CONFIG_MEMFAULT_NCS_FW_VERSION="(.*)"$/\1/p' "$CONF") TYPE=$(sed -nE 's/^CONFIG_MEMFAULT_NCS_FW_TYPE="(.*)"$/\1/p' "$CONF") echo "fw_version=$VER" >> "$GITHUB_OUTPUT" echo "fw_type=$TYPE" >> "$GITHUB_OUTPUT" echo "Memfault software: type=$TYPE version=$VER" + - name: Assemble the flashable hex + id: hex + # Single deliverable per board. nRF54H20 sysbuild emits one hex per + # domain (app + ipc_radio + radio_loader + UICR/BICR/periphconf); merge + # them into a single flashable image so every board ships one .hex, like + # the L-series plain build. mergehex.py is bundled with Zephyr (no + # dependency on external command-line tools). + run: | + set -euo pipefail + if [ "${{ inputs.sysbuild }}" = "true" ]; then + python3 zephyr/scripts/build/mergehex.py \ + -o build/merged.hex \ + build/app/zephyr/zephyr.hex \ + build/ipc_radio/zephyr/zephyr.hex \ + build/radio_loader/zephyr/zephyr.hex \ + build/uicr/zephyr/uicr.hex \ + build/uicr/zephyr/periphconf.hex \ + build/app/zephyr/bicr.hex + echo "path=build/merged.hex" >> "$GITHUB_OUTPUT" + else + echo "path=build/zephyr/zephyr.hex" >> "$GITHUB_OUTPUT" + fi + - name: Install SBOM (west ncs-sbom) dependencies # scancode-toolkit (one of the default license detectors) needs these # system libs on top of its pip deps. See: @@ -98,17 +154,26 @@ jobs: pip3 install --quiet -r nrf/scripts/requirements-west-ncs-sbom.txt - name: Generate SPDX SBOM + # ncs-sbom is sysbuild-aware: given the top-level build dir it reads + # domains.yaml and covers every image. run: | west ncs-sbom -d build \ --package-supplier "Nordic Semiconductor ASA" \ - --output-spdx build/zephyr/quickstart-bluetooth.spdx + --output-spdx build/quickstart-bluetooth.spdx + + - name: Stage build artifacts with canonical names + # Normalize names so downstream (release) handling is board-agnostic: + # the H20 merged hex and the L-series zephyr.hex both become zephyr.hex. + run: | + set -euo pipefail + mkdir -p out + cp "${{ steps.hex.outputs.path }}" out/zephyr.hex + cp "${{ steps.appdir.outputs.dir }}/zephyr/zephyr.elf" out/zephyr.elf + cp build/quickstart-bluetooth.spdx out/quickstart-bluetooth.spdx - name: Upload build artifacts (hex + elf + spdx) uses: actions/upload-artifact@v7 with: - name: ${{ inputs.artifact-name }} - path: | - build/zephyr/zephyr.hex - build/zephyr/zephyr.elf - build/zephyr/quickstart-bluetooth.spdx + name: quickstart-bluetooth-${{ inputs.board-id }} + path: out/ if-no-files-found: error diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 1d62555..a44cd4e 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -21,7 +21,27 @@ permissions: jobs: build: + strategy: + fail-fast: false + # Supported boards. Keep in sync with the matrix in release.yml. + # board-id (-) is used for artifact/release file names. + # sysbuild=true for nRF54H20 (multi-core: BLE controller on cpurad). + matrix: + include: + - board: nrf54l15dk/nrf54l15/cpuapp + board-id: nrf54l15dk-nrf54l15 + sysbuild: false + - board: nrf54lm20dk/nrf54lm20a/cpuapp + board-id: nrf54lm20dk-nrf54lm20a + sysbuild: false + - board: nrf54h20dk/nrf54h20/cpuapp + board-id: nrf54h20dk-nrf54h20 + sysbuild: true uses: ./.github/workflows/build-app.yml + with: + board: ${{ matrix.board }} + board-id: ${{ matrix.board-id }} + sysbuild: ${{ matrix.sysbuild }} secrets: MEMFAULT_PROJECT_KEY: ${{ secrets.MEMFAULT_PROJECT_KEY }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index f002dbc..96f03e5 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -3,16 +3,18 @@ # Manually dispatched. Given a release version string it: # 1. validates the version format and derives the git tag + Memfault software # version (scripts/app_version.py); -# 2. builds the firmware with app/VERSION set to that version (build-app.yml), -# so the device reports the release version to Memfault, and generates the -# SPDX SBOM (west ncs-sbom) alongside zephyr.hex + zephyr.elf; -# 3. renames those three build outputs to include the version + board/chip -# (matching the Asset Tracker Template release naming, e.g. +# 2. builds the firmware for every supported board with app/VERSION set to that +# version (build-app.yml), so each device reports the release version to +# Memfault, and generates the SPDX SBOM (west ncs-sbom) alongside +# zephyr.hex + zephyr.elf; +# 3. renames each board's three build outputs to include the version + +# board/chip (matching the Asset Tracker Template release naming, e.g. # quickstart-bluetooth-v1.2.3-nrf54l15dk-nrf54l15.hex) — no zip; -# 4. uploads zephyr.elf as symbols to the Memfault "quickstart-shared-project" -# (registers the software version for symbolication); -# 5. tags the target commit and creates a GitHub release with the renamed -# .hex, .elf, and .spdx attached directly. +# 4. uploads each board's zephyr.elf as symbols to the Memfault project +# (registers the software version for symbolication; every board has its +# own GNU Build ID); +# 5. tags the target commit and creates a single GitHub release with all +# boards' renamed .hex, .elf, and .spdx attached directly. # # The version input is the single knob — do NOT hardcode versions elsewhere; the # committed app/VERSION stays at 0.0.0-dev and is overwritten at build time. @@ -73,17 +75,45 @@ jobs: build: needs: validate + strategy: + fail-fast: false + # Supported boards. Keep in sync with the matrix in build.yml and the + # publish job below. sysbuild=true for nRF54H20 (BLE controller on cpurad). + matrix: + include: + - board: nrf54l15dk/nrf54l15/cpuapp + board-id: nrf54l15dk-nrf54l15 + sysbuild: false + - board: nrf54lm20dk/nrf54lm20a/cpuapp + board-id: nrf54lm20dk-nrf54lm20a + sysbuild: false + - board: nrf54h20dk/nrf54h20/cpuapp + board-id: nrf54h20dk-nrf54h20 + sysbuild: true uses: ./.github/workflows/build-app.yml with: version: ${{ inputs.version }} + board: ${{ matrix.board }} + board-id: ${{ matrix.board-id }} + sysbuild: ${{ matrix.sysbuild }} secrets: MEMFAULT_PROJECT_KEY: ${{ secrets.MEMFAULT_PROJECT_KEY }} + # Per-board: rename the build outputs, upload that board's symbols to Memfault, + # and stage the renamed files as a workflow artifact for the release job. publish: needs: [validate, build] + strategy: + fail-fast: false + # Keep in sync with the build matrix above. + matrix: + include: + - board-id: nrf54l15dk-nrf54l15 + - board-id: nrf54lm20dk-nrf54lm20a + - board-id: nrf54h20dk-nrf54h20 runs-on: ubuntu-24.04 permissions: - contents: write # tag push + release creation + contents: read defaults: run: shell: bash @@ -96,7 +126,7 @@ jobs: - name: Download build artifacts (hex + elf + spdx) uses: actions/download-artifact@v8 with: - name: quickstart-bluetooth-nrf54l15dk + name: quickstart-bluetooth-${{ matrix.board-id }} path: artifacts - name: Locate build outputs @@ -114,29 +144,29 @@ jobs: - name: Rename release files (version + board/chip) # Matches the Asset Tracker Template release naming convention: # ---. — attached directly, no zip. + # matrix.board-id is already -. id: release_files run: | set -euo pipefail - BASENAME="quickstart-bluetooth-${{ needs.validate.outputs.tag }}-nrf54l15dk-nrf54l15" + BASENAME="quickstart-bluetooth-${{ needs.validate.outputs.tag }}-${{ matrix.board-id }}" mkdir -p release cp "${{ steps.files.outputs.hex }}" "release/${BASENAME}.hex" cp "${{ steps.files.outputs.elf }}" "release/${BASENAME}.elf" cp "${{ steps.files.outputs.spdx }}" "release/${BASENAME}.spdx" - echo "hex=release/${BASENAME}.hex" >> "$GITHUB_OUTPUT" - echo "elf=release/${BASENAME}.elf" >> "$GITHUB_OUTPUT" - echo "spdx=release/${BASENAME}.spdx" >> "$GITHUB_OUTPUT" - - name: Upload release files as workflow artifacts (always, for inspection) + - name: Upload renamed release files as a workflow artifact + # Collected by the release job (and available for inspection on dry runs). uses: actions/upload-artifact@v7 with: - name: quickstart-bluetooth-${{ needs.validate.outputs.tag }}-release-files + name: quickstart-bluetooth-${{ needs.validate.outputs.tag }}-release-files-${{ matrix.board-id }} path: release/ if-no-files-found: error - name: Upload symbols to Memfault # Software type/version come from the build config so the symbol file is - # linked to the version the device reports. --check-uploaded skips - # symbols already uploaded (idempotent re-runs). + # linked to the version the device reports. Each board's ELF has its own + # GNU Build ID, so all three are uploaded under the same software + # version. --check-uploaded skips symbols already uploaded (idempotent). env: MEMFAULT_ORG_TOKEN: ${{ secrets.MEMFAULT_ORG_TOKEN }} MEMFAULT_ORG_SLUG: ${{ secrets.MEMFAULT_ORG_SLUG }} @@ -154,8 +184,25 @@ jobs: --check-uploaded \ "${{ steps.files.outputs.elf }}" + # Single GitHub release with every board's renamed files attached. + release: + needs: [validate, publish] + if: ${{ !inputs.dry_run }} + runs-on: ubuntu-24.04 + permissions: + contents: write # tag push + release creation + defaults: + run: + shell: bash + steps: + - name: Download all boards' renamed release files + uses: actions/download-artifact@v8 + with: + pattern: quickstart-bluetooth-${{ needs.validate.outputs.tag }}-release-files-* + path: release + merge-multiple: true + - name: Create or update GitHub release (also creates the tag) - if: ${{ !inputs.dry_run }} # gh release create makes the tag via the Releases API. Unlike git push, # that path is not blocked by the GITHUB_TOKEN workflow-file restriction, # so it works even when the target commit's workflows differ from main. @@ -163,9 +210,6 @@ jobs: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} GH_REPO: ${{ github.repository }} # gh has no checkout to infer the repo from TAG: ${{ needs.validate.outputs.tag }} - HEX: ${{ steps.release_files.outputs.hex }} - ELF: ${{ steps.release_files.outputs.elf }} - SPDX: ${{ steps.release_files.outputs.spdx }} TARGET_SHA: ${{ inputs.sha != '' && inputs.sha || github.sha }} run: | set -euo pipefail @@ -175,14 +219,19 @@ jobs: # Idempotent: create if missing, else re-attach the files (--clobber) so # re-running an existing version refreshes the assets instead of failing. if gh release view "$TAG" >/dev/null 2>&1; then - gh release upload "$TAG" "$HEX" "$ELF" "$SPDX" --clobber + gh release upload "$TAG" release/* --clobber else - gh release create "$TAG" "$HEX" "$ELF" "$SPDX" --target "$TARGET_SHA" \ + gh release create "$TAG" release/* --target "$TARGET_SHA" \ --title "$TAG" --generate-notes $PRERELEASE fi + dry-run-summary: + needs: [validate, publish] + if: ${{ inputs.dry_run }} + runs-on: ubuntu-24.04 + steps: - name: Dry-run summary - if: ${{ inputs.dry_run }} run: | echo "DRY RUN: skipped git tag and GitHub release for ${{ needs.validate.outputs.tag }}." - echo "Built ${{ steps.release_files.outputs.hex }}, ${{ steps.release_files.outputs.elf }}, ${{ steps.release_files.outputs.spdx }} and uploaded symbols to Memfault." + echo "Built and renamed all boards' files and uploaded symbols to Memfault." + echo "Renamed release files are attached as workflow artifacts for inspection." diff --git a/CLAUDE.md b/CLAUDE.md index 17e5bc1..8aca239 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -11,7 +11,15 @@ Service (MDS) BLE gateway path: the device collects heartbeat metrics and coredu serves them as chunks to a phone/desktop gateway that performs the HTTPS upload. **The device never does on-device HTTP/TLS.** -Target board is **`nrf54l15dk/nrf54l15/cpuapp` only**. +Supported boards: + +- `nrf54l15dk/nrf54l15/cpuapp` — single-core, plain single image. +- `nrf54lm20dk/nrf54lm20a/cpuapp` — single-core, plain single image. +- `nrf54h20dk/nrf54h20/cpuapp` — **multi-core**, built with **sysbuild** (see below). + +The L-series boards keep the on-app-core Bluetooth controller and the plain +single-image, no-bootloader model. The nRF54H20's Bluetooth controller runs on +the radio core (cpurad) over HCI/IPC, so it is the one board built with sysbuild. ## Build & run (T2 west workspace) @@ -24,13 +32,26 @@ west init -m https://github.com//quickstart-bluetooth cd west update -# Build / flash for the only supported board -west build -b nrf54l15dk/nrf54l15/cpuapp project/app +# Build / flash (L-series: plain single image) +west build -b nrf54l15dk/nrf54l15/cpuapp --no-sysbuild project/app +west flash + +# nRF54LM20 DK +west build -b nrf54lm20dk/nrf54lm20a/cpuapp --no-sysbuild project/app + +# nRF54H20 DK (multi-core: sysbuild builds the app + ipc_radio + radio loader) +west build -b nrf54h20dk/nrf54h20/cpuapp --sysbuild project/app west flash ``` -The single `zephyr.hex` is the complete image — there is **no MCUboot/sysbuild**, no -merged/signed hex, no DFU zip (OTA is explicitly out of scope). +For the **L-series** boards the single `zephyr.hex` is the complete image — **no +MCUboot/sysbuild**, no merged/signed hex, no DFU zip (OTA is explicitly out of +scope). The **nRF54H20** is the sole exception: sysbuild is required because its +Bluetooth controller is a separate `ipc_radio` image on cpurad, loaded from MRAM +into TCM by a radio loader (still **no MCUboot** — the radio-loader path is the +bootloader-free variant). CI merges the per-domain hexes into one release `.hex`. +The H20 support lives under `app/boards/`, `app/common/`, `app/sysbuild/`, and +`app/Kconfig.sysbuild`, mirroring the upstream `peripheral_lbs` sample. ## Architecture & key constraints @@ -70,10 +91,11 @@ merged/signed hex, no DFU zip (OTA is explicitly out of scope). ## Testing (on-hardware) The tests under `test/` are **on-hardware bench tests**: they drive a connected -**nRF54L15 DK** over its USB serial console and BLE. There is no pure-host test suite. -**If no DK is connected, ask the user whether they want to connect an nRF54L15 DK so -you can verify functionality before running anything.** On macOS a connected DK shows -up as `/dev/tty.usbmodem*01/03` (VCOM1 = `…03`); no ports means no board. +**DK** (nRF54L15, nRF54LM20, or nRF54H20) over its USB serial console and BLE. +There is no pure-host test suite. **If no DK is connected, ask the user whether +they want to connect one of the supported DKs so you can verify functionality +before running anything.** On macOS a connected L-series DK shows up as +`/dev/tty.usbmodem*01/03` (VCOM1 = `…03`); no ports means no board. - `test/gateway/` — a phone-free Node/noble MDS gateway that stands in for the mobile app (connects, secures the link, drains chunks, optionally uploads). Needs Node on diff --git a/README.md b/README.md index c7e2f5e..1473d49 100644 --- a/README.md +++ b/README.md @@ -12,8 +12,20 @@ metrics and coredumps and serves them over the Memfault Diagnostic Service (MDS) to a phone or desktop **gateway**, which uploads them to Memfault over HTTPS. **The device itself performs no HTTP/TLS.** -**Supported board:** nRF54L15 DK (`nrf54l15dk/nrf54l15/cpuapp`). This is the only -supported target. +**Supported boards:** + +| Board | Target | Build | +|-------|--------|-------| +| nRF54L15 DK | `nrf54l15dk/nrf54l15/cpuapp` | single image (`--no-sysbuild`) | +| nRF54LM20 DK | `nrf54lm20dk/nrf54lm20a/cpuapp` | single image (`--no-sysbuild`) | +| nRF54H20 DK | `nrf54h20dk/nrf54h20/cpuapp` | sysbuild (multi-image) | + +The L-series boards are single-core: the Bluetooth controller runs on the +application core, so the app is a single plain image. The nRF54H20 is multi-core +— its Bluetooth controller runs on the radio core (cpurad) reached over HCI/IPC +— so it is built with sysbuild, which also produces the radio image and a radio +loader. Releases ship one flashable `.hex` per board (for the nRF54H20 the +per-domain images are merged into one). ## What the device does @@ -58,12 +70,22 @@ west update ## Build & flash ```sh +# nRF54L15 DK / nRF54LM20 DK (single-core, plain single image): west build -b nrf54l15dk/nrf54l15/cpuapp --no-sysbuild project/app west flash + +# nRF54LM20 DK: +west build -b nrf54lm20dk/nrf54lm20a/cpuapp --no-sysbuild project/app + +# nRF54H20 DK (multi-core, needs sysbuild for the radio-core image): +west build -b nrf54h20dk/nrf54h20/cpuapp --sysbuild project/app +west flash ``` -The single `zephyr.hex` is the complete image - there is no MCUboot/sysbuild, -no DFU (OTA is out of scope). +For the L-series boards the single `zephyr.hex` is the complete image - there is +no MCUboot, no DFU (OTA is out of scope). The nRF54H20 build produces one image +per core plus board configuration (UICR/BICR); `west flash` programs them all, +and releases attach a single merged `.hex`. ## Provision the Memfault project key @@ -114,8 +136,9 @@ Memfault so coredumps and traces are symbolicated - without a matching ELF, Memfault shows *"Unknown location"*. ```sh -# zephyr.elf is produced next to zephyr.hex in the build directory: -# build/zephyr/zephyr.elf +# zephyr.elf is produced in the build directory: +# build/zephyr/zephyr.elf # L-series (plain build) +# build/app/zephyr/zephyr.elf # nRF54H20 (sysbuild: the app-core image) ``` Upload it via the Memfault web app (Software → Symbol Files) or the Memfault CLI. diff --git a/RELEASE.md b/RELEASE.md index 40a810b..14cd0bd 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -34,13 +34,19 @@ Given a version, it: 1. validates the version format and derives the git tag + Memfault software version (`scripts/app_version.py`); -2. builds the firmware with `app/VERSION` set to that version - (`build-app.yml`), so the device reports the release version to Memfault; -3. zips `zephyr.hex` + `zephyr.elf`; -4. uploads `zephyr.elf` as symbols to the Memfault - `quickstart-shared-project` (registers the software version for - symbolication); -5. tags the target commit and creates a GitHub release with the zip attached. +2. builds the firmware for **every supported board** with `app/VERSION` set to + that version (`build-app.yml`), so each device reports the release version to + Memfault (the nRF54H20 is built with sysbuild; see the board table in the + README); +3. renames each board's `.hex`, `.elf`, and `.spdx` to include the version and + board/chip (`quickstart-bluetooth---.`) and attaches + them directly — no zip (for the nRF54H20 the per-domain images are merged + into one `.hex`); +4. uploads each board's `.elf` as symbols to the Memfault project (each board + has its own GNU Build ID; all are registered under the same software version + for symbolication); +5. tags the target commit and creates a single GitHub release with every board's + renamed files attached. Inputs: diff --git a/app/Kconfig.sysbuild b/app/Kconfig.sysbuild new file mode 100644 index 0000000..34a8a9a --- /dev/null +++ b/app/Kconfig.sysbuild @@ -0,0 +1,20 @@ +# +# Copyright (c) 2026 Nordic Semiconductor +# +# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause +# + +# nRF54H20 is multi-core: the Bluetooth controller runs on the radio core +# (cpurad) and the app talks to it over HCI/IPC. Build the ipc_radio image for +# cpurad and load it from MRAM into TCM with the radio loader (no MCUboot, to +# match this app's single-image, bootloader-free model on the L-series boards). +config NRF_RADIO_LOADER + default y if SOC_NRF54H20 + +config NRF_DEFAULT_IPC_RADIO + default y + +config NETCORE_IPC_RADIO_BT_HCI_IPC + default y + +source "share/sysbuild/Kconfig" diff --git a/app/boards/nrf54h20dk_nrf54h20_cpuapp.conf b/app/boards/nrf54h20dk_nrf54h20_cpuapp.conf new file mode 100644 index 0000000..040bf22 --- /dev/null +++ b/app/boards/nrf54h20dk_nrf54h20_cpuapp.conf @@ -0,0 +1,24 @@ +# +# Copyright (c) 2026 Nordic Semiconductor ASA +# +# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause +# + +# Hardware Revision reported over the BLE Device Information Service. +CONFIG_BT_DIS_HW_REV_STR="nrf54h20" + +# Enable the radio core so sysbuild builds and flashes the ipc_radio image +# that provides the Bluetooth controller over HCI/IPC. +CONFIG_SOC_NRF54H20_CPURAD_ENABLE=y + +# Power management (mirrors the peripheral_lbs sample defaults for this board). +CONFIG_PM_DEVICE=y +CONFIG_PM_DEVICE_RUNTIME=y +CONFIG_PM=y +CONFIG_POWEROFF=y + +# MRAM latency optimization - allow MRAM to power off when idle. +CONFIG_MRAM_LATENCY_AUTO_REQ=n + +# Peak Current Management. +CONFIG_MPSL_FORCE_RRAM_ON_ALL_THE_TIME=n diff --git a/app/boards/nrf54h20dk_nrf54h20_cpuapp.overlay b/app/boards/nrf54h20dk_nrf54h20_cpuapp.overlay new file mode 100644 index 0000000..38a099f --- /dev/null +++ b/app/boards/nrf54h20dk_nrf54h20_cpuapp.overlay @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2025 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#include "../common/memory_map_tcm.dtsi" + +/* Disable unused peripherals to reduce power consumption */ +&usbhs { + status = "disabled"; +}; + +&canpll { + status = "disabled"; +}; + +&can120 { + status = "disabled"; +}; + +&adc { + status = "disabled"; +}; + +&pwm130 { + status = "disabled"; +}; + +&cpuapp_ieee802154 { + status = "disabled"; +}; + +&exmif { + status = "disabled"; +}; + +&uart120 { + status = "disabled"; +}; + +&uart135 { + status = "disabled"; +}; + +/* Enable PM runtime for uart136 (console UART) */ +&uart136 { + zephyr,pm-device-runtime-auto; +}; diff --git a/app/boards/nrf54l15dk_nrf54l15_cpuapp.conf b/app/boards/nrf54l15dk_nrf54l15_cpuapp.conf new file mode 100644 index 0000000..523966b --- /dev/null +++ b/app/boards/nrf54l15dk_nrf54l15_cpuapp.conf @@ -0,0 +1,4 @@ +# Board-specific overrides for the nRF54L15 DK. + +# Hardware Revision reported over the BLE Device Information Service. +CONFIG_BT_DIS_HW_REV_STR="nrf54l15" diff --git a/app/boards/nrf54lm20dk_nrf54lm20a_cpuapp.conf b/app/boards/nrf54lm20dk_nrf54lm20a_cpuapp.conf new file mode 100644 index 0000000..bd8acb4 --- /dev/null +++ b/app/boards/nrf54lm20dk_nrf54lm20a_cpuapp.conf @@ -0,0 +1,4 @@ +# Board-specific overrides for the nRF54LM20 DK. + +# Hardware Revision reported over the BLE Device Information Service. +CONFIG_BT_DIS_HW_REV_STR="nrf54lm20" diff --git a/app/common/memory_map_ram_cpurad.dtsi b/app/common/memory_map_ram_cpurad.dtsi new file mode 100644 index 0000000..4494afd --- /dev/null +++ b/app/common/memory_map_ram_cpurad.dtsi @@ -0,0 +1,13 @@ +/* + * Copyright (c) 2026 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +/* Use the whole TCM region for the remote firmware. */ +&cpurad_ram0 { + compatible = "mmio-sram"; + /delete-property/ reg; + reg = <0x23000000 0x30000>; + ranges = <0x0 0x23000000 0x30000>; +}; diff --git a/app/common/memory_map_ram_pm_cpurad.dtsi b/app/common/memory_map_ram_pm_cpurad.dtsi new file mode 100644 index 0000000..779761c --- /dev/null +++ b/app/common/memory_map_ram_pm_cpurad.dtsi @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2026 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +/* Power Management relocates the key functions to TCM under the address + * from the pm_ramfunc DTS node. + * It is done at the zephyr/soc/nordic/nrf54h/CMakeLists.txt file. + * Theoretically it would not be necessary to relocate it if the whole code is in TCM. + * The changes in Zephyr would be required to allow it. + * As a workaround, relocate the pm_ramfunc to the end of the RAM block but before + * the MCUboot trailer. + */ +/delete-node/ &pm_ramfunc; + +/ { + soc { + /* cache control functions - must be executed from RAM */ + pm_ramfunc: cpurad_s2ram@2302df40 { + compatible = "zephyr,memory-region", "mmio-sram"; + reg = <0x2302df40 192>; + zephyr,memory-region = "PMLocalRamfunc"; + }; + }; +}; diff --git a/app/common/memory_map_tcm.dtsi b/app/common/memory_map_tcm.dtsi new file mode 100644 index 0000000..f23cf8a --- /dev/null +++ b/app/common/memory_map_tcm.dtsi @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2026 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +/* + * CPURAD TCM load layout (no MCUboot): keep default cpuapp MRAM partitions. + * Replace the two 328 KiB cpurad XIP slots with loader + stored firmware pairs + * (see nrf/samples/nrf54h20/radio_loader/src/main.c nodelabel expectations). + */ + +/ { + soc { + /* Global RAM for radio_loader during memcpy (see board default layout). */ + cpurad_loader_ram: sram@2f039000 { + compatible = "mmio-sram"; + reg = <0x2f039000 0x1000>; + #address-cells = <1>; + #size-cells = <1>; + ranges = <0x0 0x2f039000 0x1000>; + }; + }; +}; + +&mram1x { + partitions { + /delete-node/ partition@92000; + /delete-node/ partition@152000; + + /* Primary: loader (MRAM) + ipc_radio image (MRAM, copied to TCM). */ + cpurad_slot0_partition: cpurad_loader_partition: partition@92000 { + compatible = "zephyr,mapped-partition"; + label = "cpurad_loader_partition"; + reg = <0x92000 DT_SIZE_K(8)>; + }; + + /* Max size == TCM region (see memory_map_ram_cpurad.dtsi, 192 KiB). */ + cpurad_slot2_partition: cpurad_loaded_fw: partition@94000 { + compatible = "zephyr,mapped-partition"; + label = "cpurad_loaded_fw"; + reg = <0x94000 DT_SIZE_K(192)>; + }; + + /* Secondary slot pair (same firmware sizes as primary). */ + cpurad_slot1_partition: cpurad_loader_partition_slot1: partition@152000 { + compatible = "zephyr,mapped-partition"; + label = "cpurad_loader_partition_slot1"; + reg = <0x152000 DT_SIZE_K(8)>; + }; + + cpurad_slot3_partition: cpurad_loaded_fw_slot1: partition@154000 { + compatible = "zephyr,mapped-partition"; + label = "cpurad_loaded_fw_slot1"; + reg = <0x154000 DT_SIZE_K(192)>; + }; + }; +}; diff --git a/app/prj.conf b/app/prj.conf index ac394a1..c19ec65 100644 --- a/app/prj.conf +++ b/app/prj.conf @@ -68,7 +68,7 @@ CONFIG_BT_DIS_MANUF_NAME_STR="Nordic Semiconductor" # Hardware Revision instead of Model Number; Firmware Revision tracks app/VERSION. CONFIG_BT_DIS_MODEL_NUMBER=n CONFIG_BT_DIS_HW_REV=y -CONFIG_BT_DIS_HW_REV_STR="nrf54l15" +# CONFIG_BT_DIS_HW_REV_STR is set per board in boards/.conf. CONFIG_BT_DIS_FW_REV=y # PnP_ID defaulted to an unrelated vendor (Ericsson) - not applicable here. CONFIG_BT_DIS_PNP=n diff --git a/app/src/main.c b/app/src/main.c index d346be0..9a01b60 100644 --- a/app/src/main.c +++ b/app/src/main.c @@ -12,7 +12,7 @@ * secured gateway connection; the gateway performs the HTTPS upload. The device * never does on-device HTTP/TLS. * - * Controls (nRF54L15 DK): + * Controls (nRF54L15 / nRF54LM20 / nRF54H20 DK): * Button 0 LBS button characteristic (standard LBS — central sees the press) * Button 1 DEMO-ONLY crash trigger (forces a fault -> Memfault coredump) * LED 0 run status (1 Hz heartbeat blink) diff --git a/app/sysbuild/ipc_radio/boards/nrf54h20dk_nrf54h20_cpurad.conf b/app/sysbuild/ipc_radio/boards/nrf54h20dk_nrf54h20_cpurad.conf new file mode 100644 index 0000000..d4a3848 --- /dev/null +++ b/app/sysbuild/ipc_radio/boards/nrf54h20dk_nrf54h20_cpurad.conf @@ -0,0 +1,15 @@ +# +# Copyright (c) 2026 Nordic Semiconductor ASA +# +# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause +# + +CONFIG_XIP=n + +CONFIG_BT_PERIPHERAL=y +CONFIG_BT_CENTRAL=n +CONFIG_BT_CTLR_PHY_CODED=n + +CONFIG_PSA_CRYPTO_DRIVER_OBERON=n +CONFIG_PSA_SSF_CRYPTO_CLIENT=y +CONFIG_PSA_CRYPTO=y diff --git a/app/sysbuild/ipc_radio/boards/nrf54h20dk_nrf54h20_cpurad.overlay b/app/sysbuild/ipc_radio/boards/nrf54h20dk_nrf54h20_cpurad.overlay new file mode 100644 index 0000000..e5a73b5 --- /dev/null +++ b/app/sysbuild/ipc_radio/boards/nrf54h20dk_nrf54h20_cpurad.overlay @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2026 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#include "../../../common/memory_map_tcm.dtsi" +#include "../../../common/memory_map_ram_cpurad.dtsi" +#include "../../../common/memory_map_ram_pm_cpurad.dtsi" + +/ { + chosen { + zephyr,code-partition = &cpurad_loaded_fw; + zephyr,sram = &cpurad_ram0; + zephyr,entropy = &psa_rng; + }; + + psa_rng: psa-rng { + compatible = "zephyr,psa-crypto-rng"; + status = "okay"; + }; + + /delete-node/ prng; +}; + +secondary_app_partition: &cpurad_loaded_fw_slot1 {}; diff --git a/app/sysbuild/ipc_radio/prj.conf b/app/sysbuild/ipc_radio/prj.conf new file mode 100644 index 0000000..4bbae3f --- /dev/null +++ b/app/sysbuild/ipc_radio/prj.conf @@ -0,0 +1,24 @@ +CONFIG_HEAP_MEM_POOL_SIZE=8192 +CONFIG_SYSTEM_WORKQUEUE_STACK_SIZE=2048 + +CONFIG_MBOX=y +CONFIG_IPC_SERVICE=y + +CONFIG_BT=y +CONFIG_BT_HCI_RAW=y +CONFIG_BT_CTLR_ASSERT_HANDLER=y +CONFIG_BT_MAX_CONN=4 + +CONFIG_ASSERT=y +CONFIG_EXCEPTION_STACK_TRACE=y + +CONFIG_IPC_RADIO_BT=y +CONFIG_IPC_RADIO_BT_HCI_IPC=y + +CONFIG_PM_DEVICE=y +CONFIG_PM_DEVICE_RUNTIME=y + +CONFIG_SERIAL=n +CONFIG_UART_CONSOLE=n +CONFIG_USE_SEGGER_RTT=n +CONFIG_LOG=n diff --git a/app/sysbuild/radio_loader.overlay b/app/sysbuild/radio_loader.overlay new file mode 100644 index 0000000..1436c5a --- /dev/null +++ b/app/sysbuild/radio_loader.overlay @@ -0,0 +1,17 @@ +/* + * Copyright (c) 2026 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#include "../common/memory_map_tcm.dtsi" +#include "../common/memory_map_ram_cpurad.dtsi" + +/ { + chosen { + zephyr,code-partition = &cpurad_loader_partition; + zephyr,sram = &cpurad_loader_ram; + }; +}; + +secondary_app_partition: &cpurad_loader_partition_slot1 {}; diff --git a/test/e2e/test_device_info.py b/test/e2e/test_device_info.py index c006142..fd17461 100644 --- a/test/e2e/test_device_info.py +++ b/test/e2e/test_device_info.py @@ -4,7 +4,7 @@ # # SPDX-License-Identifier: LicenseRef-Nordic-5-Clause # -# Validates the `mflt get_device_info` serial-shell command on the nRF54L15 DK +# Validates the `mflt get_device_info` serial-shell command on a supported DK # running the quickstart-bluetooth firmware. # # Regression coverage: without CONFIG_LOG, this command's output was silently diff --git a/test/e2e/test_project_key.py b/test/e2e/test_project_key.py index 6e55c1b..289638e 100644 --- a/test/e2e/test_project_key.py +++ b/test/e2e/test_project_key.py @@ -5,7 +5,7 @@ # SPDX-License-Identifier: LicenseRef-Nordic-5-Clause # # Validate the Memfault project-key serial-shell provisioning contract on the -# nRF54L15 DK running the quickstart-bluetooth firmware: +# A supported DK running the quickstart-bluetooth firmware: # # 1. After a clean flash the key is unset -> read returns "Setting not found". # 2. Writing the key and reading it back returns the written value. diff --git a/test/gateway/README.md b/test/gateway/README.md index 18707a8..de6d27a 100644 --- a/test/gateway/README.md +++ b/test/gateway/README.md @@ -14,7 +14,8 @@ Memfault's Web Bluetooth example, . - **macOS** (uses CoreBluetooth via noble; Linux works with BlueZ but is untested here). - **Node.js 18+** (uses the built-in `fetch`). -- A `nrf54l15dk` flashed with this firmware and advertising as `Quickstart_Bluetooth`. +- A supported DK (`nrf54l15dk`, `nrf54lm20dk`, or `nrf54h20dk`) flashed with this + firmware and advertising as `Quickstart_Bluetooth`. - On first run, macOS will ask to grant your terminal **Bluetooth** permission (System Settings → Privacy & Security → Bluetooth). The just-works pairing the firmware uses is handled automatically by CoreBluetooth — no manual dialog. diff --git a/test/gateway/gateway.js b/test/gateway/gateway.js index bc0a53f..f017224 100644 --- a/test/gateway/gateway.js +++ b/test/gateway/gateway.js @@ -5,7 +5,7 @@ */ /* - * Minimal MDS gateway for the nRF54L15 DK quickstart-bluetooth firmware. + * Minimal MDS gateway for the quickstart-bluetooth firmware (any supported DK). * Node port of memfault/web-ble-example's mds.js, using @abandonware/noble. * Upstream reference: https://github.com/memfault/web-ble-example * diff --git a/test/gateway/package.json b/test/gateway/package.json index 26ae2f6..e8b2a0f 100644 --- a/test/gateway/package.json +++ b/test/gateway/package.json @@ -2,7 +2,7 @@ "name": "qsbt-gateway", "version": "1.0.0", "private": true, - "description": "Phone-free MDS test gateway for the quickstart-bluetooth nRF54L15 DK firmware", + "description": "Phone-free MDS test gateway for the quickstart-bluetooth firmware", "license": "LicenseRef-Nordic-5-Clause", "scripts": { "gateway": "node gateway.js",