XIAO nRF54LM20A Matter Sensor

Hi folks,

I managed to get the SDK Matter Temperature template running on a XIAO nRF54LM20A, added a BME sensor for real temperature/humidity data, and did some supply current measurements. Source code is here: github.

I tried to compile with SDK 3.4.1, but did not get running code. The deprecated Partition Manager is the biggest issue.

Have fun,
Hannes

HI there,

SO, I looked at the GIT and this is great that it works. However :face_with_peeking_eye: ,I see the fact you used only the internal flash for this in the Partition Manager static configuration file and there is some overlap in the MCU boot area, in the addresses, You had to shrink the active app slot (0x10D800 down from 0x125800) to fit all partitions into the SoC’s flash capacity.

the build system will catch that, btw.
i.e.;

1. Fix the Overlapping Layout Defect

In this configuration, mcuboot_primary overlaps directly into mcuboot_secondary:

  • mcuboot_primary: address: 0xA000, size: 0x126000 $\rightarrow$ ends at 0x130000

  • mcuboot_secondary: starts at 0x118000

You reduced app from 0x125800 to 0x10D800, but forgot to update mcuboot_primary size (0x10D800 + 0x800 = 0x10E000). Left as-is, Partition Manager will either throw an overlap build error or cause corrupted image verification.

2. Move mcuboot_secondary to the On-Board 8 MB SPI NOR Flash

The Seeed Studio XIAO nRF54LM20A integrates an on-board 64 Mbit (8 MB) external SPI NOR flash (PY25Q64HA).

  • Current Limitation: In the repository file, mcuboot_secondary is shoehorned into internal non-volatile memory (region: flash_primary), forcing an asymmetric slot layout with 34.75% compression :backhand_index_pointing_left: :face_with_raised_eyebrow: which increases MCUboot decompression overhead, requires RAM buffering, and limits max application binary sizes.

  • Optimization: Relocate mcuboot_secondary to region: external_flash (or spi_flash). This frees roughly 768 KB (0xC0000) inside internal RRAM, enabling a symmetric primary slot up to ~1.8 MB without requiring runtime DFU decompression.

Pro -Tip :grin: :backhand_index_pointing_right: Align Partition Boundaries to 4 KB Erase Blocks

The nRF54L series uses 4 KB erase blocks. Verify all starting offsets and lengths remain multiples of 0x1000 (4096 bytes).

  • mcuboot_pad at 0xA000 and app at 0xA800 are valid logical offsets inside the primary slot for MCUboot’s 2 KB header offset.

  • Keep persistent storage areas (factory_data and settings_storage) strictly aligned to 4 KB page boundaries.

  • Matter with Thread requires storing network credentials, access control lists (ACLs), binding tables, and multiple fabric tokens.

  • The current settings_storage is set to 0xC000 (48 KB).

  • If running complex clusters or multiple Matter fabrics (e.g., Apple Home + Home Assistant concurrently), consider increasing this partition to 0x10000 (64 KB) to avoid frequent NVS sector garbage-collection cycles and wear. (per nordic’s spec)

  • Reserve Allocation for the FLPR Coprocessor (cpuflpr) :+1: it’s there you can use it. If offloading background peripheral polling, sensor pipelines, or BLE scheduler tasks to the FLPR core:

  • Allocate a dedicated static partition for the coprocessor code/data image (e.g., cpuflpr_partition, typically 32 KB – 96 KB depending on firmware).

  • Pinning this statically prevents memory collisions when multi-image builds link both cpuapp and cpuflpr binaries.

  • Expand settings_storage for Multi-Fabric Matter Deployments, The current settings_storage is set to 0xC000 (48 KB).
    If running multiple Matter fabrics (e.g., Apple Home + Home Assistant concurrently), consider increasing this partition to 0x10000 (64 KB) to avoid frequent NVS sector garbage-collection cycles and wear.(also per nordic) this is also talked about in the webcast.

I asked the LLM to produce a better “the PM file”, you are SOoooright it’s the Biggest PITA in all the build stuff. partitions :laughing:, but now it’s going away and a lot of us are glad. :grin: :+1:

Great GIT , thanks for the contribush :v: proves the Matter stuff works at least.

Cleaned Static Configuration (Internal Only with Overlap Fixed)

If keeping all slots strictly internal without external SPI flash:

mcuboot:
  address: 0x0
  region: flash_primary
  size: 0xA000

mcuboot_pad:
  address: 0xA000
  region: flash_primary
  size: 0x800

mcuboot_primary_app:
  address: 0xA800
  orig_span: &id002
    - app
  region: flash_primary
  size: 0x10D800
  span: *id002

app:
  address: 0xA800
  region: flash_primary
  size: 0x10D800

