Skip to content

Latest commit

 

History

History
525 lines (429 loc) · 25.9 KB

File metadata and controls

525 lines (429 loc) · 25.9 KB

Flashing HiveInside

There are two ways to get firmware onto a board:

  • Flash a prebuilt release image — a download and one command, no Zephyr toolchain. Start below.
  • Build it yourself with west --sysbuild — the reference path, and the one to use when you are changing the firmware. That is the rest of this document, starting at Target: XIAO nRF54LM20A Sense.

Flashing a prebuilt release image

Every release carries the images CI built, in two variants, plus the tools to put them on a board.

Pick the right file

File What it is Flash it with
hiveinside-nrf54lm20a-v<version>-<variant>-factory.hex MCUboot at 0x0 plus the signed application in slot 0 — a complete, bootable device image SWD over USB (below)
hiveinside-nrf54lm20a-v<version>-<variant>.signed.bin The signed application alone: the BLE OTA payload HiveHub, over the air

<variant> is either lowpower — the deployment profile of low-power.md, no console, for a hive — or bringup, with the console on at 115200 8N1 for bench work. Both advertise identically, so a bringup image in a sealed hive looks healthy from HiveHub and just drains the battery. Check the suffix before flashing.

<version> is the firmware version from src/hive_config.h — the same numbers the node advertises — and is independent of the repository's release tag.

⚠️ Never flash a .signed.bin over SWD. It links at the slot-0 offset behind an MCUboot header, so on its own it leaves nothing at 0x0: the CPU faults before main() and the device is completely silent. Use the -factory.hex for SWD and the .signed.bin only for OTA.

Over USB, with the flashing bundle

Download hiveinside-flash-tools.zip and the factory hex for your variant from the same release, unzip, and run:

./flash.sh hiveinside-nrf54lm20a-v0.5.0-lowpower-factory.hex
.\flash.ps1 -Image hiveinside-nrf54lm20a-v0.5.0-lowpower-factory.hex

It needs OpenOCD on PATH and nothing else; the bundle carries the board's OpenOCD config from the Zephyr revision the images were built against. On Linux add the udev rule from the bundle's README so the debugger is usable without sudo. The scripts run the same OpenOCD sequence west flash --verify runs, verify the image by reading it back, and — importantly — correct the board's RRAM loader first (see the OpenOCD RRAM bug; the bundle's README explains the one-line fix).

The equivalent by hand, if you would rather not run a script, is:

openocd -f <board-openocd.cfg-with-the-RRAM-fix> \
  -c init -c "targets nrf54lm20a.cpu" -c "reset init" \
  -c "nrf54lm20a-load <image>.hex" \
  -c "reset init" -c "verify_image <image>.hex" \
  -c "reset run" -c shutdown

Over the air, for a node that already runs

Upload the .signed.bin in HiveHub's firmware form. It reads the target and version straight out of the file name, streams the image into the node's secondary slot, and MCUboot test-swaps with automatic rollback. Nothing is committed until size and CRC-32 verify, so a failed transfer leaves the node on its old firmware. See ota-over-ble.md.

Check what you downloaded

sha256sum -c SHA256SUMS      # shasum -a 256 -c SHA256SUMS on macOS

manifest.txt in the release lists each payload's size and CRC-32 (the values the OTA BEGIN frame carries), the commit and the Zephyr revision behind the build, and west-manifest-frozen.yml plus build-info-<variant>.zip are there to reproduce it.

The release workflow requires a project-owned MCUboot signing key and refuses to produce these artifacts when its HIVEINSIDE_SIGNING_KEY_PEM secret is absent. This is a required deployment step: the SDK's default development key is public, so a node built with it accepts firmware from anyone using that known key. For a local deployment build, generate and select the key with SB_CONFIG_BOOT_SIGNATURE_KEY_FILE exactly as shown in the ota-over-ble.md production checklist. Keep the private key outside the repository and preserve the matching public key plus an SWD recovery path. Signing is currently the trust boundary because the unencrypted OTA GATT writes have no HMAC; the filter-accept-list TODO remains in ota_init() pending coordinated HiveHub bonding support.

