This topic is not yet updated to nRF Connect SDK v2.7.0 or v2.8.0. This work is ongoing.
Apologies for any inconvenience.
So far, we have covered general concepts related to bootloaders. In this section, we will dive into specific implementations used in the nRF Connect SDK. We will learn about the multi-image build system and the Partition Manager script, as we will use these to configure our bootloaders later. These have been briefly covered in the Fundamentals course, we will learn about both in more depth here.
To explain multi-image builds, let’s first have a look at how a program for an nRF chip would look without any framework for building multiple images. In this case, we first build the bootloader and then the application. Both are built separately, without any knowledge of each other. It is up to the developer to make sure that the bootloader and application agree on where in flash they are stored. We can call this Partition Information. After the images have been built, the output file (typically hex) from each build is written to the nRF chip separately. Here is a figure to illustrate the process:
It is possible to use the above method in the nRF Connect SDK, but it is not the default. Instead, we use our Multi-Image Build system to make the build system handle the building of multiple images.
In multi-image builds, an application is built as the main image, and all other images are added as child images to this main image.
Here is a simplified figure which shows how the multi-image build system can be illustrated:
We will now cover the main features of multi-image builds in the following subsections.
When the main application is built, the multi-image build system will automatically build all child images in the correct order. As an example, if we add CONFIG_BOOTLOADER_MCUBOOT=y
to the Zephyr Hello World sample, this will enable multi-image builds. (We will have a closer look at this in upcoming exercises) From the resulting build log, observe that a child image is built in the below figure, marked as (1):
While it is possible to add custom child images to applications, a few types of child images make up the majority of use-cases. These are Bootloaders and network core images. In the nRF Connect SDK, we have a set of default samples which can be used as child images for these. When enabling certain features, the multi-image build system will be enabled, and automatically choose a fitting image for that based on configurations in the main application, as follows:
If Child images are often built from samples in the nRF Connect SDK, and we do not recommend making changes to the nRF Connect SDK source, then how can we configure the child images?
Multi-image builds gives us the possibility for configuring Image-specific variables, either by overlaying or changing existing child image configurations. There are two main ways to configure child images: Either using CMake arguments, or by using a predefined folder structure and configuration files.
To use CMake arguments, we can either overlay individual Kconfig options using -D<child_image>_CONFIG_WHATEVER=y
, for example: -Dmcuboot_CONFIG_LOG_DEFAULT_LEVEL=3
. An illistration is shown below:
We can also overlay using files using CMake arguments. This is done by using -D<child_image>_EXTRA_CONF_FILE
or -D<child_image>_DTC_OVERLAY_FILE
. Or to overwrite Kconfig, we can use -D<child_image>_CONF_FILE
.
The alternative is to use a folder structure to add files to the project. This is the method we recommend, and the method most of our examples use to configure child images. These files must be located inside a directory named child_image
, and are named after the child image. This is typically child_image/mcuboot.conf
or child_image/mcuboot/prj.conf
, but a full list can be found at Multi-image builds: Permanent configuration changes to child images:
Here is an example of how a simple project with MCUboot overlays would look:
The multi-image build system will also generate output files, used for programming or DFU of the nRF chip. When we use the “Flash” function in the VS Code Extension for a single image build, build/zephyr/zephyr.hex
is programmed to the nRF chip. For multi-image builds, build/zephyr/merged.hex is used instead. A list over output build files generated by multi-image builds can be found under Multi-image builds: What image files are. A more comprehensive list over generated output build in the nRF Connect SDK can be found in our Build and configuration system docs. The multi-image build system will also generate different output build files for different cores when relevant.
As previously mentioned: When building multiple images for a single core, we must make sure that the memory addresses (Partitions) are the same in configuration for the main application and the child image, so they do not overlap. This is also needed so that the first image can continue from the next application, for example when a bootloader boots the application.
For this, we have a standalone Python script called the Partition Manger. The multi-image build system uses the Partition Manager for partitioning. Since the Partitioning is a standalone script, it will be covered in its own section below.
In nRF Connect SDK there are three partitioning schemes, which one to utilize is something dependent on the SoC/SiP and the application as explained below:
To quote from “DFU over UART from the application”:
Usually, the bootloader and the application must be configured with the same start address and size for where in non-volatile memory the new firmware is stored. These pre-decided memory areas are referred to as slots.
Zephyr uses the DTS binding fixed partitions to partition the memory into different slots. However, since the nRF Connect SDK has need for a more dynamical partition method, we have the Partition Manager. The Partition Manager uses yaml to define partitions, and generates a partition scheme to fit with features needed by an application.
When we have built a project, we can see how the partitioning was done by looking at build/partitions.yml or by using the VS Code Memory Report tool:
If you use a terminal, you can also use west build -t partition_manager_report
. See Partition Manager report docs.
To see if the Partition Manager has been enabled for an application, check CONFIG_PARTITION_MANAGER_ENABLED
. This is a read-only configuration that will automatically be set if the Partition Manager has been enabled.
In a multi-image build, Partition Manager partitioning for the main app is inherited by child images.
Activation of some features in the nRF Connect SDK will automatically enable the Partition Manager. We can see a list of those at CONFIG_PARTITION_MANAGER_ENABLED
.
When the Partition Manager is enabled, partitions will be created to fill all the memory. Which different partitions are created depends on which features are enabled in the application. For example, if CONFIG_NVS
is enabled, a nvs
partition is created, or if MCUboot is enabled, the partitions mcuboot
, mcuboot_primary
and mcuboot_secondary
are created. These partitions are defined in pm.yml files. pm.yml files for child images can be found in child image sample folder, such as nrf/samples/bootloader/pm.yml
. pm.yml
files for other partitions can be found in the nrf/subsys/partition_manager/
folder.
To configure Partition Manager configuration, we can use Kconfig options related to each partition. For example, to configure the size of MCUboot, we can use CONFIG_PM_PARTITION_SIZE_MCUBOOT
. I recommend looking at relevant pm.yml files to find configuration options. It is also possible to search for CONFIG_PM_
in our Kconfig search, but keep in mind that CONFIG_PM_
can refer to both Partition Manager and Power Management here.
It is possible to “freeze” the partitioning scheme by creating doing static configuration, aka creating a pm_static.yml
file in the main application. A good starting point for pm_static.yml
is to copy build/partitions.yml
. Static Partitioning is relevant for DFU, and we will explain why later in this course.