Return to notes
Embedded systems· 4 min read

Real-time software changes when physics joins the system

Lessons from an ESP32 flight controller about task rates, coordinate frames, saturation, failsafes, and debugging software attached to hardware.

#ESP32#C++#FreeRTOS#Control systems

Most application bugs stay inside a screen, a log, or a database. In a flight controller, software decisions become motor commands. Timing, sensor orientation, radio loss, and battery state are no longer implementation details; they influence how the physical system behaves.

Building the ESP32 Flight Controller changed how I think about correctness. A function can produce the right value in isolation and still be wrong for the system if it runs too late, uses the wrong coordinate frame, or asks an actuator to do something physically impossible.

In embedded control, correctness includes value, time, frame, and safe limits.

Different work needs different clocks

A single loop() is easy to start with, but not every responsibility deserves the same update rate. Motor control is time-sensitive. Radio packets arrive on a different cadence. Distance sensors are slower. Status LEDs should never delay stabilization.

FreeRTOS made those responsibilities explicit:

ResponsibilityTiming concernFailure if delayed
IMU sampling and controlFast and consistentStale attitude estimate
Motor outputDeterministic updatesUneven or late correction
RF command handlingResponsive but boundedOld pilot command
Distance sensingSensor-limitedNoisy or blocked control loop
Status feedbackBest effortMisleading indication, not loss of control

The lesson was not simply “use more tasks.” Concurrency creates shared-state problems and can hide timing mistakes. The useful part was assigning a clear owner, priority, and update contract to each responsibility.

struct ControlFrame {
  float throttle;
  float roll;
  float pitch;
  float yaw;
  uint32_t receivedAtMs;
};

A timestamp is as important as the command values. It lets the control layer decide whether an apparently valid frame is too old to trust.

Coordinate frames are part of the data model

The physical MPU6050 board is rotated relative to the drone body. Raw sensor axes therefore do not directly mean roll, pitch, and yaw in the aircraft frame.

That creates a subtle class of bug: every number can look plausible while the correction is applied to the wrong axis or with the wrong sign. Calibration offsets must also be transformed consistently; remapping readings but not offsets produces a system that is almost correct and difficult to diagnose.

I learned to make frame conversions a named boundary:

raw sensor frame
  -> calibrated sensor frame
  -> drone body frame
  -> attitude estimate
  -> controller correction

The same principle appears in web systems under different names. UTC versus business-local time, provider identifiers versus internal IDs, and display currency versus ledger currency all require explicit transformations. Ambiguous data is dangerous even when its type is technically valid.

Motor mixing needs saturation awareness

A quadcopter controller combines throttle with roll, pitch, and yaw corrections. The naive result can exceed the motors' valid output range. Clamping each motor independently keeps the value legal but can distort the requested balance between axes.

Conceptually, a mixer produces four commands:

front-left  = throttle + pitch + roll - yaw
front-right = throttle + pitch - roll + yaw
rear-left   = throttle - pitch + roll + yaw
rear-right  = throttle - pitch - roll - yaw

When one command saturates, the controller needs to preserve useful authority rather than pretend every request was achieved. This is also why integral wind-up matters: accumulating correction while an actuator is already at its limit can cause a large overshoot when capacity returns.

The broader lesson is that downstream limits should shape upstream logic. Rate limits, queue capacity, database constraints, and API payload limits are the software equivalent of actuator saturation.

Failsafes are normal control states

Radio loss is not an exceptional code path that can be added after the controller works. It is an expected operating condition. The system needs a defined response when commands become stale, sensors fail initialization, or state becomes implausible.

My safety checklist became:

  • reject stale RF frames using elapsed time, not only a connection flag;
  • keep motors disarmed until initialization and command conditions are valid;
  • constrain every actuator command at the final output boundary;
  • make sensor and communication health visible through telemetry;
  • prefer a controlled safe state over continuing with uncertain input;
  • test components independently before integrating them into the aircraft.

These controls do not make experimental hardware risk-free. Props-off bench tests, restrained tests, clear arming procedures, and physical separation remain essential.

Debug observability before tuning

It is tempting to tune PID gains as soon as the motors respond. But tuning cannot repair an inverted axis, stale command, inconsistent task interval, or incorrect mixer.

Before adjusting gains, I want to observe:

  1. raw and remapped sensor values;
  2. loop intervals and task overruns;
  3. incoming command age;
  4. attitude estimates and controller terms;
  5. unclamped and final motor outputs;
  6. active failsafe reasons.

Telemetry turns “the drone feels unstable” into questions that can be tested. It also keeps debugging grounded in system state instead of guesswork.

The lesson I carried back to application development

The flight controller made several abstract engineering ideas tangible. Ownership matters because tasks share state. Observability matters because failures cross boundaries. Backpressure matters because outputs have limits. Time matters because correct data expires.

My strongest takeaway is to define the system's invariants before optimizing its happy path: which inputs are trustworthy, how fresh they must be, who owns each state transition, what the output limits are, and which safe state should win when those assumptions fail.

Continue reading

Enterprise software is a problem of state and ownership