Using QSPI XIP split image
The QSPI XIP split image feature lets you gain more flash storage space for applications by splitting application code into two parts:
Code that runs on the internal flash memory.
Code that runs on supported external flash memory over the Quad Serial Peripheral Interface (QSPI) using Execute in Place (XIP).
This feature is supported on nRF52840 and nRF5340.
Warning
On the nRF52840, do not relocate interrupts to the QSPI XIP flash. Doing so can lock up or brick the device by making the debug access port inaccessible.
The QSPI XIP split images are supported in MCUboot, which allows for updating them over-the-air.
For the nRF5340 DK and Nordic Thingy:53, you can also check out the SMP Server with external XIP sample, which demonstrates this feature.
Requirements
To use this feature, meet the following requirements:
Board based on nRF52840 or nRF5340, with file structure compatible with Hardware model v2 (HWMv2)
External QSPI flash chip with supported commands connected to QSPI pins
QSPI flash chip in always-on mode (meaning, no DPM or low-power modes)
MCUboot configuration in the Swap using move mode (
MCUBOOT_SWAP_USING_MOVE
), the Upgrade only mode (MCUBOOT_OVERWRITE_ONLY
), or in the direct-XIP modeLinker file with QSPI XIP flash offset and size
Caution
Currently, MCUboot images cannot be linked together. This means that both parts of the firmware update must be loaded properly before an update is initiated. If one part fails to update, the image on the device will be unbootable and that will likely brick the device.
Network core image updates
The QSPI XIP split image feature supports network core image updates on nRF5340 devices if MCUboot is configured for using either the swap-using-move or the upgrade-only mode.
Create the Partition Manager static files
Create the following files:
A static Partition Manager file to set up the partitions that will be used on the device. The following three files from the SMP Server with external XIP sample provide an example of the Partition Manager layouts for nRF5340:
app: address: 0x10200 end_address: 0xe4000 region: flash_primary size: 0xd3e00 external_flash: address: 0x120000 device: MX25R64 end_address: 0x800000 region: external_flash size: 0x6e0000 mcuboot: address: 0x0 end_address: 0x10000 region: flash_primary size: 0x10000 mcuboot_pad: address: 0x10000 end_address: 0x10200 region: flash_primary size: 0x200 mcuboot_primary: address: 0x10000 end_address: 0xe4000 orig_span: &id001 - mcuboot_pad - app region: flash_primary size: 0xd4000 span: *id001 mcuboot_primary_1: address: 0x0 device: flash_ctrl end_address: 0x40000 region: ram_flash size: 0x40000 mcuboot_primary_app: address: 0x10200 end_address: 0xe4000 orig_span: &id002 - app region: flash_primary size: 0xd3e00 span: *id002 mcuboot_secondary: address: 0x0 device: MX25R64 end_address: 0xd4000 region: external_flash size: 0xd4000 mcuboot_secondary_1: address: 0xd4000 device: MX25R64 end_address: 0x114000 region: external_flash size: 0x40000 EMPTY_1: address: 0x114000 device: MX25R64 end_address: 0x120000 region: external_flash size: 0xc000 mcuboot_primary_2: address: 0x120000 device: MX25R64 end_address: 0x160000 orig_span: &id003 - mcuboot_primary_2_pad - mcuboot_primary_2_app region: external_flash size: 0x40000 span: *id003 mcuboot_primary_2_pad: address: 0x120000 end_address: 0x120200 region: external_flash size: 0x200 mcuboot_primary_2_app: address: 0x120200 device: MX25R64 end_address: 0x40000 region: external_flash size: 0x3FE00 mcuboot_secondary_2: address: 0x160000 device: MX25R64 end_address: 0x1a0000 region: external_flash size: 0x40000 otp: address: 0xff8100 end_address: 0xff83fc region: otp size: 0x2fc pcd_sram: address: 0x20000000 end_address: 0x20002000 region: sram_primary size: 0x2000 ram_flash: address: 0x40000 end_address: 0x40000 region: ram_flash size: 0x0 rpmsg_nrf53_sram: address: 0x20070000 end_address: 0x20080000 placement: before: - end region: sram_primary size: 0x10000 settings_storage: address: 0xf0000 end_address: 0x100000 region: flash_primary size: 0x10000 sram_primary: address: 0x20002000 end_address: 0x20070000 region: sram_primary size: 0x6e000
app: address: 0x10200 end_address: 0xe4000 region: flash_primary size: 0xd3e00 external_flash: address: 0x120000 device: MX25R64 end_address: 0x800000 region: external_flash size: 0x6e0000 mcuboot: address: 0x0 end_address: 0x10000 region: flash_primary size: 0x10000 mcuboot_pad: address: 0x10000 end_address: 0x10200 region: flash_primary size: 0x200 mcuboot_primary: address: 0x10000 end_address: 0xe4000 orig_span: &id001 - mcuboot_pad - app region: flash_primary size: 0xd4000 span: *id001 mcuboot_primary_1: address: 0x120000 device: external_flash end_address: 0x160000 orig_span: &id003 - mcuboot_primary_1_pad - mcuboot_primary_1_app region: external_flash size: 0x40000 span: *id003 mcuboot_primary_1_pad: address: 0x120000 end_address: 0x120200 region: external_flash size: 0x200 mcuboot_primary_1_app: address: 0x120200 device: MX25R64 end_address: 0x160000 region: external_flash size: 0x3FE00 mcuboot_primary_app: address: 0x10200 end_address: 0xe4000 orig_span: &id002 - app region: flash_primary size: 0xd3e00 span: *id002 mcuboot_secondary: address: 0x0 device: MX25R64 end_address: 0xd4000 region: external_flash size: 0xd4000 mcuboot_secondary_1: address: 0xd4000 device: MX25R64 end_address: 0x114000 region: external_flash size: 0x40000 EMPTY_1: address: 0x114000 device: MX25R64 end_address: 0x120000 region: external_flash size: 0xc000 otp: address: 0xff8100 end_address: 0xff83fc region: otp size: 0x2fc rpmsg_nrf53_sram: address: 0x20070000 end_address: 0x20080000 placement: before: - end region: sram_primary size: 0x10000 settings_storage: address: 0xf0000 end_address: 0x100000 region: flash_primary size: 0x10000 sram_primary: address: 0x20000000 end_address: 0x20070000 region: sram_primary size: 0x70000
app: address: 0x10200 end_address: 0x7a000 region: flash_primary size: 0x69e00 external_flash: address: 0x120000 device: MX25R64 end_address: 0x800000 region: external_flash size: 0x6e0000 mcuboot: address: 0x0 end_address: 0x10000 region: flash_primary size: 0x10000 mcuboot_pad: address: 0x10000 end_address: 0x10200 region: flash_primary size: 0x200 mcuboot_primary: address: 0x10000 end_address: 0x7a000 orig_span: &id001 - mcuboot_pad - app region: flash_primary size: 0x6a000 span: *id001 mcuboot_primary_app: address: 0x10200 end_address: 0x7a000 orig_span: &id002 - app region: flash_primary size: 0x69e00 span: *id002 mcuboot_secondary_pad: address: 0x7a000 end_address: 0x7a200 region: flash_primary size: 0x200 mcuboot_secondary: address: 0x7a000 end_address: 0xe4000 orig_span: &id003 - mcuboot_secondary_pad - mcuboot_secondary_app region: flash_primary size: 0x6a000 span: *id003 mcuboot_secondary_app: address: 0x7a200 end_address: 0xe4000 region: flash_primary size: 0x69e00 EMPTY_3: address: 0xe4000 end_address: 0x100000 region: flash_primary size: 0x1c000 mcuboot_primary_1: address: 0x120000 device: external_flash end_address: 0x160000 orig_span: &id004 - mcuboot_primary_1_pad - mcuboot_primary_1_app region: external_flash size: 0x40000 span: *id004 mcuboot_primary_1_pad: address: 0x120000 end_address: 0x120200 region: external_flash size: 0x200 mcuboot_primary_1_app: address: 0x120200 device: MX25R64 end_address: 0x160000 region: external_flash size: 0x3FE00 mcuboot_secondary_1: address: 0xd4000 device: MX25R64 end_address: 0x114000 region: external_flash size: 0x40000 EMPTY_1: address: 0x0 device: MX25R64 end_address: 0xd4000 region: external_flash size: 0xd4000 EMPTY_2: address: 0x114000 device: MX25R64 end_address: 0x120000 region: external_flash size: 0xc000 otp: address: 0xff8100 end_address: 0xff83fc region: otp size: 0x2fc rpmsg_nrf53_sram: address: 0x20070000 end_address: 0x20080000 placement: before: - end region: sram_primary size: 0x10000 settings_storage: address: 0xf0000 end_address: 0x100000 region: flash_primary size: 0x10000 sram_primary: address: 0x20000000 end_address: 0x20070000 region: sram_primary size: 0x70000
A board overlay file to set the chosen Partition Manager external flash device:
/ { chosen { nordic,pm-ext-flash = &mx25r64; }; };
You can either place this file as a board overlay in the
boards
folder of the application or name itapp.overlay
and place it in the application folder so that it applies to all board targets.
Create a linker file
Create a linker file that has the same information as the static Partition Manager file for the external QSPI flash device.
The following code example from the SMP Server with external XIP sample shows how to set up a linker file for direct-XIP mode, with the direct-XIP secondary image configuration in the CONFIG_NCS_IS_VARIANT_IMAGE
section.
You can omit this section if you are using other MCUboot operating modes.
Place this file in the application directory with a name similar to linker_arm_extxip.ld
.
/*
* Copyright (c) 2022 Carlo Caione <ccaione@baylibre.com>
* Copyrigt# Copyright (c) 2024 Nordic Semiconductor
*
* SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
*/
/**
* @file
* @brief External QSPI flash Linker command/script file
*
* Linker script for moving desired .text and and .data to the external
* memory space.
*/
#include <zephyr/linker/sections.h>
#include <zephyr/devicetree.h>
#include <zephyr/linker/linker-defs.h>
#include <zephyr/linker/linker-tool.h>
/* Let SystemInit() be called in place of z_arm_platform_init() by default. */
PROVIDE(z_arm_platform_init = SystemInit);
/*
* nRF5340dk and thingy53 are shipping QSPI external flashes.
* These memories are mapped beginning from 0x1000_0000 internal SoC memory
* address. This addressing space can be used for XIP and direct data access.
*/
MEMORY
{
#if CONFIG_NCS_IS_VARIANT_IMAGE
/* This maps in mcuboot_secondary_1 partition defined in pm_static_no_network_core_directxip.yml
* components for ORIGIN calculation:
* - 0x10000000: offset of QSPI external memory in SoC memory mapping.
* - 0xD4000: mcuboot_secondary_1 offset in QSPI external memory
* - 0x200: image header size.
* The size of this region is size of mcuboot_secondary_1 reduced by the
* image header size.
*/
EXTFLASH (wx) : ORIGIN = 0x100D4200, LENGTH = 0x3FE00
#else
/* This maps in mcuboot_primary_2 partition defined in pm_static.yml
* components for ORIGIN calculation:
* - 0x10000000: offset of QSPI external memory in SoC memory mapping.
* - 0x120000: mcuboot_primary_2 offset in QSPI external memory
* - 0x200: image header size.
* The size of this region is size of mcuboot_primary_2 reduced by the
* image header size.
*/
EXTFLASH (wx) : ORIGIN = 0x10120200, LENGTH = 0x3FE00
#endif
}
#include <zephyr/arch/arm/cortex_m/scripts/linker.ld>
Set additional Kconfig options
Set additional Kconfig option in the application’s prj.conf
to allow it to execute code from XIP:
# Update the filename here if you have given it a different name
CONFIG_CUSTOM_LINKER_SCRIPT="linker_arm_extxip.ld"
Configure sysbuild
Configure Sysbuild to enable the required Kconfig options for this functionality:
Create a
sysbuild.conf
file in the application directory withs the following options, depending on the required functionality:SB_CONFIG_BOOTLOADER_MCUBOOT=y SB_CONFIG_PM_EXTERNAL_FLASH_MCUBOOT_SECONDARY=y SB_CONFIG_NETCORE_APP_UPDATE=y SB_CONFIG_SECURE_BOOT_NETCORE=y SB_CONFIG_QSPI_XIP_SPLIT_IMAGE=y # This will enable the hci_ipc image for the network core, change to the desired image SB_CONFIG_NETCORE_HCI_IPC=y
SB_CONFIG_BOOTLOADER_MCUBOOT=y SB_CONFIG_PM_EXTERNAL_FLASH_MCUBOOT_SECONDARY=y SB_CONFIG_QSPI_XIP_SPLIT_IMAGE=y
SB_CONFIG_BOOTLOADER_MCUBOOT=y SB_CONFIG_MCUBOOT_MODE_DIRECT_XIP=y SB_CONFIG_PM_EXTERNAL_FLASH_MCUBOOT_SECONDARY=y SB_CONFIG_QSPI_XIP_SPLIT_IMAGE=y
Create a
sysbuild.cmake
file. This file applies the devicetree overlay file for enabling the external flash device for the Partition Manager (created in Create the Partition Manager static files) to MCUboot. The following examples assume you created theapp.overlay
file valid for all board targets; adjust as needed if you created as a board overlay instead.list(APPEND mcuboot_EXTRA_DTC_OVERLAY_FILE "${CMAKE_CURRENT_SOURCE_DIR}/app.overlay")
list(APPEND mcuboot_EXTRA_DTC_OVERLAY_FILE "${CMAKE_CURRENT_SOURCE_DIR}/app.overlay") set(mcuboot_CONFIG_BOOT_IMAGE_ACCESS_HOOKS n)
Relocate code to QSPI flash
Relocate the code to run from the QSPI flash inside the applications CMakeLists.txt
file using the zephyr_code_relocate
function.
This can be used to relocate files and libraries.
zephyr_code_relocate(FILES src/complex_sensor_calculcation_code.c LOCATION EXTFLASH_TEXT NOCOPY)
zephyr_code_relocate(FILES src/complex_sensor_calculcation_code.c LOCATION RAM_DATA)
Note
This assumes that the application has a src/complex_sensor_calculcation_code.c`
file.
zephyr_code_relocate(LIBRARY subsys__mgmt__mcumgr__mgmt LOCATION EXTFLASH_TEXT NOCOPY)
zephyr_code_relocate(LIBRARY subsys__mgmt__mcumgr__mgmt LOCATION RAM_DATA)
Note
The library name comes from the Zephyr code.
Set QSPI flash initialization priority
Set the initialization priority of the QSPI flash so that it is configured before any code on the device runs, otherwise the application operation is going to be undefined.
There is no built-in check for this setting.
Therefore, you must manually set the priority based on the relocated code.
For example, if you relocate the shell subsystem, its initialization priority must be higher than the priority of QSPI, and the value of CONFIG_SHELL_BACKEND_SERIAL_INIT_PRIORITY
must be greater than CONFIG_NORDIC_QSPI_NOR_INIT_PRIORITY
.
Note
Not all libraries or subsystems can be relocated.
Any code that runs early in the boot process or uses SYS_INIT()
with a non-configurable init priority cannot be relocated.
Library and subsystem code needs to be manually checked before relocating it.
Programming with the QSPI XIP split image
Programming of the application is supported using the standard procedure. The standard procedure will program the firmware using the default nrfjprog configuration which, for QSPI, is PP4IO mode.
Programming using a different SPI mode
If you are using an a different SPI mode on the QSPI interface, such as DSPI, you must use a custom Qspi.ini
file.
The following is an example for the Thingy:53, which supports DSPI and PP:
;----------------------------------------------
; Deprecated, use config.toml format instead
;----------------------------------------------
; nrfjprog QSPI configuration file.
[DEFAULT_CONFIGURATION]
; Define the capacity of the flash memory device in bytes. Set to 0 if no external memory device is present in your board.
MemSize = 0x800000
; Define the desired ReadMode. Valid options are FASTREAD, READ2O, READ2IO, READ4O and READ4IO
ReadMode = READ2IO
; Define the desired WriteMode. Valid options are PP, PP2O, PP4O and PP4IO
WriteMode = PP
; Define the desired AddressMode. Valid options are BIT24 and BIT32
AddressMode = BIT24
; Define the desired Frequency. Valid options are M2, M4, M8, M16 and M32
Frequency = M16
; Define the desired SPI mode. Valid options are MODE0 and MODE3
SpiMode = MODE0
; Define the desired SckDelay. Valid options are in the range 0 to 255
SckDelay = 0x80
; Define SPI interface timing. Valid options are in the range of 0 to 7.
; This argument is only used for devices where the dll function NRFJPROG_qspi_set_rx_delay() is supported.
RxDelay = 2
; Define the desired IO level for DIO2 and DIO3 during a custom instruction. Valid options are LEVEL_HIGH and LEVEL_LOW
CustomInstructionIO2Level = LEVEL_LOW
CustomInstructionIO3Level = LEVEL_HIGH
; Define the assigned pins for the QSPI peripheral. Valid options are those existing in your device
; For nRF53, QSPI pins are not configurable and these values are ignored.
CSNPin = 17
CSNPort = 0
SCKPin = 19
SCKPort = 0
DIO0Pin = 20
DIO0Port = 0
DIO1Pin = 21
DIO1Port = 0
DIO2Pin = 22
DIO2Port = 0
DIO3Pin = 23
DIO3Port = 0
; Define the Index of the Write In Progress (WIP) bit in the status register. Valid options are in the range of 0 to 7.
WIPIndex = 0
; Define page size for commands. Valid sizes are PAGE256 and PAGE512.
PPSize = PAGE256
; Custom instructions to send to the external memory after initialization. Format is instruction code plus data to send in between optional brakets.
; These instructions will be executed each time the qspi peripheral is initiated by nrfjprog.
; To improve execution speed on consecutive interations with QSPI, you can run nrfjprog once with custom initialization, and then comment out the lines below.
; Numbers can be given in decimal, hex (starting with either 0x or 0X) and binary (starting with either 0b or 0B) formats.
; The custom instructions will be executed in the order found.
; This example includes two commands, first a WREN (WRite ENable) and then a WRSR (WRite Satus Register) enabling the Quad Operation and the High Performance
; mode for the MX25R6435F memory present in the nRF52840 DK.
;InitializationCustomInstruction = 0x06
;InitializationCustomInstruction = 0x01, [0x40, 0, 0x2]
; If retention is enabled, device RAM contents will be read and buffered during QSPI driver initialization.
; The buffered data will be written back to the device when unitializing the driver, restoring the original device RAM state.
; Enabled: RetainRAM = 1, Disabled: RetainRAM = 0
RetainRAM = 0
To use this file when programming, add the following lines to the application’s CMakeLists.txt
file before the find_package()
line:
macro(app_set_runner_args)
# Replace with the filename of your ini file
board_runner_args(nrfjprog "--qspiini=${CMAKE_CURRENT_SOURCE_DIR}/Qspi_thingy53.ini")
endmacro()
This will enable programming the target board successfully when using west flash
.
Firmware updates
The build system will automatically generate the signed update files that can be loaded to a device to run the newer version of the application.
The dfu_application.zip
file also contains these files and you can use the nRF Connect for Mobile application (available for Android and iOS) to upload the firmware over Bluetooth® Low Energy.
File descriptions
The following table lists the files generated when building a QSPI XIP split-image application.
In some file names, <application>
is the name of the application and <kernel_name>
is the value of CONFIG_KERNEL_BIN_NAME
.
File name and location |
Description |
---|---|
|
Initial HEX output file, unsigned, containing internal and external QSPI flash data. |
|
Initial HEX output file, unsigned, containing internal flash data. |
|
Initial HEX output file, unsigned, containing external QSPI flash data. |
|
Signed internal flash data HEX file. |
|
Signed external QSPI flash data HEX file. |
|
Signed internal and external QSPI flash data HEX file. |
|
Signed internal flash data binary file (for firmware updates). |
|
Signed external QSPI flash data binary file (for firmware updates). |
|
Initial HEX output file, unsigned, containing internal and external QSPI flash data for direct-XIP mode secondary slot image. |
|
Initial HEX output file, unsigned, containing internal flash data for direct-XIP mode secondary slot image. |
|
Initial HEX output file, unsigned, containing external QSPI flash data for direct-XIP mode secondary slot image. |
|
Signed internal flash data HEX file for direct-XIP mode secondary slot image. |
|
Signed external QSPI flash data HEX file for direct-XIP mode secondary slot image. |
|
Signed internal and external QSPI flash data HEX file for direct-XIP mode secondary slot image. |
|
Signed internal flash data binary file (for firmware updates) for direct-XIP mode secondary slot image. |
|
Signed external QSPI flash data binary file (for firmware updates) for direct-XIP mode secondary slot image. |
|
Merged HEX file containing all images, includes both internal and external QSPI flash data for all images. |
|
Created if |