Radio Stack
The Flipper Zero (STM32WB55) and Chameleon Ultra (nRF52840) have dedicated radio coprocessors that run a pre-compiled BLE stack. The stack binary is encrypted and signed by the chip vendor - it cannot be built from source and must be installed separately from the Fantasi application firmware.
The Proxmark3 has no radio coprocessor.
Flipper Zero & Kiisu (STM32WB55)
The Kiisu uses the same STM32WB55 as the Flipper Zero, so this entire section - stack variants, FUS, and the install flow - applies to it identically (the web flasher installs the stack automatically for both).
The STM32WB55 has two CPU cores:
- CPU1 (Cortex-M4) - runs Fantasi
- CPU2 (Cortex-M0+) - runs the BLE wireless stack
CPU2's firmware lives in a protected "secure flash" region managed by the FUS (Firmware Upgrade Services) - a small ROM-like service on CPU2 that handles installation, deletion, and verification of wireless stack binaries. FUS is the only entity that can install the stack because the binaries are encrypted and signed by ST.
Prerequisites
- Python 3 with
pyusb:pip install pyusb - The device in DFU mode:
fantasi> dfu
Which Binary?
The binary must match the FUS version on your device. The Flipper Zero ships with FUS 1.2.0. By default radio_flash.py fetches the right one for you - stm32wb5x_BLE_Stack_full_extended_fw.bin, pinned to flipperdevices/stm32wb_copro tag v1.20.0, verified against a known SHA-256 and cached under build/copro/ so later runs are offline. You can override with an explicit path.
**Use the flipperdevices/stm32wb_copro binaries, not ST's STM32CubeWB ones** - the STM32CubeWB builds are incompatible with the Flipper's FUS 1.2.0 and fail on-device with IMG_NOT_AUTHENTIC. The SHA gate in radio_flash.py enforces this: it refuses to flash anything but the pinned binary unless you pass a path yourself.
Variants (all in that repo; pass a path to use one other than the default):
| Binary | Size | Features |
|---|---|---|
stm32wb5x_BLE_Stack_light_fw.bin | ~117 KB | Peripheral + GATT server, up to 4 links |
stm32wb5x_BLE_Stack_full_fw.bin | ~152 KB | Central + peripheral, up to 8 links |
stm32wb5x_BLE_Stack_full_extended_fw.bin | ~189 KB | Full + extended advertising/scanning (default) |
Flashing the Stack
# 1. Enter DFU mode
fantasi> dfu
# 2. Run the flashing tool (no arg: fetch + SHA-verify the pinned default)
python3 tools/radio_flash.py
# ...or flash a specific binary you already have:
python3 tools/radio_flash.py path/to/stm32wb5x_BLE_Stack_full_extended_fw.bin
The tool performs these steps:
- Reads the current FUS state
- Activates FUS (switches CPU2 from wireless-stack mode to FUS mode)
- Deletes the old wireless stack (
FUS_FW_DELETE) - Stages the new binary in user flash
- Sends
FUS_FW_UPGRADE- FUS verifies the ST signature, decrypts, and installs into secure flash - Sends
FUS_START_WSto boot the new stack
The process takes 30-60 seconds. The device resets multiple times as FUS reconfigures the secure flash boundaries.
Verifying
fantasi> radio
secure flash: 512 KB @ 0x08080000
FUS: 1.2.0
stack: 1.20.0 (type 0x01)
BLE: on
The secure flash size reflects the installed stack:
| Stack | Typical Secure Flash |
|---|---|
| None (FUS only) | ~48 KB |
| BLE Light | ~164 KB |
| BLE Full Extended | ~512 KB |
How It Works
radio_flash.py communicates with FUS through the STM32 ROM DFU bootloader using the protocol documented in ST's AN5185. FUS commands are sent as single-byte DFU DNLOAD payloads:
| Command | Byte | Purpose |
|---|---|---|
| FUS_FW_DELETE | 0x52 | Delete installed wireless stack |
| FUS_FW_UPGRADE | 0x53 | Install staged binary |
| FUS_GET_STATE | read 0xFFFF0054 | Poll FUS state and error code |
| FUS_START_WS | 0x5A | Boot the wireless stack |
This is the same protocol used by qFlipper and STM32CubeProgrammer.
Recovery
If flashing is interrupted (power loss, USB disconnect):
- FUS is never at risk. It lives in a protected region and cannot be erased by any of these operations.
- If the wireless stack was deleted but the new one wasn't installed: enter DFU and run
radio_flash.pyagain. FUS will be in idle state, ready to accept a new stack. - If the device doesn't boot: enter DFU via the button combo (hold OK + BACK for 25 seconds) and reflash Fantasi with
dfu-util.
Important Notes
- An SWD debug probe connected to the Flipper's SWD pins can prevent CPU2 (CM0+) from booting. Disconnect the probe before running
radio_flash.pyor using theradioCLI command. - The
radioCLI command reports the secure-flash size (from the SFSA option byte), the FUS and installed wireless-stack versions, and whether BLE is active.
Chameleon Ultra (nRF52840)
The nRF52840 uses Nordic's SoftDevice as its BLE stack. It occupies flash from 0x0000 to 0x27000 and is already present from the stock ChameleonUltra firmware. Fantasi's linker starts the application at 0x27000 to preserve it.
Radio stack management for the Chameleon Ultra is not yet implemented in Fantasi. The existing SoftDevice from the stock firmware continues to work.