Skip to main content
emobility.academy
M.TechTelematics & data loggingAdvanced About 10 weeksKit + add-ons

Firmware Update over Bluetooth with Staged Image and Fallback

Students build an over-the-air update chain: a PC tool sends a new firmware image over Bluetooth, the running application stages it in SPI flash, and a small bootloader verifies it and copies it into internal flash, keeping the previous image so it can fall back if the new one fails.

Uses: Bluetooth · SPI · UART

The problem

Connected EVs need firmware updates without a workshop visit, but a failed update must never leave the vehicle unable to start. The STM32F103 has no dual-bank flash, so a safe update needs a staging area, verification and a fallback path designed by hand.

Objectives

  • Partition internal flash into a bootloader and an application, and SPI flash into staging and backup slots.
  • Transfer an image over Bluetooth with a chunked, acknowledged protocol.
  • Verify the staged image (size, version, CRC-32) before installing it.
  • Install with power-loss safety and roll back automatically if the new image does not confirm itself.
  • Measure update time, failure recovery and flash usage.

What you need

From the EV Embedded Development Kit

  • Bluetooth module header
  • Serial UART interface
  • SPI flash memory
  • 16×2 LCD
  • RGB LEDs
  • STM32F103C8T6 main controller

Software

  • STM32CubeIDE with HAL drivers
  • Embedded C with custom linker scripts
  • Python 3 with pyserial for the update tool
  • STM32CubeProgrammer for recovery flashing

Extra parts

  • HC-05 (or compatible) Bluetooth serial module — The kit has a Bluetooth module header but no module.

Key concepts

Bootloader and application split
A small bootloader at the start of flash decides which image to run; the application is linked to start at a higher address with its vector table moved via SCB->VTOR.
Staged update
The new image is first written to external SPI flash and fully verified, so the running application keeps working until the install.
Image header
A small structure with magic number, version, size and CRC that lets the bootloader check an image before trusting it.
Confirm-or-rollback
A new image starts in a trial state and must mark itself good after a self-test; if it resets before that, the bootloader restores the previous image.
Internal flash programming
On the STM32F103 internal flash is erased in 1 KB pages and programmed in 16-bit half-words using HAL_FLASH_Unlock, HAL_FLASHEx_Erase and HAL_FLASH_Program.

