Skip to content

About

Minimal dual-slot firmware bootloader for the STM32U575ZI in Rust: two slots, one word of SRAM, no swap partition or state machine

Resources

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

 

History

2 Commits

Folders and files

Repository files navigation

STM32U575ZI Dual-Firmware Bootloader Demo

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.

How it works

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.

Firmware update flow

  1. Running firmware receives a new image (USB / UART / CAN / …)
  2. Writes it to the inactive slot's flash range
  3. Sets BOOT_FLAG = 1 (or 0) in SRAM
  4. Triggers a system reset → bootloader picks the new slot

Two bootloader implementations

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.

Prerequisites

  • probe-rs ≥ 0.24
  • Rust target: thumbv8m.main-none-eabihf
rustup target add thumbv8m.main-none-eabihf

Quick start

# 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-b

Expected 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

Using the raw bootloader

BIN=raw_bootloader make flash
BIN=raw_bootloader make run-a
BIN=raw_bootloader make run-b

Verify flash contents

make 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.

Project structure

.
├── 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

About

Minimal dual-slot firmware bootloader for the STM32U575ZI in Rust: two slots, one word of SRAM, no swap partition or state machine

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages