Skip to main content

Playground · research instrument

On-device AI

PID & MPC Flight Arena

A 3-D rigid-body quadrotor simulated with unit quaternions and RK4 in your browser. Tune Kₚ,Kᵢ,K_d and MPC Q/R weights live, throw wind gusts, watch force arrows and coordinate paths, feel crash shake, and hear motor whine pitch-tracked to thrust commands.

Independent research instrument — not claimed as MakerPortal shipped product code. Methods, equations, assumptions, and limitations are disclosed so you can inspect what the page does and does not establish.

Controller Tuning

Altitude PID — z error → collective

Attitude — roll/pitch rate

MPC Weights — J = xᵀQx + uᵀRu

Setpoint

Pos
Vel m/s
RPY deg
Thrust N
Mot %
Integrator

3D flight viewport — top-down + side, force vectors

Space: thrustArrows: forcesBlue trail: pathRed: crash energy
Quaternion |q|
1.000
Energy J (MPC)
Loop dt
4.00 ms @250Hz

Anatomy of the arena

Quaternion rigid body & PID/MPC

  1. State: p,v ∈ ℝ³, q∈ℍ unit, ω∈ℝ³ body rates. Mass m=1.02 kg, I=diag(0.01,0.012,0.02).
  2. Dynamics: m·ṗ = v, m·ṽ = R(q)·[0,0,T]ᵀ - mg + F_gust, q̇=½ q⊗[0,ω], I·ω̇=τ - ω×Iω.
  3. PID altitude: e_z = z_sp−z, T = m(g+Kp e + Ki∫e + Kd ė). Clamped to [0.2mg, 2.2mg].
  4. Attitude: outer x/y PID → desired roll/pitch, inner P-D tracks via τ.
  5. MPC LQR: Q=diag(Qpos), R=R_thrust, K_lqr from DARE for hover double integrator.

Quaternion derivative

q˙=12q[0ωxωyωz],q=1\dot{q}=\frac12 q \otimes \begin{bmatrix}0\\ \omega_x\\ \omega_y\\ \omega_z\end{bmatrix},\quad |q|=1

Gimbal-lock-free attitude propagated by RK4 then renormalized.

Visual-audio coupling

  • Canvas shows quad X frame: 4 rotors, shadow, force arrows: green thrust, red gravity, cyan PID correction.
  • Trajectory trail: 400-point buffer fading alpha, blue = normal, red when |v| impact high.
  • Crash shake: CSS translate random proportional to kinetic energy, cuts thrust 0.8s.
  • Audio: 4 motor oscillators at f_i = 80 + 350·(ω_i/ω_max). Master gain = thrust/hover. Stereo panning L/R maps roll.
  • MPC readouts: J cost adds position and control penalty, displayed live.

MPC Cost

J=k=0N1xkTQxk+ukTRuk,u=KlqrxJ=\sum_{k=0}^{N-1} x_k^T Q x_k + u_k^T R u_k,\quad u^*=-K_{lqr}x

Gear behind this build

Flight simulator stack · 8 picks

Flight controller & drone gear8

More gear across every app: the full Gear list →

Hardware Kit Builder

Build the physical hardware platform. Select your components below to generate a live, real-time bill of materials and build instructions.

Build this lab

Hover-ready flight stack

Betaflight stack + ELRS radio + SparkFun ICM-20948 IMU — take PID gains from the arena to a real quad bench. IMU earns SparkFun Originals commission.

Prices shown were retrieved from the Amazon Product Advertising API on 19 July 2026 and are indicative only — the price and availability on Amazon at the time of purchase apply.

Estimated total

Prices from Amazon catalog cache · may change

Open primary listing ↗

Kit Total

Buy ↗

Equations of motion

Force balance

mp¨=R(q)[00T]mge3+Fgustm\ddot{p}=R(q)\begin{bmatrix}0\\0\\T\end{bmatrix} - m g e_3 + F_{gust}

Collective thrust rotated to world via R(q).

Moment & gyro

Iω˙=τω×IωI\dot{\omega}= \tau - \omega\times I\omega

PID

u=Kpe+Kiedt+Kde˙u=K_p e + K_i\int e dt + K_d \dot{e}

Core solver — TypeScript

// Quadrotor state: pos, vel, quat q=(w,x,y,z), omega
type State = { p: Vec3; v: Vec3; q: Quat; w: Vec3; };
type Quat = { w:number; x:number; y:number; z:number; };
type Vec3 = { x:number; y:number; z:number; };