mcuboot_primary:
  address: 0xA000
  orig_span: &id001
    - app
    - mcuboot_pad
  region: flash_primary
  size: 0x10E000     # Fixed: matches pad (0x800) + app (0x10D800) to end at 0x118000
  span: *id001

mcuboot_secondary_pad:
  address: 0x118000
  region: flash_primary
  size: 0x800

mcuboot_secondary_app:
  address: 0x118800
  region: flash_primary
  size: 0xBF800

mcuboot_secondary:
  address: 0x118000
  orig_span: &id003
    - mcuboot_secondary_pad
    - mcuboot_secondary_app
  region: flash_primary
  size: 0xC0000
  span: *id003

factory_data:
  address: 0x1D8000
  region: flash_primary
  size: 0x1000

settings_storage:
  address: 0x1D9000
  region: flash_primary
  size: 0xC000

HTH
GL :slight_smile: PJ :v:

Hi Glasso,

thx for your input!

The reason I went for the ‘internal’ build is (quote from here):

Internal Flash variant: Disables PY25Q64 (reduces BOM cost and power consumption);

Is it correct that the external flash can’t be used because it will be sent to deep sleep for power savings?

Hannes

If had to adjust the pm_static_xiao_nrf54lm20a_nrf54lm20a_cpuapp_internal.yml from the seeedstudio wiki, it gives compiler error:

Partition manager failed: Incorrect amount of gaps found in static configuration. There must be exactly one gap in the static configuration to support placing the dynamic partitions (such as ‘app’). Gaps found (2):0xa800-0x130000 0x1fd000-0x1e5000 The most common solution to this problem is to fill the smallest of these gaps with statically defined partition(s) until there is only one gap left. Alternatively re-order the already defined static partitions so that only one gap remains.
Failed to partition region flash_primary, size of region: 1986560

Any idea how to interpret Gaps found (2): 0xa800-0x130000 0x1fd000-0x1e5000? Especially the latter makes no sense to me at all, as 0x1fd000 is bigger than 0x1e5000.

Hannes

Hi there,

So, Great questions! Here is what is happening with the external flash power, plus why Partition Manager is throwing that specific gap error:

1. Regarding External Flash & Deep Sleep

You can definitely use the external flash without taking a continuous power hit!

When configured properly in Zephyr (CONFIG_PM_DEVICE=y), the NOR flash driver puts the chip into deep power-down mode (typically sub-microamp standby, around 0.1–1 µA on modern SPI NORs like the Puya PY25Q64) whenever the bus is idle. MCUboot only powers it up briefly to stream, verify, and swap image chunks during an actual DFU / OTA update, after which it goes right back to sleep.

The internal-only build variant is usually recommended by Seeed when cost-optimizing bare modules or avoiding external hardware dependencies altogether. If you stay internal, you save a tiny bit of quiescent leakage, but you have to live with the tighter internal partition squeeze.

2. What Partition Manager Means by “Gaps”

In Partition Manager, a gap is simply an unallocated block of memory between statically defined partitions.

When PM is left to place and size the app dynamically, it requires exactly ONE contiguous gap in flash_primary. If it finds zero or more than one, the solver aborts.

Here is why it reported those two:

  • Gap 1 (0xa800 - 0x130000): Because app was left dynamic or stripped out, PM sees an empty slot starting at 0xA800 (right after mcuboot_pad) and ending at 0x130000 where the upper partitions began.

  • Gap 2 (0x1fd000 - 0x1e5000): Notice how the numbers are backwards! flash_primary on the nRF54LM20A is 1,986,560 bytes (exactly 0x1E5000). If a partition (or the default template) has an offset placed at or beyond 0x1FD000 (assuming a full 2 MB flash instead of ~1.89 MB usable non-volatile storage), the partition end overshoots the hardware region. The PM script tries to calculate the remaining space (region_end - partition_start), causes an arithmetic underflow, and prints out that inverted range.

3. How to Fix It

If you want to use the internal layout cleanly, paste this complete, non-overlapping pm_static.yml into your project root.

It defines every partition end-to-end so there are zero gaps, resolves the MCUboot secondary overlap, fits inside the 0x1E5000 flash boundary, and aligns all partitions cleanly:

mcuboot:
  address: 0x0
  region: flash_primary
  size: 0xA000

mcuboot_pad:
  address: 0xA000
  region: flash_primary
  size: 0x800

mcuboot_primary_app:
  address: 0xA800
  orig_span: &id002
    - app
  region: flash_primary
  size: 0x10D800
  span: *id002

app:
  address: 0xA800
  region: flash_primary
  size: 0x10D800

mcuboot_primary:
  address: 0xA000
  orig_span: &id001
    - app
    - mcuboot_pad
  region: flash_primary
  size: 0x10E000
  span: *id001

