# STM32 Bootloader: in-application firmware updates from an SD card

> akospasztor/stm32-bootloader is a portable STM32 bootloader with a worked example that updates a firmware image from a FAT32 microSD card. The v2.0.0 rewrite moved it to a CMake and BSP based layout, while the legacy Make and SCons builds remain with reduced scope.

**akospasztor/stm32-bootloader** — Customizable Bootloader for STM32 microcontrollers. This example demonstrates how to perform in-application-programming of a firmware located on an external SD card with FAT32 file system.

- Repository: https://github.com/akospasztor/stm32-bootloader
- Website: https://akospasztor.github.io/stm32-bootloader
- Stars: 1,071 · Forks: 308
- Language: C
- License: NOASSERTION
- Published: 2026-10-19 · Updated: 2026-10-19 · Language: en
- Canonical page: https://hysenlabs.com/projects/akospasztor-stm32-bootloader

## What the SD card update example is really for

The repository is a bootloader for STM32 microcontrollers whose centre of gravity is a demonstration: the STM32L496-Discovery board pulling a firmware image off a microSD card and programming itself from it. That is in-application programming, the pattern where a device already running code replaces its own application without a host computer attached. The audience is embedded engineers who need that capability on their own board rather than the one the vendor built, because the flash layout, the application space and the update interface are all meant to be changed. The README is explicit that the bootloader can be tailored to update firmware over other interfaces, and that a board with a radio can extend the same mechanism into an over-the-air update, which is the direction most adopters take it in.

## The flash layout and the two files the card must hold

By default the first 64 kBytes of flash, 32 pages, are reserved for the bootloader and everything above that is application space, and the boundaries are not hard coded: the flash layout and addresses are taken from the linker script automatically. The update itself needs two files on the FAT32 card. The binary is a compiled image, and the CRC is stored separately in a file named app-demo.crc that must sit next to it and hold the checksum in hex without the 0x prefix. When checksum verification is enabled, the binary additionally has to carry the checksum value at its end, and that value is calculated over the whole image except the checksum area itself. The parameters are fixed and narrow:

```yaml
Algorithm: CRC32
Width: 4 bytes (32 bits)
Polynomial: 0x04C11DB7
Initial value: 0xFFFFFFFF
```

The README notes that the CRC-32/MPEG-2 preset in a standard tool gives the same result, and links an online CRC calculator configured with the same width, polynomial and initial value.

## Signalling, buttons and where the debug output goes

The hardware story is deliberately small. Two LEDs on the discovery board, labelled LD2 and LD3, carry the status signalling, and the README notes they are reachable without going through the IO expander. User interaction comes from the centre button of the blue joystick, referred to simply as the button. Debug output leaves the board over UART2, which is wired straight to the on-board ST-LINK; with the ST-LINK plugged into a PC over USB, the debugger presents a virtual com port, and the assigned port number is visible in the Windows Device Manager. The example uses that port for its debug messages. The microSD card is wired to the MCU over the SDIO interface, and the implementation sits on top of the official ST HAL library, with FatFs in the lib directory handling the file system.

## What happens on the target before the jump

The bootloader list in the README is short enough to be worth reading in full, because it is the real specification. It erases, programs and then verifies the flash after programming, checks the flash protection status and can enable or disable write protection, verifies the CRC, and reports errors with a fail-safe design. It can also update itself, and it can perform a full chip re-programming by entering the ST built-in system bootloader from software, so the BOOT pin does not have to be asserted. The application space is configurable, and the layout follows the linker script, which is the detail that makes porting possible: change the linker script and the bootloader follows, rather than hard coding sector addresses. The jump to the application is the last step in that sequence, and the failure paths are what make it safe to leave in a field device.

## Three build systems, one of them current

The v2.0.0 release was a refactor of a project that had been around for close to ten years, and the build system was part of it. The current path is CMake, driven by a CMakeLists.txt and a CMakePresets.json at the root, and the project layout follows the author's stm32-project-template, with a dedicated BSP layer, a bootloader/ directory, a project/ directory, lib/, lint/ and script/ directories and a test/ directory. The legacy Make and SCons builds are still in the tree through a Makefile and a SConstruct with a SConscript, and the README describes them as available with limited functionality. The Makefile shows how little has to change to retarget the project: the target name, the debug flag, the optimisation level and the MCU define are the knobs.

```make
TARGET = stm32-bootloader
DEBUG = 1
OPT = -Og
BUILD_DIR = build
```

