Bare-Metal Bootloader

Project Overview
A ground-up bare-metal bootloader for the STM32F407VGT6 that partitions internal flash, relocates the vector table, configures the main stack pointer and hands off cleanly to a secondary application image via the Reset Handler.
Off-the-shelf HAL bootloaders hide the memory model, vector table setup and stack pointer handoff behind abstraction layers, which makes it hard to reason about failure modes during firmware updates.
- ›Partition internal flash manually into bootloader and application regions
- ›Perform explicit vector table relocation via SCB->VTOR
- ›Set the Main Stack Pointer (MSP) from the application vector table
- ›Jump to the application's Reset Handler with peripherals in a known state
- ›Keep the entire pipeline understandable in under a few hundred lines of C
System Architecture
Flash is split into a Bootloader region (0x0800 0000) and an Application region at a fixed offset. On reset, the bootloader validates the application image, disables interrupts, relocates VTOR, loads MSP and branches to the app Reset Handler.
Power-on → MCU boots the bootloader from 0x0800 0000 → performs sanity checks on the application region → clears peripheral state → SCB->VTOR = APP_BASE → __set_MSP(*(uint32_t*)APP_BASE) → jump to *(uint32_t*)(APP_BASE + 4).
Hardware Components
- ▹STM32F407VGT6 Discovery board
- ▹ST-Link V2 in-circuit programmer
- ▹USB-UART bridge for logging
Software Stack
- ▹GCC ARM (arm-none-eabi)
- ▹OpenOCD + ST-Link
- ▹Custom linker script (bootloader + app regions)
- ▹Startup file with vector table
Development Journey
Started by studying the Cortex-M4 boot sequence and memory map, then built the linker script to reserve the two regions. Iterated on VTOR/MSP handoff until the application ran independently of the bootloader.
Direct register access made the boot sequence auditable and eliminated hidden HAL initialisation from the hot path.
A fixed offset keeps the app linker simple and lets both images be flashed independently.
Challenges & Solutions
Root cause was missing vector table relocation. Setting SCB->VTOR before the jump restored correct interrupt handling.
Application stack pointer was uninitialised. Loading MSP from the application vector table's first word fixed the fault.
Performance Results
Visuals & Media
Downloads
Improvements & Lessons
- +Add CRC verification of the application image before jump
- +Support dual-bank A/B firmware slots with rollback
- +Add UART-based firmware update protocol (XMODEM / custom)
- ›The boundary between bootloader and application is fundamentally about vector table + stack pointer, nothing more.
- ›A minimal linker script is easier to reason about than a generated one.
