STM32 Bootloader Usage: A Practical Guide


Here’s a practical, engineer-friendly guide to using the STM32 Bootloader—both the built-in ST ROM bootloader and a custom bootloader you place in Flash.
A) Using ST’s ROM Bootloader (a.k.a. “System Memory” boot)
The ROM bootloader lives in System Memory and lets you program the MCU without any user code—handy for production, recovery, or field updates.
1) How to enter the ROM bootloader
You must make the core boot from System Memory at reset. Do it in one of two ways (family-dependent):
BOOT0 pin method (classic):
Pull BOOT0 = 1 (HIGH).
Reset or power-cycle the MCU.
The core vectors into System Memory (the ROM bootloader).
After flashing, set BOOT0 = 0 and reset to run your application.
Option-bytes method (newer families):
Configure “boot from System Memory” in Option Bytes (e.g.,
nBOOT0
,BOOT_ADDx
).Reset to enter the ROM bootloader without touching pins.
Keep a recovery path (don’t permanently lock yourself out).
Tip: For one-off recovery on boards that expose BOOT0 and NRST, the pin method is simplest.
2) Which interfaces can you use?
Depends on the exact STM32 family, but commonly:
UART/USART (very common)
USB DFU (Device Firmware Upgrade)
I²C, SPI
(F)DCAN
(Some families offer additional/alternative ports.)
Check your part’s reference table (ST’s “AN2606”) for which peripheral instance & pins the ROM bootloader uses.
3) Wiring & tools (quick recipes)
UART bootload (generic)
Wiring: USB-to-UART adapter
Adapter TX → MCU RX (the specified USARTx pin)
Adapter RX → MCU TX
GND ↔ GND
Enter bootloader: BOOT0=1 then reset (or OB route).
Flash with STM32CubeProgrammer (GUI or CLI):
GUI: “UART” → pick COM port → “Connect” → “Erasing & Programming”.
CLI example (HEX file):
STM32_Programmer_CLI -c port=COM5 -w app.hex -v -rst
CLI example (BIN file):
STM32_Programmer_CLI -c port=COM5 -w app.bin 0x08000000 -v -rst
(If parity/baud matters for your device, CubeProgrammer handles it; otherwise add
br=115200
etc.)
USB DFU bootload
Wiring: Just USB (device enumerates as DFU).
Enter bootloader: BOOT0=1 then reset (or OB).
Flash with:
STM32CubeProgrammer (GUI: “USB” target).
dfu-util (open source), example for BIN:
dfu-util -a 0 -s 0x08000000:leave -D app.bin
(
:leave
resets to run your app after writing.)
I²C / SPI / (F)DCAN bootload
Use STM32CubeProgrammer with the matching interface (requires correct transceiver/wiring).
Useful in production jigs where UART/USB isn’t available.
4) Typical flashing workflow (ROM bootloader)
Enter the bootloader (BOOT0=1 or OB).
Connect with your tool (CubeProgrammer/dfu-util).
Erase target Flash.
Program your image (HEX/ELF/BIN at
0x0800_0000
by default).Verify, then reset (or
leave
in DFU).Restore normal boot (BOOT0=0, or revert OB) and reset to run the app.
B) Using your own bootloader (in main Flash)
You might want your own bootloader for OTA, BLE/Wi-Fi/Ethernet updates, SD-card updates, custom encryption, A/B images, etc.
1) Memory map (example)
Bootloader at start of Flash (e.g.
0x0800_0000 … 0x08003FFF
)Application at APP_ADDR (e.g.
0x0800_4000
)
Adjust sizes to your project and Flash page/sector boundaries.
2) Linker changes (application)
Set application Flash origin to APP_ADDR.
Place the vector table at APP_ADDR (so the first 8 bytes are MSP & Reset vectors).
Example (GNU ld snippet):
FLASH (rx) : ORIGIN = 0x08004000, LENGTH = 0x100000 - 0x4000
3) Bootloader → Application handoff
Disable interrupts, stop SysTick, deinit peripherals you touched.
Point vector table at the app and jump.
#define APP_ADDR 0x08004000u
typedef void (*app_entry_t)(void);
static void jump_to_app(void) {
uint32_t app_msp = *(uint32_t *)(APP_ADDR + 0x0);
uint32_t app_reset = *(uint32_t *)(APP_ADDR + 0x4);
__disable_irq();
SysTick->CTRL = 0; // stop SysTick
// Deinit clocks/peripherals as needed…
SCB->VTOR = APP_ADDR; // app’s vector table
__set_MSP(app_msp);
__DSB(); __ISB();
((app_entry_t)app_reset)(); // go!
}
4) Update strategies
Single-image: erase app region, program new app, verify, then jump.
- Guard with CRC/SHA to ensure integrity before jumping.
Dual-image (A/B): write new app to the inactive slot; after verification, flip a flag/option and reboot.
- Provides power-fail safety and rollback.
5) Entering your bootloader
Common triggers:
GPIO “boot key” held at reset (e.g., user button).
Double-tap reset timing window.
Invalid app check (bad CRC) → stay in bootloader.
Command from the running app (e.g., write a magic value to BKP/RTC RAM, then reset).
6) Security (recommended)
Authenticate the image (ED25519/ECDSA or at least HMAC) before activation.
Use encrypted transport if the link is exposed (BLE/Wi-Fi).
Consider device Readout Protection and write protection for the bootloader pages.
On TrustZone-capable parts, keep the secure root (verification) in the secure world.
C) Quick troubleshooting checklist
Nothing runs after flashing?
Did you program to the correct address (BIN needs explicit address)?
Is BOOT0 back low or OB set to boot from Flash?
Vector table correct for app (MSP/PC valid at APP_ADDR)?
Interrupts misbehave after jump?
Did you set
SCB->VTOR = APP_ADDR
and disable old IRQs?Clear/disable peripheral IRQ flags before jumping.
USB DFU not enumerating?
Not all chips support DFU in ROM; confirm your part and pins.
For a custom USB bootloader, verify clock setup (48 MHz requirement).
UART bootloader won’t connect?
Check the exact USART instance & pins for your part’s bootloader.
Swap TX/RX, confirm GND, try a different baud, and reset right before connect.
TL;DR
ROM bootloader = fastest way to flash or recover: set boot mode → use CubeProgrammer/dfu-util → program Flash at
0x08000000
.Custom bootloader = full control: reserve a Flash region, relocate your app, implement jump + integrity checks, and choose your update transport.
Always keep a reliable recovery path (BOOT0 pads, or an app command that enters the ROM bootloader/custom BL).
Subscribe to my newsletter
Read articles from ampheo directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