Alongside the C defines that select the STM32L496 family, the file lists the include paths for the bootloader core, the board support package, FatFs and CMSIS. The quality tooling is part of the same story. The source is described as fully MISRA conformant, it passes clang-tidy and cppcheck checks, and the tree carries a .clang-tidy file, a .clang-format file and a lint/ directory. A devcontainer configuration lets the environment be reproduced without installing a toolchain by hand, the CI pipeline has its own workflow file under .github/, and the Python tools are pinned in requirements.txt, which lists editorconfig-checker, flake8, yamllint and scons.

## The gaps a field deployment has to close

The obvious gap is security, and it is worth being blunt about: the image check is a CRC, so it detects corruption and nothing else. There is no signature, no key and no replay protection in the described flow, which means a card with a modified image passes verification if the checksum was recomputed. The example also assumes the card itself: the boot flow reads from the SD card, so physical access to the card is access to the update channel. The board support package is specific, and the demo runs on the STM32L496-Discovery with a STM32L496AG and the SDIO wiring of that board, so a port means writing a new BSP rather than changing a define. The over-the-air path is described as a possibility enabled by the customisable design, not as something the repository implements.

## A custom bootloader next to the vendor system bootloader

The alternative is what the chip already ships with: the ST built-in bootloader, which the project can even hand control to. That ROM bootloader re-programs the whole chip, so the update flow is defined by ST rather than by your own code. A custom bootloader reverses the arrangement. The device decides when to update, the update medium can be anything the board can read, the application area is defined by your own linker script, and no vendor flashing tool is in the loop. The trade is ownership: with the vendor path, the update mechanism is somebody else's problem, while with this repository the fail-safe behaviour, the flash protection and the update policy become your code to test and to maintain.

## Release history, licence and the gap between versions

The version history explains the shape of the project. v2.0.0 was published on 2026-09-12 and carries the refactor, the CMake build and the MISRA and static analysis work, and the last push was on the same date. Before that, v1.1.3 dates from 2020-07-22 and v1.1.2 from 2020-06-08, so the project sat unchanged for several years and was then rewritten in one go. The licence is a custom one: the terms sit in a LICENSE.md file in the root, and GitHub cannot categorise them, which means anyone embedding this code in a product has to read that file rather than assume a standard permissive grant. The CHANGELOG.md holds the full list of changes, and the generated documentation is published alongside the repository.

## Conclusion

The STM32 bootloader fits a team shipping an STM32L4 field device that must be updated in the field without a laptop and a debug probe, and it fits a team that wants to own its update logic outright. It does not fit a product that needs signed or encrypted images, because the verification here is a CRC and nothing more, and the worked example targets one specific discovery board rather than a shipping design. Before adopting it, read LICENSE.md, because the terms are custom and GitHub cannot categorise them, and confirm that the flash layout in your linker script leaves the 64 kByte region the bootloader expects.

## FAQ

### How does the STM32 bootloader update firmware from an SD card?

The binary and a companion CRC file are copied to a FAT32 microSD card, and the bootloader reads the image, checks the checksum, erases the application area, programs it and verifies the result before jumping.

### Which CRC algorithm does the STM32 bootloader expect?

CRC32 with a 4 byte width, polynomial 0x04C11DB7 and initial value 0xFFFFFFFF, which matches the CRC-32/MPEG-2 preset in common tools. When checksum verification is enabled, the value is also appended to the end of the binary.

### Which hardware does the STM32 bootloader example target?

The demo runs on the STM32L496-Discovery board with a STM32L496AG, with the microSD card connected over SDIO, two status LEDs and the centre joystick button, and debug output going to the ST-LINK virtual com port.

### Can the STM32 bootloader be ported to other STM32 microcontrollers?

That is the stated purpose of the design. The application space follows the linker script rather than fixed addresses, the board support package is isolated in bsp/, and the MCU family is selected through a compile time define such as STM32L496xx.

### How is the STM32 bootloader built?

The current build is CMake based, with CMakeLists.txt and CMakePresets.json at the root and a devcontainer for a reproducible environment. The older Make and SCons builds remain available with limited functionality, and the GNU Arm Embedded Toolchain is used for compiling.

### Which licence applies to the STM32 bootloader?

It is a custom licence that GitHub cannot categorise, so the terms have to be read directly in the LICENSE.md file at the repository root.

## Sources

- [akospasztor/stm32-bootloader on GitHub](https://github.com/akospasztor/stm32-bootloader)
- [Issues](https://github.com/akospasztor/stm32-bootloader/issues)
- [Project website](https://akospasztor.github.io/stm32-bootloader)
- [README](https://github.com/akospasztor/stm32-bootloader/blob/master/README.md)
- [Releases](https://github.com/akospasztor/stm32-bootloader/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/akospasztor-stm32-bootloader
