Secure (STM32)
S/NS test program for STM32 MCUs
Introduction
This example program implements a simple, canonical Secure/Non-secure segregation as described in Secure/Non-secure Program Structure. For the design rationale – when and why to split a program S/NS – see Secure/Non-secure Program Design.
Functionality
Overview
The program implements a blinker across the S/NS boundary. The Secure (S) program configures the S/NS separation and the resources released to the Non-secure side, then starts the Non-secure (NS) program. The NS loop toggles the green LED on the Non-secure side directly, and the red LED on the Secure side via a call across the boundary.
The same program runs on both supported STM32 MCUs, the STM32U585 and the STM32H573; only a few memory addresses differ between them (see Memory Configuration).
The Secure code lives in directory sec, the Non-secure code in nonsec.
Module Map
The two programs that make up each project, and where to start reading the code.
Secure program – sec/:
| Module | Role |
|---|---|
Main |
Local Main: the standard framework start-up (clocks, console, RuntimeErrors, FPU), with Security.Config as the final step |
Security |
Program-specific S/NS configuration (see Memory Configuration below): SRAM split via GTZC, SAU regions, separate Secure/Non-secure fault handling, peripheral security; releases the green LED pin and USART1 to the Non-secure world |
S |
The Secure program proper: once Main has run, it hands control to NS via Secure.StartNonSecProg |
S0 |
A Secure service exposed to NS – toggles the Secure red LED |
Non-secure program – nonsec/:
| Module | Role |
|---|---|
Main |
Local Main for the Non-secure program |
Console |
Non-secure serial console |
NS |
The Non-secure control program: toggles the green LED directly, and the red LED via a Secure call to S0.ToggleLED |
ns_/NS_S0 |
Generated veneer stub that routes S0 calls across the boundary |
NS_S0 is generated by gen-secure.
Secure Program
Module Security performs the program-specific S/NS configuration, invoked as the final step of the Secure Main:
-
the flash, SRAM, and SAU memory configuration described under Memory Configuration below;
-
separate Secure / Non-secure fault handling: it sets
BFHFNMINSinPPB.AIRCR, so Bus Faults, Hard Faults, and NMI are handled by the corresponding handlers in the Secure and the Non-secure vector tables; -
peripheral security: all Devices are Secure (GPIO is Secure by default after reset).
Securityreleases only what NS needs – the green LED's GPIO pin and USART1 for the Non-secure console – and enables Non-secure access to the FPU.
Once Main has completed (start-up phase 1), the S module body hands control to the Non-secure program via Secure.StartNonSecProg. S imports S0 only so the Secure service module is linked into the image; S itself does not call it.
Non-secure Program
After being started by S, the Non-secure program NS enters a loop with simple counter-based timing. Each pass toggles the green LED directly (released to NS by Security), toggles the Secure red LED via a call to S0.ToggleLED (routed through the NSC gateway as described in Secure/Non-secure Program Structure), and prints a counter to the Non-secure console.
Sharing the UART
The Non-secure program runs a serial console of its own, yet it never configures a UART. The Secure side has already set USART1 up – clock, pins, baud rate – and released the peripheral to the Non-secure world. The NS Console only needs a software handle to drive it.
So Console.Install calls UART.Init(uartDev, UART.USART1) and nothing more: UART.Init fills in a Device record but touches no hardware. The UART definitions come from the MCU's base/* definition modules, resolved with the Non-secure addresses, so the record holds USART1's Non-secure registers. The record is in effect a "cloak" – an NS-side handle over the running UART, driving the hardware the Secure side configured, through Non-secure addresses.
Run-time Errors
Both NS and S0 toggle their LED through a short chain of procedures. Un-commenting the error-inducing ASSERT statements in those chains triggers a run-time error, useful for exercising the fault handling and stack-trace reporting – see Run-time Errors.
Build and Load
Building and loading any S/NS program entails creating two images, one for the Secure program and the NSC veneer gateways, and one for the Non-secure program.
Compiling and linking the S/NS program combo is best done using the build script build-elf.cmd in the project directory. build-elf.cmd uses the Astrobe config files in sec and nonsec.
See Set-up: Build Scripts on STM32 for instructions how to set up the environment for build scripts.
See Build & Load: STM32 (S/NS) for build and load instructions.
Memory Configuration
The configuration of the flash memory and the SRAM is essential for any S/NS program.
Flash Memory
Both STM32 have two 1M banks of flash memory. Using watermark registers, the flash controller is parametrised to configure the flash memory as follows upon reset:
-
Bank1: Secure, for the Secure address range starting at0C000000H; -
Bank2: Non-secure, for the Non-secure address range starting at08100000H.
Note that address 08100000H actually accesses a higher physical memory cell than 0C000000H due to aliasing. 0C100000H and 08100000H access the same physical memory cell, the former Secure, the latter Non-secure.
As outlined above, this flash memory configuration is stored in non-volatile memory and will survive a reset. That is, the Secure program does not need to (re-) configure this at each run.
Caution: you can lock yourself out of the MCU with certain parameter combinations. What we want to set here are just the two watermarks for flash bank1 and bank2.[1]
SRAM
Using GTZC1, the Secure program configures SRAM as follows (the GTZC config is volatile):
-
SRAM1: Secure, for the Secure address range starting at030000000H; -
SRAM3: Non-secure, for the Non-secure address range starting at020040000H(STM32U585) or at020050000H(STM32H573).
Analogous to flash memory, 020040000H (STM32U585) and 020050000H (STM32H573) access a higher physical address than 030000000H due to aliasing.
SAU
The SAU regions overlay the address ranges above, downgrading them from the "all Secure" default to Non-secure or Non-secure Callable (see Secure/Non-secure Program Structure for how the IDAU and SAU interact). The SAU configuration must be kept consistent with the flash controller and GTZC settings.
The Secure program configures the following SAU regions (the SAU config is volatile):
STM32U585
- Region 0:
0C0FE000Hto0C0FFFFFHas Non-secure Callable flash - Region 1:
08100000Hto081FFFFFHas Non-secure flash - Region 2:
020040000Hto0200BFFFFHas Non-secure SRAM (SRAM3) - Region 3:
040000000Hto04FFFFFFFHas Non-secure peripherals (see remark below)
STM32H573
- Region 0:
0C0FE000Hto0C0FFFFFHas Non-secure Callable flash - Region 1:
08100000Hto081FFFFFHas Non-secure flash - Region 2:
020050000Hto02009FFFFHas Non-secure SRAM (SRAM3) - Region 3:
040000000Hto04FFFFFFFHas Non-secure peripherals (see remark below)
The Non-secure Callable (NSC) flash memory will contain the gateway code from the Non-secure to the Secure world. We put NSC in the uppermost 8k of the Secure flash memory.
A remark regarding the peripherals: the flash controller is exempted from being accessible from NS using the SAU, since the Device does not have reasonable S/NS protection mechanisms, neither in itself or via GTZC.
Memory Secure Configuration Overview
This schema depicts the memory layout for the STM32U585.
flash memory SRAM
+------------------+ - SRAM3 +------------------+ -
| 0081FFFFFH | ^ | 0200BFFFFH | ^
| | | | | |
| Non-secure | | | Non-secure | |
| SAU: region 1 | | FLASH controller | SAU region 2 | | GTZC
| IDAU: Non-secure | | watermark-based | IDAU: Non-secure | | Non-secure
~ ~ | non-volatile cfg ~ ~ | volatile cfg
| | | | | |
| 008100000H | V | 020040000H | V
+------------------+ - +------------------+ -
+------------------+ - +------------------+
| 00C0FFFFFH | ^ SRAM2 | unused |
| | | +------------------+
| NSC | | +------------------+ -
| SAU: region 0 | | SRAM1 | 03002FFFFH | ^
| IDAU: NSC | | | | |
| | | | Secure | | GTZC
| 00C0FE000H | | | SAU: no region | | Secure
+------------------+ | FLASH controller | IDAU: NSC | | block-based
| 00C0FDFFFH | | Secure ~ ~ | volatile cfg
| | | watermark-based | | |
| Secure | | non-volatile cfg | 030000000H | V
| SAU: no region | | +------------------+ -
| IDAU: NSC | |
~ ~ |
| | |
| 00C000000H | V
+------------------+ -
Lower physical addresses are at the bottom of the figure.
No SAU region means Secure.
Further Reading
For the development history and a deeper walk-through of the call mechanics (stack handling, the SG / BXNS sequence, the veneer binary), see the experimental write-ups Secure/Non-secure Part 1 (basics, all in Non-secure space) and Secure/Non-secure Part 2 (the STM32 implementation). These predate the current tools and framework structure but explain the underlying mechanism in detail.
References
- Secure/Non-secure Program Structure
- Secure/Non-secure Program Design
- Set-up: Build Scripts on STM32
- Build & Load: STM32 (S/NS)
Repository
<repo>/examples/v3.1/stm/u585i-iot/Secure<repo>/examples/v3.1/stm/h573i-dk/Secure
This is probably not obvious, at least it wasn't for me: to set a bank to Non-secure, set the starting page index to the last page number (127), and the ending page index to the first page number (0). ↩︎
Last updated: 27 May 2026