mcuboot_secondary_pad:
  address: 0x118000
  region: flash_primary
  size: 0x800

mcuboot_secondary_app:
  address: 0x118800
  region: flash_primary
  size: 0xBF800

mcuboot_secondary:
  address: 0x118000
  orig_span: &id003
    - mcuboot_secondary_pad
    - mcuboot_secondary_app
  region: flash_primary
  size: 0xC0000
  span: *id003

factory_data:
  address: 0x1D8000
  region: flash_primary
  size: 0x1000

settings_storage:
  address: 0x1D9000
  region: flash_primary
  size: 0xC000

(Note: 0x1D9000 + 0xC000 = 0x1E5000, landing right on the hardware limit without overshooting).

Give your build folder a pristine clean (west build -t pristine or wipe the /build folder) so PM regenerates the layout map from scratch, and it will link through cleanly! :+1:

HTH
GL :slight_smile: PJ :v:

Hi there,

So, for Completeness :wink: I went looking more at it and here, if you want to move to flash, try it this way.
When moving mcuboot_secondary (the DFU download/staging slot) to the external SPI NOR flash, the Partition Manager handles two distinct flash regions:

  1. flash_primary: The on-chip non-volatile storage (internal RRAM, capped at 0x1E5000).

  2. nordic_external_flash_controller (or external_flash depending on the board definition): The external SPI NOR chip.

This configuration gives the internal application breathing room to expand across internal flash up to settings_storage, while placing the entire secondary swap slot off-chip.

Prerequisites in Device Tree / Kconfig

To target external flash, the board DTS and prj configs must have the external flash node and driver enabled:

  • In prj.conf:
CONFIG_PM_EXTERNAL_FLASH_MCUBOOT_SECONDARY=y
CONFIG_NORDIC_QSPI_NOR=y   # or CONFIG_SPI_NOR=y depending on bus
CONFIG_PM_DEVICE=y

Region name check: Check the generated build report (build/partitions.yml) to ensure whether the DTS label for the QSPI/SPI flash is named nordic_external_flash_controller or external_flash.

External Flash pm_static.yml

# ==========================================
# Primary Flash (Internal RRAM: 0x0 - 0x1E5000)
# ==========================================
mcuboot:
  address: 0x0
  region: flash_primary
  size: 0xA000

mcuboot_pad:
  address: 0xA000
  region: flash_primary
  size: 0x800

# Primary App Slot gets the lion's share of internal flash
mcuboot_primary_app:
  address: 0xA800
  orig_span: &id002
    - app
  region: flash_primary
  size: 0x1CD800
  span: *id002

app:
  address: 0xA800
  region: flash_primary
  size: 0x1CD800

mcuboot_primary:
  address: 0xA000
  orig_span: &id001
    - app
    - mcuboot_pad
  region: flash_primary
  size: 0x1CE000
  span: *id001

# Factory Data and Settings placed at top of internal RRAM
factory_data:
  address: 0x1D8000
  region: flash_primary
  size: 0x1000

settings_storage:
  address: 0x1D9000
  region: flash_primary
  size: 0xC000

# ==========================================
# External Flash (SPI / QSPI NOR)
# ==========================================
mcuboot_secondary_pad:
  address: 0x0
  region: external_flash
  size: 0x800

mcuboot_secondary_app:
  address: 0x800
  region: external_flash
  size: 0x1CD800

mcuboot_secondary:
  address: 0x0
  orig_span: &id003
    - mcuboot_secondary_pad
    - mcuboot_secondary_app
  region: external_flash
  size: 0x1CE000
  span: *id003

Key Differences & Benefits:

  1. Primary Slot Expands: mcuboot_primary_app jumps from 0x10D800 (~1.1 MB) to 0x1CD800 (~1.89 MB), giving Matter + Thread clusters plenty of headroom.

  2. Matching Secondary Slot Size: MCUboot swap algorithms require mcuboot_secondary size (0x1CE000) to match mcuboot_primary size (0x1CE000).

  3. No Regional Collisions: factory_data and settings_storage still sit cleanly at the tail of internal flash (0x1D8000–0x1E5000), completely insulated from OTA download operations.

You’ll get the complete handle on the PM settings getting it to work, Very good tech, Funny though just as the world figures it out , the Vikings are doing away with “PM” :grin:
They felt our pain…:v:

HTH
GL :slight_smile: PJ :v:

Hi GL :slight_smile: PJ :v:,

thank for your help, I really appreciate it!

So I will give the non internal build a try (well ok, another couple of hours of repeated compiler runs. Everyone how has ever compiled the matter examples knows what I’m talking about).

Thx,
Hannes

Hi there,

:hourglass_not_done:
indeed
:grin:
:v: