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
| Component | Description | Update Method |
|---|---|---|
| App Firmware | Main application logic (MCU flash) | OTA slot A/B or direct write |
| BLE Stack (SoftDevice/FUS) | BLE stack, often separate binary | OTA, FUS or special mode |
| DSP / Sensor MCU | Coprocessor firmware (I2C/SPI connected) | SPI/UART write during OTA |
| Filesystem / Storage | Configuration partition or QSPI storage | Flash 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:
- FUS is triggered from app or bootloader
- 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:
- Send new firmware via UART/SPI
- Verify checksum and ACK from the coprocessor
- 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
| Practice | Benefit |
|---|---|
| Keep version info for all modules | Prevents mismatch |
| Use unified manifest file (JSON/binary) | Simplifies parsing & OTA coordination |
| Update BLE stack first | Avoids boot crash on stack API mismatch |
| Ensure power and battery stability | Prevent mid-update corruption |
| Use watchdog + status flags per component | Enables granular rollback logic |
| Store rollback info per module | Allows partial rollback if needed |
Tools & Examples
- STM32WB:
FUS_FW_UPGRADE()for BLE stack - Nordic SDK:
nrfutil dfuwith multi-image support - Zephyr: Multi-image OTA via MCUboot
- ESP32: App + SPIFFS OTA via
esp_ota_ops.h

Leave a comment