Flipper Zero · Volume 7
Flipper Zero Volume 7 — GPIO, FAP Development, JS Runtime
ufbt, view_dispatcher, the Momentum JS modules, SWD debugging
Contents
1. About this Volume
This is the volume you read when you want to write code for the
Flipper. It covers the canonical 18-pin GPIO header pinout (the table the
rest of the series links back to), the FAP build pipeline with ufbt,
the Furi runtime patterns the firmware is built around, the Momentum JS
runtime modules, and SWD debugging via the official Wi-Fi Devboard
running Black Magic Probe.
2. The 18-pin GPIO Pinout (canonical)
This is the table other volumes link back to. From the Flipper Devices docs.1
| Pin | Net | STM32 pin | Default | Alt functions |
|---|---|---|---|---|
| 1 | 5V | (load switch) | Switched +5 V out, 1.2 A max, OFF on battery by default | — |
| 2 | PA7 | PA7 | GPIO | SPI1_MOSI, TIM17_CH1 |
| 3 | PA6 | PA6 | GPIO | SPI1_MISO, TIM16_CH1 |
| 4 | PA4 | PA4 | GPIO | SPI1_NSS, DAC1_OUT1 |
| 5 | PB3 | PB3 | GPIO | SPI1_SCK, USART1_RTS |
| 6 | PB2 | PB2 | GPIO | LPUART1_RX |
| 7 | PC3 | PC3 | GPIO | ADC1_IN4, LPUART1_RTS |
| 8 | GND | — | Ground | — |
| 9 | 3V3 | — | +3.3 V out, 1.2 A max, ON by default (sags during SD ops/FW update) | — |
| 10 | SWC | PA14 | SWCLK | — |
| 11 | GND | — | Ground | — |
| 12 | SIO | PA13 | SWDIO | — |
| 13 | TX | PB6 | USART1_TX | I²C1_SCL (= internal i2c if reconfigured) |
| 14 | RX | PB7 | USART1_RX | I²C1_SDA |
| 15 | PC1 | PC1 | GPIO | I²C3_SDA, ADC1_IN2 |
| 16 | PC0 | PC0 | GPIO | I²C3_SCL, ADC1_IN1 |
| 17 | 1W | PB14 | iButton 1-Wire (shared with iButton pad) | — |
| 18 | GND | — | Ground | — |
Electrical:
- All I/Os are 3.3 V tolerant only.
- Each pin has a 51 Ω series resistor (ESD).
- Each pin sources up to 20 mA.
- Aggregate I/O current ≤ 5 W or PMIC trips.
- 3V3 rail community-rated ~150 mA continuous for external loads.
- 5 V rail up to 1.2 A on USB; ~600 mA on battery boost.
3. The Furi Runtime in 5 Minutes
The Flipper firmware is built on FreeRTOS (the kernel) plus Furi (the runtime / HAL). Furi gives you:
- Threads —
furi_thread_*for FreeRTOS task wrapper - Synchronization —
furi_mutex_*,furi_semaphore_*,furi_event_flag_*,furi_message_queue_* - HAL —
furi_hal_gpio_*,furi_hal_spi_*,furi_hal_i2c_*,furi_hal_subghz_*,furi_hal_nfc_*,furi_hal_ibutton_* - Records (service registry) —
furi_record_open("gui")returns a service handle (the GUI service);furi_record_closereleases it. Common records:gui,notification,storage,dialogs,cli. - Logging —
FURI_LOG_I("MyApp", "starting")etc.
The pattern is acquire-via-record, do work, release-via-record. RAII in spirit; manual in execution.
4. The Application UI Stack
The GUI service exposes a ViewDispatcher that owns a stack of Views. Each View is a screen with input handler, drawing callback, and state. Common pre-built views:
| View | Purpose | When to use |
|---|---|---|
Submenu | Vertical scrollable menu | Main menus |
Popup | Modal popup with auto-dismiss | Errors, “press button to continue” |
Dialog / DialogEx | Yes/No/Cancel modal | Confirmations |
TextInput | On-screen keyboard | Filename entry, search |
ByteInput | Hex byte editor | NFC key entry, raw subghz |
VariableItemList | Settings-style list with < / > | App settings |
Widget | Compose primitives — text, buttons, frames | Custom layouts |
LoadingView | ”Working…” spinner | Long ops |
EmptyScreen | Black screen + draw callback | Custom render |
A typical app:
typedef struct {
Gui* gui;
ViewDispatcher* view_dispatcher;
Submenu* submenu;
Popup* popup;
// app-specific state
} MyApp;
int32_t my_app_main(void* p) {
MyApp* app = malloc(sizeof(MyApp));
app->gui = furi_record_open(RECORD_GUI);
app->view_dispatcher = view_dispatcher_alloc();
view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui,
ViewDispatcherTypeFullscreen);
app->submenu = submenu_alloc();
submenu_add_item(app->submenu, "Hello", 0, callback_hello, app);
submenu_add_item(app->submenu, "Quit", 1, callback_quit, app);
view_dispatcher_add_view(app->view_dispatcher, MainViewId,
submenu_get_view(app->submenu));
view_dispatcher_switch_to_view(app->view_dispatcher, MainViewId);
view_dispatcher_run(app->view_dispatcher); // blocks until quit
view_dispatcher_remove_view(app->view_dispatcher, MainViewId);
submenu_free(app->submenu);
view_dispatcher_free(app->view_dispatcher);
furi_record_close(RECORD_GUI);
free(app);
return 0;
}
Free what you alloc; the heap is shared with the OS, leaks accumulate across FAPs.
5. ufbt — the build tool
ufbt = “micro Flipper Build Tool”. It’s a Python wrapper around the Flipper SDK that builds FAPs without you having to clone the full firmware tree.2
Setup once:
pip install --upgrade ufbt
ufbt update # downloads the SDK for the current target firmware
ufbt update --branch=dev # or for a specific firmware branch
For Momentum-specific SDK:
ufbt update --index-url https://up.momentum-fw.dev/firmware/directory.json
In your FAP source directory:
ufbt # build the FAP
ufbt launch # build, upload via qFlipper-style protobuf, run
ufbt cli # open serial console
ufbt vscode_dist # generate VS Code workspace
6. FAP Anatomy
A FAP is one directory:
my_app/
├── application.fam # manifest (Python)
├── my_app.c # source
├── my_app_icon.png # 10x10 1bpp icon
└── (other .c, .h, headers as needed)
application.fam:
App(
appid="my_app",
name="My App",
apptype=FlipperAppType.EXTERNAL,
entry_point="my_app_main",
requires=["gui"],
stack_size=2 * 1024,
fap_category="Tools",
fap_icon="my_app_icon.png",
fap_icon_assets="assets",
fap_version=(0, 1),
fap_description="My app",
fap_author="tjscientist",
fap_weburl="https://example.invalid/",
targets=["f7"],
)
Build:
ufbt
# Output: dist/f7-C/my_app.fap
Upload:
ufbt launch
# Pushes to /ext/apps/Tools/my_app.fap and runs
7. The Hardware HAL — using each subsystem from C
7.1 GPIO
#include <furi_hal_gpio.h>
#include <furi_hal_resources.h>
// Set pin mode + initial state
furi_hal_gpio_init(&gpio_ext_pa7, GpioModeOutputPushPull,
GpioPullNo, GpioSpeedLow);
furi_hal_gpio_write(&gpio_ext_pa7, true);
// Read
bool v = furi_hal_gpio_read(&gpio_ext_pa6);
gpio_ext_pa4 / pa6 / pa7 / pb2 / pb3 / pc0 / pc1 / pc3 /
pb6 / pb7 are the header pins.
7.2 SPI
#include <furi_hal_spi.h>
furi_hal_spi_acquire(&furi_hal_spi_bus_handle_external);
furi_hal_spi_bus_trx(&furi_hal_spi_bus_handle_external,
tx_buf, rx_buf, len, FuriHalSpiTimeout);
furi_hal_spi_release(&furi_hal_spi_bus_handle_external);
The “external” bus uses pins 2/3/4/5 with PA4 as CS. Mutually exclusive
with the internal CC1101 — furi_hal_subghz_* calls fight with this for
the bus. Pick one mode per app.
7.3 I²C
#include <furi_hal_i2c.h>
furi_hal_i2c_acquire(&furi_hal_i2c_handle_external);
furi_hal_i2c_tx(&furi_hal_i2c_handle_external, addr,
buf, len, FuriHalI2cTimeout);
furi_hal_i2c_release(&furi_hal_i2c_handle_external);
External I²C is on pins 15/16 (PC1/PC0). Pull-ups on the device side are ~10 kΩ to 3V3 — you don’t need to add your own unless your slave needs stronger pull-ups.
7.4 UART (USART1)
#include <furi_hal_uart.h>
furi_hal_uart_init(FuriHalUartIdUSART1, 115200);
furi_hal_uart_set_irq_cb(FuriHalUartIdUSART1, on_byte_rx, NULL);
furi_hal_uart_tx(FuriHalUartIdUSART1, buf, len);
USART1 on pins 13/14 (PB6/PB7). This is the bus all the ESP32-based Wi-Fi modules talk over.
7.5 Sub-GHz
#include <furi_hal_subghz.h>
furi_hal_subghz_reset();
furi_hal_subghz_load_preset(FuriHalSubGhzPresetOok650Async);
furi_hal_subghz_set_frequency(433920000);
furi_hal_subghz_rx(); // enter RX
// ... read packets via callbacks ...
furi_hal_subghz_sleep();
The high-level subghz_devices API in lib/subghz/ is what most apps
use; direct furi_hal_subghz_* is for specialized cases.
8. The JS Runtime (Momentum first-class)
Momentum’s JS engine is mJS — a subset of JavaScript optimized for embedded. It’s not full ES2020; closures, generators, and Promises behave in restricted ways. Treat it as ES5 with a few selected ES6 features.
JS apps live as .js files at /ext/apps/Scripts/. Run via Apps →
Scripts → pick → run. They have access to a curated set of modules.
8.1 Module surface (Momentum mainline)
| Module | Purpose | Example |
|---|---|---|
event_loop | Cooperative scheduler | eventLoop.tick(handler) |
gui | Screen + input | gui.viewDispatcher.add(...) |
gui/submenu | Standard submenu | let m = submenu.create(); m.addItem('Hello', 0) |
gui/dialog, gui/popup | Modals | dialog.show('Title', 'Message') |
gui/text_input, byte_input | Inputs | |
gui/widget | Composite widget | |
notification | LED + vibration + speaker | notification.success(); notification.error() |
storage | File IO | storage.read('/ext/test.txt') |
serial | UART (USART1) | serial.setup(115200); serial.write('AT\r') |
i2c | External I²C bus | i2c.write(0x50, [...]) |
spi | External SPI bus | |
gpio | Raw GPIO | gpio.setMode('PA7', 'output'); gpio.write('PA7', 1) |
subghz | Sub-GHz | subghz.setFrequency(433920000); subghz.rx() |
usbdisk | Mount SD as USB-MSC | |
keyboard | USB HID keyboard | keyboard.string('Hello'); keyboard.press('ENTER') |
usb | Generic USB | |
ble / bt | Bluetooth | bt.beacon.start({...}) |
vgm | Video Game Module IMU | let imu = vgm.imu(); print(imu.yaw) |
dialog (top-level) | Quick dialogs |
8.2 A minimal JS app
let event_loop = require("event_loop");
let gui = require("gui");
let submenu = require("gui/submenu");
let m = submenu.create({
header: "Hello, JS",
items: ["First", "Second", "Quit"],
});
m.subscribe("chosen", function (item) {
if (item === 2) event_loop.stop();
else print("Chose:", item);
});
gui.viewDispatcher.switchTo(m);
event_loop.run();
Save as /ext/apps/Scripts/hello.js; run from Apps → Scripts → hello.
8.3 The VGM JS module
If a VGM is attached, require("vgm") exposes:
let vgm = require("vgm");
let imu = vgm.imu();
// imu.yaw, imu.pitch, imu.roll - in degrees, refreshed every call
// imu.acc.x, imu.acc.y, imu.acc.z - accelerometer in g
// imu.gyro.x, imu.gyro.y, imu.gyro.z - gyro in deg/s
Useful for motion-controlled apps; also feeds the official “Air Mouse” implementation.
9. SWD Debugging via Black Magic Probe
The official Wi-Fi Devboard ships with Black Magic Probe firmware. That makes it a cross-platform GDB server over USB-CDC.
9.1 Cabling
Wire the Devboard’s SWD output to the Flipper’s SWD pins (10, 12) plus GND. The Devboard has labeled SWD output headers; on the Flipper, attach to GPIO header pins SWC (10) and SIO (12), GND on 8 or 11. Double-check voltages — both should be on 3V3.
9.2 GDB session
arm-none-eabi-gdb path/to/your_fap.elf
(gdb) target extended-remote /dev/cu.usbmodem71B19E101 # macOS
(gdb) monitor swdp_scan
(gdb) attach 1
(gdb) load
(gdb) break my_app_main
(gdb) continue
monitor swdp_scan enumerates the target. The STM32WB55 should appear.
Standard GDB commands work — bt, info reg, display *(uint8_t*)0x...,
watch, etc.
9.3 VS Code + Cortex-Debug
Cortex-Debug extension. Configure .vscode/launch.json:
{
"configurations": [{
"name": "Flipper FAP Debug",
"type": "cortex-debug",
"servertype": "external",
"gdbTarget": "/dev/cu.usbmodem71B19E101",
"request": "attach",
"executable": "${workspaceFolder}/dist/f7-C/my_app.fap.elf",
"device": "STM32WB55RG"
}]
}
Click Run → Debug. Variables view, breakpoints, step-through all work.
10. Common Pitfalls
| Pitfall | Symptom | Fix |
|---|---|---|
Forgetting to furi_record_close | App leaks; later apps fail | Pair every _open with _close |
Calling subghz and spi external from the same app | One fails or returns garbage | They share the SPI bus; serialize access |
| Stack size too small | Random hard fault when calling deep into firmware | Bump stack_size in .fam to 4 KB+ |
| Running a JS app that blocks the event loop | UI hangs, hardware watchdog reboot | Use event_loop.tick() for periodic work |
| Using a removed API after firmware update | Loader refuses (“API mismatch”) | ufbt update and rebuild against new SDK |
| Crashing in interrupt context | Hard fault, sometimes silent | Avoid mallocs in IRQ; minimize work in IRQ |
| Forgetting that UART pins are 3.3 V | Module silently dies after first 5 V signal | Level-shift external UART signals |
11. Useful Repos and Pointers
flipperdevices/flipperzero-firmware— main firmwareflipperdevices/flipperzero-ufbt— ufbt itselfNext-Flip/Momentum-Firmware— MomentumRogueMaster/RogueMaster_Flipper_IRDB— RogueMaster IR DBjamisonderek/flipper-zero-tutorials— worked examplesderskythe/flipperzero-firmware-derskythe— DeepWiki for FAP creationUberGuidoZ/Flipper— the largest community resources collection
API symbol table: firmware/targets/f7/api_symbols.csv — defines what’s
exported to FAPs.
12. What’s next
Vol 8 — Official Modules. Full chapter on the Video Game Module (which tjscientist owns), plus WiFi Devboard, NRF24, and the official external CC1101 amp. Then Vol 9 covers Game Over, AWOK V3, and the rest of the third-party catalog.
Footnotes
-
Flipper Devices, “GPIO & Modules”, https://docs.flipper.net/zero/gpio-and-modules. ↩
-
https://pypi.org/project/ufbt/; repo
flipperdevices/flipperzero-ufbt. ↩