I recently completed a field-upgrade path from an nRF5 SDK 17.1 application (with the legacy Secure DFU bootloader) to a fresh NCS-based firmware stack, while preserving user data stored in FDS. It was easier than expected, so sharing the approach here in case it helps anyone in the same boat, and I'd appreciate any feedback.
Goal
- Move from nRF5 SDK 17.1 + Secure DFU bootloader → NCS with MCUboot + firmware_loader + application.
- Preserve the legacy FDS records over-the-air, so end users don't lose state after the upgrade.
- Survive the transition with the smallest possible window where a power loss could brick the device.
NCS-side preparation
- Build the NCS firmware for the target SoC (nRF52832 / nRF52833) and confirm it fits flash and RAM.
- Use an MCUboot + firmware_loader + application layout (MCUboot in firmware-updater mode).
- Place the legacy FDS region at exactly its original absolute address in the static partition table. On first boot the NCS app reads the records from that area, migrates them into ZMS (or wherever you want them), and then the partition can be reused.
Partition layout used (identical for both 512 KB parts):
0x00000 mcuboot 24 KB
0x06000 slot0 288 KB application (MCUboot primary)
0x4E000 slot1 128 KB firmware_loader (MCUboot secondary)
0x6E000 fds_storage 40 KB legacy FDS region (read once, then ZMS)
0x78000 settings 32 KB Zephyr settings + PSA keys (Trusted Storage)
fds_storage at 0x6E000..0x77FFF covers the address range the legacy SDK used for FDS, so the migration code can fds_init() once, read everything out, and then ZMS reformats it.
Booting the firmware_loader when slot0 is empty
The critical piece that makes the rest of the flow safe is configuring MCUboot to boot the firmware_loader image whenever the application slot is invalid or empty. This is what catches the device after the MBR-swap reset, and is also your recovery path if a future app update is interrupted.
sysbuild config (firmware-updater mode + use a dedicated loader image in slot1):
SB_CONFIG_BOOTLOADER_MCUBOOT=y
SB_CONFIG_MCUBOOT_MODE_FIRMWARE_UPDATER=y
SB_CONFIG_MCUBOOT_MODE_FIRMWARE_UPDATER_BOOT_MODE_ENTRANCE=y
SB_CONFIG_FIRMWARE_LOADER_IMAGE_SMP_SVR=y
MCUboot per-board overlay (sysbuild/mcuboot/boards/<board>.conf):
# Boot the firmware loader when slot0 is invalid / empty.
CONFIG_BOOT_FIRMWARE_LOADER_NO_APPLICATION=y
Without CONFIG_BOOT_FIRMWARE_LOADER_NO_APPLICATION=y, MCUboot will just hang on an empty slot0 after the transitional app finishes, instead of jumping into the loader. With it set, an empty slot0 is treated as "go to loader" rather than "fail".
nRF5 SDK side: the transitional image
You can't jump directly from the legacy bootloader to MCUboot via a single Secure DFU update, because:
- The legacy bootloader's ACL locks the MBR/SoftDevice region and will refuse the write.
- A normal Secure DFU update only rewrites the application slot, not the MBR/bootloader.
The trick is a transitional application delivered through one final Secure DFU update:
- In the legacy bootloader source, remove the ACL protection of the MBR/SoftDevice region and rebuild it.
- Build a tiny transitional app whose payload contains two embedded blobs: the MCUboot binary and the firmware_loader binary.
- Package the patched bootloader + transitional app into a single signed Secure DFU
.zip.
Update flow on the device
-
End user / installer performs a normal Secure DFU update with the
.zip. This will happen in 2 stages: first bootloader + softdevice, then the transitional app. -
The legacy bootloader installs the patched bootloader and then the transitional app, and boots into it.
-
The transitional app:
- Writes the firmware_loader image into slot1 (its final MCUboot secondary location).
- Erases the MBR / legacy bootloader region and writes MCUboot at
0x00000. - Resets.
The window between erasing the MBR and finishing the MCUboot write is the only "brick window" — in practice well under a second on these parts.
-
After reset, MCUboot runs, sees slot0 is empty, and — thanks to
CONFIG_BOOT_FIRMWARE_LOADER_NO_APPLICATION=y— boots the firmware_loader from slot1. -
The firmware_loader exposes SMP/Bluetooth (or whatever transport you prefer) and accepts the real NCS application image, which lands in slot0.
-
On the very first boot of the NCS application, the FDS migration code reads the legacy records out of
fds_storage, writes them into the new ZMS-backed settings, and marks migration complete. The 40 KBfds_storagepartition can then be repurposed (or left for retry on a failed migration).
Notes / gotchas
- The transitional app has to be small enough to fit in the legacy app slot, since it's delivered through Secure DFU — embedding two binaries (MCUboot + firmware_loader) drove most of the size budgeting.
- Make sure the legacy FDS partition address in your
pm_static.ymlmatches the absolute address the SDK 17.1 build used, including any SoftDevice-size offset. - The patched legacy bootloader (ACL removed) is the only piece that's "risky" to ship — once it's on the device the brick window is owned by your transitional app, not the bootloader.
Happy to share more detail on any of the steps if it's useful.