Part 1: Why I'm Building a Flight Controller from Scratch
Why I'm Building a Flight Controller From Scratch
I'm building an RC plane (a 1.2m wingspan MQ-9 Reaper) and instead of buying a flight controller off the shelf, I'm writing one from scratch. Bare metal STM32, FreeRTOS, custom radio protocol, hand-soldered wires, the whole thing.
Friends asked me why. ArduPilot exists. Betaflight exists. You can buy a complete flight controller for 30 euros that does everything. So why spend weeks writing SPI drivers and debugging register values at midnight?
Because I don't want to fly a plane. I want to understand how a plane flies.
The idea
It started because I had a Black Pill board lying around, an STM32F411 dev board you can get for a few euros. I also had a gyro module (MPU6050, or so I thought; more on that later) and an NRF24L01 radio. The NRF was one of those long-range PA+LNA versions from AliExpress. Total cost for everything: maybe 17 euros.
I figured if I can read a gyroscope, talk over radio, and output PWM to servos, that's basically what a flight controller does. How hard can it be?
Pretty hard, it turns out.
What I actually wanted to learn
I'm a web developer. I write apps with React and TypeScript, I deal with AI, APIs, and databases. But I've always been curious about what happens at the other end, the hardware side. The stuff where you're dealing with voltages and registers and timing, not HTTP requests and JSON.
So this project is really about learning:
- How microcontrollers actually work at the register level (no HAL, no libraries)
- How RTOS task scheduling works in practice
- How sensors turn physical movement into numbers
- How radio communication works over SPI
- How control theory (PID) keeps a plane stable
- How all of this comes together into something that flies
I could learn all of this from a textbook or a course, but I learn by building things. And building a thing that flies is way more motivating than completing a tutorial.
The hardware
Here's what I'm working with:
-
STM32F411CE (Black Pill) - The brain. ARM Cortex-M4 running at 16MHz (I haven't set up the PLL yet, so it's running on the internal oscillator). It has a hardware floating-point unit, which matters when you're doing sensor math 100 times per second.
-
MPU6050 (actually MPU6500) - A 6-axis inertial measurement unit. It has an accelerometer (measures gravity and acceleration) and a gyroscope (measures rotation). I bought it labeled as MPU6050, but when I read the
WHO_AM_Iregister it returned0x70instead of0x68. Turns out it's actually an MPU6500, a newer and better chip. Same interface, so it doesn't matter. -
NRF24L01+PA+LNA - A 2.4GHz radio transceiver for communication between the plane and the ground. The PA+LNA version has a power amplifier for longer range. Mine is definitely a Si24R1 clone, not a genuine Nordic chip. I know this because the
RF_CHregister is completely broken. You can't write to it. Every other register works fine. Just not that one. So I'm stuck on channel 0, which is fine for now. -
Hobbywing Skywalker 80A V2 ESC - Electronic speed controller for the brushless motor. Takes a 50Hz PWM signal (same as servos) and drives the motor. Getting this to work was an adventure in itself.
-
T-Motor MN4010 KV580 - The brushless motor. 580 KV, which means it spins at 580 RPM per volt. On a 4S battery (about 15V), that's around 8,700 RPM. Plenty for a pusher prop on a Reaper.
-
Bashing 5000mAh 4S LiPo - The battery. These things scare me a little. They're basically spicy pillows that can supply enormous current. The first time I plugged it into the ESC, it sparked at the connector and I nearly dropped it. Turns out that's normal. The ESC capacitors draw an inrush current on first contact. You just push the connector together fast.
The plan
I wrote out a protocol guide before I started coding. It's a step-by-step plan that goes from implementing CRC checksums all the way to frequency hopping. Each step builds on the last, and you test each step before moving on.
The rough order is:
- CRC-16 for packet integrity
- Packet definition (32 bytes, fixed size)
- Serialization / deserialization
- SPI driver
- NRF24L01 radio driver
- Radio communication task (FreeRTOS)
- Command handler
- Ground station MCU (UART to radio bridge)
- Frequency hopping
In parallel with that, there's the sensor side:
- I2C driver
- MPU6050 gyro/accel driver
- Sensor fusion (complementary filter)
- PWM driver for servos and ESC
- PID controller
- Servo mixer
And then there's the ground station app, a React + TypeScript application that shows the plane on a map, displays telemetry, and takes PS5 controller input.
The rules
Since I'm in Romania, this falls under EU drone regulations. My plane is in the Open Category A3 under EASA rules, which means:
- Register with AACR (the Romanian civil aviation authority)
- Complete the free online theory test
- Fly at least 150m from residential, commercial, and recreational areas
- Maximum 120m altitude
- Visual line of sight at all times
- No flying over uninvolved people
The NRF24L01 operates in the 2.4GHz ISM band and at 0dBm (1mW) output power, which is way below the EU limit of 100mW EIRP, so the radio side is legal.
I checked all of this before buying parts because I didn't want to build the whole thing and then find out I can't legally fly it.
What's coming next
In the next post, I'll talk about writing the first drivers (SPI, I2C, and UART) from scratch on the STM32. No libraries, no HAL, just reading the reference manual and writing to registers. It's the part of embedded development that most people skip by using Arduino or STM32Cube, but it's also where you actually learn what's happening.
I'll also talk about the FreeRTOS task system and a fun bug where C++ references break when FreeRTOS copies your task object with memcpy.
The code for this project is on GitHub.