
Microcontroller Bootloader
Sometimes the project may forbid to use some programming interfaces, either to spare costs, or mainly because there is simply no space left on where to put it. In any case, ST thankfully gives us some options on how to program their products, being JTAG and SWD being the most popular. However, most microcontrollers have what is called a bootloader.
In this blog post we are going to explore a little bit about the ST bootloader, how to use it, give a little bit more depth to it, show you an example with two different devices and give you a bash wrapper script that you may use.
ST Bootloader
The ST bootloader is a piece of proprietary firmware present in most microcontrollers of the ST brand, whose job is to rewrite, erase and read out flash memory at user request via an interface, just like any other programming tool. The interface may be UART, I²C, SPI, CAN, USB to name a few. The bootloader protocol varies according to which interface, having each its own specification document (e.g. AN3155 for UART).
Because it is so common among projects, our example is going to use the USART/serial/RS232 – hereafter called UART. Our examples will be based on the Nucleo-L452RE board, whose main device is the STM32L452RE: an ARM Cortex-M4 based processor.
Using the ST bootloader via UART
We are going to first show you:
- How to do it (TL;DR)
1. with a USB to UART device
2. with the Raspberry Pi SBC - Why are these steps to be executed?
The steps are not guaranteed to be the same for all microcontrollers.
Objectives:
- Program an STM32L452RE through its bootloader using the USB-UART converter SH-V09C5
- Program an STM32L452RE through its bootloader using the UART of a Raspberry Pi Single-board Computer (SBC)
Tested materials:
- STM32L452RE or Nucleo-L452RE board
- Raspberry Pi model 3B+ with latest Raspbian Lite OS
- Linux Operating System on host computer
- SH-V09C5 USB-UART converter
- stm32flash: UART and I²C firmware programmer software
- STM32CubeProgrammer: firmware and register options programmer software (optional)
If you don’t know what the following means, or don’t know how to check it, just ignore this part for now and then read the why are these steps to be executed part later.
NSWBOOT0
bit is 0 (FLASH_OPTR[26]
) ¹NBOOT1
bit is 0 (FLASH_OPTR[23]
)
Pin connections
The bootloader is accessible with most interfaces with the STM32L452RE: UART1, UART2, UART3, I²C1, I²C2, I²C3, SP1, SP2, CAN1 and DFU (USB) – but only via certain pins ². We are going to use UART1 for our example, which does not require the user to modify the development board, like the UART2 ³. In our example, UART1 has its Tx and Rx pins at different locations — but the bootloader on UART1 may only be accessed through pins PA9
(Tx) and PA10
(Rx).
UART user configuration is independent from bootloader UART configuration — eg. user may choose a baudrate of 4800 bps for the project and still talk to the bootloader at baudrate 115200 bps.
If you are planning to produce your own PCB and use the bootloader to program it, we recommend you to use a pull-down resistor for NBOOT0
pin and a capacitor between NRST
and ground.
Steps
- Configure boot mode
- Program target
The boot mode entry is executed by changing the NRST
and NBOOT0
(PH3
) pins logical levels, this can be done either with jumpers (as in this USB-UART converter) or with GPIOs (as in Raspberry Pi).
Program target with USB-UART converter

The recommended way to program the target with a bridge device is to use jumpers as stated below:
- Link
NBOOT0
pin toVDD
(the upper pin) - Don’t connect the
NRST
pin

Type in stm32flash /dev/ttyUSB0
: the bootloader should respond with the targets information.
stm32flash -r <file>.bin /dev/ttyUSB0 # read back to <file>.bin stm32flash -vw <file>.bin /dev/ttyUSB0 # programm <file>.bin
The recommended -v
flag is to verify after write.
Some people recommend using the DTR and RTS pins instead of manually using jumpers. However this particular converter could not enter the bootloader mode. Comment below if you were able to achieve it!
Program target with Raspberry Pi SBC

After having installed stm32flash on the Raspberry Pi (see Installing stm32flash on Raspberry Pi section), use /dev/serial0
(Tx: GPIO-14; Rx: GPIO-15) and any other two GPIOs for NRST
and NBOOT0
. In this example we are using GPIO-23 for NBOOT0
and GPIO-24 for NRST
. Then:
# creating the GPIOs echo "23" > /sys/class/gpio/export echo "24" > /sys/class/gpio/export echo "out" > /sys/class/gpio/gpio23 echo "out" > /sys/class/gpio/gpio24 # read back to <file>.bin stm32flash -R -r <file>.bin -i 23,-24,24,:-23,-24,24 /dev/serial0 # programm <file>.bin stm32flash -R -vw <file>.bin -i 23,-24,24,:-23,-24,24 /dev/serial0 # freeing the GPIOs echo "23" > /sys/class/gpio/unexport echo "24" > /sys/class/gpio/unexport
Please note the extra comma before the colon. It means to wait 100 ms before executing the next command, the connection timeout without this wait. Running the bash script gave us the following result:

