A minimal bootloader for STM32U575ZI that holds two firmware images in flash and selects which one to boot based on a flag written to SRAM before reset.
Flash layout
┌─────────────────────────────────┐ 0x0800_0000
│ bootloader (64 KiB) │
├─────────────────────────────────┤ 0x0801_0000
│ Firmware A (64 KiB) │
├─────────────────────────────────┤ 0x0802_0000
│ Firmware B (64 KiB) │
└─────────────────────────────────┘
On every reset the bootloader reads a single word at 0x200A_0000 (mid-SRAM,
safe from .data/.bss and the stack):
| Flag value | Action |
|---|---|
1 |
jump to Firmware B |
| anything else | jump to Firmware A |
SRAM contents survive a system reset but are lost on power-cycle, so the selection is purely runtime — no flash wear, no state sector.
- Running firmware receives a new image (USB / UART / CAN / …)
- Writes it to the inactive slot's flash range
- Sets
BOOT_FLAG = 1(or0) in SRAM - Triggers a system reset → bootloader picks the new slot
| Binary | File | Description |
|---|---|---|
bootloader |
src/bin/bootloader.rs |
Uses embassy-boot-stm32::BootLoader — handles icache invalidation and VTOR setup |
raw_bootloader |
src/bin/raw_bootloader.rs |
Hand-rolled: disables interrupts, sets MSP/VTOR/PC directly via inline asm |
Both are functionally equivalent. bootloader is the default.
probe-rs≥ 0.24- Rust target:
thumbv8m.main-none-eabihf
rustup target add thumbv8m.main-none-eabihf# Build and flash all three binaries (bootloader + firmware A + firmware B)
make flash
# Boot into Firmware A and stream RTT output
make run-a
# Boot into Firmware B and stream RTT output
make run-bExpected output:
$ make flash
cargo build -p bootloader --bin bootloader
Finished `dev` profile [optimized + debuginfo] target(s) in 0.05s
cargo build -p app-a
Finished `dev` profile [optimized + debuginfo] target(s) in 0.05s
cargo build -p app-b
Finished `dev` profile [optimized + debuginfo] target(s) in 0.05s
probe-rs download --chip STM32U575ZI --probe 0483:374e:003800323532510F31333430 target/thumbv8m.main-none-eabihf/debug/bootloader
Erasing ✔ 100% [####################] 8.00 KiB @ 246.93 KiB/s (took 0s)
Programming ✔ 100% [####################] 1.00 KiB @ 29.62 KiB/s (took 0s) Finished in 0.07s
probe-rs download --chip STM32U575ZI --probe 0483:374e:003800323532510F31333430 target/thumbv8m.main-none-eabihf/debug/app_a
Erasing ✔ 100% [####################] 8.00 KiB @ 247.96 KiB/s (took 0s)
Programming ✔ 100% [####################] 6.00 KiB @ 51.24 KiB/s (took 0s) Finished in 0.15s
probe-rs download --chip STM32U575ZI --probe 0483:374e:003800323532510F31333430 target/thumbv8m.main-none-eabihf/debug/app_b
Erasing ✔ 100% [####################] 8.00 KiB @ 248.29 KiB/s (took 0s)
Programming ✔ 100% [####################] 6.00 KiB @ 52.66 KiB/s (took 0s) Finished in 0.15s
=== Flash slots: [initial-MSP] [Reset-Handler] ===
bootloader @ 0x08000000 -> 200c0000 08000239
app_a @ 0x08010000 -> 200c0000 08010239
app_b @ 0x08020000 -> 200c0000 08020239
$ make run-a
probe-rs write --chip STM32U575ZI --probe 0483:374e:003800323532510F31333430 b32 0x200A0000 0
probe-rs run --chip STM32U575ZI --probe 0483:374e:003800323532510F31333430 --preverify --rtt-scan-memory target/thumbv8m.main-none-eabihf/debug/bootloader
Finished in 0.03s
14:59:28.914: Hello from Firmware A!
14:59:28.914: Hello from Firmware A!
14:59:28.914: Hello from Firmware A!
14:59:29.821: Hello from Firmware A!
14:59:30.830: Hello from Firmware A!
14:59:31.839: Hello from Firmware A!
^C
$ make run-b
probe-rs write --chip STM32U575ZI --probe 0483:374e:003800323532510F31333430 b32 0x200A0000 1
probe-rs run --chip STM32U575ZI --probe 0483:374e:003800323532510F31333430 --preverify --rtt-scan-memory target/thumbv8m.main-none-eabihf/debug/bootloader
Finished in 0.03s
14:59:38.908: Hello from Firmware B!
14:59:38.908: Hello from Firmware B!
14:59:38.908: Hello from Firmware B!
14:59:39.816: Hello from Firmware B!
^C
BIN=raw_bootloader make flash
BIN=raw_bootloader make run-a
BIN=raw_bootloader make run-bmake verify=== Flash slots: [initial-MSP] [Reset-Handler] ===
bootloader @ 0x08000000 -> 200c0000 08000239
app_a @ 0x08010000 -> 200c0000 08010239
app_b @ 0x08020000 -> 200c0000 08020239
The Reset-Handler addresses confirm each slot contains a distinct binary.
.
├── Makefile
├── bootloader/
│ ├── Cargo.toml
│ ├── memory.x # flash/RAM layout for the bootloader
│ └── src/bin/
│ ├── bootloader.rs # embassy-boot-stm32 version (default)
│ └── raw_bootloader.rs # inline-asm version
├── app_a/ # prints "Hello from Firmware A!" via RTT
└── app_b/ # prints "Hello from Firmware B!" via RTT