Sitemap
ILLUMINATION

We curate & disseminate outstanding stories from diverse domains to create synergy. Apply: https://digitalmehmet.com & https://substackmastery.com Subscribe to content marketing strategy: https://drmehmetyildiz.substack.com/ External: https://illumination-curated.com

Technology for defense

FPV autonomous operation with Betaflight and Raspberry Pi

12 min readFeb 24, 2025

--

Start building your own Autopilot for FPV Combat Drone with Betaflight Flight Controller, simple 10 files on Python and Raspberry Pi.

Press enter or click to view image in full size
FPV drone with an RPi on board is flying in the Kill House hangar

Betaflight based FPV drones have become so popular on the frontline due to their affordability and simplicity. However, they were never designed with autonomous operations in mind, unlike ArduPilot based drones and planes. This doesn’t mean autonomy is out of reach — it’s just a far more complex challenge to implement using Betaflight-firmware.

Why pursue FPV autopilot on Betaflight? Autonomy offers advantages, such as avoiding interference from electronic warfare systems during flight, achieving greater precision in targeting, navigating through interference zones without FPV operator control, etc. These capabilities can significantly enhance military operations, especially on the Ukrainian frontline. However, achieving this autonomy on Betaflight is no small feat.

As a software developer passionate about advancing FPV drone technology, I took on the challenge of researching and developing a straightforward FPV autopilot based on Betaflight. My goal is to provide developers in Ukraine with an initial template they can use to start building their own autopilots for FPV combat drones, based on Betaflight, from the ground up.

In this article, I share the results of my research — a detailed, step-by-step guide for building an FPV autopilot on Betaflight with Raspberry Pi. Whether you are a hobbyist, an enthusiast, or a developer facing similar challenges, this guide will save you months.

So, let’s dive in and make these amazing things together!

Autopilot

Unlike the combat-ready autopilots currently used by the Ukrainian Army, this ‘Empty-pilot’ (autopilot) lacks the ability to recognize or follow targets on the battlefield. Instead, it is designed for basic autonomous operation, capable of flying forward for 2 seconds and releasing its payload (bomb).

This autopilot serves as a developer template, providing a starting point for those interested in launching their own R&D programs. The goal is to advance the technology toward creating combat-grade autopilots capable of autonomous target bombing while avoiding interference from electronic warfare systems on the battlefield.

The concept involves utilizing a standard FPV Combat Drone, widely used on the Ukrainian battlefield, paired with a companion computer (such as a Raspberry Pi) preloaded with Python-based code. This setup implements the declared functionality, enabling the drone to fly and release its payload autonomously, without FPV operator involvement.

Wiring diagram

Let’s start by examining the wiring diagram. The setup features an FPV drone built around the Aocoda F460 Stack, a high-performance system comprising the Aocoda F405 v2 Flight Controller (FC) and the 3060S (60A) Electronic Speed Controller (ESC). The diagram below illustrates how various components, including the servo module, video camera, ELRS receiver, VTX, and Raspberry Pi, solder with the flight controller.

Press enter or click to view image in full size
F405 v2 FC and FPV Combat Drone components

Here are the wiring details for each component:

  • Video Camera: Yellow (CAM), Red (5V), Black (GND)
  • Drop System: Red (5V), Black (GND), Pink (S5), which corresponds to SERVO1 on the flight controller (FC).
  • VTX (Video Transmitter): Red (9V), Black (GND), Green (VTX), Blue T4 (TX on UART4) connects to the corresponding RX pin on the VTX.
  • ELRS Receiver: Red (4.5V), Black (GND), Blue (T2), Blue (R2). T2 and R2 on the FC align with UART2, where T2 connects to RX and R2 connects to TX on the receiver.
  • Raspberry Pi (RPi): A Type-C to USB cable connects the flight controller to the Raspberry Pi for communication via the MSP protocol.

As usual, the ESC connects to the FC using the 8-pin wire included in the stack bundle, eliminating the need for additional explanation.

Betaflight Configuration

As mentioned earlier, configuring the FPV drone is essential part of the solution. However, instead of covering all the required settings in detail, I will focus solely on the key configurations needed to support the basic scenario for autonomous flight.

To configure the FPV drone for autonomous flight, we need to assign the following modes to the appropriate AUX channels on the radio controller:

Press enter or click to view image in full size
Modes section in Betaflight configurator
  • ARM: used to arm and disarm the motors.
  • ANGLE: activates ANGLE mode, enabling the autopilot to control the drone in a stabilized flight mode.
  • ALTHOLD: maintains the current altitude, preventing any unintended altitude changes during flight.
  • BEEPER: activates the drone’s beeper, useful for locating the drone in emergencies, such as losing it in a wooded area.
  • MSP OVERRIDE: the most critical mode, allowing the autopilot to take control via toggles defined in the msp_override_channels_mask variable.

Detailed instructions for configuring MSP OVERRIDE are provided below in the appropriate section ‘MSP OVERRIDE’.

Proper configuration of the servo module is also necessary. As demonstrated in the image below, SERVO1 has been set to respond to AUX2 toggling. Ensure you replicate this setup in your configuration.

Press enter or click to view image in full size
Servos section. AUX2 set to control SERVO1 servo device

Finally, to complete the configuration, we need to enable and set up the MSP OVERRIDE mode. It requires to configure msp_override_channels_mask variable in the CLI. For the purposes of our autopilot, the mask value has been calculated as 47, which corresponds to the bitmask 00101111.

Press enter or click to view image in full size
Setting up the msp_override_channels_mask in CLI

To set it up, execute the following script.

set msp_override_channels_mask = 47

save

get msp_override_channels_mask

Don’t forget to save the configuration after each change by entering the save command in the CLI.

MSP OVERRIDE

Now, you will understand why we set up the bitmask 00101111 (47) for the msp_override_channels_mask in the previous section.

The MSP OVERRIDE mode in Betaflight enables the autopilot (Raspberry Pi) to override specific AUX values, granting it direct control of the drone. When activated, this mode allows the autopilot to adjust parameters such as ROLL, PITCH, YAW, THROTTLE, and AUX values. In this setup, we are using MSP OVERRIDE to manage the drone’s direction, speed, overall flight, and to trigger the servo module linked to SERVO1 (AUX2) for bomb release when necessary.

The msp_override_channels_mask variable in Betaflight determines which transmitter (radio controller) channels will be overridden by the autopilot. In our scenario, channels are reserved for ROLL, PITCH, YAW, and THROTTLE for flight control, and AUX2 for activating SERVO1 to be able to release the bomb when necessary.

It’s important to note that the mask logic follows a reversed bit-order. For clarity, refer to the diagram below, which explains how the mask value was calculated as 47 (0x00101111) and why these channels were selected.

Press enter or click to view image in full size
Mask for variable msp_override_channels_mask

In the msp_override_channels_mask, a value of 1 indicates that the override is enabled, while 0 means it is disabled. As shown, we enable the override for the first four channels dedicated to flight control (ROLL, PITCH, YAW, THROTTLE) and also for AUX2, which controls the release of the payload.

RPi Configuration

As per the initial design, the Raspberry Pi (RPi) functions as a companion computer, communicating with the Betaflight firmware on the flight controller (FC) via the MSP protocol.

While Betaflight was not initially designed for autonomous flights, recent updates introduced the powerful MSP OVERRIDE mode, enabling the development of autopilots, as we discussed earlier.

Press enter or click to view image in full size
Raspberry Pi mounted on the battery pack of an FPV Combat Drone

In order to begin exploring the autopilot, we should follow the steps outlined in the Raspberry Pi Configuration section of the repository Autopilot for FPV Combat Drone on Betaflight (Empty version), including copying files, setting up the appropriate service, and creating the Logs folder to allow gathering telemetry data and general logs.

Architecture

The autopilot has a multi-threaded architecture. At its core is a command queue managed by a command router, which processes all incoming commands. The router operates in its dedicated thread, known as the Router thread, while two additional threads — the Telemetry thread and the Empty Pilot thread — focus on appending commands to the queue.

Simple autopilot for Betaflight

The Telemetry thread handles system monitoring and telemetry requests, queuing commands to provide the Autopilot with critical data such as current altitude, aircraft speed, and real-time RC channel values. While the Empty Pilot thread manages commands related to autonomous flight and payload delivery, such as bomb deployment.