Something is wrong… (troubleshooting)
Do the following:
- Link
NBOOT0
pin toVDD
(the upper pin) - Don’t connect the
NRST
pin - Power the target on and then reset (press the reset button if you are using the board)
- If you are using the board, read the board User Guide and make sure that the solder bridges are correct
Then type in stm32flash /dev/ttyUSB0
(with a converter) or stm32flash /dev/serial0
(with the Raspberry Pi). You should get the microcontroller details. Good luck!
Why did we do that?
The STM32 proprietary firmware checks for some pins plus some registers and decide where to start the execution.
The bootloader firmware makes a „symbolic link“ from the target memory region to 0x0000 0000
and then executes whatever is at 0x0000 0000
. The target region may be:
- System memory – aka. bootloader memory
- Flash: where the user code is programmed
- RAM: useful for debugging
In this example target the boot will be according to the logic 4:
flash
system memory
SRAM1
Where:
- A is
nBOOT1
(FLASH_OPTR[23]
) - B is
nBOOT0
(FLASH_OPTR[27]
) - C is
BOOT0
(PH3
) - D is
nSWBOOT0
(FLASH_OPTR[26]
) - E is whether the main flash empty (1) or not (0)
Moreover:
- Flash memory is where the application is stored, located at
0x0800 0000
- System memory is where the bootloader is located, located at
0x1FFF 0000
- SRAM1 is the main RAM, ie. whatever is in the memory currently, data here is lost at power off, located at
0x2000 0000
The system memory is already protected against writes. AN2606 gives you the appropriate patterns to use for the target device.
To get or modify the boot bits (NBOOT0
, NBOOT1
, NSWBOOT0
), we recommend that you use the official STM32CubeProgrammer software. It may also be used with the bootloader connection (!).
Now why did it work before? Assuming that nSWBOOT0 = nBOOOT1 = 1
(the default, if you never modified it) gives you the following options at power-up / reset:
PH3 = 0
→flashPH3 = 1
→bootloader
Which is exactly the sequence that we entered5.
Installing stm32flash on Raspberry Pi
Install the usual apt
package: sudo apt install stm32flash
. Or if you want to install it from source:
git clone https://git.code.sf.net/p/stm32flash/code stm32flash cd stm32flash sudo make install
Edit /boot/config.txt
:
# UART in GPIO pins, not bluetooth device dtoverlay=pi3-miniuart-bt
Edit /boot/cmdline.txt
:
# remove following sentence to free serial0 for communication console=serial0,115200
Test code
We used a very simple program to test the communication after using the bootloader. If you are interested it is basically the STM32CubeMx generated project with the UART1 enable, with this UART sending the compilation time after every 500 ms:
int main(void) { // ... while (1) { HAL_UART_Transmit(&huart1, __TIME__, 8, 1000); // send compilation date HAL_Delay(500); // wait 500 ms } }
Bash script
This bash script basically executes stm32flash for Raspberry Pi, automating the GPIO reserve and freeing process while making the interface more pleasant. To use it, first run: sudo usermod -aG gpio ${USER}
. See the details with ./raspi-flasher.sh -h
(after chmod +x raspi-flasher.sh
).
#!/usr/bin/env bash # Date 2022-10-13 # Copyright nubix Software-Design GmbH # Author Vinícius Gabriel Linden # Brief Program STM32 through bootloader with a Raspberry Pi's UART model=$(tr -d '\0' 2>/dev/null < /proc/device-tree/model) [[ ! "${model}" =~ "Raspberry Pi" ]] && \ echo "Please run this script only in a Raspberry Pi" && \ exit 5 set -e # Tested setup: # Target = Nucleo-L452RE without SB2 and SB12 # Raspberry Pi 3B+ # stm32flash version 0.7 declare -A GPIO # You can change the default GPIOs here: GPIO[nboot0]="23" GPIO[nrst]="24" usage() { script="${BASH_SOURCE[0]##*/}" cat << EOF ${script}: Program STM32 device via bootloader UART. Configure the /boot/cmdline.txt and /boot/config.txt appropriately. OPTIONS: -h Display this help menu -R GPIO Change the NRST pin to GPIO (default is GPIO-${GPIO[nrst]}) -B GPIO Change the NBOOT0 pin to GPIO (default is GPIO-${GPIO[nboot0]}) -e Erase flash -w FILE Program file -r FILE Read out flash and save to FILE EXAMPLES ./${script} Get target details / test target connection ./${script} -R 12 -B 13 -e Erase target flash, with NRST and NBOOT0 pins connected to 12 and 13 respectively ./${script} -r target.bin Read out target memory and save it to ./target.bin in binary format ./${script} -w firmware.bin Program target with ./firmware.bin COPYRIGHT nubix Software-Design GmbH 2023 EOF exit "${1}" } declare cmd while getopts "R:B:ew:r:h" i; do case "${i}" in R) GPIO[nrst]="${OPTARG}" ;; B) GPIO[nboot0]="${OPTARG}" ;; e) cmd="-o" ;; w) cmd+="-vw ${OPTARG}" ;; r) cmd="-r ${OPTARG}" ;; h) usage 0 ;; *) usage 1 ;; esac done command -v stm32flash > /dev/null || \ ( echo "stm32flash is required to run this script" && exit 2 ) reserve_output() { for number in "${@}"; do [ ! "${number}" ] && echo "Missing GPIO number" && return 3 [[ ! "${number}" =~ ^[0-9]+$ ]] && \ echo "\"${number}\" is not a valid gpio number" && return 3 ((number>27)) && echo "\"${number}\" is outside the range" && return 3 path="/sys/class/gpio/gpio${number}" [ -h "${path}" ] && echo "GPIO-"${number}" already exists" && \ echo "Reserving it for out purposes" && \ echo "out" > "${path}/direction" && \ continue echo "Reserving GPIO-${number}" echo "${number}" > "/sys/class/gpio/export" sleep 0.1 echo "out" > "${path}/direction" done } free_output() { for number in "${@}"; do [ ! "${number}" ] && echo "Missing GPIO number" && return 4 [[ ! "${number}" =~ ^[0-9]+$ ]] && \ echo "\"${number}\" is not a valid gpio number" && return 4 ((number>27)) && echo "\"${number}\" is outside the range" && return 4 path="/sys/class/gpio/gpio${number}" [ ! -h "${path}" ] && echo "GPIO-"${number}" was never created" && \ continue echo "Freeing GPIO-${number}" echo "${number}" > "/sys/class/gpio/unexport" done } reserve_output "${GPIO[@]}" # Entry sequence: # NBOOT0 = VDD # NRST = 0V # NRST = VDD # Exit sequence: # NBOOT0 = 0V # NRST = 0V # NRST = VDD set +e stm32flash \ -R \ ${cmd} \ -i "${GPIO[nboot0]}",-"${GPIO[nrst]}","${GPIO[nrst]}",:\ -"${GPIO[nboot0]}",-"${GPIO[nrst]}","${GPIO[nrst]}" \ /dev/serial0 ret="${?}" set -e free_output "${GPIO[@]}" exit "${ret}"
Remarks
¹ Check out RM0394 – „3.7.8 Flash option register (FLASH_OPTR)“ for details.
² Checkout AN2606 „63.1 Bootloader configuration“ for details.
³ The interface may be connected to certain pins, but not to another that interfaces with the bootloader.
4 Check out RM0394 — „Table 6. Boot modes“ for the original table.
5 The sequence "${GPIO[nboot0]}",-"${GPIO[nrst]}","${GPIO[nrst]}",:-"${GPIO[nboot0]}",-"${GPIO[nrst]}","${GPIO[nrst]}"
means exactly: raise NBOOT0
, reset pulse to enter the bootloader; lower NBOOT0
, reset pulse to exit the bootloader.
Resources
- AN2606 STM32 microcontroller system memory boot mode
- RM0394 STM3245XXX Reference manual
- DS11912 STM32L452xx datasheet
- AN3155 USART protocol used in the STM32 bootloader
- Flashing the STM32F board using a Raspberry Pi 3 — RPi-STM32-flash (not tested)
Image 4: 2020; STMicroelectronics, life.autmented; UM1724, User manual, STM32 Nucleo-64 boards (MB1136); https://www.st.com/resource/en/user_manual/dm00105823-stm32-nucleo-64-boards-mb1136-stmicroelectronics.pdf; P. 34
Cover Image: Raspi Programming (© nubix, 2023)