function quatMul(a:Quat,b:Quat):Quat{
  return { w:a.w*b.w - a.x*b.x - a.y*b.y - a.z*b.z,
           x:a.w*b.x + a.x*b.w + a.y*b.z - a.z*b.y,
           y:a.w*b.y - a.x*b.z + a.y*b.w + a.z*b.x,
           z:a.w*b.z + a.x*b.y - a.y*b.x + a.z*b.w };
}
function quatNorm(q:Quat){ const n=Math.hypot(q.w,q.x,q.y,q.z)||1; return {w:q.w/n,x:q.x/n,y:q.y/n,z:q.z/n}; }
function quatDeriv(q:Quat, w:Vec3):Quat{
  const omega:Quat={w:0,x:w.x,y:w.y,z:w.z};
  const qd=quatMul(q, omega);
  return {w:qd.w*0.5,x:qd.x*0.5,y:qd.y*0.5,z:qd.z*0.5};
}
function rk4(f:(s:State)=>StateDeriv, s:State, dt:number):State {
  const k1=f(s), s2=add(s,scale(k1,dt/2));
  const k2=f(s2), s3=add(s,scale(k2,dt/2));
  const k3=f(s3), s4=add(s,scale(k3,dt));
  const k4=f(s4);
  return add(s, scale(combine(k1,k2,k3,k4), dt/6));
}

Export · Soft gate

Export PID gains + Betaflight snippet

Tuned Kp/Ki/Kd for roll/pitch/yaw/thr from the arena as JSON + Betaflight CLI snippet — free watermarked, clean after email unlock. Includes hover stack BOM link.

File · pid-gains.json

pid-gains.jsonapplication/json+ watermark line on free path

Free download adds a small footer: /* Export from makerportal.ai — free watermarked build. Unlock …Clean export removes footer. Both are generated fresh from your current sim tuning.

Privacy: email stays in your browser localStorage (mp_export_email_pid-flight-arena) + unlock flag (mp_export_unlock_pid-flight-arena). If Buttondown username is configured, we also POST to Buttondown (privacy-first mode, no tracking pixels per D-014). See privacy → affiliates & email.

Unlock clean export

Soft gate — no hard paywall, no Clerk. Email stays local unless you explicitly check the newsletter box. Unsubscribe anytime. RSS at /rss.xml.

Export → Fab bonusAfter export, your tuned stackup can be ordered via PCBWay/JLCPCB CTA (when live) — see /privacy#affiliates for live merchants.

Frequently asked questions

Why quaternions and not Euler angles?

Euler angles suffer gimbal lock when pitch approaches ±90° and their composition depends on order. Quaternions are unit 4-vectors q=(w,x,y,z) that rotate via q⊗[0,ω]⊗q* without singularities. The flight dynamics integrate q̇ = 0.5 q ⊗ [0, ω_body] and renormalize |q|=1 every step to prevent drift. Roll/pitch/yaw readouts are derived from q only for the pilot display.

How is the MPC blending with PID?

For hover-linearized dynamics ẋ=Ax+Bu, MPC minimizes J=∑ xᵀQx + uᵀRu over horizon N. With diagonal Q_pos, R_thrust, the unconstrained solution collapses to LQR gain K = (R+BᵀPB)^-1 BᵀPA from DARE. We pre-solve K analytically for the 1D altitude subsystem and blend K·x with PID output proportionally to MPC authority slider. Increasing Q/R weight ratio makes MPC more aggressive on position error.

What does crash detection do numerically?

Collision when z ≤ 0.05 m. Velocity decay uses restitution e=0.2, so v_z⁺ = -e·v_z⁻, v_xy⁺ = (1-0.4)·v_xy⁻. Integrated shaking energy E = m·|v|² triggers viewport CSS shake amplitude A = clamp(E/40,0,8) px and cuts motor commands to zero for 0.8s, forcing integral unwind.

How accurate is RK4 here?

RK4 is 4th-order accurate with global error O(dt⁴). At dt=0.004s (250 Hz control loop) energy drift over 30s hover is <0.2%. Euler would artificially dampen high-Q roll oscillations and destabilize Ki buildup. Quaternion integration additionally normalizes after each RK4 stage.

How to tune without instability?

Start with Ki=0, Kd=0. Increase Kp until 10-15% overshoot. Add Kd ≈ Kp·0.15 to damp overshoot. Finally add Ki ≈ Kp/8 but clamp integrator to thrust margin to avoid windup. For MPC, increase Q_pos slowly - too high causes aggressive collective that saturates motors, heard as maximum motor whine pitch.

Shareable still

The instrument, captured—not illustrated.

This 16:9 frame is rendered from the real browser instrument above. It is the page's canonical preview for image search, link unfurls, and posts that need to show what the tool actually does.

Download 1280 × 720 JPEG
PID & MPC Flight Arena — live MakerPortal instrument screenshot
Canonical capture · real UI · no generated scientific artwork