Mastering Multi-Component OTA: Update Strategies and Best Practices


OTA in Multi-Component Systems — Updating BLE Stack, Coprocessors, and Filesystems Safely

As embedded products grow in complexity, so does the OTA process.

Modern consumer devices often include multiple firmware targets:

  • Application MCU firmware
  • BLE stack or SoftDevice
  • Coprocessor firmware (e.g., DSP, secure enclave)
  • External components (e.g., QSPI flash filesystem)

This episode dives into:

  • Multi-component OTA architecture
  • Update sequencing and dependency resolution
  • Real-world product strategies (Fitbit, STM32WB, Oura Ring)
  • Safely updating BLE stacks (SoftDevice, FUS)
  • Failsafe rollback and version alignment

Why Multi-Component OTA is Complex

You don’t want your app firmware upgraded while your BLE stack is still old. Or worse — your app gets updated, but the coprocessor doesn’t, breaking communication.

Problems that can happen:

  • App expects newer BLE stack APIs (but stack wasn’t updated)
  • Filesystem format changed (but older firmware can’t parse it)
  • Incomplete update across modules → inconsistent state
  • Power failure halfway through multiple flashes

Typical Multi-Component Firmware Structure

ComponentDescriptionUpdate Method
App FirmwareMain application logic (MCU flash)OTA slot A/B or direct write
BLE Stack (SoftDevice/FUS)BLE stack, often separate binaryOTA, FUS or special mode
DSP / Sensor MCUCoprocessor firmware (I2C/SPI connected)SPI/UART write during OTA
Filesystem / StorageConfiguration partition or QSPI storageFlash erase + binary write

Coordinated OTA Update Strategy

To update multiple firmware modules atomically, follow these steps:

1. Download all components together

  • App, BLE stack, co-processor image
  • Store in external flash or reserved OTA area

2. Verify integrity (hash + signature) of all

  • Abort if any component fails validation

3. Update in dependency order

[ BLE Stack ] → [ Coprocessor ] → [ Filesystem ] → [ App Firmware ]

4. Final reboot only after full update success


BLE Stack Updates (STM32WB / Nordic)

STM32WB: FUS (Firmware Upgrade Service)

  • STM32WB BLE stack is stored in system flash, separate from app
  • Must be updated via special FUS interface (not regular flash writes)
  • Update occurs in two stages:
    1. FUS is triggered from app or bootloader
    2. FUS updates the stack and reboots
// Request FUS update via command
fus_request_stack_update(address, size);
wait_for_fus_completion();

Precautions:

  • Don’t update app before BLE stack is complete
  • Update FUS version itself first if needed

Nordic: SoftDevice OTA (nRF52 / nRF53)

  • SoftDevice is stored in fixed memory (0x0000…)
  • OTA updates use MCUBoot + DFU init packet
  • App must declare compatible SoftDevice version
if (incoming_softdevice_version != current_version)
    perform_softdevice_update();


Coprocessor Firmware Update (e.g., DSP or Sensor MCU)

If your device has a secondary MCU or DSP (common in:

  • Smartwatches
  • TWS earbuds
  • Health monitors),

You must:

  1. Send new firmware via UART/SPI
  2. Verify checksum and ACK from the coprocessor
  3. Mark update done in shared flash

Example:

  • Fitbit updates its optical sensor MCU via I2C
  • Oura Ring updates its PPG and thermistor sensor controller over SPI
  • Apple Watch updates Secure Enclave and motion coprocessor separately from app

Filesystem Partition OTA

If your app includes a filesystem (LFS, FAT, or KV-based config system), sometimes you must update the FS itself, especially after:

  • Migration to new schema
  • Firmware-provided file templates
  • Persistent storage initialization

Strategy:

  • Keep FS partition isolated from app OTA
  • Use OTA package to wipe and re-initialize FS
  • Backup user config (if needed) to RAM or cloud
fs_erase_partition();
fs_write_default_config();


Rollback Protection for Multi-Image OTA

Techniques:

  • Use unified metadata header for all components
  • Commit only after all components pass CRC/hash
  • Write a “OTA_PENDING” flag and clear only after first boot succeeds
  • On failure, revert all components (if possible) to last known good state
typedef struct {
  uint32_t version_main_fw;
  uint32_t version_ble_stack;
  uint32_t version_dsp;
  uint32_t sha256_all[32];
  uint8_t  rollback_allowed;
} ota_bundle_metadata_t;


Real Product Example: Fitbit Sense

  • App firmware: ARM Cortex-M4
  • BLE stack: Custom + Nordic SoftDevice
  • Sensor MCU: Analog Devices PPG processor (I2C)
  • OTA flow:
    • All images downloaded via mobile app
    • App triggers SoftDevice update first
    • Coprocessor update done next via I2C
    • Main firmware flashed last
    • Reboot and telemetry sent post-success

Best Practices for Multi-Component OTA

PracticeBenefit
Keep version info for all modulesPrevents mismatch
Use unified manifest file (JSON/binary)Simplifies parsing & OTA coordination
Update BLE stack firstAvoids boot crash on stack API mismatch
Ensure power and battery stabilityPrevent mid-update corruption
Use watchdog + status flags per componentEnables granular rollback logic
Store rollback info per moduleAllows partial rollback if needed

Tools & Examples

  • STM32WB: FUS_FW_UPGRADE() for BLE stack
  • Nordic SDK: nrfutil dfu with multi-image support
  • Zephyr: Multi-image OTA via MCUboot
  • ESP32: App + SPIFFS OTA via esp_ota_ops.h


Leave a comment