Customizable Bootloader for STM32 microcontrollers. This project includes a demonstration for the STM32L496-Discovery board how to perform in-application-programming of a firmware located on external SD card with FAT32 file system.
Update: After almost 10 years, I've decided to give a proper refactor for this project. With the v2.0.0 release the project adopted a modern, modular and scalable project structure based on the stm32-project-template. The build system is CMake-based, while the legacy Make & SCons-based build systems are still available with limited functionality. The bootloader demonstration is refactored: the source code is fully MISRA-conform and passes clang-tidy and cppcheck static code analysis checks. Check out the CHANGELOG.md for the complete list of changes!
The source code of the entire bootloader project is documented and commented thoroughly. The generated documentation page is published here: https://akospasztor.github.io/stm32-bootloader
- Contents
- Bootloader features
- Hardware setup
- How to use
- Build and debug
- Detailed operation
- Configuration
- Source code organization
- References
- Configurable application space; flash layout & addresses are taken from the linker script automatically
- Flash erase, programming and verification after programming
- Flash protection check, write protection enable/disable
- CRC verification
- Extended error handling, fail-safe design
- Bootloader firmware update and the ability to perform full chip re-programming: enter ST's built-in bootloader from software (without triggering the BOOT pin)
- CMake-based build system
- Platform-independent development environment via devcontainer support
- Well-abstracted project hierarchy, with dedicated BSP layer
- Easy to customize and port to other microcontrollers
This demo is implemented on the 32L496GDISCOVERY board from ST [1], equipped with a STM32L496AG microcontroller [2]. Please refer to the user manual of the discovery board for the complete schematics and board description [3].
The microSD card is connected to the MCU over SDIO interface. The implementation uses the official HAL library of ST [4] and can be compiled with the GNU Arm Embedded Toolchain. Programming and debugging are performed over the on-board ST-LINK debugger.
There are two LEDs located on the discovery board, which are easily accessible,
and they don't require the use of the IO expander. These LEDs are used for
signaling various status to the user and they are labeled as LD2 and LD3.
The middle (center) button of the blue joystick (B2) located on the discovery
board is used for user interaction. This center button is simply referred as
"button" in the following sections.
The UART2 interface of the MCU is connected directly to the ST-LINK on the board. When the ST-LINK is connected to the computer via USB, an additional virtual com port (USB VCP) is initialized by the debugger. The assigned COM port number can be checked in the Device Manager on Windows. This example project uses this virtual com port to output various debug messages.
A compiled application image named app-demo.bin in the test folder is
provided with this example project. This file can be copied directly to the SD
card of the discovery board and it can be used for testing the functionality of
the bootloader.
The microcontroller flash is organized as follows: by default, the first 64 kBytes (32 pages) of the flash is reserved for the bootloader and the rest of the flash is the application space.
The bootloader can be easily customized and tailored to the required hardware and environment, i.e. to perform firmware updates over various interfaces or even to implement over-the-air (OTA) updates if the hardware incorporates wireless communication modules.
The application image has to be in binary format. The CRC verification requires
a file (named app-demo.crc by default) to be present on the SD card alongside
of the application binary, containing the CRC value of the application.
If the checksum verification is enabled, the binary must include the checksum value at the end of the image. When creating the application image, the checksum has to be calculated over the entire image (except the checksum area) with the following parameters:
The CRC can be calculated with the following parameters:
- Algorithm: CRC32
- Width: 4 bytes (32 bits)
- Polynomial: 0x04C11DB7
- Initial value: 0xFFFFFFFF
Tip: Use the following website to quickly calculate the CRC of a binary file, with the settings specified below: https://emn178.github.io/online-tools/crc/
| Setting | Value |
|---|---|
| Input type | File |
| Model | Custom |
| Width | 32 bit |
| Polynomial | 04C11DB7 |
| Initial value | FFFFFFFF |
| XOR out | FFFFFFFF |
| Refin | Disable (Unchecked) |
| Refout | Disable (Unchecked) |
Note: Alternatively, CRC-32/MPEG-2 preset model can be selected as this
CRC model corresponds to the required CRC settings.
Follow these steps to carry out a firmware update provided by this project:
-
Prepare the SD card and copy the
app-demo.binlocated in thetestfolder onto the SD card. -
Create a new file titled
app-demo.crccontaining the CRC value (in hex format, without the0xprefix) of the application image. Note: Alternatively, theapp-demo.crcfile located in thetestfolder can also be simply copied over to the SD card as it already contains the CRC value of theapp-demo.binfile. -
Build the bootloader project (see the Build and debug section).
-
Attach an USB cable to the
CN5port (located on the shorter side of the board), perform a mass erase and flash the bootloader firmware to the STM32L496-Discovery board. -
Keep the USB cable connected, open a serial monitor tool and observe the output logs:
-------------------------------------------- Power up, Boot started Bootloader version: 2.0.0 Application area: 0x8010000-0x8100000 -------------------------------------------- Error: No application in flash -
Press and keep holding the center button of the joystick (
B2), then reset the board by pressing theB1button. Keep holding the joystick button until the following log is displayed:Release button now to update the firmware... -
The firmware update procedure starts and after a successful update, the new application image is automatically started. Observe the output logs:
-------------------------------------------- Power up, Boot started Bootloader version: 2.0.0 Application area: 0x8010000-0x8100000 -------------------------------------------- Release button now to update the firmware... Starting the firmware update process... Size of application on the SD card: 15648 bytes CRC matches. CRC value: 0xfccdb7d2 Starting programming... Programming successful. Flashed: 15648 bytes Starting verification... Verification successful. Verified: 15648 bytes Launching application... Starting application v1.0 ... ***
Important notes:
- In order to perform a successful application jump from the bootloader, the
vector table of the application firmware should be relocated. On system reset,
the vector table is fixed at address 0x00000000. When creating an application,
the microcontroller startup code sets the vector table offset to 0x0000 in the
system_stm32l4xx.cfile by default. This has to be either disabled (the bootloader can be configured to perform the vector table relocation before the jump) or manually set the vector table offset register (VTOR) to the appropriate offset value which is the start address of the application space. For more information, please refer to [5]. - The linker settings of the application firmware need to be adjusted from their default settings so that the start address of flash reflects the actual start address of the application space.
The project can be built with the GNU Arm Embedded Toolchain. CMake is used as the main build system, however the legacy build options with Make and SCons are still available with limited functionality.
Note: As of the v2.0.0 release, this project is based on the modern
stm32-project-template
project. Check out its
README.md
for detailed description of available features, commands, devcontainer & VSCode
integration details, and more.
- CMake is installed and available on your PATH.
- Cppcheck is installed and available on your PATH.
- Ninja is installed and available on your PATH. Alternatively, you can also use Make instead of Ninja as the underlying build system.
- GCC for ARM (GNU Arm Embedded
Toolchain) is installed and available in your PATH. Recommended version:
10-2020-q4. - Clang-format and clang-tidy tools are installed and available in your PATH. These tools are used for source code formatting and performing style checks. You can obtain these tools via your preferred package manager or by installing the LLVM toolchain.
- Python is required to run clang-format from the build system. Additionally,
the
requirements.txtfile contains packages that are used for style checks. It is highly recommended to install and run the python packages in a python virtual environment (virtualenv).
Note: In case the GCC for ARM or the clang-tools are not available in your PATH
(e.g. you have multiple, different versions installed), you can specify the
toolchain paths in the respective toolchain .cmake file.
| Command | Description |
|---|---|
cmake --list-presets |
List all CMake presets |
cmake --preset Debug |
Configure the project for Debug build |
cmake --build build/Debug |
Build the firmware with Debug build type |
cmake --build build/Debug --target clean |
Clean the Debug target |
cmake --build build/Debug --target check-format |
Check source code formatting with clang-format |
cmake --build build/Debug --target run-format |
Run source code formatting with clang-format |
cmake --build build/Debug --target cppcheck |
Perform analysis and MISRA conformity check with cppcheck |
cmake --build build/Debug --target tidy |
Perform analysis and style check with clang-tidy |
cmake --build build/Debug --target doxygen |
Generate documentation with Doxygen |
Supported CMake configurations and presets:
| Preset | Description |
|---|---|
Debug |
Debug preset for debugging, without any optimization enabled |
Release |
Release preset with O3 optimization |
MinSizeRel |
Release preset with Os optimization for size with link time optimization enabled |
RelWithDebInfo |
Release preset with O2 optimization with debug information |
Steps to compile and build with GNU Make:
- If the GNU Arm Embedded Toolchain has not been added to PATH: export the
GCC_PATHenvironmental variable containing the path to thebinfolder of the installed GNU Arm Embedded Toolchain. TheMakefilechecks for theGCC_PATHenvironmental variable before checking PATH. - Open up your favorite terminal and navigate to the
GCCsubfolder where the makefile is located. - Type
makeand hit enter. - The
buildsubfolder should contain the binary, ELF and HEX output files, namedstm32-bootloader.bin,stm32-bootloader.elfandstm32-bootloader.hexrespectively.
This project currently supports two build configurations: debug (default) and release. Follow these steps to compile and build the project with SCons. Please note that the recommended usage is within a virtualenv.
- Install the requirements:
pip install -r requirements.txt. This contains SCons (among other helper tools). - If the
binfolder of the GNU Arm Embedded Toolchain does not exist in the PATH, it can be specified in theSConstructfile. - To build the project with the default debug configuration, execute:
scons -j8 - To build all build configurations at once, execute:
scons --all -j8 - To list all supported arguments, execute:
scons --help - The
buildsubfolder should contain the generated outputs, organized in subfolders with the names of the build configurations.
By default, the debug configuration is set up with a SEGGER J-Link debug probe. SEGGER offers a tool to convert the onboard ST-Link debugger on Discovery and Nucleo boards into a SEGGER J-Link debugger. The onboard ST-Link debugger on the STM32L496-Discovery board used in this project was converted to a J-Link debugger with this tool.
This project provides debug configurations for VSCode via the Cortex-Debug
extension (see .vscode/launch.json) and for SEGGER Ozone (see the Ozone
project file in the project/ozone folder).
After power-up, the bootloader starts. The bootloader checks for user-interaction:
- If the button is not pressed, then the bootloader tries to launch the application: First it checks the application space. If there is a firmware located in the application space, the bootloader calculates the checksum over the application space and compares with the application checksum (if the checksum feature is enabled). Finally, the bootloader prepares for the jump by resetting the peripherals, disabling the SysTick, setting the vector table and stack pointer, then the bootloader performs a jump to the application.
- If the button is pressed and released within 4 seconds: LD2 is blinking during
this interval and the bootloader attempts to update the application firmware
by performing the following sequence:
- Checks for write protection. If the application space is write-protected, then both LD2 and L3 LEDs are blinking for five seconds. If the button is pressed within this interval, the bootloader disables the write protection by re-programming the flash option bytes and performs a system reset (required after flash option bytes programming). Please note that after disabling the write protection, the user must invoke the application update procedure again by pressing the button in order to continue the firmware update.
- Initializes SD card, looks for application binary and opens the file.
- Checks the file size whether it fits the application space in the microcontroller flash.
- Calculates the CRC of the application binary, then opens the file on the SD card containing the CRC value and verifies that the two CRC values match.
- Performs flash programming. During flashing the LD2 LED is blinking. Note: the MCU flash driver automatically performs the required flash erase operations before executing the write operations.
- Verifies flash programming by re-opening the firmware file located on the SD card and comparing the content of the file with the flash content.
- Enables write protection of application space if this feature is enabled in the configuration.
- After successful in-application-programming, the bootloader launches the application.
- If the button is pressed for more than 4 seconds: LD3 is blinking during this interval and the bootloader launches ST's built-in bootloader located in the internal boot ROM (system memory) of the chip. For more information, please refer to [6]. With this method, the bootloader can be updated or even a full chip re-programming can be performed easily, for instance by connecting the hardware to the computer via USB and using DFU mode [7, 8].
- If the button is kept pressed for more than 9 seconds: the LEDs are switched off and the bootloader tries to launch the application located in the flash. This scenario is fully equivalent to the case when the user does not press the button after power-up (see above).
The bootloader can be configured in the bootloader.h file. The file includes
detailed comments and descriptions related to the configurable parameters and
definitions.
stm32-bootloader
├── .devcontainer
├── .github
├── bootloader
│ ├── include
│ └── source
├── bsp
│ └── stm32l496-discovery
│ ├── gcc-arm
│ ├── include
│ ├── source
│ └── svd
├── build
├── cmake
├── docs
├── lib
│ ├── CMSIS
│ ├── FatFs
│ └── STM32L4xx_HAL_Driver
├── lint
├── project
├── script
└── test
The .devcontainer folder contains the devcontainer file which enables
development inside a container.
The .github folder contains the GitHub Actions workflow file which describes
the CI pipeline that runs automatically on every git push operation.
The application-level source code of the bootloader is located in the
bootloader folder; organized into source and include subfolders.
The bsp folder stands for Board Support Package. This folder contains the
board- and microcontroller-specific files and drivers. These drivers are
interfaced by the application-level source code and they function as tiny
wrappers around the low-level (STM32 HAL) drivers. This allows the application
to interface these thin wrappers instead of the manufacturer-specific low-level
code, thus providing easy portability across different chips and
microcontrollers.
Upon building the project, a build folder is created. All build-related files
and output binaries are located in the build folder, organized into subfolders.
Each build target and their respective output files have their own subfolder.
The generated Doxygen documentation output files are also located in the build
folder.
The cmake folder contains the files related to the CMake-based build system,
including the toolchain and microcontroller-specific files.
Thedocs folder contains the doxygen configuration file (Doxyfile) and other
documentation-related static files.
The lib folder contains all third-party code, including the CMSIS (Cortex
Microcontroller Software Interface Standard) as well as the HAL (Hardware
Abstraction Layer) drivers from ST. Additionally, the lib folder contains the
FatFs library as well.
The lint folder contains the helper files for the static code analysis tools.
The project folder contains SDK- and debugger-specific files organized into
subfolders.
The script folder contains helper scripts related to the project and the build
system.
The test folder contains test and verification files and helpers, including
the compiled application image named app-demo.bin. This file can be copied
directly to the SD card of the discovery board and it can be used for testing
the functionality of the bootloader.
[1] 32L496GDISCOVERY, https://www.st.com/en/evaluation-tools/32l496gdiscovery.html
[2] STM32L496AG, http://www.st.com/en/microcontrollers/stm32l496ag.html
[3] UM2160, "User manual, Discovery kit with STM32L496AG MCU", https://www.st.com/resource/en/user_manual/dm00353127.pdf
[4] UM1884, "Description of STM32L4 HAL and Low Layer drivers", http://www.st.com/resource/en/user_manual/dm00173145.pdf
[5] PM0214, "STM32F3 Series, STM32F4 Series, STM32L4 Series and STM32L4+ Series Cortex®-M4 Programming Manual", http://www.st.com/resource/en/programming_manual/dm00046982.pdf
[6] AN2606, "STM32 microcontroller system memory boot mode", http://www.st.com/resource/en/application_note/cd00167594.pdf
[7] AN3156, "USB DFU protocol used in the STM32 bootloader", http://www.st.com/resource/en/application_note/cd00264379.pdf
[8] UM0412, "Getting started with DfuSe USB device firmware upgrade", http://www.st.com/resource/en/user_manual/cd00155676.pdf

