Betaflight is where the drone story moves from radio link to flight controller.
In the previous ExpressLRS recovery post, the key question was:
Can the receiver talk to the radio transmitter again?
Betaflight sits one layer deeper in the aircraft:
Can the flight controller read receiver input, sensors, modes, failsafe state, and turn that into motor output?
That is a much more dangerous layer to treat casually.
So this is not a “flash this target” post.
It is a source-first look at what Betaflight is, how it is structured, and how to experiment safely with the code before touching flight hardware.
Betaflight is open-source flight-controller firmware for multirotor and fixed-wing craft. It focuses on flight performance, fast feature development, and broad target support across many flight-controller boards.
Betaflight GitHub Source Code Betaflight Documentation Betaflight App Betaflight Releases License: GPL-3.0
What is Betaflight?
Betaflight is firmware for flight controllers.
It is the software that runs on the board inside many FPV drones, reading sensors and receiver commands while driving motors, OSD, telemetry, blackbox logging, VTX control, failsafe behavior, and configuration state.
That places it in the same world as ExpressLRS, but at a different layer.
ExpressLRS is the radio link.
Betaflight consumes the receiver data and runs the aircraft control stack.
That relationship matters because modern FPV builds often use CRSF between the receiver and flight controller. ExpressLRS speaks that ecosystem; Betaflight has a CRSF receiver implementation and unit tests around it.
The repo is also not just one firmware blob.
It contains:
- platform ports for STM32, ESP32, RP2350/PICO, APM32, AT32, SITL, and other MCU families
- board configuration files for real flight controllers
- receiver protocol implementations
- PID, filtering, IMU, mixer, GPS rescue, altitude hold, and position logic
- MSP support for configurator communication
- blackbox, OSD, VTX, telemetry, and sensor layers
- unit tests
- devcontainers and simulator support
Why It Matters
Open firmware is not valuable only because you can compile it.
It is valuable because you can inspect what a board target means before you flash anything.
That was the ExpressLRS lesson too.
When software talks directly to physical hardware, target names, MCU families, pins, UARTs, sensors, flash chips, receiver defaults, and protocol assumptions are not cosmetic details.
They are the difference between a deliberate build and a guess.
Betaflight makes that visible.
For example, after hydrating the board config submodule, one ELRS-oriented board config lives at:
src/config/configs/BETAFPVF405_ELRS/config.h
It defines a real board identity:
#define FC_TARGET_MCU STM32F405
#define BOARD_NAME BETAFPVF405_ELRS
#define MANUFACTURER_ID BEFH
And it sets the receiver default to serial CRSF:
#define DEFAULT_RX_FEATURE FEATURE_RX_SERIAL
#define SERIALRX_UART SERIAL_PORT_USART3
#define SERIALRX_PROVIDER SERIALRX_CRSF
That is the kind of source-level evidence worth checking before flashing firmware to a flight controller.
The Local Trial
I cloned the repo locally:
git clone https://github.com/betaflight/betaflight.git /home/jalcocert/Downloads/betaflight
cd /home/jalcocert/Downloads/betaflight
The clone I inspected was:
HEAD: 51f882c98
describe: 4.5.0-1243-g51f882c98
The first practical step was hydrating the normal board configuration submodule:
make configs
That succeeded and checked out:
src/config: 7b1f01a25d8cb6379ebeeeca9f0e91c24925e907
Then I checked the simulator target:
make target-sdk-print TARGET=SITL
The result was:
none
That makes SITL the safest local smoke test.
It does not require flashing a flight controller or installing an ARM SDK.
Building SITL
The successful local build command was:
make TARGET=SITL
That hydrated the DroneCAN libcanard submodule and built:
obj/main/betaflight_SITL.elf
obj/betaflight_2026.6.0-alpha_SITL
The resulting executable was about 530K:
obj/main/betaflight_SITL.elf: ELF 64-bit LSB pie executable, x86-64
And it exposes a simple simulator CLI:
./obj/main/betaflight_SITL.elf --help
Output:
Betaflight SITL
Usage: ./obj/main/betaflight_SITL.elf [options]
Options:
--ip <address> Simulator IP address (default: 127.0.0.1)
--config <file> Load CLI config file, save to EEPROM, and exit
--gpx Write GPS track to sitl_track.gpx
--help, -h Show this help message
That is a very good entry point for readers.
You can build something real without attaching motors, props, batteries, or a flight controller.
The CRSF Test
Because this post follows the ExpressLRS story, I also ran a focused CRSF unit test:
make test_rx_crsf_unittest
The local test toolchain was:
clang-18: 18.1.3
clang++-18: 18.1.3
The result:
12 tests from CrossFireTest
PASSED 12 tests
running test_rx_crsf_unittest: PASS
That test covers CRSF CRC behavior, frame status, captured channel data, subset frames, oversized-frame rejection, valid frame bounds, invalid frame bounds, and recovery.
The matching source is readable:
src/main/rx/crsf.c
src/test/unit/rx_crsf_unittest.cc
src/main/rx/crsf.c documents the CRSF basics directly in the source:
420000 baud
not inverted
8 bit
1 stop bit
frame: <Device address><Frame length><Type><Payload><CRC>
That is a clean bridge between the two posts.
ExpressLRS made the receiver link work.
Betaflight has the receiver-side protocol implementation that can consume that link.
What the Code Makes Clear
Betaflight is embedded firmware, but it is not a black box.
The high-level task wiring lives in:
src/main/fc/tasks.c
That file pulls together serial/MSP handling, receiver processing, sensors, modes, battery alerts, telemetry, VTX, GPS, OSD, and flight logic.
The scheduler lives in:
src/main/scheduler/scheduler.c
It tracks task queues, timing, system load, late tasks, gyro timing debug fields, and priority ordering.
The PID layer starts in:
src/main/flight/pid.c
There you can see PID defaults, rate/profile state, crash recovery settings, launch control settings, D-term filtering defaults, simplified tuning hooks, and runtime PID state.
The important point is not that a casual reader should tune from source.
They should not.
The point is that the configurator sliders and firmware behavior map back to concrete source structures, tests, and defaults.
Docker and Devcontainers
Betaflight includes a devcontainer path for reproducible builds.
The README shows a command-line Docker build and run pattern:
docker build -t betaflight-dev -f .devcontainer/containerfile .devcontainer/
docker run --rm -v "${PWD}:/workspace" -w /workspace betaflight-dev make TARGET=SPEEDYBEEF405WING
That example is useful for development environments.
For real hardware, do not copy a target name blindly.
Use the target/config that matches your exact flight controller, and prefer the Betaflight App for normal firmware flashing workflows.
There is also a Gazebo SITL devcontainer path. The docs describe:
SITL <-> Gazebo over UDP 9002/9003
RC input over UDP 9004
Betaflight App via TCP/WebSocket 5761/6761
That is the deeper path if you want to test firmware behavior with a simulator.
For a first code-reading experiment, the plain make TARGET=SITL build is enough.
Where It Fits with the Previous Posts
This project connects neatly to the recent drone and vehicle stack:
| Project | Layer |
|---|---|
| ExpressLRS | radio-control link and receiver firmware |
| Betaflight | flight-controller firmware |
| SavvyCAN | visual CAN analysis |
| opendbc | CAN databases and Python tooling |
They are not interchangeable.
But they share the same lesson:
Before hardware becomes safe to modify, the metadata has to make sense.
For Betaflight that means target configs, MCU family, board name, UARTs, sensors, receiver protocol, and flash method.
Commands That Worked
Here is the compact local sequence:
git clone https://github.com/betaflight/betaflight.git /home/jalcocert/Downloads/betaflight
cd /home/jalcocert/Downloads/betaflight
make configs
make target-sdk-print TARGET=SITL
make test_versions
make TARGET=SITL
./obj/main/betaflight_SITL.elf --help
make test_rx_crsf_unittest
The two important successes:
SITL build: passed
CRSF unit test: passed, 12/12
FAQ
Is Betaflight a self-hosted app?
No.
Betaflight is flight-controller firmware.
The Betaflight App is the normal user-facing configurator, and it is available as a progressive web app.
Should I build and flash Betaflight manually?
Most users should not start there.
Use the Betaflight App and the official documentation.
Manual builds are for development, investigation, or carefully controlled hardware work where you know the exact target/config.
What is the safest first experiment?
Build SITL:
make configs
make TARGET=SITL
./obj/main/betaflight_SITL.elf --help
That validates the build path without flashing hardware.
Why mention ExpressLRS?
Because CRSF is the practical bridge.
ExpressLRS can provide the RC link, while Betaflight consumes receiver protocol data on the flight-controller side.
The CRSF unit test makes that connection visible in code.
What is CRSF, what is it used for, and what are the alternatives?
CRSF usually means Crossfire Serial Protocol.
In FPV terms, it is a serial protocol used between the receiver and the flight controller.
The receiver gets radio control data from the transmitter, then sends channel data, link information, telemetry, and related protocol frames to Betaflight over a UART.
That is why it matters for ExpressLRS.
ExpressLRS commonly talks to Betaflight using CRSF-style serial receiver behavior, even though ExpressLRS is not the same thing as TBS Crossfire.
The practical Betaflight setup is usually:
receiver protocol: Serial-based receiver
serial receiver provider: CRSF
UART: the port wired between receiver TX/RX and the flight controller
CRSF is useful because it carries more than just stick positions.
In Betaflight’s source, CRSF is involved in:
- receiver channel frames
- link statistics
- telemetry back to the receiver/radio
- MSP-over-CRSF framing for some configuration-style data paths
- CRSF-specific OSD/link quality values such as RSSI dBm and SNR
That makes it a good fit for modern FPV radio systems where the pilot wants low-latency control plus useful link telemetry.
The main alternatives are other serial receiver protocols:
| Protocol | Where You Might See It |
|---|---|
SBUS |
Common Futaba/FrSky-style serial receiver output |
IBUS |
FlySky receiver ecosystem |
FPORT |
FrSky combined control/telemetry style protocol |
SRXL / SRXL2 |
Spektrum receiver ecosystems |
SUMD / SUMH |
Graupner-style serial receiver protocols |
GHST |
ImmersionRC Ghost ecosystem |
JETIEXBUS |
Jeti EX Bus receivers |
SPEK1024 / SPEK2048 |
Spektrum satellite-style serial protocols |
MAVLINK |
MAVLink receiver/control integration paths |
CUSTOM |
Target/custom serial receiver implementation |
There are also older or simpler receiver input styles outside this serial-provider list, such as PWM/PPM-style wiring, but modern FPV builds usually prefer a serial receiver protocol.
For an ExpressLRS receiver, the normal answer is:
set serialrx_provider = CRSF
Then make sure the correct UART has Serial RX enabled in Betaflight App’s Ports tab.
The protocol alone is not enough. The wiring and UART selection must match the actual flight controller.
What are Betaflight platform ports?
In this repo, a platform port is the firmware layer for a microcontroller family or execution environment, not a desktop operating system.
You can see them under:
src/platform/
Examples include:
src/platform/STM32/
src/platform/ESP32/
src/platform/PICO/
src/platform/APM32/
src/platform/AT32/
src/platform/X32/
src/platform/SIMULATOR/
Those directories contain the low-level code that lets the shared Betaflight flight stack run on different hardware families: startup code, linker scripts, MCU-specific build rules, USB, UART, timers, DMA, ADC, SPI/I2C, DShot, PWM, persistent storage, and board-target glue.
The platform target files are more specific:
src/platform/STM32/target/STM32F405/target.mk
src/platform/ESP32/target/ESP32S3/target.mk
src/platform/PICO/target/RP2350A/target.mk
src/platform/SIMULATOR/target/SITL/target.mk
That is different from a board config.
A platform target says “build for this MCU family.”
A board config says “build for this exact flight controller board, with these pins, sensors, UARTs, receiver defaults, OSD hardware, flash, and alignments.”
So STM32F405 is a platform target, while BETAFPVF405_ELRS is a board configuration that uses an STM32F405 MCU and maps it to a real board.
Why not include a Docker Compose file?
There is no long-running self-hosted service here.
Betaflight ships devcontainers for reproducible firmware development and SITL/Gazebo work, but this is not a /apps gallery candidate.
Can I know my Betaflight target from “iFlight Cidora SL5”?
Not safely from that name alone.
iFlight Cidora SL5 is a product/frame family name, and there were multiple versions, bundles, and second-hand/custom builds.
For example, some Cidora SL5-E listings describe a SucceX-E F4 flight controller with an STM32F405 MCU, MPU6000 gyro, and older BETAFLIGHTF4 firmware naming.
Other Cidora SL5 Advanced references describe a SucceX F7 TwinG BT controller with an STM32F722 MCU and dual ICM20689 gyros.
That means the product name is a clue, not a target.
The real answer should come from Betaflight itself.
Connect with Betaflight App, open the CLI, and run:
version
status
diff all
Look for:
target / board name
manufacturer ID
MCU
gyro / accelerometer
UART used for receiver
receiver protocol
current firmware version
custom defaults
This is especially important for iFlight boards.
Their support notes say to use the correct iFlight target and factory resources, and not to flash generic unified STM32xxx targets when the iFlight target/custom defaults are required.
What should I check before flashing a board?
At minimum:
- exact flight-controller board name
- MCU family
- manufacturer target/config
- UART used for receiver
- receiver protocol
- gyro/accelerometer hardware
- flash method
- current firmware backup or diff
- official release notes
That is the difference between a controlled firmware action and guesswork.
Comments