Argus — Quadrotor Target-Tracking MPC
Model Predictive Control for a quadrotor tracking a circular trajectory with yaw locked toward an object at the centre. The controller is formulated as a Nonlinear MPC using ACADOS, which generates a C solver that is called from C++.
Drone (blue trail) holding a 2 m-radius circle at 1.5 m altitude while the camera frustum (amber, dashed-green ideal) stays locked on the static ground target. Generated with python3 scripts/animate_xy.py --save-gif ... — see How to run.
Model
State and inputs
The quadrotor is modelled with 12 states and 4 inputs, using ZYX Euler angles.
State x ∈ ℝ¹²:
| Symbol | Indices | Unit | Description |
|---|
| x, y, z | 0–2 | m | position in world frame |
| vx, vy, vz | 3–5 | m/s | velocity in world frame |
| φ, θ, ψ | 6–8 | rad | roll, pitch, yaw (ZYX Euler) |
| p, q, r | 9–11 | rad/s | body-frame angular rates |
Input u ∈ ℝ⁴:
| Symbol | Index | Unit | Description |
|---|
| T | 0 | N | collective thrust (sum of all rotor forces) |
| p_cmd | 1 | rad/s | commanded roll rate |
| q_cmd | 2 | rad/s | commanded pitch rate |
| r_cmd | 3 | rad/s | commanded yaw rate |
The inputs are thrust and commanded body rates — matching the interface of a real flight controller such as PX4, which exposes a body-rate setpoint API. The dynamics of the inner rate controller are captured by a first-order lag on each body rate (time constants in config/quadrotor.yaml under inner_loop).
Continuous-time ODE
Position kinematics — velocity integrates into position:
ẋ = vx
ẏ = vy
ż = vz
Translational dynamics — thrust T acts along the body z-axis; rotating it to the world frame via R(φ,θ,ψ) gives the acceleration:
v̇x = T/m · (cos(ψ)·sin(θ)·cos(φ) + sin(ψ)·sin(φ))
v̇y = T/m · (sin(ψ)·sin(θ)·cos(φ) − cos(ψ)·sin(φ))
v̇z = T/m · cos(θ)·cos(φ) − g
The rotation is the ZYX convention: R = Rz(ψ) · Ry(θ) · Rx(φ). When the drone is level (φ = θ = 0) this reduces to v̇z = T/m − g, so T = mg at hover.
Euler angle kinematics — relates body rates (p, q, r) to Euler rates:
φ̇ = p + sin(φ)·tan(θ)·q + cos(φ)·tan(θ)·r
θ̇ = cos(φ)·q − sin(φ)·r
ψ̇ = (sin(φ)·q + cos(φ)·r) / cos(θ)
This transformation has a kinematic singularity at θ = ±90°. The pitch constraint (