CLI tool
okalachev/flix avatar
okalachev/flix

Flix: an ESP32 quadcopter firmware short enough to read end to end

Making an ESP32-based quadcopter from scratch

2,363 stars348 forksC++License varies

At a glance

What is it?
Flix is a flight controller for a hand-built ESP32 quadcopter, written in Arduino C++ and kept under 2k lines so the whole control loop can be read. It ships a Gazebo simulator, MAVLink over Wi-Fi or ESP-NOW, and a parts list built from commodity components, and it stops short of position control.
Who is it for?
Adopt Flix if you want to understand a flight controller rather than configure one, and if you are willing to solder the airframe yourself out of hobby-grade 8520 motors. Pass on it for position holding or any production work: position control is still listed as planned, the barometer sits in the parts list unused, and the Flix2 PCB is described as in development.
Can I use it commercially?
Not without permission. GitHub finds no licence file in the repository, and without a licence all rights are reserved by default: you may read the code but not reuse it. Check the README, or ask the authors, before using it.
Is it still maintained?
Yes. The repository last received commits 23 days ago.
What is it written in?
Mainly C++, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

Why a flight controller stays under 2k lines of Arduino

Flix is firmware for a quadcopter you assemble yourself, written in Arduino C++ for an ESP32 Mini, and the README keeps it under 2k lines. That number is the real pitch. You can read the entire control loop in an afternoon and still know what the motor mixer is doing when you close the file. The stated audience is education and research, and the parts list is made of commodity items, so the cost of entry is a soldering iron, a multimeter and a 3D printer rather than a development board.

That framing decides who should look elsewhere. If you are picking firmware for a racing build, a survey aircraft or anything you intend to certify, the same brevity works against you. Nothing in the repository describes failsafe behaviour, control loop rates, or the depth of sensor filtering that production stacks are measured on. The repository holds `flix/`, `gazebo/`, `tools/` and `docs/` at the top level, and the code directory is small enough that the missing operational detail is a real gap rather than a formatting choice.

MAVLink on two radios, with a proxy sketch for ESP-NOW

Communication is MAVLink, and Flix carries it over two radios. Wi-Fi is one path and ESP-NOW is the other, which matters because ESP-NOW needs no access point and no addressable network. The cost of that second path is a separate binary: the Makefile has an `upload_proxy` target that compiles and flashes the sketch in `tools/espnow-proxy` onto the same board.

Control input arrives as a USB gamepad, an RC receiver or a smartphone. A CC2500 board, such as the BetaFPV LiteRadio CC2500, is listed as the recommended receiver, and a DF500 is offered as an optional part; any transmitter-receiver pair speaking SBUS works too. Above the link sits a wireless command line interface for inspecting and analysing, and a Python library in `tools/pyflix` for scripting automatic flights. Flight data goes out as CSV, written by `tools/log.py` and read by PlotJuggler. That loop, fly then plot then change one number then fly again, is what the project is actually built for.

The Makefile pins the toolchain before it compiles anything

Every build goes through the Makefile, and the first two targets bootstrap the toolchain rather than touching the source. These two install the ESP32 core from Espressif's package index and the two libraries the firmware links against:

code
core .core:
	arduino-cli core install esp32:[email protected] --additional-urls https://espressif.github.io/arduino-esp32/package_esp32_index.json

libs .libs:
	arduino-cli lib install "FlixPeriph"
	arduino-cli lib install "MAVLink"@2.0.33

Both versions are exact. The core is pinned at `esp32:[email protected]` and the MAVLink library at `2.0.33`, so a build does not drift when Espressif publishes a newer index. Expect `arduino-cli` to download both on the first run, and treat the `.core` and `.libs` stamp files as the record that this already happened.

Compilation, flashing and the serial console are three more targets. Note that the build passes `build.core_debug_level=1`, which keeps debug information in the image, and that the port is found by wildcard rather than by asking you:

code
build: .core .libs
	arduino-cli compile flix --fqbn $(BOARD) --build-property "build.core_debug_level=1" $(EXTRA)

upload: build
	arduino-cli upload flix --fqbn $(BOARD) -p "$(PORT)"

monitor:
	arduino-cli monitor -p "$(PORT)" -c baudrate=115200

The `PORT` variable is a wildcard over `/dev/serial/by-id/usb-Silicon_Labs_CP21*`, `/dev/serial/by-id/usb-1a86_USB_Single_Serial_*`, `/dev/cu.usbserial-*` and `/dev/cu.usbmodem*`. That covers USB serial adapters on Linux and macOS under those names. If your board enumerates somewhere else, `PORT` resolves to an empty string and `upload` passes an empty `-p`, so you have to set it yourself on the command line. The console runs at 115200 baud, which matches the serial output the log tooling expects.

Gazebo runs the firmware, not a model of it

The simulator is the part that separates Flix from a hobby firmware dump. The README states that the Gazebo simulator runs the original Arduino code, so the control loop you read is the control loop you debug. Nothing has to be re-expressed in a separate simulation framework, and there is no second copy of the mixer to keep in sync.

The build follows the usual CMake pattern, with two environment variables pointing Gazebo at the project model and plugin directories:

code
simulator: build_simulator
	GAZEBO_MODEL_PATH=$$GAZEBO_MODEL_PATH:${CURDIR}/gazebo/models \
	GAZEBO_PLUGIN_PATH=$$GAZEBO_PLUGIN_PATH:${CURDIR}/gazebo/build \
	gazebo --verbose ${CURDIR}/gazebo/flix.world

The simulator target depends on `build_simulator`, which configures `gazebo/build` with cmake and compiles it, so the world file, the models and the firmware-linked plugin are all built from one command. The trade-off is that this path pulls in Gazebo, Gazebo's plugin toolchain and a working desktop graphics stack on a machine where the drone itself would fit in your hand. Reading `gazebo/README.md` before you start is the sensible order. The companion log targets close the loop back to the flight side:

