Flight Control

The landing page uses a delivery drone to show the two layers meeting the ground. This is that demo in full: what runs where, how it answers a failure, and what is and is not proven.

It runs on real silicon — a Raspberry Pi Pico W (RP2040, Cortex-M0+) and a Pico 2 W (RP2350, RISC-V), both with no MMU — as a hardware-in-the-loop controller. The controller runs on the chip; a simulated aircraft runs on a PC; the two exchange sensor and motor frames over ordinary Wi-Fi.

What runs where

The kernel flies the aircraft. The cascade-PID-plus-quaternion control loop and the safety envelope are native Zig, privileged, in the kernel. Each HIL frame the controller reads the sensor, the safety envelope decides the target, and the control law computes four motor commands.

The mission is one confined actor. It is the flight plan — climb from base, cruise over the sea to an island 90 m out, drop the cargo, return, land — uploaded as Lambda C bytecode, unprivileged, boxed in by the PMP (RP2350) or MPU (RP2040). It reads the controller's live position, and it proposes the next waypoint. The native safety envelope reads that proposal and decides the target the controller actually flies.

That is the two-layer split turned around: here the safety-critical part is native and the changeable part — the flight plan, the thing that differs per job — is the isolated bytecode. A bug in the flight plan cannot reach the safety envelope or the control loop; they are across the kernel boundary.

The safety envelope answers by how much capability is lost

The envelope runs native, every frame, between the mission's proposal and the target the controller flies. Its response is graded — not "stop everything," and not "come home" when coming home is no longer possible.

ConditionResponse
NominalFly the mission's proposed target (inside the geofence)
One IMU faultsIsolate it, fly on the backup — the mission continues and completes
Low batteryReturn to launch — still flyable, just low on energy
Control deadlines missed in a rowReturn to launch
Both IMUs lost, or a rotor outDeploy the parachute

The two IMUs are cross-checked with debounce: a single fault is isolated, but two readings that agree on a violent motion are treated as a real tumble, not a double fault. A dead rotor is read from the ESC telemetry, not inferred from wobble. And the parachute is the honest answer to a genuine loss of control: a quad is underactuated, so it cannot make a controlled descent on three rotors, and an aircraft that has lost both IMUs has no attitude to hold — "return to launch" would be a command it cannot obey. The chute is one-shot, latched until the aircraft is down and the fault has cleared.

The mission can propose a target. It cannot reach any of this.

The board proves the boundary before it arms

Before the RP2350 flies, it runs a pre-flight sequence and reports each check to the ground station — radio, cores, control loop, then protection.

The protection check attempts an unprivileged store into kernel memory, and the hardware must refuse it. And it proves the check is not vacuous, in the same boot: it first lifts the deny on a scratch page and confirms the store lands, then restores the deny and confirms the identical store faults — a store that never reached memory would "pass" against any hardware. If the real store lands, or a verdict is missing, the board reports the failure and does not spawn the mission. Only after the refusal is observed is the mission spawned, unprivileged and confined, and the board then holds until a human sends the launch command. The ground station can ask for the whole sequence again, which reboots the board and runs it for real rather than replaying remembered lines.

The plant, on the PC

The other half of the loop is a rigid-body quad simulator. A hardware-in-the-loop test is only as good as its plant: a clean, noise-free, lag-free model would let any controller look good and prove nothing. So the simulator gives the controller what a real drone actually meets, and the controller — tuned on the clean dynamics — has to hold against all of it. Always on:

And the operator can inject the crisis from the keyboard: fault the primary IMU (s), fault the backup (x), stop a rotor (m), drop the battery (b), a gust (g), the wind strength and direction. Each one is answered by the board, live.

The controller and the PC run in lockstep over Wi-Fi UDP: the PC sends a sensor frame of two dozen floats, the board returns four motor commands, the PC steps the physics and repeats. Median round-trip is 60 ms, no loss over 335 exchanges; the HIL frame runs at 20 Hz. The mission flies the full delivery — 1.4 kg loaded, dropping to 1.0 kg at release, the controller absorbing the mass step and climbing back out.

What is measured, and what is not

The flight and the real-time result are two separate demonstrations of one control law, and it is worth keeping them apart.

A single 1 kHz closed-loop flight is the conjunction of these — the compute fits, the scheduler holds the rate, and the loop flies — not something shown as one run. How each is measured is on the Real-Time page; the confinement the mission runs under is on the Isolation page.