External execute in place (XIP) configuration on the nRF5340 SoC
This guide describes the external execute in place (XIP) support for the nRF5340 SoC. The nRF5340 SoC is equipped with a Quad Serial Peripheral Interface (QSPI) memory interface, which is capable of exposing QSPI flash as memory for the CPU used to fetch and execute the program code. The QSPI is available for the application core. Therefore, it is possible to relocate a part of the application’s code to an external memory. The external flash memory supports on-the-fly encryption and decryption. NCS supports dividing an application into an internal and external part, along with the MCUboot support.
For more information about QSPI XIP hardware support, the Execute in place page in the nRF5340 Product Specification.
For placing individual source code files into defined memory regions, check the Code relocation nocopy sample in Zephyr.
Enabling configuration options
Application configuration must support an external XIP and image splitting.
Enable the following sysbuild options in a sysbuild.conf
file:
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
Additionally, set the following application options:
CONFIG_CUSTOM_LINKER_SCRIPT
to"<linker_file_for_relocation>"
CONFIG_FLASH_INIT_PRIORITY
to40
- You must ensure the QSPI device initialization priority, as it makes the external XIP code accessible before it is executed. If any initialization code is expected to be run from the QSPI XIP, then its initialization priority value must be lower than the QSPI device initialization priority.
Setting up QSPI flash
The QSPI flash DTS must be available for the application and the MCUboot. You must correctly set up the QSPI flash chip in the board devicetree file, including the operating mode. The flash chip does not have to run in the QSPI mode for XIP to function, but using other modes will reduce the execution speed of the application.
See the following snippet for an example of the Nordic Thingy:53 configuration that supports DSPI:
&qspi {
status = "okay";
pinctrl-0 = <&qspi_default>;
pinctrl-1 = <&qspi_sleep>;
pinctrl-names = "default", "sleep";
mx25r64: mx25r6435f@0 {
compatible = "nordic,qspi-nor";
reg = <0>;
writeoc = "pp2o";
readoc = "read2io";
sck-frequency = <8000000>;
jedec-id = [c2 28 17];
sfdp-bfp = [
e5 20 f1 ff ff ff ff 03 44 eb 08 6b 08 3b 04 bb
ee ff ff ff ff ff 00 ff ff ff 00 ff 0c 20 0f 52
10 d8 00 ff 23 72 f5 00 82 ed 04 cc 44 83 68 44
30 b0 30 b0 f7 c4 d5 5c 00 be 29 ff f0 d0 ff ff
];
size = <67108864>;
has-dpd;
t-enter-dpd = <10000>;
t-exit-dpd = <35000>;
};
};
Note
Due to QSPI peripheral product anomaly, the QSPI peripheral must be ran with the HFCLK192MCTRL=0
setting.
Any other value may cause undefined operation of the device.
Add the following to the DTS overlay for your board:
/ {
chosen {
nordic,pm-ext-flash = &mx25r64;
};
};
Setting up static partition manager
You need to complete the setup in order to use a static partitioning in your project. The configuration must have 3 images with 2 slots each:
The first set of slots is for the internal flash part of the application. These slots should be named
mcuboot_primary
andmcuboot_secondary
.The second set of slots is for the network core update. These slots should be named
mcuboot_primary_1
andmcuboot_secondary_1
.The third set of slots is for the QSPI XIP part of the application. These slots should be named
mcuboot_primary_2
andmcuboot_secondary_2
. There should also bemcuboot_primary_2_pad
(which should be the same size asmcuboot_pad
) andmcuboot_primary_2_app
.
This means a basic dual image configuration for the nRF5340 DK needs to describe an external QSPI XIP code partition as mcuboot_primary_2
partition.
Additionally, ensure that:
The
mcuboot_primary_2
address is expressed as the QSPI flash physical address.The
device
field is the QSPI device name.The
region
field is set asexternal_flash
.
See the following snippet for an example of the static configuration for partition manager:
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
Configuring linker script
To relocate code to the external flash, you need to configure a linker script.
The script needs to describe the EXTFLASH
flash memory block to which the code will be linked.
The ORIGIN
of the area can be calculated using following elements:
The QSPI memory starting with the 0x10000000 internal memory address.
The offset of an external application part image within the QSPI flash. The external application code partition is mapped by the
mcuboot_primary_2
PM partition.The image header size of the MCUboot image (0x200).
See the following example of the calculation:
#include <zephyr/linker/sections.h>
#include <zephyr/devicetree.h>
#include <zephyr/linker/linker-defs.h>
#include <zephyr/linker/linker-tool.h>
MEMORY
{
/* This maps in mcuboot_primary_2 partition defined in pm_static.yaml
* 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
}
#include <zephyr/arch/arm/cortex_m/scripts/linker.ld>
Setting up code relocation
Relocating code to QSPI XIP is a part of the project’s CMakeLists.txt
file.
You can set up the relocation on a file or library basis using the zephyr_code_relocate()
function.
For example, to relocate a file in the application, use the following configuration:
zephyr_code_relocate(FILES ${CMAKE_CURRENT_SOURCE_DIR}/src/bluetooth.c LOCATION EXTFLASH_TEXT NOCOPY)
zephyr_code_relocate(FILES ${CMAKE_CURRENT_SOURCE_DIR}/src/bluetooth.c LOCATION RAM_DATA)
where the first line relocates the XIP code (.text) and the second line relocates the data initialization content section (.data).
Similarly, it is possible to relocate certain libraries, for example:
zephyr_code_relocate(LIBRARY subsys__mgmt__mcumgr__mgmt LOCATION EXTFLASH_TEXT NOCOPY)
zephyr_code_relocate(LIBRARY subsys__mgmt__mcumgr__mgmt LOCATION RAM_DATA)
Building the project
Use the standard Building an application procedure. The XIP QSPI sample supports and uses sysbuild by default.
Programming the project
For the nRF5340 DK and other boards equipped with flash working in the QSPI mode, use the standard programming command (west flash
).
For other cases, set up a configuration file for nrfjprog, as described in the following section.
Programming to external flash in SPI/DSPI mode
Programming an application with west triggers the nrfjprog runner.
The runner uses the default system settings that configure the application in the QSPI mode when programming the external flash.
You can change this behavior by using a custom Qspi.ini
configuration file.
Note
The Qspi.ini
file is required to work on the Nordic Thingy:53.
This file can specify the mode to use when programming the QSPI flash.
For example, the following code is from the file for the Thingy:53 and uses PP
for programming and READ2IO
for reading:
;----------------------------------------------
; 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 automatically in a project, update the CMakeLists.txt
file by adding the mention of the new file.
This way, the file can be applied globally for every board built with a project or applied to specific boards if a project supports multiple board targets, each with different configurations.
The following code shows how to set the configuration file used when flashing the Thingy:53 only.
Note
This code must be placed before the find_package
line.
cmake_minimum_required(VERSION 3.20.0)
macro(app_set_runner_args)
if(CONFIG_BOARD_THINGY53_NRF5340_CPUAPP)
# Use alternative QSPI configuration file when flashing Thingy53
board_runner_args(nrfjprog "--qspiini=${CMAKE_CURRENT_SOURCE_DIR}/Qspi_thingy53.ini")
endif()
endmacro()
find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
project(smp_svr_ext_xip)
Note
The external flash chip must be connected to the dedicated QSPI peripheral port pins of the nRF5340 SoC. It is not possible to program an external flash chip that is connected to different pins using nrfjprog.
Troubleshooting
Refer to the following sections for information on how to solve the most common issues.
Module does not appear to start
When using QSPI XIP, a frequent issue is the module not starting or crashing before the application runs.
This often results from a mismatch in init
priorities between the code on QSPI flash and the QSPI flash device.
To debug this issue, you can use a debugger such as GNU Debugger (GDB) to single-step through the application code until a QSPI address is encountered.
The backtrace functionality can then show which part of the code is responsible for the issue, and you can adjust the init
priority of that module accordingly.
Given that the QSPI flash init
priority defaults to 41
at the POST_KERNEL
level, take into account the following points:
There should be no QSPI flash residing code that has an
init
priority value that is less than or equal to thePOST_KERNEL
level41
.No interrupt handlers in the QSPI flash should be enabled until the QSPI flash driver has been initialized.
Module does not boot after update
This issue can occur if there is a mismatch between the internal flash code and the QSPI XIP code. Both slots must be running the same build to successfully boot. The application will fail to boot in the following cases:
If one of the updates is not loaded.
If a different build is loaded to one of the slots.
If one of the loaded updates is corrupt and deleted.
Indication of XIP performance
The XIP code execution performance measurement was conducted to evaluate the expected performance in different operating conditions.
The nRF Machine Learning application running on the nRF5340 DK was used for the testing. This particular application was used because its application design allows to move the Edge Impulse library to external memory. There is only one call to the library from the wrapper module, and therefore this call is used to measure the time of execution. Additional measurements of the current allowed to compare total energy used.
The following table lists performance numbers that were measured under different operating conditions.
Note
The numbers in the table refer to current consumed only by the nRF5340 SoC. For complete numbers, you must add the current used by external flash, which varies between manufacturers.
CPU frequency |
Memory |
Cache |
QSPI speed |
Mode |
Time [ms] |
Current @3.0V [mA] |
Current @1.8V [mA] |
Total energy @3.0V [µJ] |
Total energy @1.8V [µJ] |
---|---|---|---|---|---|---|---|---|---|
64 MHz |
Internal flash |
Yes |
n/a |
n/a |
63 |
3.2 |
5.1 |
605 |
578 |
64 MHz |
External flash |
Yes |
48 MHz |
Quad |
68.9 |
5.63 |
8.51 |
1164 |
1055 |
64 MHz |
External flash |
Yes |
24 MHz |
Quad |
73.7 |
5.58 |
8.44 |
1234 |
1120 |
128 MHz |
Internal flash |
Yes |
n/a |
n/a |
31 |
7.65 |
12.24 |
711 |
683 |
128 MHz |
External flash |
Yes |
96 MHz |
Quad |
34.1 |
8.99 |
14.1 |
920 |
865 |
128 MHz |
External flash |
No |
96 MHz |
Quad |
88.5 |
9.15 |
12.95 |
2429 |
2063 |
128 MHz |
External flash |
Yes |
48 MHz |
Quad |
36.4 |
8.85 |
13.9 |
966 |
911 |
Additional information
For additional information regarding the QSPI XIP mode of nRF Connect SDK and how to use it, see Using QSPI XIP split image.