code
log:
	tools/log.py

plot:
	plotjuggler -d $(shell ls -t tools/log/*.csv | head -n1)

The log target produces CSV files and the plot target opens the newest one, so the simulator and the real aircraft share one analysis path.

The parts list refuses substitutes in three places

The bill of materials is where Flix is most opinionated, and the warnings are specific enough to be useful. Three entries carry constraints you should treat as hard requirements.

First, the motors. Flix calls for four 8520 3.7V brushed motors and states plainly that the motor needs the exact 3.7V voltage, not a rated working range of 3.7V to 6V, and that the motor shaft diameter and the propeller hole diameter have to match. Second, the MOSFETs: UMW 100N03A or a documented analog, with an explicit warning not to use KIA 100N03A or parts from other manufacturers because they might not work. Third, the battery: a 3.7V Li-Po with a discharge rate of 25C and 1000 mAh or more, connected through an MX2.0 2P female cable.

Around those sit four 55 mm or 65 mm propellers, six 10 kΩ resistors covering both the pull-down and the voltage measurement roles, and 28 AWG wire. A 5V output boost converter is marked optional, for a steadier supply. The IMU board has the widest tolerance, with GY-91, MPU-9265, ICM20948V2 and GY-521 all listed, which is unusual for a firmware that stays under 2k lines.

The printed frame is optimised for one IMU and needs editing for the rest

The airframe is 3D printed, and the repository ships both `stl` and `step` files for the frame main part, the ESP32 holder and two M3 washers. The recommended print settings are layer 0.2 mm, line 0.4 mm and infill 100%, which tells you the frame is meant to be solid rather than light. Assembly needs M3x5 screws for the IMU board and M1.4x5 screws for the frame, and the tools list is a 3D printer, a soldering iron, solder with flux, screwdrivers and a multimeter.

Here is the constraint that catches people out. Footnote 2 states that the frame is optimised for the GY-91 board, and that if you use a different IMU board the mount hole positions should be modified. So the widest component choice in the whole parts list is the one the mechanical design does not support, and picking an ICM20948V2 means editing the print or the mount before the firmware ever runs. Power the board from the Li-Po through VCC and GND, and check the simplified connection diagram plus the MOSFET connection scheme before soldering, since the motor half of the build is where mistakes are not recoverable in software.

Where the documentation stops, and one table contradicts itself

Several boundaries are stated rather than hidden. The parts table labels the IMU entry as an IMU and barometer board and lists boards with onboard barometers, yet footnote 1 says the barometer is not used for now. The table sells a sensor the firmware ignores, which is the kind of detail a skim misses and a build discovers. Position control is listed as planned and described as in development, demonstrated with an overhead camera in sources hosted in a separate branch repository rather than in master. The official PCB, called Flix2, is also in development, with progress tracked on the project's Telegram channel. That is where the hardware effort has gone, and the airframe is still soldered by hand.

The documentation set itself is well organised: assembly instructions, usage covering build, setup and flight, the Gazebo readme, the Python library readme, a firmware architectural overview in `docs/firmware.md`, plus troubleshooting, log analysis and a gallery of user builds. Against that, the repository lists no licence identifier among its top-level entries, so the reuse terms are not stated in the files a user sees. The README table labels the 3D-printed airframe as Version 1.1 while the build pins an ESP32 core at 3.3.10 and MAVLink at 2.0.33, which is a reminder that the version label tracks the hardware, not the firmware. A textbook on flight control theory is linked as in development at `quadcopter.dev`.

For a reader deciding whether to adopt, the honest summary is that the software is the finished part and the product around it is not.

Editorial conclusion

Adopt Flix if you want to understand a flight controller rather than configure one, and if you are willing to solder the airframe yourself out of hobby-grade 8520 motors. Pass on it for position holding or any production work: position control is still listed as planned, the barometer sits in the parts list unused, and the Flix2 PCB is described as in development. Verify first by running make build once and reading docs/firmware.md, because a firmware this short can be audited in a single sitting.

Frequently asked questions

What hardware does Flix need before it can fly?

The bill of materials calls for an ESP32 Mini, four 8520 3.7V brushed motors, four 55 mm or 65 mm propellers, four UMW 100N03A MOSFETs, six 10 kΩ resistors, a 3.7V Li-Po of 1000 mAh or more, and the 3D printed frame shipped as stl and step files. A CC2500 receiver or a two-stick gamepad covers control input.

How do I compile and flash the Flix firmware?

Run the build target to install the pinned esp32:[email protected] core along with the FlixPeriph and MAVLink 2.0.33 libraries, then the upload target to flash over the port the Makefile discovers. The monitor target opens the serial console at 115200 baud.

Can I test Flix without building the quadcopter?

Yes. The simulator target configures and builds the Gazebo model and plugin, then runs `gazebo/flix.world`, and the README states that this simulator runs the original Arduino code rather than a reimplementation.

How do I analyse a Flix flight log?

The log target runs `tools/log.py` to write CSV files, and the plot target opens the most recent one with PlotJuggler. The same path works for data coming from the Gazebo simulator.

Does Flix support ESP32-S3 and ESP32-C3 boards?

The parts table says ESP32-S3 and ESP32-C3 boards are also supported, but the Makefile hardcodes `BOARD = esp32:esp32:esp32`, so you have to override that variable when you compile for another variant.

Official sources

  1. Issues
  2. okalachev/flix on GitHub
  3. Project website
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/okalachev-flix.svg)](https://hysenlabs.com/projects/okalachev-flix)

Was this useful?