UAV Systems Engineering · Module 5
M5-T5: Attitude control
Two ways to close the attitude loop: stack an angle loop on a rate loop, or define the error on the rotation group itself and control it directly.
Everything a multirotor does rests on its attitude controller. The cascade is the industrial standard; the geometric controller is the clean mathematical form that works for any attitude error. Both reduce to the same idea: a proportional term on the attitude error and damping on the rate.
Learning objectives
After this lesson, you should be able to:
- Describe the cascaded angle-rate attitude controller and its tuning order.
- Define the attitude error on SO(3) and write the geometric control law.
- Explain the roles of inertia, torque limits and motor lag in attainable bandwidth.
- Simulate large-angle recovery with the geometric controller.
Conceptual explanation
The cascaded controller (as in PX4 and ArduPilot) has an outer angle loop ( controller turning attitude error into a desired body rate) and an inner rate loop (PID turning rate error into torque), lesson M5-T6. The geometric controller (Lee, Leok, McClamroch 2010) defines the error directly on the rotation group and computes the torque in one step, with almost-global stability on SO(3).

Engineering intuition
The attitude plant is a double integrator (torque to angular acceleration to rate to angle) with limits: the motors can deliver bounded torque, so there is a maximum angular acceleration . A proportional-derivative law sets a natural frequency and damping; asking for beyond what the torque limit and motor lag allow saturates the motors and makes the loop slow and oscillatory (lesson M1-T6). For large errors a saturated, rate-limited angle loop (a square-root controller) turns the vehicle as fast as it can without overshoot.
Mathematical formulation
Geometric attitude control for desired attitude and angular velocity , current and (body frame):
For small errors ≈ sin of the rotation angle times the axis, so the law is a PD controller with natural frequency and damping . The error function has an unstable equilibrium at a error (the “antipodal” attitude), so convergence is almost global: a small perturbation leaves it. Cascade equivalent: angle loop (saturated at ) and rate loop .
- ,
- attitude and rate errors.
- ,
- positive gains.
- inertia matrix.
- ∨
- vee map (skew matrix to vector).
Architecture and data flow
Position controller → desired thrust direction (and yaw) → and thrust magnitude → attitude controller → torque → mixer (lesson M1-T6). PX4 implements the cascaded form (multicopter attitude and rate controllers, with parameters in the PID tuning guide); the geometric form appears in research controllers and in trajectory-tracking papers that fly aggressive manoeuvres.
Real-world UAV applications
- Large-angle recovery after a disturbance or flip.
- Aggressive trajectory tracking where small-angle linearisation fails.
- Analysing attainable bandwidth from the torque limit.
- Replacing Euler-angle controllers that have singularities.
Implementation example
This runs as shown: the geometric controller recovers from a attitude error to level in about a second with bounded torque, and the closed-loop energy decreases monotonically.
import numpy as np
hat = lambda w: np.array([[0, -w[2], w[1]], [w[2], 0, -w[0]], [-w[1], w[0], 0]])
vee = lambda S: np.array([S[2, 1], S[0, 2], S[1, 0]])
def Exp(w):
th = np.linalg.norm(w)
if th < 1e-12: return np.eye(3) + hat(w)
K = hat(w / th); return np.eye(3) + np.sin(th) * K + (1 - np.cos(th)) * K @ K
J = np.diag([0.029, 0.029, 0.055]); kR, kW = 30.0 * 0.029, 8.5 * 0.029 # gains scaled by inertia: natural frequency about sqrt(30) rad/s, damping near 0.78
tau_max = 0.5; dt = 0.002
R = Exp(np.array([1.2, -0.8, 0.9]) / np.linalg.norm([1.2, -0.8, 0.9]) * np.radians(120)); W = np.zeros(3); Rd = np.eye(3)
hist = []; torque_peak = 0.0
for k in range(int(4.0 / dt)):
eR = 0.5 * vee(Rd.T @ R - R.T @ Rd); eW = W # desired rate is zero
M = -kR * eR - kW * eW + np.cross(W, J @ W)
M = np.clip(M, -tau_max, tau_max); torque_peak = max(torque_peak, np.abs(M).max())
W = W + np.linalg.solve(J, M - np.cross(W, J @ W)) * dt; R = R @ Exp(W * dt)
ang = np.degrees(np.arccos(np.clip((np.trace(Rd.T @ R) - 1) / 2, -1, 1))); hist.append(ang)
hist = np.array(hist); t_settle = np.argmax(hist[::-1] > 2.0)
print(f"initial error {hist[0]:.0f} deg, below 2 deg after {(len(hist) - t_settle) * dt:.2f} s, final {hist[-1]:.4f} deg, peak torque {torque_peak:.3f} N m")
assert hist[0] > 100 and hist[-1] < 0.1 and (len(hist) - t_settle) * dt < 3.0
assert np.allclose(R @ R.T, np.eye(3), atol=1e-9) # the rotation stays on SO(3) throughoutCommon engineering mistakes
Asking for bandwidth the motors cannot provide
The loop saturates and rings; check torque limit and lag.
Euler-angle errors near vertical
Singularity: use SO(3) errors.
Missing the gyroscopic feed-forward on large rates
Matters for fast rotations; small for modest ones.
No rate limit on the angle loop
Large errors produce huge rate commands: saturate the angle loop output.
Tuning the angle loop before the rate loop
Tune the inner (rate) loop first.
Failure modes
| Failure mode | How it shows up | Likely cause |
|---|---|---|
| Saturated oscillation | Limit cycle after a large command | Torque limit hit with an aggressive gain |
| Yaw sluggish | Heading follows slowly | Low yaw authority (drag-torque coefficient); yaw gains set lower |
| Flip near error | Unstable equilibrium and chatter | Antipodal attitude: unavoidable, ensure it is not commanded |
| Rate-loop noise amplification | Motors hot, buzzing | term on noisy gyro (lesson M5-T6) |
Debugging approach
- Step the attitude setpoint and log angle, rate and torque; check the torque saturation flags.
- Estimate and compare with the requested acceleration.
- Test large errors in simulation.
- Compare cascade and geometric on the same manoeuvre.
Practical exercises
- Derive the small-angle PD form and the natural frequency in terms of and .
- Find the largest initial error from which the example controller still converges and explain the case.
- Add a first-order motor lag to the example and re-tune.
- Compute for and .
Interview questions
Write an answer in your own words, then reveal the model answer. Nothing is sent anywhere.
Question 1
What is the geometric attitude controller?
Model answer
A control law whose error is defined on SO(3) (R from the skew part of _d) with PD feedback and gyroscopic compensation, giving almost-global stability without Euler-angle singularities.
Question 2
What limits attitude bandwidth?
Model answer
Available torque over inertia (maximum angular acceleration), motor and ESC lag, sensor noise and delay.
Question 3
Why tune the rate loop first?
Model answer
The angle loop treats the rate loop as its actuator, so the inner loop must be fast and well damped before the outer is closed.
References and further reading
Primary papers, official documentation and standards. Each claim above that is specific to a tool or version points here.