A bootloader is a small program that runs immediately after reset, before the main application, and is responsible for initializing minimal hardware and then loading, verifying, and transferring execution to the application firmware. On embedded targets it also commonly provides a mechanism to update firmware in-field without a hardware debug interface.
In practice
On bare-metal MCUs, the bootloader typically lives in a reserved, write-protected region of flash -- often the lowest addresses or a dedicated boot sector. At power-on the core's reset vector points there. The bootloader checks a condition (commonly a GPIO pin state, a flag in non-volatile memory, or a timeout waiting for a host command, though other criteria are used) to decide whether to enter update mode or jump directly to the application. Common transports for receiving new firmware include UART, USB, CAN, SPI, I2C, and Ethernet, depending on what the target hardware exposes. Many MCU families -- including various STM32 and Microchip PIC devices -- ship a ROM-resident bootloader from the factory; the presence of such a loader, its supported transports, and its protocol vary by device and subfamily. User-space bootloaders like U-Boot (dominant on Linux-capable SoCs) or open-source MCU loaders such as MCUboot are deployed on top of or instead of those ROM loaders.
On application-processor-class SoCs running Linux -- covered in depth in "An overview of Linux Boot Process for Embedded Systems" and the "Boot Sequence for an ARM based embedded system" series -- the boot chain is typically multi-stage: a ROM bootloader hands off to a secondary program loader (SPL) or trusted firmware stage, which initializes DRAM and then launches U-Boot or a similar full bootloader, which finally loads the OS kernel and device tree. Each stage is more capable than the last because earlier stages are constrained by the size of on-chip SRAM available before external memory is trained.
A critical design concern is boot security. Secure boot chains use a hardware root of trust (e.g., OTP fuses storing a public key hash) to cryptographically verify each stage's signature before executing it. ARM TrustZone-enabled SoCs and MCUs with TrustZone-M (Cortex-M23/M33) provide hardware isolation primitives that secure boot firmware can build upon, but signature verification itself is still implemented in firmware or a dedicated secure boot layer on top of that hardware foundation; MCUboot implements such verification for Cortex-M class devices. Without signature verification, a field-update mechanism is also an attack surface.
Common pitfalls include: forgetting to relocate or re-initialize the vector table offset register (VTOR on Cortex-M) before jumping to the application, leaving peripherals in a state the application does not expect, using the same stack pointer across bootloader and application without resetting it, and placing the bootloader in a flash region that shares an erase block with the application, making safe updates impossible. A reliable update scheme requires at minimum two application slots or a separate staging area so a failed write does not brick the device.
Discussed on EmbeddedRelated
Frequently asked
What is the difference between a ROM bootloader and a user bootloader?
A ROM bootloader is mask-programmed by the chip vendor into read-only memory and cannot be modified (examples: STM32 system memory bootloader, Raspberry Pi VC4 ROM loader). It provides a guaranteed recovery path but is fixed in capability and protocol. A user bootloader is firmware you or a third party writes and stores in regular
flash; it can implement any protocol or security policy you need but must itself be programmed initially via a hardware interface like
JTAG or
SWD.
How does a Cortex-M bootloader hand off execution to the application?
The typical sequence is: (1) read the application's
vector table -- the first word is the initial
stack pointer, the second is the reset handler address; (2) optionally verify a
checksum or signature over the application image; (3) update VTOR (Vector Table Offset Register, at address 0xE000ED08) to point to the application's vector table; (4) load the stack pointer and call the application reset handler, usually via a function pointer. Failing to update VTOR means
interrupts will vector into the bootloader's handlers instead of the application's.
What is MCUboot and when should I use it?
MCUboot is an open-source, hardware-agnostic bootloader for Cortex-M class MCUs, used by the Zephyr RTOS project and Nordic, NXP, and STMicroelectronics among others. It provides image signing and verification, A/B (swap) and single-image update modes, and a well-defined
flash partition scheme. It is a practical starting point when you need secure, recoverable OTA updates on a Cortex-M device and do not want to write and audit the security logic yourself.
What makes a firmware update scheme 'safe' against power loss?
A safe scheme must guarantee the device can always boot into something valid even if power is lost mid-write. Common approaches include: A/B partitioning (two full application slots -- write the new image to the inactive slot, verify it, then atomically update a boot flag); or a copy-on-write staging area (download to a scratch region, then copy page-by-page to the active slot with progress flags). Writing directly over the running image with no fallback is not safe.
Does every embedded system need a custom bootloader?
No. Many production designs rely entirely on the vendor ROM bootloader for initial programming and never need field updates, making a custom bootloader unnecessary overhead. A custom or third-party bootloader becomes worthwhile when you need OTA firmware updates over a specific interface the ROM does not support, secure boot with your own key infrastructure, or a multi-stage boot sequence (common on Linux SoCs). For simple, lab-programmed products with
JTAG or
SWD access, adding a bootloader introduces
flash space cost and an extra failure mode with limited benefit.
Differentiators vs similar concepts
A bootloader is sometimes conflated with a firmware updater or DFU (Device Firmware Upgrade) handler. The distinction is one of scope: a bootloader runs on every reset and owns the decision of what to execute next; a firmware updater is an application-mode component that only runs when an update is explicitly triggered and relies on a bootloader (or the ROM) to do the actual boot. In practice the two overlap heavily -- most embedded bootloaders include a DFU mode -- but on application-processor SoCs the term 'bootloader' (e.g., U-Boot) usually refers to the multi-stage initialization chain, not just the update path. A bootloader is also distinct from a
debugger stub or monitor: a debug monitor (e.g., a host running
OpenOCD connected to a hardware probe such as a J-Link) programs
flash from an external host over
JTAG/
SWD without any firmware running on the target, whereas a bootloader is self-hosted firmware that manages the update process on the device itself.