SYS: FANTASI WIKI PAGE: FLASHING_
← HOME

Flashing

The easiest way to flash Fantasi is the web flasher - it drives the USB bootloader paths described below straight from Chrome or Edge (no toolchain), auto-installs the BLE radio stack, and verifies the image before boot. The command-line paths on this page are for building from source, automation, and recovery.

Every target has two paths: a USB path (the blessed, user-facing flow that goes through the device's own bootloader) and an SWD/JTAG path (for bring-up, dead firmware recovery, or CI). USB paths are preferred - each is designed so a bad image can't brick the device.

All four devices run the same Fantasi CLI once flashed. Connect at 115200 baud to the enumerated CDC port (/dev/ttyACM* on Linux, /dev/tty.usbmodem* on macOS):

fantasi> device
FZ      # or KIISU or CU or PM3

Flipper Zero (STM32WB55)

USB - ST ROM DFU

The STM32WB55 has a ROM-resident DFU bootloader in system memory at 0x1FFF0000. It is physically separate from user flash and cannot be erased by dfu-util, so this path is always a safe recovery.

  1. Build: make PLATFORM=flipper
  2. Enter DFU mode. Either run fantasi> dfu from a running Fantasi, or hold OK + BACK for 30 seconds with the cable disconnected (device must be charged).
  3. Verify the ROM DFU enumerated: lsusb | grep 0483:df11
  4. Flash: make PLATFORM=flipper flash

Internally tools/flash.py runs:

dfu-util -a 0 -d 0483:df11 -s 0x08000000:leave -D build/flipper/fantasi-flipper.bin

The :leave suffix tells the ROM to detach and reset into user flash when the transfer finishes.

SWD - Black Magic Probe

Wire BMP to the Flipper's SWD test pads and attach via GDB:

arm-none-eabi-gdb \
  -ex 'target extended-remote /dev/ttyACM1' \
  -ex 'monitor swdp_scan' \
  -ex 'attach 1' \
  -ex 'monitor erase_mass' \
  -ex 'load build/flipper/fantasi-flipper.elf' \
  -ex 'kill'

This erases all user flash and reprograms it. The ROM DFU at 0x1FFF0000 is read-only and untouched - recovery via dfu-util is always possible.

Kiisu (STM32WB55)

The Kiisu is a Flipper-compatible STM32WB55 board, so the flashing path is identical to the Flipper Zero above - same ST ROM DFU (0483:df11), same address (0x08000000), same radio stack. Only the application image differs (make PLATFORM=kiisubuild/kiisu/fantasi-kiisu.bin), because the Kiisu's display is an SH1106 driven by a companion MCU rather than the Flipper's direct ST7565.

  1. Build: make PLATFORM=kiisu
  2. Enter DFU mode (as above): fantasi> dfu, or the button hold.
  3. Flash: make PLATFORM=kiisu flash

Detection. The Kiisu and Flipper share both their runtime (1209:0001) and DFU (0483:df11) USB IDs. A running device is told apart by the device command (KIISU vs FZ), which make flash uses to auto-select the platform. A device sitting in DFU is indistinguishable by USB ID alone; the flasher reads the 8th byte of the OTP device-name field (0x1FFF7000+31) - the Kiisu's factory provisioning space-pads it (0x20) while a Flipper NUL-pads it (0x00). If that byte is neither (an 8-character name has no padding), make flash asks you to pass PLATFORM=kiisu or PLATFORM=flipper explicitly.

Proxmark3 (AT91SAM7S)

USB - PM3 Bootloader

The PM3 bootloader lives in the first 8 KB of flash (0x100000-0x101FFF) and is the only recovery path without a JTAG adapter. The bundled flasher (tools/pm3_flasher.py) never writes to that region.

  1. Build: make PLATFORM=proxmark3
  2. Enter bootloader flash-mode. Either run fantasi> dfu from a running Fantasi, or hold the PM3 button while plugging in USB.
  3. Verify: lsusb | grep 9ac4:4b8f
  4. Flash: make PLATFORM=proxmark3 flash

Internally tools/flash.py runs tools/pm3_flasher.py (requires pyserial), which writes only the osimage region starting at 0x102000.

JTAG - CMSIS-DAP (dangerous)

AT91SAM7S flash has 16 KB lock regions aligned at 0x100000. The first region spans 0x100000-0x103FFF and covers both the bootloader (0x100000-0x101FFF) and the first 8 KB of the Fantasi osimage (0x102000-0x103FFF). OpenOCD's sam7 flash driver must erase a full lock region to write any page in it, so naively flashing Fantasi alone erases the bootloader as collateral damage.

Always flash bootrom + Fantasi together in one OpenOCD session:

openocd -f interface/cmsis-dap.cfg \
        -c 'transport select jtag; adapter speed 2000' \
        -f target/at91sam7sx.cfg -c '
  init; reset halt
  flash write_image erase /path/to/bootrom/obj/bootrom.elf
  flash write_image /path/to/fantasi-proxmark3.elf
  verify_image /path/to/bootrom/obj/bootrom.elf
  verify_image /path/to/fantasi-proxmark3.elf
  reset run
  exit'

If the bootrom gets erased, recovery requires either the ERASE pad (shorts GPNVM0, forces boot into the on-chip SAM-BA ROM) or a full JTAG replay of the bootrom ELF.

Post-Reset Note

If the Proxmark stalls, unplug and replug.

Chameleon Ultra (nRF52840)

USB - Signed DFU via nrfutil

The stock Chameleon Ultra ships with a Nordic Secure DFU bootloader around 0xF3000. It requires cryptographically signed packages; unsigned packages are silently rejected. The matching private key is vendored as platforms/chameleon/dfu_key.pem.

  1. Build: make PLATFORM=chameleon
  2. Enter DFU mode. Either run fantasi> dfu from a running Fantasi, or hold the user button while plugging in USB.
  3. Verify: lsusb | grep 1915:521f
  4. Flash within 30 seconds: make PLATFORM=chameleon flash

Internally tools/flash.py runs:

nrfutil pkg generate \
  --hw-version 0 --sd-req 0x00 --application-version 1 \
  --application build/chameleon/fantasi-chameleon.hex \
  --key-file platforms/chameleon/dfu_key.pem \
  build/chameleon/fantasi-chameleon-dfu.zip
nrfutil dfu usb-serial -pkg <zip> -p /dev/ttyACM<n>

30-second inactivity timeout. The bootloader is built with NRF_BL_DFU_INACTIVITY_TIMEOUT_MS=30000. If no DFU traffic arrives within 30 seconds, the bootloader chains back to the valid app.

SWD - CMSIS-DAP (requires matching settings page)

The bootloader settings page at 0xFF000 contains boot_validation_app, an ECDSA-P256-SHA256 hash of the installed app. Flashing a new Fantasi over SWD invalidates the old hash, and the bootloader refuses to chain to the new app.

Regenerate the settings page and flash both in the same session:

BUILD=build/chameleon
nrfutil settings generate --family NRF52840 \
  --application        $BUILD/fantasi-chameleon.hex \
  --application-version 1 \
  --bootloader-version  2 \
  --bl-settings-version 2 \
  --app-boot-validation VALIDATE_ECDSA_P256_SHA256 \
  --key-file            platforms/chameleon/dfu_key.pem \
  $BUILD/bl_settings.hex

openocd -c 'adapter driver cmsis-dap' \
        -c 'transport select swd' \
        -c 'adapter speed 1000' \
        -f target/nrf52.cfg -c "
  init; halt
  program $BUILD/fantasi-chameleon.hex verify
  program $BUILD/bl_settings.hex        verify reset
  exit"

Quick Reference

TargetUSB Bootloader VID:PIDApp BaseBootloader PreservedSWD Landmine
Flipper Zero0483:df11 (ST ROM DFU)0x08000000ROM at 0x1FFF0000 (read-only)None
Kiisu0483:df11 (ST ROM DFU)0x08000000ROM at 0x1FFF0000 (read-only)Shares USB IDs with Flipper - see detection above
Proxmark39ac4:4b8f (PM3 bootloader)0x001020000x100000-0x101FFF16 KB lock-region erases bootrom
Chameleon Ultra1915:521f (Nordic Secure DFU)0x000270000xF3000+Settings page at 0xFF000 must match app hash
★ DREAM // HACK ★