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.
| Condition | Response |
|---|---|
| Nominal | Fly the mission's proposed target (inside the geofence) |
| One IMU faults | Isolate it, fly on the backup — the mission continues and completes |
| Low battery | Return to launch — still flyable, just low on energy |
| Control deadlines missed in a row | Return to launch |
| Both IMUs lost, or a rotor out | Deploy 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:
- two IMUs, each with noise and a per-airframe bias; position at a GPS-like rate, slower than the gyro
- rotor thrust that lags the command (a first-order spin-up), quadratic drag, extra lift in ground effect
- a mean wind that grows with altitude plus turbulence near the ground, entering physically through the drag on the relative air velocity — so the controller leans into it, and you see it hold
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.
- The flight is the closed loop above: the real control law and safety envelope flying the delivery, on silicon, over Wi-Fi, at the HIL frame rate. It shows the mission completing through the fault responses.
- The 1 kHz deadline result is the same control code run as a scheduled periodic task — on core 1, at 1 kHz, while the other core carries the network. RP2350: 0 misses in 1,741,824 jobs (period 948.9–1056.5 µs), a full delivery flight. RP2040: 0 misses in 113,664 jobs (period 998–1002 µs), but without the VM mission — the 264 KB chip cannot hold both. It shows the scheduler holds the rate; it is not the loop that flies the aircraft.
- The control compute's worst case is 190 µs on the Cortex-M0+ (RP2040) and 98 µs on the Hazard3 (RP2350) — each measured on the board by a reproducible adversarial sweep, a 5.2× and 10.2× margin under a 1 kHz budget.
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.