Ground Embedded KO Subscribe

Notes / MCU Firmware

Bootloader: how firmware updates itself

Split Flash into a bootloader and an application, receive and verify new firmware, then jump to the application. The jump sequence and how to prepare for failure.

GROUND EMBEDDED · NOTEMCU Firmware 12BootloaderAvg 30%D1 period

Example memory split

Region Address Size
Bootloader 0x0800_0000 16 KB
Application 0x0800_4000 The rest
(Optional) staging area for a new application At the end Same size as the application

Bootloader flow

ResetBootloaderUpdate requested?if so, receive the imageVerify CRC · signatureJump to the app
What the bootloader does after reset. If verification fails it does not hand over to the application.
  1. Reset → the bootloader runs
  2. If an update is requested (button, flag, communication command), receive the new image over UART/CAN/USB
  3. Erase and write the application area, then verify a CRC or signature
  4. If the application is valid, jump to it

The sequence for jumping to the application

uint32_t sp = *(uint32_t *)APP_ADDR, pc = *(uint32_t *)(APP_ADDR + 4);
__disable_irq();
// turn off the peripherals you used and SysTick, clear pending interrupts
SCB->VTOR = APP_ADDR;
__set_MSP(sp);
((void (*)(void))pc)();

The application is built with the FLASH start address in its linker script changed to 0x0800_4000.

Preparing for failure

  • The bootloader must survive a power loss during a write. Write-protect the bootloader area.
  • Write a "valid" mark at the end of the application last, and stay in the bootloader if the mark is missing.
  • With an A/B scheme that alternates between two images, you can fall back to the previous application when the new one fails to boot.

STM32 parts also have a system bootloader in ROM (the BOOT0 pin), used for initial programming at the factory.

In a picture: dividing the Flash

0x0800_0000Bootloader16 KB, never changed0x0800_4000ApplicationupdatedEndSettingsversion, CRC, flags
The bootloader sits at the very start of Flash, with the application after it. After a reset the bootloader always runs first.

A bootloader has to be small and simple, because if the bootloader itself has a bug there is no way to fix it.

What the bootloader decides

  1. Is an update requested? (a button, a flag, a command received over a link)
  2. Is the application intact? (CRC check)
  3. If intact, jump to the application; otherwise wait for an update
int main(void) {
    if (!update_requested() && app_crc_ok()) jump_to_app();
    run_update_protocol();          // new firmware is received here
}

Jumping to the application

The first two entries of the application's vector table hold the initial stack pointer and the address of the reset handler.

#define APP_BASE 0x08004000u

void jump_to_app(void) {
    uint32_t sp    = *(uint32_t *)(APP_BASE + 0);
    uint32_t reset = *(uint32_t *)(APP_BASE + 4);

    __disable_irq();
    /* turn off every peripheral and interrupt the bootloader enabled, and clear pending requests */
    SCB->VTOR = APP_BASE;           // switch the vector table to the application's
    __set_MSP(sp);
    ((void (*)(void))reset)();
}

On the application side, the Flash start address in the linker script has to be moved to 0x08004000. Forget this and the application is built for the bootloader's location and dies right after the jump.

RoadmapSee where this note sits in MCU Firmware→

Rules for writing Flash

  • Erase before write: Flash can only change 1 to 0. To get back to 1 you must erase a whole page or sector.
  • The erase unit is large: several KB. You cannot change a single byte.
  • Limited cycles: typically 10,000 to 100,000. Writing a frequently changing value to the same place wears it out.
  • Care while writing: if power is lost during a write, that page is left half-written.

Not bricking the device when power is lost

Method Principle Cost
Check CRC before jumping Stay in the bootloader if the application is damaged The basic one; always include it
Write a valid flag last Mark "done" only after everything is written and verified Space for the flag
Two slots (A/B) Write the new version to the free slot, then switch Needs twice the Flash
Confirm after a trial boot The new version must report that it works; otherwise roll back More complex to implement

Include at least the first two. If power is lost during an update but the bootloader is alive, you can try again.

What goes into an update protocol

  • Send the total size and the version first.
  • Attach a number and a CRC to each fixed-size block, and have the receiver acknowledge it.
  • If there is no acknowledgement, send the same block again.
  • At the end, check once more with a CRC over the whole image.

Problems that come up in practice

  • Interrupts misbehave after the jump: the bootloader moved on without turning off the timer or UART interrupts it had enabled.
  • Watchdog: if the bootloader enables the watchdog, the application has to keep feeding it. Many watchdogs cannot be turned off once enabled.
  • Accepting another product's firmware: put a product identifier in the header and check it.
  • Security: without signature verification anyone can load arbitrary firmware. For a product, consider signature checks and read protection.
FREE PDFGet the 3 free ebooksIf you want to keep reading this topic as one book: BLDC motor control, serial communication and sensors at book length. PDFs are in Korean.Get them free →