This guide walks through creating and building custom flight controller targets for Betaflight — from forking the repositories and setting up submodules, to writing a target config, managing config branches, and compiling firmware with WSL on Windows.
Prerequisites
- A GitHub account
- Git installed locally (or via WSL)
- Windows Subsystem for Linux (WSL2 recommended) with Ubuntu
- Basic familiarity with the command line
Step 1 — Fork Both Repositories
Betaflight uses two separate repos: the main firmware repo and a dedicated config repo that tracks all board configurations as a Git submodule. You need forks of both.
On GitHub, fork each of these to your account:
- Firmware:
https://github.com/betaflight/betaflight - Config:
https://github.com/betaflight/config
Your forks will live at https://github.com/YOUR_USERNAME/betaflight and https://github.com/YOUR_USERNAME/config.
Step 2 — Clone the Firmware Repo and Init Submodules
Open WSL and clone your betaflight fork. Using --recurse-submodules pulls the src/config submodule and all SDK library submodules in one step.
git clone --recurse-submodules https://github.com/YOUR_USERNAME/betaflight.git
cd betaflight
If you already cloned without submodules, initialise them now:
git submodule update --init --recursive
Add the upstream betaflight remote so you can pull in future releases:
git remote add upstream https://github.com/betaflight/betaflight.git
git fetch upstream
Step 3 — Point the Config Submodule to Your Fork
By default src/config tracks the official betaflight/config repo. Redirect it to your fork so you can push your target configs.
cd src/config
git remote set-url origin https://github.com/YOUR_USERNAME/config.git
git remote add upstream https://github.com/betaflight/config.git
git fetch origin
cd ../..
Verify the remotes are correct:
cd src/config
git remote -v
# origin https://github.com/YOUR_USERNAME/config.git (fetch)
# upstream https://github.com/betaflight/config.git (fetch)
cd ../..
Step 4 — Create a Feature Branch in the Firmware Repo
Always work on a named feature branch rather than directly on master. Branch from the latest upstream master to stay current.
# In the betaflight root
git fetch upstream
git checkout -b feature/my-custom-target upstream/master
Step 5 — Create a Config Branch and Add Your Target
The board-specific configuration lives in src/config/configs/TARGET_NAME/config.h. Each target gets its own directory. Create a matching branch in the config submodule.
cd src/config
# Branch from the latest upstream config master
git fetch upstream
git checkout -b feature/my-custom-target upstream/master
Create the target directory and config file:
mkdir -p configs/MY_TARGET_NAME
touch configs/MY_TARGET_NAME/config.h
Anatomy of config.h
Below is an annotated template for a custom F405-based target. Adjust pin assignments and peripheral selections to match your hardware schematic.
/*
* This file is part of Betaflight.
* SPDX-License-Identifier: GPL-3.0-or-later
*/
#pragma once
#define FC_TARGET_MCU STM32F405 // MCU variant — must match a platform target
#define BOARD_NAME MY_TARGET_NAME
#define MANUFACTURER_ID MYCO // 4-char manufacturer code registered with Betaflight
// --- Gyro / IMU ---
#define GYRO_1_SPI_INSTANCE SPI1
#define GYRO_1_CS_PIN PA4
#define GYRO_1_EXTI_PIN PC4
#define GYRO_1_ALIGN CW0_DEG
#define GYRO_1_FULL_SCALE 2000
// --- Barometer (optional) ---
// #define BARO_I2C_INSTANCE I2CDEV_1
// #define USE_BARO_DPS310
// --- OSD (optional MAX7456) ---
#define USE_MAX7456
#define MAX7456_SPI_INSTANCE SPI2
#define MAX7456_SPI_CS_PIN PB12
// --- BlackBox Flash (optional) ---
#define FLASH_CS_PIN PB3
#define FLASH_SPI_INSTANCE SPI3
// --- UARTs ---
#define UART1_TX_PIN PA9
#define UART1_RX_PIN PA10
#define UART2_TX_PIN PA2
#define UART2_RX_PIN PA3
#define UART6_TX_PIN PC6
#define UART6_RX_PIN PC7
// --- I2C ---
#define I2C1_SCL_PIN PB6
#define I2C1_SDA_PIN PB7
// --- Motors (DSHOT) ---
// TIMER_PIN_MAP(index, pin, output_type, dma_opt)
#define TIMER_PIN_MAP \
TIMER_PIN_MAP(0, PB0, 1, 0) \
TIMER_PIN_MAP(1, PB1, 1, 1) \
TIMER_PIN_MAP(2, PA3, 1, 2) \
TIMER_PIN_MAP(3, PA2, 1, 3)
// --- LED / Buzzer ---
#define LED0_PIN PC13
#define BEEPER_PIN PC15
#define BEEPER_INVERTED
Refer to an existing similar target in src/config/configs/ as a cross-reference for your specific MCU and peripheral set.
Step 6 — Commit the Config and Update the Submodule Pointer
Commit your config inside the submodule first, then commit the updated submodule pointer in the firmware repo. These are two separate commits in two separate repos.
6a — Commit inside the config submodule
cd src/config
git add configs/MY_TARGET_NAME/config.h
git commit -m "Add MY_TARGET_NAME config"
# Push the config branch to your fork
git push origin feature/my-custom-target
6b — Update the submodule pointer in the firmware repo
cd ../.. # back to betaflight root
git add src/config
git commit -m "Update config submodule to include MY_TARGET_NAME"
git push origin feature/my-custom-target
The firmware repo stores the config submodule as a single commit SHA. Every time you add commits in src/config you must re-run git add src/config and commit in the firmware repo to advance that pointer.
Step 7 — Switching Between Config Branches
You may want to switch the config submodule between branches — for example between a stable release config branch and your feature branch. There are two approaches.
Manual switch (one-off)
cd src/config
git checkout master # switch to any branch
cd ../..
git add src/config
git commit -m "Point config submodule to master"
Restore the submodule to the committed pointer
If the submodule has drifted from what the firmware repo expects, reset it:
git submodule update --init --recursive
Switch the firmware branch and sync the matching config
When you check out a different firmware branch, always sync submodules afterwards so src/config matches the pointer recorded in that branch:
git checkout feature/my-custom-target
git submodule update --init --recursive
Step 8 — Checkout a Release, Branch, or Specific Commit
You can pin the firmware to a tagged release, any branch, or a specific commit SHA. Always sync submodules after switching.
Checkout a release tag
git fetch upstream --tags
git checkout tags/4.5.1 -b build/4.5.1
git submodule update --init --recursive
Checkout an upstream branch
git fetch upstream
git checkout -b build/4.6-maintenance upstream/4.6-maintenance
git submodule update --init --recursive
Checkout a specific commit
git checkout e60cb3902
git submodule update --init --recursive
This puts you in a detached HEAD state. Create a named branch if you plan to build on top of it:
git checkout -b build/snapshot-e60cb39
Step 9 — Build Firmware with WSL
Install build dependencies (one-time)
Inside WSL (Ubuntu):
sudo apt update
sudo apt install -y build-essential git python3 python3-pip curl wget
The Betaflight Makefile downloads the correct ARM GCC toolchain automatically on first build via the tools/ directory. No manual toolchain installation is needed.
Navigate to the repo in WSL
Windows drives are mounted under /mnt/ in WSL. If your repo is on the Windows filesystem:
cd /mnt/c/Users/YOUR_USER/Desktop/Github_Projects/betaflight
For significantly faster builds, clone the repo directly inside the WSL filesystem instead:
cd ~/
git clone --recurse-submodules https://github.com/YOUR_USERNAME/betaflight.git
cd betaflight
Build your custom target
make TARGET=MY_TARGET_NAME
Or using the config variable (equivalent for named board configs):
make CONFIG=MY_TARGET_NAME
Common build variants
| Command | Purpose |
|---|---|
make TARGET=MY_TARGET_NAME | Standard release build |
make TARGET=MY_TARGET_NAME DEBUG=GDB | Debug build with GDB symbols |
make TARGET=MY_TARGET_NAME EXST=yes | External storage / bootloader build |
make TARGET=MY_TARGET_NAME RAM_BASED=yes | RAM-based target (no flash) |
make clean TARGET=MY_TARGET_NAME | Clean build artifacts for this target |
make targets | List all available platform targets |
Build output
Compiled firmware lands in the obj/ directory:
obj/betaflight_VERSION_MY_TARGET_NAME.bin— raw binary (use for DFU/STM32CubeProgrammer)obj/betaflight_VERSION_MY_TARGET_NAME.hex— Intel HEX formatobj/betaflight_VERSION_MY_TARGET_NAME.dfu— DFU update image
Step 10 — Keeping Everything in Sync
Pull upstream changes into your firmware fork:
git fetch upstream
git rebase upstream/master # or git merge upstream/master
git submodule update --init --recursive
Pull upstream changes into your config fork (inside the submodule):
cd src/config
git fetch upstream
git rebase upstream/master
git push origin feature/my-custom-target --force-with-lease
cd ../..
Quick Reference
| Task | Command |
|---|---|
| Clone with submodules | git clone --recurse-submodules <url> |
| Init submodules after clone | git submodule update --init --recursive |
| Switch firmware branch + sync | git checkout <branch> && git submodule update --init --recursive |
| Checkout release tag | git checkout tags/4.5.1 -b build/4.5.1 |
| Build a target | make TARGET=MY_TARGET_NAME |
| Clean build | make clean TARGET=MY_TARGET_NAME |
| List all targets | make targets |
| Redirect config submodule remote | git remote set-url origin <your-fork-url> |
