Build and Load: RP2350 (S/NS)
Building and loading programs with S/NS segregation on the RP2350.
Overview
This document describes how to build, load, and run RTK-based programs with S/NS segregation for the RP2350 for two scenarios:
- simple loading, without debugger
- with debugger
S/NS programs comprise two images, one for the Secure program and the NSC veneers, one for the Non-secure programs. S/NS programs for the RP2350 can be built for loading with or without partition tables. We'll explain both loading scenarios below. But be aware that the address ranges, as well as the programs themselves are specific for each loading scenario (see the examples listed at the bottom).
Since we need to load two images for the S program, ELF files are the only option, with or without debugger (RPi may or may not adapt their proprietary UF2 extensions to allow two images in one UF2 file in the future).
For programs without Secure/Non-secure segregation refer to Build and Load: RP2350…
See also Practical Notes.
Project Set-up
For the commands below, the following configuration and setup are assumed.
-
Address ranges: see Build RTK-based Programs.
- note the different code address ranges with or without partitions
- the programs must be built accordingly
-
Directories:
- S program in
<project-dir>/sec - NS program in
<project-dir>/nonsec
- S program in
-
Commands as shown are run with
cwd = <project-dir>. -
The IMAGE_DEF metadata block required to boot a binary is prepended to the S image using
make-elf, hence the linker base address for the S program must be10000100H(CodeStartin the.iniconfig file). -
For debugging, the additional tools must be installed and configured (see section Tools below).
Build and Load, no Debugging
Build the Secure Program
1. Insert Secure epilogues for each S-side module that is exposed to NS
> python -m sec-epilogue <M0>.mod <M1>.mod ...
Notes:
- this only needs to be run when a module is changed
- use
--no-clearto omit the register clearing sequence
2. Build RTK-based Programs the S program.
3. Run gen-secure to create the NSC veneers binary, and the interface modules for the NS program:
> python -m gen-secure sec/<Sprogram>.map [options]
Notes:
- options: see gen-secure
- config file: in lieu of specifying the options a configuration file can be used – example:
[gen-secure]
nsc-addr = 1007E000
ns-dir = nonsec/ns_
nsc-dir = sec
const-leaf = BASE
modules =
S0
4. Make the ELF file for the S program, also including the NSC binary created by gen-secure:
> python -m make-elf sec/<Sprogram>.bin:10000100 sec/NSC.bin:1007E000 --image-def
Build the Non-secure Program
5. Build the NS program
6. Make the ELF file for the NS program
- Without partition table:
> python -m make-elf nonsec/<NSprogram>.bin
Notes:
-
load address is read from the
.mapfile -
no
image-defoption used -
With partition table:
> python -m make-elf nonsec/<NSprogram>.bin:10000000
Notes:
- load address must be
10000000Hwhen loading into a partition (XIP_BASE); however, the NS program must be compiled and linked with the translated address. - no
image-defoption used
Load the Images
7. Put the RP2350 in BOOTSEL mode, which mounts the virtual volume RP2350
8. Load the two ELF files to the RP2350
- Without partition table:
> picotool load nonsec/<NSprogram>.elf -v
> picotool load sec/<Sprogram>.elf -v -x
- With partition table (assuming partition 0 for S, 1 for NS):
> picotool load -p 1 nonsec/<NSprogram>.elf -v
> picotool load -p 0 sec/<Sprogram>.elf -v -x
Build and Load for Debugging
Build the Secure Program
1. sec-epilogue as above
2. Build: as above
3. gen-secure: as above
4. Run gen-rdb to create an .alst file for each module in the S program, plus _startup.alst for the startup sequence inserted by the linker at the end of the program; make-elf requires the .alst files to create symbols and DWARF debug data
> python -m gen-rdb sec/<Sprogram>.map --rdb-dir sec/rdb --nsc-dir sec
5. Make the ELF file for the S program
> python -m make-elf sec/<Sprogram>.bin:10000100 sec/NSC.bin:1007E000 --image-def --rdb-dir sec/rdb --debug --sym-prefix S
Build the Non-secure Program
6. Build: as step 5 above in the non-debug scenario
7. Run gen-rdb to create an .alst file for each module in the NS program, plus _startup.alst:
> python -m gen-rdb nonsec/<NSprogram>.map --rdb-dir nonsec/rdb
8. Make the debug ELF file for the NS program
- Without partition table:
> python -m make-elf nonsec/<NSprogram>.bin --rdb-dir nonsec/rdb --debug --sym-prefix NS
- With partition table:
> python -m make-elf nonsec/<NSprogram>.bin:10000000 --rdb-dir nonsec/rdb --debug --sym-prefix NS
Refer to the notes above about the address ranges.
Load the Images
9. Load the two debug ELF files to the RP2350
-
Refer to Debugging:
- GUI: Cortex-Debug inside VisualStudio Code
- command line: OpenOCD and GDB
- be aware that loading an ELF file via debugger will overwrite any partition table
- hence, follow the attach instructions with a partition table
-
Without partition table:
- use the load debug scenarios
-
With partition table (assuming partition 0 for S, 1 for NS):
- put the RP2350 in
BOOTSELmode, which mounts the virtual volumeRP2350 - then use
picotoolas outlined below - use the attach debug scenarios
- put the RP2350 in
> picotool load -p 1 nonsec/<NSprogram>.elf -v
> picotool load -p 0 sec/<Sprogram>.elf -v -x
Partition Targeting
Any partition can be targeted independent of its type, eg. partition 0:
> picotool load -p 0 <program>.elf -v -x
Tools
- make-elf
- {ref}gen-rdb`
- gen-secure
- sec-epilogue
- External Tools
- Environment Variables
Examples (Repository)
<repo-root>/examples/v3.1/rpi/pico2/Secure6(without partition table)<repo-root>/examples/v3.1/rpi/pico2/Secure7(with partition table)
Last updated: 17 April 2026