This repository demonstrates a way of splitting an application for nRF5340 so that it partially resides on internal flash and partially on QSPI flash. The repository includes a sample application that shows how this functionality can be used in other projects and how it can be set up.
Follow the instructions on the nRF Connect SDK documentation page to get the required tools. For the step 4 ("Get the nRF Connect SDK code"), you must instead use this repository's URL: west init -m https://github.com/NordicPlayground/ncs-nrf5340-xip-app.git
.
The following is needed for adapting the sample application to your project:
- One nRF5340-based board with SPI flash chip attached through dedicated QSPI peripheral, with DTS correctly configured for the connected flash chip. The board does not have to run in the QSPI mode; it can run in SPI or DSPI modes also.
- An application for the network core.
- An application for the application core.
- A static partition manager configuration file, with internal flash and QSPI flash correctly partitioned.
- A linker file that is set up to specify the location of the QSPI XIP code.
- Code relocation in CMake to specify what files to relocate to QSPI XIP.
- MCUboot set up as bootloader.
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.
The following snippet shows an example of the configuration for Nordic Thingy:53 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 a QSPI peripheral product anomaly, the QSPI peripheral must be ran with HFCLK192MCTRL=0
.
Setting this to any other value may cause undefined operation of the device.
You must create a static configuration for partition manager. This configuration must have 3 images of 2 slots each:
- The first set of slots is for the internal flash portion 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 portion of the application.
These slots should be named
mcuboot_primary_2
andmcuboot_secondary_2
.
The following snippet shows 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: 0xf0000
orig_span: &id001
- mcuboot_pad
- app
region: flash_primary
size: 0xe0000
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: 0xf0000
orig_span: &id002
- app
region: flash_primary
size: 0xdfe00
span: *id002
mcuboot_secondary:
address: 0x0
device: MX25R64
end_address: 0xe0000
region: external_flash
size: 0xe0000
mcuboot_secondary_1:
address: 0xe0000
device: MX25R64
end_address: 0x120000
region: external_flash
size: 0x40000
mcuboot_primary_2:
address: 0x120000
device: MX25R64
end_address: 0x160000
region: external_flash
size: 0x40000
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
In the linker file, you must specify the start address and the size of the QSPI XIP region.
For nRF5340, the XIP peripheral is mapped to 0x10000000
, which corresponds to 0x0
in the QSPI flash chip (the QSPI peripheral supports an offset address, but this is not supported in this code.)
Make sure to offset the location by the value of mcuboot_primary_2
and mcuboot_pad
, as an MCUboot header needs to be prepended to the image.
The following snippet shows an example of the file configuration:
#include <zephyr/linker/sections.h>
#include <zephyr/devicetree.h>
#include <zephyr/linker/linker-defs.h>
#include <zephyr/linker/linker-tool.h>
MEMORY
{
EXTFLASH (wx) : ORIGIN = 0x10120200, LENGTH = 0x3FE00
}
#include <zephyr/arch/arm/aarch32/cortex_m/scripts/linker.ld>
Relocating code to QSPI XIP is 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, you can 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)
You must configure MCUboot for 3 images, corresponding to the following areas:
- Internal flash
- Network core
- QSPI flash
Partial updates of either the internal flash or the QSPI flash are not supported and will likely cause the module to fault. You must update both sections when performing a firmware update.
You can apply firmware updates using the following methods:
- Using MCUmgr if the application has enabled it.
- Using MCUboot serial recovery, via UART, using the
*_update.bin
files. - Using Bluetooth LE through the nRF Connect app for iOS or Android by using the
dfu_application.zip
file.
When flashing applications using west
, this will invoke the nrfjprog
runner.
This runner will use the system default configuration that will configure the application in the QSPI mode (when flashing the external flash).
This behavior can be changed using a custom Qspi.ini
configuration file, but this will prevent flashing from being performed using west
. A sample Qspi.ini
file is provided in the root of this repository. The file is set up to work on Nordic Thingy:53. If you decide to use the Qspi.ini
file, the HEX files in the repository need to be manually flashed. For example, for the zigbee_weather_station
application, the files to flash are the following (paths are relative to the build directory):
- 802154_rpmsg/zephyr/merged_CPUNET.hex
- mcuboot/zephyr/zephyr.hex
- zephyr/internal_flash_signed.hex
- zephyr/qspi_flash_signed.hex
For the smp_svr
sample application, the files to flash are the following:
- hci_rpmsg/zephyr/merged_CPUNET.hex
- mcuboot/zephyr/zephyr.hex
- zephyr/internal_flash_signed.hex
- zephyr/qspi_flash_signed.hex
The follow commands can be used to flash and verify the application for zigbee_weather_station
(adjusting the path to the ini file):
nrfjprog -f NRF53 --coprocessor CP_NETWORK --sectorerase --program 802154_rpmsg/zephyr/merged_CPUNET.hex --verify
nrfjprog -f NRF53 --sectorerase --program mcuboot/zephyr/zephyr.hex --verify
nrfjprog -f NRF53 --sectorerase --program zephyr/internal_flash_signed.hex --verify
nrfjprog -f NRF53 --qspisectorerase --program zephyr/qspi_flash_signed.hex --qspiini <path_to>/Qspi.ini --verify
nrfjprog -f NRF53 --reset
The following commands are for the smp_svr
sample:
nrfjprog -f NRF53 --coprocessor CP_NETWORK --sectorerase --program hci_rpmsg/zephyr/merged_CPUNET.hex --verify
nrfjprog -f NRF53 --sectorerase --program mcuboot/zephyr/zephyr.hex --verify
nrfjprog -f NRF53 --sectorerase --program zephyr/internal_flash_signed.hex --verify
nrfjprog -f NRF53 --qspisectorerase --program zephyr/qspi_flash_signed.hex --qspiini <path_to>/Qspi.ini --verify
nrfjprog -f NRF53 --reset
Note: The external flash chip must be connected to the dedicated QSPI peripheral port pins of the nRF5340, it is not possible to program an external flash chip connected to different pins using nrfjprog
.
The repository here serves as a starting point to add the QSPI XIP integration into other projects. The way to do this is to use this repository as the basis for your own project manifest repository so that the CMake code and Kconfig configuration is available.
Pay attention to the following points:
- The included samples
smp_svr
andzigbee_weather_station
can be removed, but all other files should be kept. - The manifest can be freely updated to include other modules, update the NCS revision, or retract which modules of NCS are cloned. However, it must not use a revision older than
18682391decaaa989c362ec8c5b65fd6203a5fdb
. - A partition manager static configuration file must be provided for your application. You can check the example configuration in the section above. This configuration must include 3 images of 2 slots each, whereby the third is located in the QSPI flash.
- A linker file must also be provided for your application. You can check the example configuration in the section above. This must have the start and end addresses of the QSPI XIP memory region where code starts and ends, including offsets for the MCUboot header, among others. This should be placed in the root of your application folder, in the same directory where the
CMakeLists.txt
file is located. - The following Kconfig configuration must be set for your application:
CONFIG_CODE_DATA_RELOCATION=y
CONFIG_HAVE_CUSTOM_LINKER_SCRIPT=y
CONFIG_CUSTOM_LINKER_SCRIPT="linker_arm_nocopy.ld" # Change this if you have named your linker script differently
CONFIG_BUILD_NO_GAP_FILL=y
CONFIG_XIP=y
CONFIG_NORDIC_QSPI_NOR_XIP=y
CONFIG_UPDATEABLE_IMAGE_NUMBER=3
CONFIG_XIP_SPLIT_IMAGE=y
Here are solutions to some common issues.
A common issue when using QSPI XIP in an application is that the module does not appear to start or continously ends in a fault before the application can run.
This is likely to be caused by priority mismatch: the init
priority of the code residing on the QSPI flash is lower than the init
priority of the QSPI flash device itself.
To debug this issue, you can use a debugger such as GDB to single-step through the application code until a QSPI address is encountered. At that point, the backtrace functionality can show what 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
41
level. - No interrupt handlers in the QSPI flash should be enabled until the QSPI flash driver has been initialized.
This issue can arise if there is a mismatch between the internal flash code and QSPI XIP code. Both slots must be running the same build to successfully boot. If one of the updates is not loaded, or a different build is loaded to one of the slots, or one of the updates loaded is corrupt and deleted, then the application will fail to boot.