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 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.