Building an ESP32 quadcopter from a public project, and what flight control actually feels like
Table of contents
I had wanted to build a quadcopter for ages and never had a good entry point. The off-the-shelf hobby market is dominated by Betaflight on dedicated F-class flight controllers, which is the right answer for performance and the wrong answer if what you want is to understand the stack. The boards are too tuned. The firmware is too far along. You learn how to fly a quad, you do not learn how a quad works.
The entry point that clicked for me was okalachev/flix. It is an ESP32-based flight controller, simulated in Gazebo, with the actual code small enough to read in an afternoon. The thing about flix is that it is honest about being a learning project. It is not optimised for race, it is not optimised for cinematic. It is optimised for "you can read every line and understand what it does".
This post is about taking that as a starting point, printing a frame, and discovering what every layer of "a thing that hovers" actually costs.
The stack#
The frame is a 3D-printed quad with 5-inch arms in PETG-CF. The motors are 2207 brushless, 2400 KV, the kind that costs eight dollars each on the right Sunday. The ESCs are 4-in-1 BLHeli32 30A on a small board. The flight controller is an ESP32-S3-WROOM, mounted on a custom carrier PCB that breaks out four ESC PWM lines, a UART for telemetry, and an SPI to a BMI088 IMU. Power is a 4S LiPo.
The flight code is the flix firmware with my modifications. flix targets PlatformIO. The repo includes a SITL build for Gazebo, which is the thing that saved me from crashing the actual quad ten times. Everything I changed got tested in sim first.
What flix actually does#
Stripped to essentials, a flight controller does four things in a loop:
- Read the IMU. Get gyro rates and accelerometer readings.
- Run the rate controller. Given the difference between the pilot's stick input (or autonomy setpoint) and the measured rates, compute a desired motor mix.
- Update an attitude estimate using complementary or Kalman fusion of gyro + accel.
- Write the motor mix to the four ESCs as DShot or PWM signals.
That loop runs at ~250 Hz to 1 kHz depending on the controller. flix runs it at 500 Hz on the ESP32-S3, leaving headroom for radio polling, telemetry, and the slow loop (~50 Hz) that does barometer reads, GPS parsing, and high-level mode switching.
The whole codebase is about 4,000 lines. You can read it in an afternoon. You can change anything in it in another afternoon. That is what makes it a learning project instead of a product.
What surprised me#
The motor mix is trivial. The maths is six lines: take desired throttle, roll, pitch, yaw rates, and emit four motor commands as a linear combination. Anyone with a high-school grasp of vectors can write it.
The IMU pipeline is the part that drains hours. The MPU6050 datasheet looks straightforward, then you try to use the raw readings and discover they drift, ring, alias, and tilt depending on temperature. The standard fixes are well-documented: complementary filter for the cheap path, EKF for the proper path. The work is making them actually converge with your specific sensor mounted to your specific frame at your specific vibration spectrum. The "trust the IMU" line in the textbook hides about fifty hours of mounting, isolating, and tuning.
The accelerometer fights you on every level. A quad in level hover sees gravity plus motor vibration. The motor vibration peaks at the prop's rotational frequency and its harmonics. Your accelerometer reads "gravity + a 280 Hz noise floor" and your attitude estimator helpfully integrates that noise into a drift. Three things help: rubber-mount the FC, low-pass filter the accel reading at maybe 30 Hz, and notch out the propeller frequency dynamically. flix has all three. Tuning the notch filter to your specific build is the thing that takes a week.
The third surprise: the radio. The simple ELRS link I started with worked in sim. On the real board it dropped packets the instant I powered the ESCs. The motors radiate at the switching frequency, the receiver picks it up, the packet rate collapses to ten per second, the quad falls out of the sky. The fix is ferrite chokes on the ESC power leads, the receiver antenna placed clear of the power harness, and shielded twisted pair from the receiver to the FC. None of this is in the textbook either.
What flix taught me that the manuals did not#
The PID tuning is the part everyone writes about. It is also the easiest part. Once the IMU is clean and the radio is reliable, PID tuning is methodical: increase P until oscillation, back off 30%, raise D to damp, raise I just enough to hold attitude under wind. An hour with a setpoint sweep and a log analyser gets you there.
The hard parts are upstream: clean IMU readings, vibration management, a radio link that does not glitch under power. Get those right and the controller is forgiving. Get them wrong and no PID gains rescue you.
The other thing flix taught me is why Betaflight makes the choices it does. Betaflight's dynamic notch filter, its gyro filter cascade, its motor-output protocol: every one of those is a mitigation for something the simpler flix code does not handle. After reading flix's IMU code and watching it fight a noisy quad, every Betaflight tuning guide reads differently. The flags I used to memorise now correspond to actual physics I had to fight on my own bench.
The printed parts#
The frame is a True-X layout with 5-inch arms. The arm thickness is 6 mm in PETG-CF, which sounds excessive and is necessary. PETG flexes under hover load, and the flex shows up in your IMU as low-frequency noise that no amount of filtering removes cleanly. Stiffer arms is the only real fix.
The top plate carries the FC and the receiver. I printed three versions before settling: the first one was too thin (resonance), the second too thick (mass), the third one has a small isolation pad cut into the FC mount with TPU vibration dampers. That isolation pad is the single biggest gain I made on noise figures.
Battery strap, antenna mounts, GPS standoff: all generic Printables files with minor edits. The FreeCAD source for the custom plate is in my notes; nothing about it is special, it is a 2 mm flat with mounting holes and a TPU-pad pocket.
What I would change next time#
Start in sim, longer. flix has a Gazebo SITL that is faithful enough to catch most of my early code bugs. I rushed past it on the way to the physical build and paid for it in broken props. Two weeks of sim before powering the real ESCs would have saved a propeller set.
Pick the IMU first. I started with an MPU6050 because every tutorial used one. The BMI088 I ended up with is dramatically less work to filter. The cost difference is twelve dollars. The time difference is a week.
Build the harness as if every wire is an antenna. Because every wire is an antenna. Ground returns close to power leads, shielded radio harness, ferrite where it costs nothing. The cost of doing this from the start is half an hour. The cost of doing it after the first radio failure is rebuilding the harness from scratch with the quad on the bench.
flix is a great starting point. The real teacher is the quad itself, sitting on the bench, telling you which of your assumptions were wrong this time.