Microcontroller Bootloader

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:

  1. How to do it (TL;DR)
    1. with a USB to UART device
    2. with the Raspberry Pi SBC
  2. Why are these steps to be executed?

The steps are not guaranteed to be the same for all microcontrollers.

Objectives:

  1. Program an STM32L452RE through its bootloader using the USB-UART converter SH-V09C5
  2. 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

  1. Configure boot mode
  2. 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

Img. 1: USB to UART connection (© nubix, 2023)

The recommended way to program the target with a bridge device is to use jumpers as stated below:

  • Link NBOOT0 pin to VDD (the upper pin)
  • Don’t connect the NRST pin
Img. 2:Nucleo64 board pinout with highlighted pins (Source: STMicroelectronics)

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

Img. 3: Raspberry Pi connection (© nubix, 2023)

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:

Img. 4: Programming with raspi-flasher script (© nubix, 2023)

Something is wrong… (troubleshooting)

Do the following:

  • Link NBOOT0 pin to VDD (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 →flash
  • PH3 = 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

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)

 

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert