PROJECTS/PRJ_01

Bare-Metal Bootloader

Custom firmware architecture on STM32F407VGT6
COMPLETED6 weeks · 2025 Q1
STM32Bare-MetalFlash MemoryFirmwareEmbedded C
Bare-Metal Bootloader
// PRJ_01
// OVERVIEW

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.

Problem Statement

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.

Objectives
  • ›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
// ARCHITECTURE

System Architecture

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.

Working Principle

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

Hardware Components

  • ▹STM32F407VGT6 Discovery board
  • ▹ST-Link V2 in-circuit programmer
  • ▹USB-UART bridge for logging
// SOFTWARE

Software Stack

  • ▹GCC ARM (arm-none-eabi)
  • ▹OpenOCD + ST-Link
  • ▹Custom linker script (bootloader + app regions)
  • ▹Startup file with vector table
// JOURNEY

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.

// DECISION
Bare-metal over HAL

Direct register access made the boot sequence auditable and eliminated hidden HAL initialisation from the hot path.

// DECISION
Fixed application base address

A fixed offset keeps the app linker simple and lets both images be flashed independently.

// CHALLENGES

Challenges & Solutions

// CHALLENGE
Application ran under the debugger but not standalone
// SOLUTION

Root cause was missing vector table relocation. Setting SCB->VTOR before the jump restored correct interrupt handling.

// CHALLENGE
Hard fault immediately after jump
// SOLUTION

Application stack pointer was uninitialised. Loading MSP from the application vector table's first word fixed the fault.

// RESULTS

Performance Results

// Boot time
< 50 ms
// Flash footprint
~4 KB bootloader
// Handoff
Stable across resets
// DOWNLOADS

Downloads

GitHub RepositoryProject Report (PDF)· pending
// FUTURE

Improvements & Lessons

Future Improvements
  • +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)
Lessons Learned
  • ›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.