If the board does not come up

The troubleshooting further down this document applies unchanged — start at Device is silent after flashing.


Target: XIAO nRF54LM20A Sense

This firmware boots through MCUboot so it can accept firmware-over-BLE updates (see ota-over-ble.md). A bootable image is therefore MCUboot plus a signed application in slot 0. Only a west --sysbuild build produces both, and both must reach the chip:

# From a west workspace carrying the XIAO nRF54LM20A board definition. The
# repository's own west.yml pins an upstream Zephyr revision that has it; an
# nRF Connect SDK workspace needs the board added out of tree (see below).
west build --sysbuild -b <board-target> -d debug path/to/firmware-nrf54lm20a
west flash -d debug           # programs every sysbuild image over the on-board debugger
picocom -b 115200 /dev/ttyACM0  # serial console over the same cable (see below)

west flash run against the top-level sysbuild build directory reads build/domains.yaml and programs every image in flash order — MCUboot at 0x0 and the signed application at slot 0 (zephyr.signed.hex; Zephyr's cmake/mcuboot.cmake repoints the runner at it). Flashing a single image, or flashing build/firmware-nrf54lm20a/zephyr/zephyr.hex by hand, leaves the device unbootable — see "Device is silent after flashing" below.

merged.hex is an nRF Connect SDK artifact, not an upstream Zephyr one. NCS's sysbuild concatenates the images into build/merged.hex; a plain zephyrproject workspace does not create that file, and it is not needed — west flash handles the multi-image case on its own. Where this document and ota-over-ble.md refer to merged.hex as the SWD factory/recovery image, that applies to NCS builds; on upstream Zephyr the equivalent is "everything west flash programs".

Use a pristine build when changing the board, partition layout, bootloader, or signing configuration (west build --pristine --sysbuild ...). Before treating the result as a release, inspect the generated partition report and retain the artifacts for their distinct purposes: the full-flash image is for SWD factory/recovery, while the application's zephyr.signed.bin is the BLE OTA payload. Never send a full-flash image or the unsigned zephyr.bin through the OTA characteristic. See the production-key, release-test, and recovery checklist in ota-over-ble.md.

The board target is the west name of the Seeed board definition with its nRF54L core qualifier (for example xiao_nrf54lm20a/nrf54lm20a/cpuapp); use the exact name the board's board.yml declares.

The XIAO nRF54LM20A Sense has an on-board SAMD11 CMSIS-DAP debugger (Seeed USB VID:PID 0x2886:0x0068) connected to the SoC's SWD lines and brought out on the USB-C connector. No external probe is required — plug the board into USB and west flash programs the merged image over SWD through the on-board debugger.

A build without --sysbuild is not a flashing path

A plain west build produces a single application image. It never builds MCUboot, signs the app, or emits a merged hex (sysbuild.conf and sysbuild/mcuboot.conf are only read by a sysbuild build). It is useful as a compile check and nothing more.

⚠️ Do not flash an image built without --sysbuild. With MCUboot enabled, the application links at the slot-0 offset (behind an MCUboot header), so flashing it alone leaves nothing at 0x0: the CPU faults before main() runs and the device goes completely silent — no serial output, no BLE. This is the classic "builds fine but never boots" symptom. Always build with --sysbuild and flash with west flash.

The board definition's default runner uses OpenOCD's CMSIS-DAP path. Its support/openocd.cfg sources the generic interface/cmsis-dap.cfg without filtering on VID:PID, so with several CMSIS-DAP adapters attached OpenOCD binds to whichever it finds first — unplug the others, or select one explicitly with an adapter usb location/-i argument.

Do not use pyocd on the current silicon: it aborts during APPROTECT recovery with Memory transfer fault @ 0x00ffc31c-0x00ffc31f. CMSIS-DAP (OpenOCD) is the supported path. The board's board.cmake registers exactly three runners — openocd (the default), jlink and nrfutil — so west flash --runner probe-rs and --runner pyocd are not available for this board however the probe is attached.

Serial console over the same USB cable

The on-board SAMD11 also exposes a USB CDC ACM serial port and bridges it to the SoC's uart20, so application printk() and Zephyr logs appear on the host over the same USB-C cable used for flashing — no second adapter needed. It runs at 115200 8N1 and enumerates on Linux as /dev/ttyACM0 (the index can differ when other ACM devices are attached).

# Confirm which ACM node is the Seeed on-board debugger (VID 2886)
udevadm info -q property -n /dev/ttyACM0 | grep -E 'ID_VENDOR_ID|ID_MODEL|ID_SERIAL'
# ID_VENDOR_ID=2886  → Seeed XIAO nRF54LM20A on-board debugger

picocom -b 115200 /dev/ttyACM0      # or: screen /dev/ttyACM0 115200

On macOS the same port appears as /dev/cu.usbmodem* — there is no /dev/ttyACM0, so a copied-and-pasted /dev/ttyACM0 fails or silently binds nothing. List the ports first and pick the Seeed one; note that the board presents both a CDC ACM data port and the CMSIS-DAP interface, so more than one entry can appear:

ls /dev/cu.usbmodem*
picocom -b 115200 /dev/cu.usbmodemXXXX

The startup banner ([HiveInside] nrf54lm20a fw <version> | USB + HiveHub BLE beacon) prints once at boot, followed by a readout block every measurement cycle, so if the monitor is opened afterwards, press RST with it connected to see the banner and the first readout. The console is a plain polled UART, so the firmware never blocks on a missing terminal — it boots and keeps sampling regardless. (The nRF54's native usbhs is not wired to the port — the SAMD11 owns it — so the console must ride the debugger's UART bridge rather than a native USB-CDC device on the nRF54.)

Optional: using a XIAO RP2040 as an external CMSIS-DAP probe

The on-board debugger is sufficient for normal use; this is only for boards whose debugger is unavailable, or for bench setups that prefer an external probe. A spare XIAO RP2040 works as the SWD probe:

  1. Flash it with Raspberry Pi Debugprobe firmware: double-tap reset to mount the RPI-RP2 drive, then drag on debugprobe_on_pico.uf2 (from the raspberrypi/debugprobe releases). It re-enumerates as a CMSIS-DAP probe (0x2E8A:0x000C).

  2. Wire probe → target (both are 3.3 V, no level shifting):

    Probe (XIAO RP2040) Signal Target (XIAO nRF54LM20A)
    GP2 (pad D8) SWCLK SWCLK / SWDCLK
    GP3 (pad D10) SWDIO SWDIO
    GND GND GND

    Power the target from its own USB (or the probe's 3V3 — not both).

  3. Flash the merged image through the external probe with the OpenOCD runner. The board registers no probe-rs or pyocd runner, and the CMSIS-DAP interface config does not filter on VID:PID, so unplug the target's own debugger (or keep only one adapter attached) to be sure OpenOCD binds to the RP2040 (0x2E8A:0x000C) rather than to the on-board SAMD11.

On Linux, add a udev rule so the probe is accessible without sudo (SUBSYSTEM=="usb", ATTRS{idVendor}=="2e8a", MODE="0666"), then reload rules and replug.

The nRF54LM20A firmware reads all four sensors (SHT40, IMU, microphone, nPM1300 battery), prints the readout to this serial console, runs the vibration and acoustic FFT band analysis, broadcasts the HiveHub measurement beacon, and accepts firmware-over-BLE updates through MCUboot. See firmware-nrf54lm20a/README.md for the readout format, the build details, and the serial-console setup, and ota-over-ble.md for the OTA protocol.

The west --sysbuild build is the flashing path

Because the firmware boots through MCUboot, a bootable device needs both the bootloader and the signed application, and only west --sysbuild produces them — see the top of this document. Build it in a west workspace that has the XIAO nRF54LM20A board definition available: the repository's west.yml pins an upstream Zephyr revision carrying it, and an nRF Connect SDK workspace needs the board added out of tree (see the board-target section below). west flash programs every sysbuild image over the on-board CMSIS-DAP debugger; with an external probe (below) select the matching runner, e.g. west flash --runner jlink. Those are the only alternatives the board registers — openocd, jlink and nrfutil — and jlink needs a real J-Link probe.

⚠️ Not the "nRF Connect SDK Bare Metal" option (nrf-bm, board targets prefixed bm_). That is a separate, RTOS-free SDK line built on the SoftDevice and raw nrfx drivers — it has no Zephyr kernel, no Zephyr device model or devicetree-driven peripherals, and no Zephyr Bluetooth host. This firmware is a Zephyr application (zephyr/kernel.h, zephyr/bluetooth/*, zephyr/drivers/{regulator,i2c,sensor,gpio}.h, zephyr/audio/dmic.h, zephyr/dfu/mcuboot.h, plus the board overlays), so it cannot build there at any version. Use upstream Zephyr or the RTOS-based nRF Connect SDK. Note also that bm_nrf54lm20dk is Nordic's nRF54LM20 DK, a different board from the Seeed XIAO nRF54LM20A Sense this firmware targets.

The board target is missing from an nRF Connect SDK workspace

xiao_nrf54lm20a ships in upstream Zephyr, and only on main — no tagged Zephyr release up to v4.4.2 contains boards/seeed/xiao_nrf54lm20a/, which is why this repository's west.yml pins a main commit rather than a release. The nRF Connect SDK uses its own Zephyr fork (nrfconnect/sdk-zephyr), which does not carry it — not on main, v3.2-branch or v3.1-branch — so it never appears in the VS Code extension's board-target dropdown, however new the SDK is. Only Nordic's own nrf54lm20dk is there. Nothing is broken; the definition is simply absent.

Two ways forward:

  • Build against upstream Zephyrwest init -m <this repo> gives you a workspace at the pinned revision. This is the path the rest of this document describes and needs no extra setup.

  • Add the board out-of-tree to the NCS workspace. Copy the whole boards/seeed/xiao_nrf54lm20a/ directory out of upstream Zephyr into a board root of your own, keeping the boards/<vendor>/<board>/ structure:

    my-boards/
    └── boards/seeed/xiao_nrf54lm20a/     ← the upstream directory, unmodified
    

    Then point the build at the directory that contains boards/ — not at the board directory itself:

    west build --sysbuild -b xiao_nrf54lm20a/nrf54lm20a/cpuapp \
      path/to/firmware-nrf54lm20a -- -DBOARD_ROOT=/abs/path/to/my-boards

    In the VS Code extension the same value goes in the nRF Connect board roots setting, after which the target shows up in the dropdown. Use an absolute path: sysbuild resolves relative *_ROOT variables against the application directory, which is rarely what you mean.

    The copy is self-contained: seeed_xiao_connector.dtsi lives inside the board directory, and the SoC-level includes it pulls in (nordic/nrf54lm20a_cpuapp.dtsi, vendor/nordic/nrf54lm20_a_b_cpuapp_partition.dtsi) are both present in sdk-zephyr. Expect to re-sync the copy whenever upstream changes the board — this repo's ncs_fixups.overlay already exists because the two trees do not always agree.

Two failure modes of the VS Code route are worth recognising, because neither is caused by the board files:

  • "Loading boards…" that never finishes, or an error about an unrelated SoC (The SoC stm32c5a3xx was not found in the SDK). Adding a board root makes the extension re-scan every board it knows about, and it trips over its own stale board/SoC cache — the message says as much ("could be a result of the west update of the SDK that cleared the SoC cache"). Restart VS Code, or have the extension regenerate the cache; the STM32 name is noise, not a hint.
  • A build that fails "because of the Bare Metal SDK" after switching the SDK in the dialog. The selected SDK is baked into the build configuration and into build/CMakeCache.txt when it is first created. Changing the dropdown does not retarget an existing configuration — delete the build configuration and its build/ directory, then create it again against the RTOS-based SDK.

None of this affects the command line. west build --sysbuild from a plain zephyrproject workspace is the reference path and stays the quickest way to a known-good image.

If the MCUboot image fails to link

A --sysbuild build that dies while linking mcuboot/zephyr/zephyr_pre0.elf with undefined reference to z_impl_k_mutex_lock / k_work_submit / z_impl_k_usleep (usually preceded by the Kconfig warning I2C ... was assigned the value 'n' but got the value 'y') is the board's power-management drivers leaking into the bootloader: MCUboot is built single-threaded on Nordic SoCs, but the XIAO board tree enables CONFIG_REGULATOR plus the power_en regulator and the nPM1300 on a bit-banged I²C bus for every sysbuild image, and those drivers need the kernel mutex/work-queue APIs. sysbuild/mcuboot.conf turns CONFIG_REGULATOR and CONFIG_MFD off for the bootloader image to prevent this; if you see the error, check that fragment is being picked up (it must sit next to the application, i.e. firmware-nrf54lm20a/sysbuild/mcuboot.conf) and rebuild with --pristine. The failure is not host- or toolchain-specific, and a plain west build does not reproduce it — it never builds MCUboot.

Device is silent after flashing (no console, no BLE)

The firmware prints its banner as the very first statement of main(), before touching any sensor, and starts advertising a few lines later. Missing sensors never cause silence — an unconnected SHT40 just prints climate : n/a. So no console output and no advertising means the application is not running at all, and the fault is in the boot chain rather than in the application. Work through it in this order:

  1. Is the console itself proven? Check the port name (macOS uses /dev/cu.usbmodem*, not /dev/ttyACM0) and press RST with the monitor already attached — the banner prints once at reset and is easy to miss. Silence on BLE too makes a pure console problem unlikely, but this is the cheapest check.
  2. Did both images reach the chip? Run west flash against the top-level sysbuild build directory, not a sub-image directory, and read its output: it should program two images. Re-flash explicitly with west flash --domain mcuboot and west flash --domain firmware-nrf54lm20a if in doubt. An application alone at slot 0 with nothing at 0x0 faults before main() and is completely silent — this is the same brick the non-sysbuild warning above describes.
  3. Make MCUboot talk. sysbuild/mcuboot.conf disables the bootloader's console for size, which also means a bootloader that refuses to start slot 0 fails silently. Temporarily comment out the CONFIG_SERIAL=n / CONFIG_CONSOLE=n / CONFIG_UART_CONSOLE=n lines there, add CONFIG_MCUBOOT_LOG_LEVEL_INF=y, rebuild with --pristine and re-flash. MCUboot then reports on uart20 whether it found and validated a slot-0 image. A signature or image-magic complaint points at the signing key; no MCUboot output at all points back at step 2.
  4. Check the signing key matched. The application must be signed with the key the bootloader was built with. Zephyr does not fail the build when the application has no signing key — cmake/mcuboot.cmake only warns that the result "will not be bootable by MCUboot unless it is signed manually", and west flash then programs an unsigned image that MCUboot silently rejects. Confirm build/firmware-nrf54lm20a/zephyr/zephyr.signed.hex exists and that the configure log names the same key file for both images. See the next section for how to tell the two apart.

E: Image in the primary slot is not valid!

With the bootloader console enabled (step 3 above) a rejected application looks like this:

I: Starting bootloader
I: Primary image: magic=unset, swap_type=0x1, copy_done=0x3, image_ok=0x3
I: Boot source: none
E: Image in the primary slot is not valid!
E: Unable to find bootable image

magic=unset and image_ok=0x3 are not the problem — those describe the image trailer, which an SWD-flashed image legitimately does not have, and MCUboot boots such an image happily. The failure is boot_validate_slot() rejecting slot 0, which has exactly two causes: the header/signature does not verify against the key built into the bootloader, or the bytes on the chip are not the bytes imgtool produced. Two commands separate them:

# (a) Is the image itself validly signed with the bootloader's key?
#     Default sysbuild config uses MCUboot's RSA-2048 development key.
cd <west-topdir>
python bootloader/mcuboot/scripts/imgtool.py verify \
  -k bootloader/mcuboot/root-rsa-2048.pem \
  <build>/firmware-nrf54lm20a/zephyr/zephyr.signed.bin

# (b) Do the bytes on the chip match the hex? (openocd runner only)
west flash --domain firmware-nrf54lm20a --verify

If (a) fails, the build is at fault — compare the two images' Kconfig, which is where a signing-type or key mismatch shows up:

grep -E 'MCUBOOT_SIGNATURE_KEY_FILE|MCUBOOT_GENERATE_UNSIGNED_IMAGE|ROM_START_OFFSET|MCUBOOT_BOOTLOADER_MODE' \
  <build>/firmware-nrf54lm20a/zephyr/.config
grep -E 'BOOT_SIGNATURE_TYPE|BOOT_SIGNATURE_KEY_FILE|BOOT_VALIDATE_SLOT0|BOOT_SWAP|SINGLE_APPLICATION_SLOT' \
  <build>/mcuboot/zephyr/.config

The bootloader and the application must agree on the signature type and the key, and on the MCUboot mode. Sysbuild derives both sides from the same SB_CONFIG_BOOT_SIGNATURE_TYPE_* / SB_CONFIG_BOOT_SIGNATURE_KEY_FILE (default: RSA with $(ZEPHYR_MCUBOOT_MODULE_DIR)/root-rsa-2048.pem), so a mismatch means something overrode one side — most often a CONFIG_BOOT_* line added to sysbuild/mcuboot.conf, which changes only the bootloader and leaves the application signed the old way. Set signing options in sysbuild.conf as SB_CONFIG_* instead, so both images move together.

If (a) passes but (b) fails, the image is good and the programming step is at fault — see the next section, which is the known cause on this board.

Known bug: the OpenOCD RRAM loader drops the last partial write

Symptom. imgtool verify says the image is fine, but west flash --verify reports a handful of 0xff bytes at the very end of the image:

Error: checksum mismatch - attempting binary compare
diff 0 address 0x00035130. Was 0xff instead of 0xf3
...
No more differences found.

Cause. The board's flash loader in boards/seeed/xiao_nrf54lm20a/support/openocd.cfg is:

proc nrf54lm20a-load {file} {
    mww 0x5004e500 0x101
    load_image $file
}

0x5004e500 is RRAMC.CONFIG; 0x101 sets WEN=1 and a one-line (16-byte) write buffer. load_image then writes the image, and the proc stops — it never commits the buffer. The nRF54L RRAM controller only writes a 128-bit line out when that line fills, so whatever does not reach a 16-byte boundary is left in the buffer and never lands in RRAM.

In the build this was diagnosed on, the application image was 151864 bytes; 151864 mod 16 = 8, so the final 8 bytes — the tail of the signature TLV — were silently dropped. MCUboot then reads a truncated signature and reports E: Image in the primary slot is not valid!. Zephyr's own RRAM driver gets this right: soc_flash_nrf_rram.c has a commit_changes() that triggers NRF_RRAMC_TASK_COMMIT_WRITEBUF whenever the write length is not a multiple of the buffer size. The OpenOCD script has no equivalent. This is upstream board support, not a HiveInside bug, and it affects any image whose length is not 16-byte aligned.

Fix. Disable the write buffer in that proc — one character, and it needs no register offsets:

proc nrf54lm20a-load {file} {
    mww 0x5004e500 0x1     ;# WEN=1, write-buffer size 0 -> every write commits
    load_image $file
}

Flashing gets slower, and correct. Keeping the buffer means flushing it after load_image, either by triggering TASKS_COMMITWRITEBUF or by reading back the last written byte — the Product Specification commits a line on "a read operation from a 128-bit word line in the buffer that has already been written to", which is the fallback Zephyr's driver uses when the commit task is unavailable.

Re-flash both images afterwards, not just the application. MCUboot is written in three sections of 34636, 2720 and 60 bytes; the first and last are also not 16-byte aligned, so up to 12 bytes are missing from each. It boots regardless — those bytes happen not to be on any path that matters — but the bootloader on the device is not the bootloader that was built. Confirm with:

west flash --domain mcuboot --verify
west flash --domain firmware-nrf54lm20a --verify

See ota-over-ble.md for the firmware-over-BLE protocol and the production release/recovery checklist.