Step-by-step guide

  1. 1

    Plan the memory map

    Split the 64 KB internal flash: for example 12 KB bootloader at 0x08000000, the last 1 KB page for boot state, and the rest for the application starting at 0x08003000. In SPI flash define a staging slot and a backup slot, each large enough for the application, plus a metadata sector. Do not rely on any undocumented extra flash in the chip. Write the map in a table that both projects' linker scripts follow.

    Checkpoint: A memory map table with addresses and sizes that add up exactly, reviewed by the team.

  2. 2

    Fit the Bluetooth module and test the link

    With power off, plug the HC-05 into the Bluetooth header, matching VCC, GND, TX and RX. Pair it with the laptop and configure the USART at the module's data baud rate. Test raw throughput by sending a 32 KB test file and checking it with a CRC, so you know the realistic transfer time before designing the protocol. Repeat at the distance you expect to use during updates.

    Checkpoint: A test file of known CRC transfers intact and the measured throughput is recorded.

  3. 3

    Write the bootloader

    Create a separate CubeIDE project linked at 0x08000000 with a 12 KB limit. At reset it initialises only clocks, SPI and a GPIO LED, reads the boot state page, decides whether to install, roll back or jump, then jumps: de-initialise peripherals, disable SysTick and interrupts, set the main stack pointer from the application's first word and call the reset handler at the second word.

    Checkpoint: The bootloader jumps to a simple blinking application linked at 0x08003000.

  4. 4

    Adapt the application project

    Change the application's linker script FLASH origin to 0x08003000 and its length to match the map, and set the vector table offset (VECT_TAB_OFFSET or SCB->VTOR = 0x08003000) before interrupts are enabled. Add an image header in a dedicated section at a fixed offset holding magic, version and size, and a post-build Python step that computes the CRC-32 over the binary and patches it into the header.

    Checkpoint: The application runs from the bootloader with interrupts working, and the post-build script prints the image CRC and version.

  5. 5

    Design the transfer protocol

    Define commands: START (size, version, CRC), DATA (offset, up to 128 bytes, CRC-16 per chunk), END and ABORT, with ACK or NAK replies carrying the next expected offset so a lost chunk is simply resent. The application erases the staging slot sector by sector ahead of writing, writes chunks with page programs and updates progress on the LCD. Add a timeout so a stalled transfer is aborted cleanly.

    Checkpoint: Transfers survive deliberately dropped and corrupted chunks injected by the PC tool, finishing with the correct image.

  6. 6

    Verify the staged image

    After END, read the staging slot back and compute CRC-32 over it. The STM32's CRC unit (HAL_CRC_Calculate) uses the CRC-32 polynomial on 32-bit words without bit reflection, so the PC tool must compute the same variant; check both with a test vector. Check magic, size limits and that the version is newer. Only then write a 'pending install' flag to the metadata sector and reset.

    Checkpoint: A corrupted staged image is rejected and the running application continues unchanged.

  7. 7

    Install with power-loss safety

    In the bootloader, when the install flag is set, first copy the current application from internal flash to the SPI backup slot and verify it, then record 'backup valid'. Erase application pages and program the staged image half-word by half-word, verifying each page. Record each phase in the boot state so an interrupted install resumes or reverts after a reset.

    Checkpoint: Cutting power at random points during installation always ends in a running application, old or new.

  8. 8

    Implement confirm-or-rollback

    After install, mark the new image as 'trial' and count boot attempts. The application runs a self-test (peripherals initialise, main loop reached, optional user check) and then writes 'confirmed'. If the bootloader sees a trial image fail to confirm within a set number of boots, or the independent watchdog resets it repeatedly, it restores the backup. Store the boot attempt counter in the boot state page or a backup register so it survives resets.

    Checkpoint: A deliberately broken test image (one that hangs before confirming) is rolled back automatically to the previous version.

  9. 9

    Build the PC update tool

    Write a Python tool that reads the binary, checks the header, connects to the Bluetooth serial port, performs the transfer with retries and shows progress. Log each update with version numbers, duration and retries. Refuse to send an image whose header version is not newer than the running one unless a force option is given, and verify the image CRC locally before starting. Add a resume option that continues from the next expected offset after a dropped connection instead of starting again.

    Checkpoint: A full update completes from the command line and the LCD shows the new version after reboot.

  10. 10

    Measure and compare

    Measure transfer time, verify time, install time and total downtime for images of several sizes, and the number of power-cut scenarios recovered. Compare against a baseline that writes directly into internal flash with no staging or backup, showing which failures each design survives. Inject power cuts at fixed points with a relay or by hand at logged progress percentages, and repeat each point several times. Report recovery for every phase: transfer, verify, backup, erase, program and first boot of the new image.

    Checkpoint: A table of timings and a failure-injection matrix for both designs.

  11. 11

    Document the update system

    Write up the memory map, protocol, state machine, verification method, failure tests and limitations, including that CRC detects errors but does not prove who made the image. Describe how a signature check would be added. Include the memory map, frame definitions, boot state machine with every state and transition, timing results and the full failure matrix. Explain which failures the design cannot handle, such as a failure in the bootloader itself, and how that risk would be reduced in production.

    Checkpoint: A report with the boot state diagram and the failure-injection results.

Testing & validation

  • Corrupted transfers and corrupted staged images are always rejected.
  • Power cuts injected at every install phase end with a bootable image.
  • A non-confirming image is rolled back within the set number of boots.
  • CRC values match between the PC tool and the STM32 CRC unit on test vectors.

Expected results

A fail-safe Bluetooth firmware update for a single-bank STM32 using SPI flash staging and backup, with measured update times and demonstrated recovery from interrupted and faulty updates.

Research angle for your M.Tech thesis

Research questions: what is the minimum-downtime, power-safe update scheme for single-bank MCUs with external flash, and what does each safety feature cost in time and memory? Metrics: downtime, total update time, flash and RAM overhead, and recovery rate under systematic power-cut injection. Baseline: direct in-place update. A strong thesis would test recovery exhaustively at every phase boundary and report the full failure matrix.

Take it further

  • Add an ECDSA or Ed25519 signature check in the bootloader and measure its time and size cost.
  • Send only a binary diff of the new image to cut transfer time.
  • Move the transfer onto CAN for an in-vehicle update between two kits.

Viva and review questions

Why not update in place directly?

If power fails mid-write the device has no valid image. Staging and a backup keep a working image available at every step.

Why must the application set VTOR?

Its vector table is not at 0x08000000, so interrupts would jump into the bootloader's table unless the offset is set.

Does CRC make the update secure?

No. It detects corruption, not tampering; a cryptographic signature is needed to prove the image's origin.

How does rollback get triggered?

A new image starts in trial state; if it does not confirm itself within a set number of boots, the bootloader restores the backup.

Related EV projects

All 100 EV projects