The following goes through a recommended Zephyr project setup and integrates with a single stage MCUBoot bootloader. The first section uses the Renode emulator and then moves to actual hardware.
In previous posts we introduced Renode Docker Setup on Ubuntu 24.04 and a Bare-metal MCUBoot Port on Renode. This article moves up an abstraction layer and uses Zephyr with MCUBoot. Zephyr and the build tools are installed locally (Ubuntu 24.04) similarly to the instructions in the Docs.
Source files for this example are available here.
I’m using an nrf52840 target on Renode to speed up development and a nrf52832. Renode is setup in a Docker image as described in this post.
It’s cliché at this point to say Zephyr has a steep learning curve, but the problem isn’t just complexity it’s that most guides optimize for what’s easy to explain, not what’s useful for getting stuck-in. This is compounded by the west
tool which, although a great tool, does obfuscate important details and does more than is actually helpful.
My goal here is not to make a minimal configuration that functions but rather to set up a project that can actually be built on in real development.
There’s a good talk by Mike Szczys at EOSS 2022 that hits on this topic.
Our workspace is going to look like this after setup:
project-workspace
.west/
.config
app/
VERSION
prj.conf
CMakeLists.txt
src/
main.c
sysbuild/
mcuboot.conf
sysbuild.conf
west.yml
deps/
bootloader/
mcuboot/
...
modules/
...
zephyr/
...
Everything in deps/ is a git repo that acts like a git sub-repository.
app
is what Zephyr refers to as the main application or the manifest repository. We need to add a manifest file in app/
(be default this is west.yml
) which will then tell west what to put into deps/
and .west/
.
Most tutorials start with west init
. Personally I find this misleading and recommend avoiding it.
All it does is:
Example of a local initialization:
west init -l app --mf west.yml o
cat .config/west.yml
[manifest]
path = app
file = west.yml
Example with a remote repo:
west init -m git@[site]:[user]/[repository] --mf west.yml
cat .config/west.yml
[manifest]
path = app
file = west.yml
Zephyr’s location is automatically added after building west
for the first time:
[manifest]
path = app
file = west.yml
[zephyr]
base = deps/zephyr
There are additional settings for defaults etc. that you can find by calling west config -h
and reading docs.zephyrproject.org: west-apis, and docs.zephyrproject.org: west/config.
Instead of relying on west init
magic, I recommend versioning a complete .west/config and treating it like part of your build system.
Here’s the config I use:
[manifest]
path = app-git
file = west.yml
[zephyr]
base = deps/zephyr
[build]
dir-fmt = build/{board}/{app}
cmake-args = "-DCMAKE_EXPORT_COMPILE_COMMANDS=ON -DCMAKE_VERBOSE_MAKEFILE=ON"
board = nrf52840dk/nrf52840
generator = Ninja
sysbuild = true
[update]
sync-submodules = true
[log]
verbosity=3 # extreme
Note dir-fmt
which organizes builds by board type.
This project has each of the following files.
Note on Kconfig files. There are 3x Kconfig files:
- one for the main project
- one for the mcuboot project
- one for the
sysbuild
build tool After building, you’ll find a generated configuration at{BUILDPATH}/zephyr/.config
, the generated conf file. This has all the defined settings and their values. It is also a good place to get a list of available settings as values that aren’t set are still listed. Sysbuild itself also leaves a trail, its effective configuration is written to [PROJECT]/zephyr/.config.sysbuild.
This manifest defines the application location and dependencies.
Since we’re building such a simple application we don’t need much in the way of libraries or other projects. Instead, we’re deliberately using name-allowlist to keep the dependency tree small.
Here Zephyr is just a project like other projects are added.
We specify Zephyr’s own manifest file which will be located at [path-prefix]/[name]/[import.file]
which is deps/zephyr/west.yml
in this configuration.
Zephyr’s west.yml has a huge number of modules that you won’t need.
To avoid pulling in the entire world we can specify the what we need in the name-allowlist. I’m building MCUBoot and a basic hello_world application on an nrf52840 so we need the ARM dependencies (cmsis, cmsis_6) and our hardware drivers (hal_nordic). cbor is included for MCUBoot’s serial recovery functions.
For further reading see Zephyr’s Application Development docs.
manifest:
version: 0.8
projects:
- name: zephyr
url: https://github.com/zephyrproject-rtos/zephyr
revision: v4.2.0.rc3
import:
file: west.yml
path-prefix: deps
name-allowlist:
- zephyr
- cmsis
- cmsis_6
- hal_nordic
- mcuboot
- zcbor
self:
path: app
Normally you would include mbedtls or tinycrypt for signing packages but for this example I’m using unsigned images (don’t do this in production).
Zephyr’s `west manifest`` tool helps sanity-check your configuration.
west manifest --resolve
west manifest --validate
west manifest --freeze
Use --freeze -o west-frozen.yml
to pin exact commit hashes for each component.
This should be pretty self explanatory.
cmake_minimum_required(VERSION 3.20.0)
find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
project(sample_with_mcuboot)
test_sysbuild()
target_sources(app PRIVATE src/main.c)
This is where you enable application-level features. For the basic functionality this can be completely blank. Minimal isn’t always helpful so let’s enable the shell and add the mcuboot shell so we can inspect our images once we have them loaded.
Note: MCUBOOT_SHELL is a new feature available on main or (presumably) in releases > 4.2.0.
MCUBOOT_SHELL is defined in zephyr/subsys/dfu/Kconfig
along with its dependencies: MCUBOOT_SHELL -> MCUBOOT_IMG_MANAGER -> FLASH_MAP
FLASH_MAP is defined in zephyr/subsys/storage/flash_map/Kconfig
: FLASH_MAP -> FLASH_HAS_DRIVER_ENABLED.
The flash drivers are enabled with CONFIG_FLASH=y
in zephyr/drivers/flash/Kconfig
.
The Nordic QSPI interface isn’t implemented in Renode and enabling flash uses the QSPI external flash interface. When loading into Renode out application get stuck reading from the undefined QSPI flash interface. I didn’t track down the root cause of that in time so if you know how to disable the external flash interface through Kconfig please comment!
I went with disabling the QSPI in the device tree:
/delete-node/ &qspi;
I included a few of the other interesting shell features that are worth playing with:
# MCUBoot Kconfig.mcuboot
CONFIG_BOOTLOADER_MCUBOOT=y
CONFIG_MCUBOOT_GENERATE_CONFIRMED_IMAGE=y
# Flash
CONFIG_FLASH=y
CONFIG_STREAM_FLASH=y
CONFIG_STREAM_FLASH_PROGRESS=y
CONFIG_FLASH_MAP=y
CONFIG_STREAM_FLASH_INSPECT=y
CONFIG_STREAM_FLASH_ERASE=y
# DFU Kconfig
CONFIG_IMG_MANAGER=y
CONFIG_MCUBOOT_IMG_MANAGER=y
CONFIG_MCUBOOT_SHELL=y
CONFIG_UPDATEABLE_IMAGE_NUMBER=1
CONFIG_MCUBOOT_BOOTUTIL_LIB=y
CONFIG_HWINFO=y
CONFIG_STDOUT_CONSOLE=y
CONFIG_SETTINGS=y
CONFIG_SETTINGS_NONE=y
CONFIG_SETTINGS_RUNTIME=y
CONFIG_RESET=y
CONFIG_CONSOLE=y
CONFIG_REBOOT=y
CONFIG_SHELL=y
CONFIG_SHELL_MINIMAL=n
CONFIG_SETTINGS_SHELL=y
CONFIG_KERNEL_SHELL=y
CONFIG_POSIX_ENV_SHELL=y
CONFIG_POSIX_UNAME_SHELL=y
CONFIG_UART_SHELL=y
CONFIG_HWINFO_SHELL=y
CONFIG_FLASH_SHELL=y
CONFIG_FLASH_SHELL_TEST_COMMANDS=y
CONFIG_DEVICE_SHELL=y
CONFIG_DATE_SHELL=y
CONFIG_DEVMEM_SHELL=y
CONFIG_APP_VERSION_SHELL=y
CONFIG_SHELL_DEVICE_HELPERS=y
CONFIG_KERNEL_SHELL_PANIC_CMD=y
CONFIG_UUID=y
CONFIG_UART_CONSOLE=y
CONFIG_PRINTK=y
CONFIG_LOG_PRINTK=y
CONFIG_LOG=y
Like app/prj.conf, app/sysbuild/mcuboot.conf contains the Kconfig settings for the MCUBoot Zephyr image. This image is just a zephyr project but the build and configuration is given special treatment by sysbuild.
See the MCUBoot Konfig file and Nordic’s ‘Essential MCUBoot configuration items’ for a list of options.
There’s overlap between the sysbuild and mcuboot options which can be confusing. See this reference for a useful mapping between the two. Rule of thumb: if it controls how sysbuild wires MCUBoot into your project, it belongs in sysbuild.conf. If it controls MCUBoot’s own features (serial, validation, logging), it belongs in mcuboot.conf.
## subsys/logging/Kconfig
CONFIG_LOG=y
CONFIG_LOG_MODE_MINIMAL=n
CONFIG_CONSOLE=y
CONFIG_UART_CONSOLE=y
## mcuboot/Kconfig.serial_recovery
CONFIG_MCUBOOT_SERIAL=n
CONFIG_BOOT_SERIAL_UART=n
CONFIG_BOOT_VALIDATE_SLOT0=y
sysbuild is aimed at building multiple projects at once and looking after shared state.
See Nordic Dev Academy for a primer on sysbuild.conf.
SB_CONFIG_BOOTLOADER_MCUBOOT=y
SB_CONFIG_MCUBOOT_MODE_OVERWRITE_ONLY=n
SB_CONFIG_MCUBOOT_MODE_SWAP_USING_MOVE=y
SB_CONFIG_BOOT_SIGNATURE_TYPE_NONE=y
The sysbuild settings that get used for a project end up in [PROJECT]/zepyhr/.config.sysbuild. For mcuboot this reads:
# sysbuild controlled configuration settings
CONFIG_BOOT_SIGNATURE_KEY_FILE=""
CONFIG_SINGLE_APPLICATION_SLOT=n
CONFIG_BOOT_SWAP_USING_OFFSET=n
CONFIG_BOOT_SWAP_USING_SCRATCH=n
CONFIG_BOOT_UPGRADE_ONLY=n
CONFIG_BOOT_SWAP_USING_MOVE=n
CONFIG_BOOT_DIRECT_XIP=y
CONFIG_BOOT_RAM_LOAD=n
CONFIG_BOOT_FIRMWARE_LOADER=n
CONFIG_SINGLE_APPLICATION_SLOT_RAM_LOAD=n
CONFIG_BOOT_DIRECT_XIP_REVERT=n
CONFIG_BOOT_RAM_LOAD_REVERT=n
CONFIG_BOOT_SIGNATURE_TYPE_NONE=y
CONFIG_BOOT_SIGNATURE_TYPE_RSA=n
CONFIG_BOOT_SIGNATURE_TYPE_ECDSA_P256=n
CONFIG_BOOT_SIGNATURE_TYPE_ED25519=n
The values in VERSION create CMake variables that are available to your project. See docs.zephyrproject.org.
VERSION_MAJOR = 100
VERSION_MINOR = 1
PATCHLEVEL =
VERSION_TWEAK = 0
EXTRAVERSION = develop
These values are also automatically used to create
CONFIG_MCUBOOT_IMGTOOL_SIGN_VERSION
which is read by the bootloader to control image versions. This version can be used by MCUBoot to decide
whether or not to accept an image update. See MCUBoot Downgrade Protection.
After setting up the workspace and main application run west update
to fetches the packages specified in app/west.yml
.
west update
=== updating zephyr (deps/zephyr):
HEAD is now at 413b789deb3 release: Zephyr v4.2.0
=== updating cmsis (deps/modules/hal/cmsis):
HEAD is now at 512cc7e cmsis: pac_armv81.h missing
=== updating cmsis_6 (deps/modules/hal/cmsis_6):
HEAD is now at 06d952b6 IAR LDR{,B,H}T fix (#242)
=== updating hal_nordic (deps/modules/hal/nordic):
HEAD is now at 9587b1d nrfx: cracen: align to nrf54lm20a
=== updating mcuboot (deps/bootloader/mcuboot):
HEAD is now at 4eba8087 boot: zephyr: boards config of the stm32h573 disco kit
We should now be able to build the project:
west build app
This uses the board, generator, sysbuild, and build dir format specified in .west/config. To override those options you can call something like:
west build --board nrf52dk/nrf52832 --build-dir app/build -- -GUnix\ Makefiles
The build.dir-fmt = build/{board}/{app}
option in our west config will result in a different directory for each one of our boards:
ls build
nrf52840dk nrf52dk
There are discussions about where to put your build-dir. I think the preferred method is to have a build under each app which you can do. I find it nice to use a central build under the workspace.
Now we have an app image and an MCUBoot image for each of our board targets.
build/
nRF52840k/
nrf52840/
app/
app/
zephyr/
zephyr.elf
zephyr.bin
zephyr.signed.bin
...
mcuboot/
zephyr/
zephyr.elf
zephyr.bin
...
nrf52dk/
nrf52832/
...
We have an image for the bootloader and a signed image for the application ready to go.
We can read the version number using the MCUBoot imgtool. Using the version include with MCUBoot in our deps/
folder shows we have a valid image with the values in our VERSION
file:
python3 deps/bootloader/mcuboot/scripts/imgtool.py verify build/nrf52840dk/nrf52840/app/app/zephyr/zephyr.signed.bin
Image was correctly validated
Image version: 100.1.0+0
Image digest: e5e90cd15a7b08067b6f79af4ba2fd78d6d0894d4cdf10254899a0e0ffe68ffb
The confirmed image will have the magic numbers set and be padded to the partition size:
python3 deps/bootloader/mcuboot/scripts/imgtool.py dumpinfo build/nrf52840dk/nrf52840/app/app/zephyr/zephyr.signed.confirmed.bin
Printing content of signed image: zephyr.signed.confirmed.bin
#### Image header (offset: 0x0) ############################
magic: 0x96f3b83d
load_addr: 0x0
hdr_size: 0x200
protected_tlv_size: 0x0
img_size: 0x18c40
flags: 0x0
version: 100.4.0+0
############################################################
#### Payload (offset: 0x200) ###############################
| |
| FW image (size: 0x18c40 Bytes) |
| |
############################################################
#### TLV area (offset: 0x18e40) ############################
magic: 0x6907
area size: 0x28
---------------------------------------------
type: SHA256 (0x10)
len: 0x20
data: 0x29 0x5c 0x11 0x92 0x62 0x3f 0xb6 0x98
0x81 0x31 0x68 0x73 0x6f 0xbf 0xe2 0x41
0x4e 0x94 0x86 0x11 0xdf 0xf4 0x1b 0xf2
0x67 0x69 0x04 0x3f 0xa6 0x8b 0x45 0x73
############################################################
#### Image padding (offset: 0x18e68) #######################
| |
| padding (0xff) |
| |
############################################################
#### Image trailer (offset: unknown) #######################
(Note: some fields may not be used, depending on the update
strategy)
swap status: (len: unknown)
enc. keys: Image not encrypted
swap size: unknown
swap_info: INVALID (0xff)
copy_done: INVALID (0xff)
image_ok: SET (0x1)
boot magic: 0x77 0xc2 0x95 0xf3 0x60 0xd2 0xef 0x7f
0x35 0x52 0x50 0x0f 0x2c 0xb6 0x79 0x80
#### End of Image #########################################
dumpinfo has run successfully
Now that we have an application and bootloader let’s load and test it. I’m using Renode to evaluate the builds so we need to put together the .resc file.
In a previous article (Bare-metal MCUBoot Port on Renode) it was obvious where to load the image as we included a linker file. With Zephyr we need to dig into the device trees.
The device tree defines the flash layout and partitions. From zephyr/dts/vendor/nordic/nrf52840_partition.dtsi
:
&flash0 {
partitions {
compatible = "fixed-partitions";
#address-cells = <1>;
#size-cells = <1>;
boot_partition: partition@0 {
label = "mcuboot";
reg = <0x00000000 0x0000C000>;
};
slot0_partition: partition@c000 {
label = "image-0";
reg = <0x0000C000 0x00076000>;
};
slot1_partition: partition@82000 {
label = "image-1";
reg = <0x00082000 0x00076000>;
};
/*
* The flash starting at 0x000f8000 and ending at
* 0x000fffff is reserved for use by the application.
*/
/*
* Storage partition will be used by FCB/LittleFS/NVS
* if enabled.
*/
storage_partition: partition@f8000 {
label = "storage";
reg = <0x000f8000 0x00008000>;
};
};
};
The nrf52832 has less flash so has a different default configuration with the same area for MCUBoot:
&flash0 {
partitions {
compatible = "fixed-partitions";
#address-cells = <1>;
#size-cells = <1>;
boot_partition: partition@0 {
label = "mcuboot";
reg = <0x00000000 0xc000>;
};
slot0_partition: partition@c000 {
label = "image-0";
reg = <0x0000C000 0x37000>;
};
slot1_partition: partition@43000 {
label = "image-1";
reg = <0x00043000 0x37000>;
};
storage_partition: partition@7a000 {
label = "storage";
reg = <0x0007a000 0x00006000>;
};
};
};
All we need to do for the Renode script is specify the loading locations.
:name: NRF52840 Application Loading
:description: Bootloader project demo on NRF52840.
using sysbus
$name?="NRF52840"
$bootloader_elf?=@build/nrf52840dk/nrf52840/app/mcuboot/zephyr/zephyr.elf
$application_0_bin?=@build/nrf52840dk/nrf52840/app/app/zephyr/zephyr.signed.confirmed.bin
$application_1_bin?=@build/nrf52840dk/nrf52840/app/app/zephyr/zephyr.signed.confirmed.bin
# Create Machine & Load config
mach create $name
include @https://raw.githubusercontent.com/snhobbs/renode-nrf52840_flash/refs/heads/main/nrf52840_nvcm.cs
machine LoadPlatformDescription @renode-platforms/boards/nrf52840dk_nrf52840.repl
machine LoadPlatformDescription @https://raw.githubusercontent.com/snhobbs/renode-nrf52840_flash/refs/heads/main/nrf52840_flash.repl
emulation CreateServerSocketTerminal 2345 "term"
connector Connect sysbus.uart0 term
# Enable GDB
machine StartGdbServer 3333
macro load
"""
sysbus LoadELF $bootloader_elf
#sysbus LoadBinary $application_0_bin 0xc000
#sysbus LoadBinary $application_1_bin 0x82000
"""
runMacro $load
Note: When using Renode make sure MCUBoot is not configured for MCUBoot serial as mentioned above since it won’t start launch the application for some reason TBD.
Start Renode from the Docker Container
docker run -it --rm \
-e DISPLAY=$DISPLAY -e DOTNET_BUNDLE_EXTRACT_BASE_DIR=/home/user \
-p 2345:2345 -p 3456:3456 -p 3333:3333 -p 1234:1234 \
-v /tmp/.X11-unix:/tmp/.X11-unix -v $(pwd):/home/user \
--name renode renode-1.15.3 renode renode-config-application.resc
Once we start the emulation we see the mcuboot log and land in a shell:
*** Booting MCUboot v2.2.0-118-gaa4fa2b6e173 ***
*** Using Zephyr OS build v4.2.0-3488-g18bff321be08 ***
I: Starting bootloader
I: Primary image: magic=good, swap_type=0x1, copy_done=0x3, image_ok=0x1
I: Secondary image: magic=good, swap_type=0x1, copy_done=0x3, image_ok=0x1
I: Boot source: none
I: Image index: 0, Swap type: perm
I: Starting swap using move algorithm.
I: Bootloader chainload address offset: 0xc000
I: Image version: v100.12.0
I: Jumping to the first image slot
*** Booting Zephyr OS build v4.2.0-3488-g18bff321be08 ***
Address of sample 0xc000
Hello sysbuild with mcuboot! nrf52840dk
[00:00:00.000,274] <inf> mcuboot_util: Image index: 0, Swap type: none
Swap Type: 1
VERSION: 100.12.0-develop
>
Use the mcuboot
option to check the images:
> mcuboot
swap type: none
confirmed: 1
primary area (1):
version: 100.12.0+0
image size: 85964
magic: good
swap type: perm
copy done: set
image ok: set
secondary area (2):
version: 100.12.0+0
image size: 85964
magic: unset
swap type: none
copy done: unset
image ok: unset
[00:05:13.605,865] <inf> mcuboot_util: Image index: 0, Swap type: none
>
On reboot:
> kernel reboot
*** Booting MCUboot v2.2.0-118-gaa4fa2b6e173 ***
*** Using Zephyr OS build v4.2.0-3488-g18bff321be08 ***
I: Starting bootloader
I: Primary image: magic=good, swap_type=0x3, copy_done=0x1, image_ok=0x1
I: Secondary image: magic=unset, swap_type=0x1, copy_done=0x3, image_ok=0x3
I: Boot source: none
I: Image index: 0, Swap type: none
I: Bootloader chainload address offset: 0xc000
I: Image version: v100.12.0
I: Jumping to the first image slot
*** Booting Zephyr OS build v4.2.0-3488-g18bff321be08 ***
Address of sample 0xc000
Hello sysbuild with mcuboot! nrf52840dk
[00:00:00.000,244] <inf> mcuboot_util: Image index: 0, Swap type: none
Swap Type: 1
VERSION: 100.12.0-develop
> mcuboot
swap type: none
confirmed: 1
primary area (1):
version: 100.12.0+0
image size: 85964
magic: good
swap type: perm
copy done: set
image ok: set
secondary area (2):
version: 100.12.0+0
image size: 85964
magic: unset
swap type: none
copy done: unset
image ok: unset
[00:00:06.027,343] <inf> mcuboot_util: Image index: 0, Swap type: none
>
There are a few useful functions enabled like the flash interface set in the device tree:
> flash partitions
partition@0 mcuboot 0x00000000 48 KiB
partition@c000 image-0 0x0000c000 472 KiB
partition@82000 image-1 0x00082000 472 KiB
partition@f8000 storage 0x000f8000 32 KiB
We can swap images with mcuboot request_upgrade
:
> mcuboot request_upgrade
> mcuboot
swap type: test
confirmed: 1
primary area (1):
version: 100.12.0+0
image size: 85964
magic: good
swap type: perm
copy done: set
image ok: set
secondary area (2):
version: 100.12.0+0
image size: 85964
magic: good
swap type: test
copy done: unset
image ok: unset
[00:00:30.912,445] <inf> mcuboot_util: Image index: 0, Swap type: test
>
Note the swap type has changed and the magic numbers are set.
After a reboot this image with be switched in. I’m using the exact same images in this example but you can tell the swap happened from the bootloader log:
> kernel reboot
*** Booting MCUboot v2.2.0-118-gaa4fa2b6e173 ***
*** Using Zephyr OS build v4.2.0-3488-g18bff321be08 ***
I: Starting bootloader
I: Primary image: magic=good, swap_type=0x3, copy_done=0x1, image_ok=0x1
I: Secondary image: magic=good, swap_type=0x2, copy_done=0x3, image_ok=0x3
I: Boot source: none
I: Image index: 0, Swap type: test
I: Starting swap using move algorithm.
I: Bootloader chainload address offset: 0xc000
I: Image version: v100.12.0
I: Jumping to the first image slot
*** Booting Zephyr OS build v4.2.0-3488-g18bff321be08 ***
Address of sample 0xc000
Hello sysbuild with mcuboot! nrf52840dk
[00:00:00.000,274] <inf> mcuboot_util: Image index: 0, Swap type: revert
Swap Type: 4
VERSION: 100.12.0-develop
> mcuboot
swap type: revert
confirmed: 0
primary area (1):
version: 100.12.0+0
image size: 85964
magic: good
swap type: test
copy done: set
image ok: unset
secondary area (2):
version: 100.12.0+0
image size: 85964
magic: unset
swap type: none
copy done: unset
image ok: unset
[00:01:39.098,388] <inf> mcuboot_util: Image index: 0, Swap type: revert
The swap type is set to revert so unless the image is confirmed it will switch back on the next reboot.
Confirm the image:
> mcuboot confirm
> mcuboot
swap type: none
confirmed: 1
primary area (1):
version: 100.12.0+0
image size: 85964
magic: good
swap type: test
copy done: set
image ok: set
secondary area (2):
version: 100.12.0+0
image size: 85964
magic: unset
swap type: none
copy done: unset
image ok: unset
[00:03:18.053,588] <inf> mcuboot_util: Image index: 0, Swap type: none
You can erase and image with:
> mcuboot erase 2
> mcuboot
swap type: none
confirmed: 1
primary area (1):
version: 100.12.0+0
image size: 85964
magic: good
swap type: test
copy done: set
image ok: set
failed to read secondary area (2) header: -5
[00:04:33.944,580] <inf> mcuboot_util: Image index: 0, Swap type: none
I switched over to the nrf52dk as that’s what I have on hand so it’s time for the device trees to shine! There are no changes to needed for the nrf52832 so no overlay is needed. If something needed to be changed we could drop an overlay file in app/boards/nrf52dk_nrf52832.overlay which will be used for both the bootloader and the application.
Since the device trees are already setup all that we need to do for this new target is:
west build --board nrf52dk/nrf52832 app
Now we actually have to worry about obnoxious things like loading the image.
The bootloaders UART logging is useful to get the device twitching but we can switch over to MCUBoot serial to enable serial recovery by editing app/sysbuid/mcuboot.conf
:
## subsys/logging/Kconfig
CONFIG_LOG=y
CONFIG_LOG_MODE_MINIMAL=n
CONFIG_CONSOLE=y
CONFIG_UART_CONSOLE=n
CONFIG_BOOT_SERIAL_NO_APPLICATION=y
## mcuboot/Kconfig.serial_recovery
CONFIG_MCUBOOT_SERIAL=y
CONFIG_BOOT_SERIAL_UART=y
CONFIG_BOOT_VALIDATE_SLOT0=y
# Enable flash operations
CONFIG_FLASH=y
CONFIG_BOOT_SERIAL_NO_APPLICATION
ensures that the bootloader will sit in serial recovery if no valid image is found.
Note: The bootloader never launches the application when using MCUBoot serial recovery. There’s some peripheral or register that isn’t supported. This isn’t an issue on the actual hardware. If you manage to figure out why before I do please leave a comment!
Using JLinkExe to first load the bootloader elf:
./JLinkExe
...
J_Link> connect
...
J_Link> loadfile build/nrf52dk/nrf52832/app/mcuboot/zephyr/zephyr.elf
J_Link> go
Alternatively you can use nrfjprog distributed from nrfutils:
nrfjprog --program build/nrf52dk/nrf52832/app/mcuboot/zephyr/zephyr.elf --chiperase -f nrf52 --reset
If the MCU is running with a valid image in slot 1 then the device will need to be forced into serial recovery mode either by erasing the chip, sending the recovery code at bootup, or using the recovery GPIO at bootup (button 1 in this example).
Now that the MCUBoot serial mode is running we can use an SMP tool smpmgr tool to upload the image:
smpmgr --port /dev/ttyACM0 upgrade --slot 1 build/nrf52dk/nrf52832/app/app/zephyr/zephyr.signed.confirmed.bin
smpmgr --port /dev/ttyACM0 upgrade --slot 2 build/nrf52dk/nrf52832/app/app/zephyr/zephyr.signed.confirmed.bin
After the image is uploaded the bootloader should load and start the application.
Using a serial terminal that supports unicode like minicom:
minicom -D /dev/ttyACM0 -b 115200
...
*** Booting Zephyr OS build v4.2.0-3488-g18bff321be08 ***
Address of sample 0xc000
Hello sysbuild with mcuboot! nrf52dk
[00:00:00.001,159] <inf> mcuboot_util: Image index: 0, Swap type: none
Swap Type: 1
VERSION: 100.12.0-develop
> mcuboot
swap type: none
confirmed: 1
primary area (1):
version: 100.12.0+0
image size: 85480
magic: unset
swap type: none
copy done: unset
image ok: unset
secondary area (2):
version: 100.12.0+0
image size: 85480
magic: unset
swap type: none
copy done: unset
image ok: unset
[00:00:09.041,412] <inf> mcuboot_util: Image index: 0, Swap type: none
>
After rolling the version number, recompiling, and loading the slot 2 binary using smpmgr:
*** Booting Zephyr OS build v4.2.0-3488-g18bff321be08 ***
Address of sample 0xc000
Hello sysbuild with mcuboot! nrf52dk
[00:00:00.001,159] <inf> mcuboot_util: Image index: 0, Swap type: none
Swap Type: 1
VERSION: 100.12.0-develop
> mcuboot
swap type: none
confirmed: 1
primary area (1):
version: 100.12.0+0
image size: 85480
magic: unset
swap type: none
copy done: unset
image ok: unset
secondary area (2):
version: 100.12.1+0
image size: 85480
magic: unset
swap type: none
copy done: unset
image ok: unset
[00:00:06.010,559] <inf> mcuboot_util: Image index: 0, Swap type: none
Request image upgrade:
> mcuboot request_upgrade
> mcuboot
swap type: test
confirmed: 1
primary area (1):
version: 100.12.0+0
image size: 85480
magic: unset
swap type: none
copy done: unset
image ok: unset
secondary area (2):
version: 100.12.1+0
image size: 85480
magic: good
swap type: test
copy done: unset
image ok: unset
[00:00:55.549,865] <inf> mcuboot_util: Image index: 0, Swap type: test
On reboot:
> kernel reboot
*** Booting Zephyr OS build v4.2.0-3488-g18bff321be08 ***
Address of sample 0xc000
Hello sysbuild with mcuboot! nrf52dk
[00:00:00.001,190] <inf> mcuboot_util: Image index: 0, Swap type: revert
Swap Type: 4
VERSION: 100.12.1-develop
> mcuboot
swap type: revert
confirmed: 0
primary area (1):
version: 100.12.1+0
image size: 85480
magic: good
swap type: test
copy done: set
image ok: unset
secondary area (2):
version: 100.12.0+0
image size: 85480
magic: unset
swap type: none
copy done: unset
image ok: unset
[00:00:03.813,018] <inf> mcuboot_util: Image index: 0, Swap type: revert
Without confirming the image reverts:
> kernel reboot
*** Booting Zephyr OS build v4.2.0-3488-g18bff321be08 ***
Address of sample 0xc000
Hello sysbuild with mcuboot! nrf52dk
[00:00:00.001,159] <inf> mcuboot_util: Image index: 0, Swap type: none
Swap Type: 1
VERSION: 100.12.0-develop
> mcuboot
swap type: none
confirmed: 1
primary area (1):
version: 100.12.0+0
image size: 85480
magic: good
swap type: revert
copy done: set
image ok: set
secondary area (2):
version: 100.12.1+0
image size: 85480
magic: unset
swap type: none
copy done: unset
image ok: unset
[00:00:02.051,208] <inf> mcuboot_util: Image index: 0, Swap type: none
If instead we confirm before a reboot:
> mcuboot request_upgrade
> kernel reboot
*** Booting Zephyr OS build v4.2.0-3488-g18bff321be08 ***
Address of sample 0xc000
Hello sysbuild with mcuboot! nrf52dk
[00:00:00.001,159] <inf> mcuboot_util: Image index: 0, Swap type: revert
Swap Type: 4
VERSION: 100.12.1-develop
> mcuboot confirm
> mcuboot
swap type: none
confirmed: 1
primary area (1):
version: 100.12.1+0
image size: 85480
magic: good
swap type: test
copy done: set
image ok: set
secondary area (2):
version: 100.12.0+0
image size: 85480
magic: unset
swap type: none
copy done: unset
image ok: unset
[00:00:08.708,038] <inf> mcuboot_util: Image index: 0, Swap type: none
> kernel reboot
*** Booting Zephyr OS build v4.2.0-3488-g18bff321be08 ***
Address of sample 0xc000
Hello sysbuild with mcuboot! nrf52dk
[00:00:00.001,159] <inf> mcuboot_util: Image index: 0, Swap type: none
Swap Type: 1
VERSION: 100.12.1-develop
> mcuboot
swap type: none
confirmed: 1
primary area (1):
version: 100.12.1+0
image size: 85480
magic: good
swap type: test
copy done: set
image ok: set
secondary area (2):
version: 100.12.0+0
image size: 85480
magic: unset
swap type: none
copy done: unset
image ok: unset
[00:00:10.464,599] <inf> mcuboot_util: Image index: 0, Swap type: none
Getting your bootloader right is important and should be done early in the productization. Hope this saves you some headaches getting your system working. Feel free to comment with anything I missed, extra tips, or how your team has deployed DFU and bootloaders!