Skip to main content

Bootloaders & DFU/FOTA

Once a device leaves your desk, the only way to fix bugs is over-the-air updates. The standard answer in Zephyr is MCUboot: a tiny bootloader that lives in flash before your application, verifies new images, and swaps them in.

info

References: Zephyr DFU subsystem and Sysbuild for the build setup.



The slot model

MCUboot splits your flash into:

PartitionPurpose
boot_partitionMCUboot itself
slot0_partitionCurrently-running app (the "primary" slot)
slot1_partitionWhere new images land (the "secondary" slot)
scratch_partitionTemporary swap space (optional)

A FOTA update writes the new image into slot1, marks it as a candidate, and reboots. MCUboot validates the signature, swaps slots, and runs the new image. If the new image doesn't confirm itself within a few boots, MCUboot reverts.



Enable MCUboot

In sysbuild.conf:

SB_CONFIG_BOOTLOADER_MCUBOOT=y

In your application prj.conf:

CONFIG_BOOTLOADER_MCUBOOT=y
CONFIG_MCUBOOT_SIGNATURE_KEY_FILE="bootloader/mcuboot/root-rsa-2048.pem"
warning

The default key in the MCUboot tree is for development only — it's public knowledge. Generate your own before shipping, or anyone can sign firmware your device will accept.

Build:

west build -b esp32s3_devkitc --sysbuild
west flash

The application image is now signed and bootable by MCUboot.



Confirm a new image

After the first boot of a new image, your app must "confirm" it — otherwise MCUboot reverts on the next reboot:

#include <zephyr/dfu/mcuboot.h>

if (!boot_is_img_confirmed()) {
/* Run self-test here. Only confirm if everything works. */
boot_write_img_confirmed();
}

This is the rollback safety net: ship a bug that crashes on boot, and the device automatically reverts to the last known-good image.



FOTA transports

The new image can arrive over any transport. Zephyr provides three out of the box:

TransportUse caseKconfig
BLE (SMP)Phone-based updates, low bandwidthCONFIG_MCUMGR_TRANSPORT_BT=y
WiFi/Ethernet (HTTP)Cloud-driven updates, fastApp-level — fetch and flash_img_* API
UART (SMP)Wired updates from a host PCCONFIG_MCUMGR_TRANSPORT_UART=y

For BLE, the mcumgr subsystem handles the SMP protocol. The phone app sends image chunks; the device writes them to slot1 and the standard MCUboot flow takes over.



Sketch: BLE FOTA on ESP32-S3

# In prj.conf
CONFIG_BOOTLOADER_MCUBOOT=y
CONFIG_NET_BUF=y
CONFIG_ZCBOR=y
CONFIG_MCUMGR=y
CONFIG_MCUMGR_TRANSPORT_BT=y
CONFIG_MCUMGR_GRP_IMG=y
CONFIG_MCUMGR_GRP_OS=y
CONFIG_IMG_MANAGER=y
CONFIG_STREAM_FLASH=y

Pair with the nRF Device Manager app (iOS/Android — works on any device exposing the SMP service, not just nRF) and you can push a new .bin straight from your phone.



What can go wrong

SymptomCause
Device boots into MCUboot but never starts the appImage not signed, or signed with the wrong key
New image installs, then reverts on next bootApp never called boot_write_img_confirmed()
Update fails halfway, device bricksNo scratch partition + power loss during swap — use the overwrite algorithm for single-shot updates if rollback isn't required
Slot 1 too smallSlot 0 grew past slot 1's size — they must match. Re-partition.
tip

Always test the full update path on real hardware before shipping. The combination of partition map, signing key, and confirm logic is impossible to validate without an end-to-end FOTA dry run.