What Is a Bootloader in an Embedded System?
Last updated 26 June 2026 · 7 min read
Direct Answer
A bootloader is a small, first-stage firmware program that runs immediately after a microcontroller powers on or resets. Its primary jobs are to validate the main application image in flash and launch it, and optionally to accept a new firmware image over a communication interface (USB, UART, CAN, Ethernet) and write it to flash — enabling over-the-air or over-the-wire firmware updates without a physical debug probe.
Detailed Explanation
Every microcontroller starts execution at a fixed address — on ARM Cortex-M, this is determined by the vector table at the start of flash. If there is nothing at that address, the chip faults. A bootloader occupies that address space and is the very first firmware that runs.
The boot sequence
A typical two-stage boot sequence works as follows:
- Power-on reset: the CPU hardware fetches the initial stack pointer and reset handler address from the vector table at address 0x00000000 (or its flash mirror).
- Bootloader executes: the bootloader initialises minimum hardware (clocks, UART or USB peripheral if needed), checks whether the system has been asked to enter firmware update mode, and validates the main application in flash.
- Jump to application: if the application is valid and no update is requested, the bootloader performs a vector table relocation (setting the
VTORregister on Cortex-M to point to the application's vector table), loads the application's initial stack pointer, and jumps to the application's reset handler. - Application runs: from this point, the application believes it started normally — it initialises peripherals, starts the RTOS if one is used, and begins its main loop.
How the bootloader decides to update firmware
The bootloader checks one or more conditions on startup to decide whether to enter firmware update mode:
- A dedicated BOOT pin or button is held: a GPIO pin (commonly BOOT0 on STM32) is sampled; if it is high (or low, depending on the design), the bootloader enters update mode rather than launching the application.
- A flag in non-volatile memory: the application can write a value to a specific flash location or backup register before triggering a software reset, signalling to the bootloader that an OTA update has been staged and is ready to install.
- Application validation failure: if a CRC check or cryptographic signature verification fails, the bootloader knows the application image is corrupt and enters update mode (or reverts to a backup image) rather than launching bad firmware.
Factory bootloaders vs custom bootloaders
Most MCUs include a factory-programmed bootloader in protected ROM or locked flash. STM32 parts, for example, contain a system memory bootloader that accepts firmware over USART1, USB DFU, SPI, or I2C, depending on the specific part family. It cannot be erased and serves as the permanent recovery path for any device.
A custom bootloader is firmware written by the developer and stored in the first sector(s) of user flash. It offers capabilities the factory bootloader lacks:
- Any update protocol: MQTT over cellular, proprietary BLE profile, encrypted USB bulk transfer.
- Cryptographic image verification: public-key signature checking using an embedded certificate, preventing unsigned firmware from being installed (critical for security-sensitive products).
- Dual-bank (A/B) updates: two complete application slots in flash; the bootloader always runs the current good slot, downloads the new firmware into the inactive slot, verifies it, then atomically switches — the device always has a fallback if the new firmware is bad. See how OTA firmware updates work for a detailed walkthrough of the A/B swap mechanism, MCUboot, and confirmation/rollback design.
- Delta updates: applying a binary patch rather than a complete image, reducing the data transferred over a constrained link.
Flash memory layout with a custom bootloader
A typical layout for an MCU with 512 kB flash:
0x08000000 Bootloader (32 kB)
0x08008000 Application A (240 kB) ← active
0x08044000 Application B (240 kB) ← inactive / update staging
The bootloader occupies the first sector(s) and is marked write-protected. The two application slots allow a complete new firmware image to be written while the device is running, with the switch happening only after verification completes.
STM32 and DFU mode
STM32 has a well-documented hardware boot mode: asserting BOOT0 high during reset causes the CPU to execute from system memory (the factory bootloader) rather than user flash. From there, the factory bootloader can accept firmware via USB DFU, allowing reprogramming without a dedicated debug probe. This is the standard recovery path for any STM32 device — understanding it is important context for the stm32-usb-not-detected-after-bootloader-jump discussion, which covers the case where USB DFU mode is entered from application code rather than via the BOOT0 pin.
Practical Examples
OTA firmware update on a cellular IoT device: the MCU is an STM32L4 with 1 MB flash, split into a 64 kB bootloader region and two 480 kB application slots. The application connects to an MQTT broker, checks for a firmware update manifest, downloads the image into the inactive slot over the cellular link, verifies the SHA-256 hash and RSA-2048 signature against a certificate embedded in the bootloader, then writes a flag to a backup register and triggers a software reset. On the next boot, the bootloader sees the flag, verifies the staged image once more, switches the active slot pointer, and launches the new firmware.
Simple UART bootloader for factory programming: a contract manufacturer flashes firmware by connecting a UART cable and running a script. A minimal 8 kB bootloader waits for 200 ms on startup for the XModem protocol on UART2. If no host connects, it jumps to the application. If it receives a firmware image, it writes it to the application region and resets. No debug probe required on the assembly line.
Design Considerations
- Write-protect the bootloader sector: place the bootloader in flash sector 0 and enable sector write protection via option bytes. A corrupt or overwritten bootloader leaves the device permanently in a recovery state until it can be connected to a debug probe.
- Watchdog behaviour at the bootloader stage: configure the watchdog before entering the bootloader loop. If the bootloader hangs waiting for a firmware image that never arrives, a watchdog reset recovers the device rather than leaving it in an infinite wait.
- Dual-bank atomicity: for safety-critical products, the boot-slot switch must be atomic — if power is lost mid-switch, the device must always revert to the previous good image, never to an indeterminate state. Implement the switch as a single write to a boot-slot variable that the bootloader checks on startup.
- Firmware update security: unsigned OTA updates on an internet-connected device are a security liability. Even simple products should verify at minimum a hash of the downloaded image. Production IoT products should use asymmetric signature verification. Zeus Design's embedded software team designs and implements custom bootloaders with secure OTA for IoT products across STM32, ESP32, and nRF platforms.
- Vector table offset after jump: on Cortex-M, the application must set
SCB->VTORto its own vector table address before initialising peripherals. HAL libraries do this automatically if the application linker script defines the correct flashORIGIN; forgetting it causes interrupt handlers to point at the bootloader's vector table, producing unpredictable faults.
Common Mistakes
- Jumping to the application before fully de-initialising the bootloader's peripherals: if the bootloader enables UART or USB and leaves the peripheral in an active state, the application may fight the bootloader for control of the peripheral. De-initialise all clocked peripherals and reset the clock to its default before jumping.
- Using the factory bootloader as the sole update mechanism: the factory bootloader typically has no authentication, meaning anyone with physical access to the UART or USB port can replace the firmware. For security-sensitive products, a custom bootloader with signature verification is essential.
- Forgetting to disable the watchdog before a long erase: erasing a 256 kB flash sector can take hundreds of milliseconds. If the watchdog is running with a short timeout, it will reset the MCU mid-erase, corrupting the flash. Either disable the watchdog during the erase/write sequence or refresh it explicitly.
- Assuming the factory bootloader supports all peripherals on all package variants: the factory bootloader on STM32 parts supports different interfaces depending on the specific part number and package. Check AN2606 for the exact interface availability on your chosen part before designing your field-update scheme around a specific peripheral.
Frequently Asked Questions
- What is the difference between a factory bootloader and a custom bootloader?
- Most MCUs ship with a factory-programmed bootloader in protected system memory (ROM or locked flash) that cannot be erased. STM32's system bootloader, for example, accepts firmware over USART, USB DFU, SPI, or I2C. A custom bootloader is firmware written by the application developer and stored in the main flash, typically in a dedicated low-address region. Custom bootloaders can implement any protocol (including OTA), validate images with a cryptographic signature, and manage dual-bank A/B firmware updates — capabilities the factory bootloader usually does not provide.
- What is DFU mode?
- DFU (Device Firmware Upgrade) is a USB protocol standard that allows a device to accept a new firmware image over a USB connection without a debug probe. The MCU enters DFU mode (usually by holding a BOOT button during power-on, or by jumping to it from the application), enumerates as a DFU device, and accepts the firmware image from a host tool like STM32CubeProgrammer or dfu-util. After writing is complete, the MCU resets and launches the new application.
- Can a bootloader brick a device permanently?
- A corrupt main application cannot brick a device that has a working bootloader — the bootloader simply fails to validate the application and either waits for a new image or reverts to a known-good backup. However, if the bootloader itself is in user flash (not protected factory memory) and gets partially overwritten, the device may become unresponsive to normal firmware update attempts. This is why production bootloaders are typically placed in write-protected flash sectors, or the MCU's factory bootloader is used as the recovery path.
References
Related Questions
What Is a Microcontroller (MCU)?
A microcontroller (MCU) combines a CPU, flash, RAM, and peripherals on one chip. Learn how MCUs work and how they differ from microprocessors and FPGAs.
What Is an RTOS (Real-Time Operating System)?
An RTOS is a lightweight operating system that gives embedded firmware deterministic task scheduling. Learn how RTOSes work and when you actually need one.
What Are Interrupts in Embedded Systems and How Do They Work?
Interrupts let a microcontroller respond to hardware events instantly without polling. Learn how ISRs, NVIC priority, and interrupt latency work.
How Do You Use the STM32 DFU Bootloader to Flash Firmware?
The STM32 DFU bootloader lets you flash firmware over USB without a debug probe. Learn how to enter DFU mode, use dfu-util, and fix common detection issues.
How Does an OTA Firmware Update Work?
OTA firmware updates require dual-bank flash, image verification, and rollback. Covers MCUboot swap, ESP32 OTA API, image signing, and power-loss-safe design.
What Is a Linker Script and What Does It Do?
A linker script controls where firmware code and data land in flash and RAM. Covers MEMORY regions, SECTIONS, LMA/VMA, and the startup symbols it exports.
Related Forum Discussions
Global variable initialized to non-zero value but always reads as zero in main() — bare-metal STM32
Bit of a basic question maybe, but I'm genuinely stuck. Moving from ESP32/Arduino to bare-metal STM32 (STM32G031, Makefile project, arm-none
STM32 GPIO interrupt configured but ISR never fires — what am I missing?
Trying to use a button on PA0 to trigger an interrupt on an STM32F411 Nucleo board. Using HAL, generated the init code with CubeMX. The GPIO
Can't decide between FreeRTOS and bare-metal for a simple sensor node — what's the tipping point?
Working on a temperature and humidity monitoring node — STM32F103 target, BME280 over I2C, reports data every 60 seconds over UART to a Rasp