For a more detailed explanation of autopilot’s architecture, refer to the README_DEV.md file available in the repository on GitHub.

Modes

Unlike the original Autopilot, which I previously cloned for experimental purposes, we now have a real Remote Controller — the RadioMaster Pocket M2 — allowing us to toggle AUX switches and related modes as needed.

Press enter or click to view image in full size
RadioMaster Pocket M2 radio controller

In this implementation of the autopilot, I have designed it to operate with just two modes:

  1. OFF the autopilot remains inactive, awaiting operator input to transition to another mode. During this state, only telemetry monitoring and logging are functional.
  2. READYactivates basic functionality, where the drone flies forward for two seconds and then deploys its payload (e.g., a bomb).

The mode is managed by the command_telemetry_mode_change(telemetry) function, which depends on the value of AUX3. A low value (1000) corresponds to the OFF mode, a mid-range value (1503) is unassigned, and a high value (2000) activates the READY mode.

The function is implemented in router.py file.

def command_telemetry_mode_change(telemetry):
previous_throttle = autopilot.state['throttle']
rc_chs = struct.unpack('<' + 'H' * (len(telemetry) // 2), telemetry)

autopilot.state['roll'] = rc_chs[0]
autopilot.state['pitch'] = rc_chs[1]
autopilot.state['yaw'] = rc_chs[2]
autopilot.state['throttle'] = rc_chs[3]
autopilot.state['aux1'] = rc_chs[4]
autopilot.state['aux2'] = rc_chs[5]
autopilot.state['aux3'] = rc_chs[6]
autopilot.state['aux4'] = rc_chs[7]

aux3_raw = int(autopilot.state['aux3'])
autopilot_mode = autopilot.state['bee_state']
if aux3_raw == 1000:
autopilot_mode = 'OFF'
elif aux3_raw == 1503:
mavs.prepare_go_forward(previous_throttle)
time.sleep(0.1)
elif aux3_raw == 2000:
autopilot_mode = 'READY'

if autopilot_mode != autopilot.state['bee_state']:
autopilot.state['bee_state'] = autopilot_mode
messages.display(
messages.bee_state_changed_to, [autopilot_mode])
command_queue.queue.clear()

As you’ve likely noticed, the mid-range value (1503) triggers the autopilot to perform preparation steps for its main scenario, even though it isn’t assigned a distinct mode name. I made this choice deliberately, fully aware that it deviates from the intended architecture. However, I believe it’s acceptable for the code to have at least one imperfect piece — an attentive developer will likely spot and correct this quickly.

Telemetry

as was mentioned earlier, the Telemetry thread handles queuing commands for system monitoring and telemetry requests. It provides the Autopilot with critical data such as current altitude, aircraft speed, and real-time RC channel values.

import time
import autopilot
import messages
import router
import definitions as vars

def telemetry_requestor(stop_command):
while autopilot.state['connection'] == False and not stop_command.is_set():
try:
time.sleep(5)
messages.display(messages.telemetry_process_connecting, [vars.companion_computer])
except Exception as e:
messages.display(messages.fatal_error, [e])
pass

if not stop_command.is_set():
messages.display(messages.telemetry_process_connected, [vars.companion_computer])

while not stop_command.is_set():
try:
router.put_command(router.Command(2,'MONITOR',{'target':'MSP_ANALOG'}))

router.put_command(router.Command(2,'TELEMETRY',{'target':'MSP_ALTITUDE'}))
router.put_command(router.Command(1,'TELEMETRY',{'target':'MSP_RC'}))
time.sleep(4)
except:
pass

stopped_time = time.strftime("%H:%M:%S, %Y, %d %B", time.localtime())
messages.display(messages.telemetry_requestor_done, [stopped_time])

Telemetry requests are added to the queue at 2-second intervals.

As shown in the listing above, MSP_ALTITUDE and MSP_ANALOG requests are sent every 2 seconds to provide the autopilot with current altitude and aircraft speed data. Meanwhile, MSP_RC values, which are critical for mode switching and overall control, are requested every second to ensure more responsive and efficient control of the autonomous aircraft.

Empty pilot

The Empty Pilot thread manages queuing commands related to flight forward and payload delivery, such as bomb deployment. These commands are added to the queue every 1-second.

def go_forward():
set_row_rc(
vars.default_roll,
vars.default_pitch + 20,
vars.default_yaw,
int(autopilot.state['throttle']),
vars.default_servo_aux2)

wait_for_execution('go_forward')

set_row_rc(
vars.default_roll,
vars.default_pitch,
vars.default_yaw,
int(autopilot.state['throttle']),
vars.default_servo_aux2)

wait_for_execution('go_forward')

return True

def deliver():
set_row_rc(
vars.default_roll,
vars.default_pitch,
vars.default_yaw,
int(autopilot.state['throttle']),
2000) # 2000 to open the servo-device (to deliver the bomb)

wait_for_execution('deliver')
return True

When the FPV operator switches the aircraft to READY mode, it flies forward for 2 seconds and then delivers the bomb. This logic is implemented in the go_forward() and deliver() functions within the commands.py.

MSP commands

At the logic level, the command router (router.py) manages the pilot logic by executing commands from commands.py. These commands encapsulate more precise FPV drone control at a lower logic level.

import serial
import time
import autopilot
import definitions as vars
import msp_helper as msp

command_delays = {
'go_forward': 2,
'deliver': 1
}

command_target_ids = {
'MSP_ANALOG': msp.MSP_ANALOG,
'MSP_ALTITUDE': msp.MSP_ALTITUDE,
'MSP_RC': msp.MSP_RC
}

serial_port = {}

def wait_for_execution(target, delay=0):
if delay == 0:
delay = command_delays.get(target)
time.sleep(delay)

def get_target_id(target):
return int(command_target_ids.get(target))

def connect():
global serial_port
serial_port = serial.Serial(
vars.companion_computer,
vars.companion_baud_rate,
timeout=1)

def disconnect():
serial_port.close()

def reboot():
disconnect()
time.sleep(1)
connect()

def set_row_rc(roll, pitch, yaw, throttle, servo_aux):
# ROLL/PITCH/THROTTLE/YAW/AUX1/AUX2/AUX3/AUX4
data = [roll,
pitch,
throttle,
yaw, 0,
servo_aux, 0, 0]
msp.send_msp_command(serial_port, msp.MSP_SET_RAW_RC, data)

msp_command_id, payload = msp.read_msp_response(serial_port)
if msp_command_id != msp.MSP_SET_RAW_RC:
return False
return True

def copter_init():
# connect()

return set_row_rc(
vars.default_roll,
vars.default_pitch,
vars.default_yaw,
vars.default_throttle,
vars.default_servo_aux2)

def telemetry(target):
msp_target_command_id = get_target_id(target)
msp.send_msp_request(serial_port, msp_target_command_id)
time.sleep(0.1)
msp_command_id, payload = msp.read_msp_response(serial_port)
if msp_command_id == msp_target_command_id:
return payload

return {}

def prepare_go_forward(throttle):
set_row_rc(
vars.default_roll,
vars.default_pitch,
vars.default_yaw,
int(throttle),
vars.default_servo_aux2)

As shown in the listing above, it handles main scenario preparation prepare_go_forward(throttle), telemetry requests telemetry(target), and system monitoring. Additionally, it manages copter initialization connect() and configures AUX channels to certain specific values set_row_rc(roll, pitch, yaw, throttle, servo_aux), enabling autonomous control and operation during the automated drone flight.

MSP helper

During autonomous flight, the drone is controlled by the Betaflight firmware through a Companion Computer (Raspberry Pi) using the MSP protocol, as mentioned earlier. This protocol provides telemetry data about the drone’s current status and enables flight control (yaw, pitch, roll, throttle) as well as servo mechanism by toggling AUX channels.

To encapsulate all functions related to low-level or protocol-specific communication, I implemented the msp_helper.py module.

import struct

# MSP command IDs
MSP_ANALOG = 110
MSP_ALTITUDE = 109

MSP_RC = 105
MSP_SET_RAW_RC = 200

def get_checksum(msp_command_id, payload):
checksum = 0
length = len(payload)

for byte in bytes([length, msp_command_id]) + payload:
checksum ^= byte

checksum &= 0xFF
return checksum

def send_msp_command(serial_port, msp_command_id, data):
payload = bytearray()
for value in data:
payload += struct.pack('<1H', value)

header = b'$M<'
length = len(payload)
checksum = get_checksum(msp_command_id, payload)

msp_package = header + bytes([length, msp_command_id]) + payload + bytes([checksum])
serial_port.write(msp_package)

def send_msp_request(serial_port, msp_command_id):
header = b'$M<'
length = 0
checksum = get_checksum(msp_command_id, bytes([]))

msp_package = header + struct.pack('<BB', length, msp_command_id) + bytes([checksum])
serial_port.write(msp_package)


def read_msp_response(serial_port):
response = serial_port.readline()
if response.startswith(b'$M>'):
length = response[3]
msp_command_id = response[4]
payload = response[5:5 + length]
return msp_command_id, payload
else:
raise ValueError("Invalid MSP response")

As shown in the listing above, msp_helper.py encapsulates commands such as sending MSP commands, sending MSP requests, calculating checksums, and reading MSP responses. For the purposes of our autopilot, I’ve included only four MSP commands: MSP_ANALOG, MSP_ALTITUDE, MSP_RC, and MSP_SET_RAW_RC. However, the MSP protocol supports many additional commands beyond these.

Messages and Logs

During operation, the Autopilot generates and sends messages to various targets, including the application console, while also storing messages and logs in log files. A new log file is created each time the Autopilot starts.

Press enter or click to view image in full size
FPV drone’s flight records

Log files are saved in the Logs folder and can be reviewed at any time after the drone’s flight. Similar to the black-box in an aircraft, these files help analyze the flight and investigate any emergency situations that may occur.

I strongly recommend keeping logger.py set to DEBUG mode to capture detailed information. This practice will maximize learning from the logs and facilitate the continuous improvement of your Autopilot system with each flight of your version of FPV Combat Drone.

What is Next?

This guide serves as a foundational template for building autopilots compatible with Betaflight-firmware. If you are considering developing an autopilot, this guide is a great starting point for you. You can enhance it by incorporating advanced features like computer vision, target recognition, target tracking, target following, cruise control, and more.

To dive deeper into this topic, check out my other articles:

  1. How to build an Autopilot with Computer Vision and Target Following for FPV Combat Drone
  2. How to build the Eyes of an Autopilot for FPV Combat Drone
  3. FPV autonomous flight with MAVLink and Raspberry Pi. Part II

By designing and refining autopilots, starting production, and supplying FPV drones to the Ukrainian army, you contribute directly to strengthening Ukraine’s defense capabilities.

Support Ukrainian Army

Like many other Kyiv-based drone suppliers ramping up production of affordable, electronically jam-resistant, computer-guided drones, you can help Ukrainian defenders by developing and supplying your own version of an autopilot for FPV combat drones on Betaflight.

Press enter or click to view image in full size
Killer robots are about to fill Ukrainian skies, The Wall Street Journal, Nov. 15, 2024

Once you have assembled your autonomous FPV drone, debugged it, and tuned it well, send it to your friends on the Ukrainian frontline and take pride in the satisfaction of making a difference.

UNCLASSIFIED_20250115_OP_TI_FPV_AUTOPILOT_BEE_EPT_v1.0

As usually, I like to add a touch of mystery to my articles, and this one is no exception. To add a creative twist, I’ve used NATO-style military notation to emphasize the public nature of the following files.

Source: Autopilot for FPV Combat Drone on Betaflight (Empty version).

Related source: Video-overlay (eyes) feature for the Raspberry Pi.

Get in touch

Don’t ask me on Twitter any questions, I will not reply :)

Twitter: https://twitter.com/dmytro_sazonov

AI-driven autonomous FPV drone development: for hobbyists

I have a few free eBook copies if you’d like to read and share honest feedback on Amazon. Just claim yours in the comments on this article :)

--

--

ILLUMINATION
ILLUMINATION

Published in ILLUMINATION

We curate & disseminate outstanding stories from diverse domains to create synergy. Apply: https://digitalmehmet.com & https://substackmastery.com Subscribe to content marketing strategy: https://drmehmetyildiz.substack.com/ External: https://illumination-curated.com

Dmytro Sazonov
Dmytro Sazonov

Written by Dmytro Sazonov

Blockchain enthusiast and artificial intelligence researcher. I believe with these tools we will build better tomorrow :)

Responses (8)