PID Control for Robotics — Tuning, Anti-Windup, Gain Scheduling
See also (Tier 3 family index): Control Algorithm Zoo
Scope. This note is the robotics-applied counterpart to
[[Engineering/classical-control]]. The Laplace algebra, Bode plots, stability margins, root-locus, IMC, Smith predictor, and tuning-rule derivations live there. Here we look at PID as it actually runs in robot joints, drone attitude loops, mobile-base velocity loops, and the rest of the wet-bench reality of moving mechanical objects with embedded computers in the loop.
1. At a glance
PID is the workhorse control law in robotics. Empirically, on every commercial robot fielded between 2010 and 2026:
- ~80% of robot joint control loops use a PID structure (the rest are LQR, computed-torque variants, impedance, or admittance — most of which still wrap a PID inside for low-level regulation).
- ~100% of drone attitude/altitude loops on PX4, ArduPilot, Betaflight, INDI-based, and DJI stacks use cascaded PID. Even modern INDI controllers terminate in PID at the angular-rate level.
- ~100% of mobile-base velocity loops (differential drive, Ackermann, omnidirectional) use a PID per wheel or per axis.
- ~100% of motor current loops inside servo drives are PI on
i_dandi_q(field-oriented control).
Why PID and not the modern alternatives? Three reasons that stack:
- Simple, reliable, and well-understood by operators. A field engineer in a paper mill can retune a PID at 3 AM. They cannot retune an MPC.
- No plant model required beyond a step test or a relay-feedback experiment. Computed-torque, LQR, and MPC need an inertia matrix; PID needs
K_p, K_i, K_d. - Wraps cleanly inside model-based feedforward. The modern robotics pattern is
τ = M(q)·q̈_des + C(q,q̇)·q̇ + g(q) + PID(e)— the feedforward removes the gross nonlinearity, leaving PID to handle residual error and disturbances. Spong calls this the “PD-plus-gravity” controller; computed-torque is the limit case.
Where it sits in the design stack. Sensors → state estimation → outer-loop planner → cascaded PID → motor drivers → mechanical plant. PID typically appears at two or three layers in a robot: motor current (1–10 kHz), joint velocity (100 Hz–1 kHz), and joint position (50–500 Hz). Adding cartesian or operational-space control adds another outer PID layer at 50–250 Hz.
Modern robotics adds to textbook PID:
- Gravity-comp feedforward for arms (
g(q)removed before the PID sees it). - Coriolis/centrifugal compensation for fast multi-joint moves.
- Inertia decoupling (full mass-matrix feedforward → computed-torque).
- Anti-windup at every saturating actuator (motor current, valve, thruster).
- Derivative-on-PV instead of derivative-on-error (no setpoint kick).
- Gain scheduling over configuration
q(joint inertia varies 2–10× across an arm’s workspace). - Quaternion-based attitude PID for drones (avoids gimbal lock).
- Notch / structural filtering to keep K_d from exciting flexible modes.
First ask before applying PID: Can my actuator saturate during normal operation? If yes → you need anti-windup. Does my plant inertia change with configuration? If yes → you need gain scheduling or computed-torque feedforward. Is my sensor noisy? If yes → filter D, or use D-on-PV with a heavier filter, or drop D entirely. Are there cross-axis couplings (multi-DOF dynamics)? If yes → consider per-joint PID + Coriolis feedforward before stepping up to MIMO methods.
2. First principles
Theory is in [[Engineering/classical-control]] §2; here we cover only the robotics-specific signal-flow shapes.
Continuous-time PID law
The control torque (or thrust, or velocity command) from the ideal PID law:
u(t) = K_p · e(t) + K_i · ∫₀ᵗ e(τ) dτ + K_d · de(t)/dt
with e(t) = r(t) − y(t), the tracking error. Units depend on what the loop is regulating:
| Loop | y | u | K_p units |
|---|---|---|---|
| Joint position | rad | N·m | N·m / rad |
| Joint velocity | rad/s | N·m | N·m·s / rad |
| Motor current (FOC) | A | V | V / A = Ω |
| Drone altitude | m | N (thrust) | N/m |
| Mobile-base velocity | m/s | PWM duty (0–1) | duty·s / m |
| Cartesian position | m | N (force) | N/m |
Discrete-time forms (the ones that actually run)
A robot control loop runs at a fixed sample rate f_s = 1/T_s. Two algebraically equivalent discrete realisations dominate.
Positional form (output is full u[k] every cycle):
u[k] = K_p · e[k]
+ K_i · T_s · Σᵢ₌₀ᵏ e[i]
+ K_d · (e[k] − e[k−1]) / T_s
Velocity (incremental) form (output is Δu[k]; integrator implicit):
Δu[k] = K_p · (e[k] − e[k−1])
+ K_i · T_s · e[k]
+ K_d · (e[k] − 2·e[k−1] + e[k−2]) / T_s
u[k] = u[k−1] + Δu[k]
The velocity form is preferred for cascaded robot loops for four reasons:
- Bumpless transfer is automatic — at handover, just set u[k−1] to the current actuator value and resume.
- No integrator state to initialise — the integrator’s role is played by
u[k] = u[k−1] + Δu[k]accumulation, which is whatever the upstream block is doing. - Anti-windup is trivial — when the actuator saturates, freeze u[k] and the integration stops automatically (no separate windup logic needed for simple cases; for back-calculation, see §3).
- Auto/manual handoff requires no special code — same accumulator path.
The downside: integrator drift from accumulated floating-point error after weeks of uptime. Production code uses 32-bit float at minimum, often 64-bit double, and resets u[k−1] to a known value at startup.
Derivative-on-PV (always, in modern robotics)
Pure derivative of the error de/dt = dr/dt − dy/dt has a discontinuity when an operator (or trajectory generator) steps the setpoint. That impulse-like spike — “derivative kick” — slams the motor with maximum torque for a single sample. Fix: take the derivative of −y only:
u(t) = K_p·(r − y) + K_i·∫(r − y) dτ + K_d·(− dy/dt)
This is the default in ros2_control’s control_toolbox::Pid, in PX4 (mc_attitude_control), in ArduPilot (AC_PID::update_all), and in every robot company’s joint firmware. The only exception is when you genuinely want the controller to anticipate setpoint changes — rare in robotics because trajectory generators output smooth r(t) with continuous derivatives already ([[Robotics/trajectory-generation]]).
Filtered derivative
Pure derivative has +20 dB/decade slope — at 1 kHz it amplifies noise 20 dB more than at 100 Hz. Production controllers implement the filtered derivative:
D_filt(s) = K_d · s / (τ_f·s + 1), τ_f = T_s · N, N ∈ [5, 20]
N is the derivative filter ratio. N = 10 is the universal default. Larger N → less filtering → noisier D but more lead phase. Smaller N → more filtering → cleaner D but reduced damping benefit. On a robot joint with optical encoder noise at the LSB level (~10⁻⁵ rad), N = 10 keeps motor whine inaudible while preserving most of the lead phase at the crossover.
Anti-windup (back-calculation, Åström 1989)
When the actuator saturates, the integrator keeps accumulating as if u could grow without bound. When the error finally reverses, the controller is “wound up” and overshoots wildly. The dominant production fix is back-calculation:
u_unsat = K_p·e + I + K_d·ė (PID output before clipping)
u_actual = clamp(u_unsat, u_min, u_max)
I[k+1] = I[k] + T_s·(K_i·e[k] − K_b·(u_unsat − u_actual))
The back-calculation gain K_b controls how fast the integrator unwinds. A standard rule: K_b = √(K_i / K_p) for parallel-form PID without D, or K_b = 1 / √(T_i · T_d) for full PID with both I and D (Åström-Hägglund 2006).
Alternative: clamp-and-conditional-integrate — freeze I whenever u_unsat ≠ u_actual AND sign(e) = sign(u_unsat − u_actual). Slightly less smooth than back-calc but trivial to code; standard in Siemens TIA PID_Compact.
Without anti-windup, a saturated drone attitude loop produces 90°+ overshoot on aggressive maneuvers. This is non-negotiable in production robotics.
3. Practical math & worked examples
Example A — Joint position PID with gravity feedforward (6-DOF arm)
Plant. A 6-DOF arm holding a 5 kg payload at 800 mm reach. Single joint dynamics, after reducing the multi-joint Lagrangian to one DOF and absorbing the rest as disturbances ([[Robotics/dynamics-rigid-body]]):
J_eff · q̈ + b · q̇ + g(q) = τ
with J_eff ≈ 5 kg·m² (estimated from CAD), b ≈ 2 N·m·s/rad (viscous friction from manufacturer), g(q) the gravitational torque (computed online from the arm Jacobian and known link masses).
Control law:
τ = g(q) ← feedforward, computed every cycle
+ K_p · (q_des − q) ← P
+ K_d · (q̇_des − q̇) ← D (on velocity error directly)
+ K_i · ∫(q_des − q) dτ ← I
+ τ_friction(q̇) ← optional Coulomb friction feedforward
After gravity comp the joint “feels weightless” to the PID — the residual plant is J_eff · q̈ + b · q̇ = τ_PID, a clean second-order linear plant. Apply the canonical second-order design ([[Engineering/classical-control]] §2):
Specifications: critically damped (ζ = 1), settle in ~400 ms, no overshoot.
ω_n = 10 rad/s (settling time t_s ≈ 4/(ζ·ω_n) = 400 ms)
K_p = J_eff · ω_n² = 5 · 100 = 500 N·m/rad
K_d = 2 · ζ · J_eff · ω_n = 2 · 1 · 5 · 10 = 100 N·m·s/rad
K_i = K_p · ω_n / 3 = 500 · 10 / 3 ≈ 1667 N·m / (rad·s)
The K_i = K_p·ω_n/3 rule comes from pole placement: put the integrator zero a factor of 3 below ω_n so the integrator’s −90° phase lag doesn’t eat the phase margin at crossover (Åström-Hägglund 2006 §6.4).
Anti-windup tuning:
K_b = √(K_i / K_p) = √(1667 / 500) = 1.83 s⁻¹
This unwinds the integrator over ~0.55 s after the actuator returns from saturation.
Sanity check — actuator size. Maximum torque demand on a fast 90° move (q_des steps from 0 to π/2 in 0.4 s; q̈_des ≈ 20 rad/s²):
τ_inertial = J_eff · q̈_des = 5 · 20 = 100 N·m
τ_gravity = m · g · L · cos(q) = 5 · 9.81 · 0.8 ≈ 39 N·m (worst case horizontal)
τ_total ≈ 140 N·m peak
Joint motor (with harmonic drive 100:1) needs ~1.4 N·m motor-side peak. A Maxon EC-i 40 at 200 W delivers this comfortably.
Example B — Quadrotor altitude PID (PX4-style)
Plant. A 1.2 kg quadrotor in hover. Translational dynamics along the body z-axis:
m · z̈ = T − m · g
with T total thrust (N), m = 1.2 kg, g = 9.81 m/s². Hover thrust T_hover = m·g = 11.77 N. Linearise about hover: δT = m · z̈ + (perturbation) → the perturbation thrust is the control input, and the plant from δT → z is a double integrator 1/(m·s²).
PID gains (typical PX4 values for a 1.2 kg X-frame quad, parallel form):
K_p = 8 N/m (response stiffness)
K_d = 4 N·s/m (velocity damping)
K_i = 1 N/(m·s) (steady-state wind / weight estimate error)
Sample rate: f_s = 250 Hz for the position loop in PX4 (the inner attitude-rate loop runs at 1 kHz on STM32H7-based flight controllers, 8 kHz on newer F7 boards in P-only mode).
Thrust mixing. PID output is total thrust T (plus roll, pitch, yaw moments from the attitude loop). A 4×4 thrust-mixer matrix maps [T, τ_x, τ_y, τ_z] to four motor commands:
[w₁²] [ 1 +1 +1 +1 ] [T ]
[w₂²] = [ 1 −1 +1 −1 ] [τ_x]
[w₃²] [ 1 −1 −1 +1 ] [τ_y]
[w₄²] [ 1 +1 −1 −1 ] [τ_z]
(signs vary by airframe; this is X-quad rotors-spin-inward convention). After mixing, each w_i is clipped to [w_min, w_max] and sent to the ESC.
Anti-windup. PX4 uses prioritized saturation handling: if any motor would saturate, first reduce yaw command, then roll/pitch, and finally thrust (yaw is least critical; thrust is the safety-critical one). The integrator on altitude is frozen whenever any motor command is saturated. This is the right ordering for a multirotor — you’d rather drift off heading than fall out of the sky.
Wind/payload adaptation. If a 200 g extra payload is attached, hover thrust shifts up by 0.2 · 9.81 / m · g = 17%. The altitude integrator absorbs this — within ~3 s of takeoff, I accumulates the extra hover bias and the PID acts on perturbations only.
Example C — Differential-drive mobile robot velocity loop
Plant. A 30 kg differential-drive AGV with two driven wheels of radius R = 0.10 m, separated by L = 0.50 m. Each wheel has its own brushed DC motor with PWM driver.
Inverse kinematics (commanded body linear velocity v and angular velocity ω → wheel velocities):
v_L = v − (ω · L) / 2
v_R = v + (ω · L) / 2
(in m/s at the wheel contact patch; convert to motor rad/s by dividing by R).
Per-wheel PID (PI is usually enough; D adds noise from encoder quantization):
K_p = 0.5 (PWM duty / (m/s)) ← 100% duty per 2 m/s error
K_i = 1.0 (PWM duty / (m·s/s) · s = 1/(m/s))
K_d = 0
Sample rate: 200 Hz inner velocity loop, 50 Hz outer position loop.
Cascaded outer position loop (commands v_des, ω_des from x_des, y_des, θ_des):
v_des = K_p_pos · ρ + K_d_pos · ρ̇ (forward error ρ = distance to goal)
ω_des = K_p_ω · α + K_d_ω · α̇ (heading error α)
K_p_pos = 1.0 (1/s), K_d_pos = 0.5 (s/s) ← 0.5 s settle on linear
K_p_ω = 2.0 (1/s), K_d_ω = 0.2 (s/s) ← 0.25 s settle on heading
Anti-windup on outer loop. Saturate v_des at v_max = 1.5 m/s and ω_des at ω_max = 2 rad/s. When the outer position loop’s commanded v_des saturates, freeze I on the position term; otherwise the AGV approaches a tight corner and overshoots into the obstacle.
Inner-outer bandwidth ratio: outer 50 Hz, inner 200 Hz → 4× separation, marginally below the textbook 5× rule ([[Engineering/classical-control]] §6p Example A). Acceptable for tracked-floor warehouse use but a hard-real-time material-handling AGV would push the inner loop to 500 Hz.
4. Design heuristics
Tuning order (in this order, every time)
- P first. Set
K_i = K_d = 0. RaiseK_puntil the plant shows small sustained oscillation. Drop back by ~30%. - D next. Add
K_dto damp the residual oscillation. Stop when noise starts to dominate the actuator (audible whine on motors, jittery valve on a pneumatic). - I last. Add
K_ijust enough to remove steady-state error within the spec’s settling time. Too muchK_ibrings overshoot and slow oscillation back.
For a robot joint, this typically gives K_p, K_d, K_i in ratio roughly 1 : 0.2 : 0.3·K_p·ω_n.
Classical tuning rule families (quick reference; full theory in [[Engineering/classical-control]] §3)
| Method | Use when | K_p | T_i | T_d |
|---|---|---|---|---|
| Ziegler-Nichols ultimate | Bench testing, you can drive plant to oscillation | 0.6·K_u | 0.5·T_u | 0.125·T_u |
| Tyreus-Luyben | Robust tuning, plant uncertainty | 0.45·K_u | 2.2·T_u | T_u / 6.3 |
| Skogestad SIMC | FOPDT model known | τ/(K·(λ+θ)) | min(τ, 8θ) | — |
| IMC (lambda) | FOPDT model, want one tuning knob | τ/(K·(λ+θ)) | τ | — |
| Astrom relay-feedback auto | Embedded auto-tuner | (1.4/π)·K_u | 0.5·T_u | T_u / 8 |
For robotics, after gravity-comp / model feedforward, the plant is effectively J_eff·s² + b·s — a second-order plant where ZN-on-ultimate-gain over-aggressively places the dominant poles. Prefer direct pole placement as in Example A.
Cascaded loops — typical bandwidth ladder
| Loop layer | Sample rate | Closed-loop bandwidth |
|---|---|---|
| Motor current (FOC inner) | 5–20 kHz | 500–2000 Hz |
| Motor velocity | 1–5 kHz | 100–500 Hz |
| Joint position | 250 Hz–1 kHz | 25–100 Hz |
| Cartesian position | 50–250 Hz | 5–25 Hz |
| Operational-space / impedance | 50–250 Hz | 5–25 Hz |
| Outer planner / waypoint | 10–50 Hz | 1–5 Hz |
Rule of thumb: inner-loop bandwidth ≥ 5× outer-loop bandwidth. Violating this rule is the most common cause of cascaded-loop instability — the inner loop appears as a non-rigid actuator to the outer loop, adding phase lag at the outer crossover.
Gain scheduling — when the plant changes
A robot arm’s effective joint inertia varies 2–10× across its workspace as the arm extends. Fixed-gain PID either over-controls when folded (oscillation) or under-controls when extended (sluggish). Two production approaches:
- Lookup-table gain schedule. Discretise
qinto 4–16 regions; storeK_p, K_i, K_dper region; interpolate. Used in ABB, KUKA, Fanuc industrial arm firmware (proprietary; reverse-engineered from KSS, RobotWare service logs). - Computed-torque (model-based). Compute
M(q)online and applyτ = M(q)·u_PID + C(q,q̇)·q̇ + g(q). The PID sees a unit-mass plant regardless of configuration. Standard in research stacks (Drake, MuJoCo MPC, OCS2) and in cobots (UR e-Series, Franka FR3).
Drones gain-schedule on airspeed (aerodynamics shift between hover and forward flight) and battery voltage (motor K_t changes with cell voltage). PX4’s MC_PITCHRATE_* parameters can be scheduled via the TUNE_* mechanism; ArduPilot’s “Autotune” command computes per-axis schedules from in-flight excitation.
Cross-coupling and decoupling
Multi-DOF robot dynamics couple joints through the mass matrix M(q):
M(q) · q̈ + C(q,q̇) · q̇ + g(q) = τ
The simplest decoupling is per-joint PID + diagonal gravity comp — works for slow-moving 6-DOF arms (most industrial palletisers). For fast multi-axis moves (cobot pick-and-place at 1 m/s end-effector speed), the off-diagonal terms in M(q) and the Coriolis term C(q,q̇)·q̇ become significant:
- Inertia decoupling (computed-torque):
τ = M(q)·q̈_des + C(q,q̇)·q̇ + g(q) + K_p·e + K_d·ė. Reduces multi-joint to per-joint unit-mass control. Needs an accurateM(q)— Lagrangian or recursive Newton-Euler ([[Robotics/dynamics-rigid-body]]). - Operational-space control (Khatib 1987): project the PID into Cartesian coordinates, then map back via
τ = J(q)ᵀ · F_cartesian. The end-effector behaves like a 6-DOF mass-spring-damper independent of joint dynamics.
Friction compensation
Joint friction has a stiction-Coulomb-viscous structure (Armstrong-Hélouvry 1991):
τ_friction(q̇) = τ_stiction · sign(q̇) · e^(−|q̇|/v_s)
+ τ_Coulomb · sign(q̇)
+ b · q̇
At low velocities, stiction dominates and causes the well-known stick-slip “thumping” on direct-drive arms. Feedforward τ_ff = sign(q̇)·τ_Coulomb + b·q̇ removes most of it. The sign(q̇) discontinuity at q̇ = 0 is smoothed in practice with tanh(q̇/ε) for some small ε ≈ 0.01 rad/s.
Backlash compensation in geared joints
Harmonic drives have <0.5 arcmin backlash (negligible for most uses). Cycloidal and planetary reducers carry 3–15 arcmin ([[Robotics/gears-power-transmission]] if/when written), which presents as a dead-zone in the torque path. Two fixes:
- Feedforward torque jump at zero crossing. When
q̇_desreverses sign, briefly inject a torque pulse to cross the backlash without the PID’s slow integrator buildup. - Pre-loaded gear pairs. Two motors driving opposed gears on the same output shaft; always one is in mesh.
Quaternion-based attitude PID (drones)
Roll/pitch/yaw Euler-angle PID hits gimbal lock at pitch = ±90° (singularity in the Euler kinematics). Production drone stacks use quaternion error:
q_err = q_des ⊗ q_meas⁻¹ ← quaternion multiplication
v_err = 2 · sign(q_err.w) · q_err.xyz ← axis-angle vector (small-angle)
ω_cmd = K_p · v_err + K_d · (ω_des − ω_meas) + K_i · ∫v_err
PX4’s AttitudeControl::update() and ArduPilot’s AC_AttitudeControl::input_quaternion implement this exactly. The sign(q_err.w) factor picks the short rotation (q and −q represent the same orientation; pick the one with positive scalar part to avoid spinning the long way around).
Time-delay handling
A pure delay e^(−s·θ) reduces phase margin by ω_c · θ · (180°/π) degrees at crossover. Robotic sources of delay:
- Sensor latency: IMU at 1 kHz → 0.5 ms; vision SLAM at 30 Hz → 33 ms.
- CAN bus traversal: 1 ms typical at 1 Mbit/s.
- EtherCAT cycle: 0.1–1 ms (Beckhoff TwinCAT).
- ROS 2 DDS communication: 2–10 ms typical.
Rule: if θ > T_loop / 4 (delay exceeds a quarter of the control period), switch from straight PID to a Smith predictor ([[Engineering/classical-control]] §4) or move the PID closer to the actuator. For ROS 2 robots, the standard pattern is to keep tight loops in ros2_control (real-time C++), and put non-real-time logic in higher-level nodes.
5. Components & sourcing
PID is software, but it runs on hardware, and the hardware/library combination shapes what you can do.
MCU / SoC platforms for PID loops
| Platform | Loop rate (typical) | Use case |
|---|---|---|
| ATmega328 (Arduino Uno) | 100 Hz–1 kHz | Hobby robots, education |
| RP2040 | 1–10 kHz | Hobby drones, mobile bots; dual core, 133 MHz |
| STM32F4 (84 MHz, FPU) | 1–10 kHz | Servo drives, light arms |
| STM32G4 (170 MHz, math accel) | 10–50 kHz | Modern servo drives, motor control |
| STM32H7 (480 MHz, dual core) | 10–100 kHz | High-end flight controllers (PX4 Pixhawk 6X) |
| TI C2000 F2837x / F28004x | 50–200 kHz | High-end industrial motor drives |
| Teensy 4.x (Cortex-M7 600 MHz) | 50 kHz | Education, prototype servos |
| Jetson Orin (ARM Cortex-A78 + GPU) | 250 Hz–1 kHz | High-level control, perception fusion |
| PLC + RTOS (Beckhoff CX, Siemens S7-1500) | 1–10 kHz | Industrial AGV, palletiser |
Production PID libraries (named, with file paths)
- Arduino PID Library by Brett Beauregard —
https://github.com/br3ttb/Arduino-PID-Library. The standard educational reference. Implements anti-windup via clamping, derivative-on-PV. Used in countless classroom projects. - ros2_control
control_toolbox::Pid—https://github.com/ros-controls/control_toolbox, filesrc/pid.cpp. The standard for ROS 2 robots. Supports anti-windup back-calculation, derivative-on-PV, configurable I-clamp, integral-out. - PX4 multirotor attitude/position controllers —
https://github.com/PX4/PX4-Autopilot, filessrc/modules/mc_att_control/AttitudeControl.cpp,src/modules/mc_rate_control/RateControl.cpp,src/modules/mc_pos_control/PositionControl.cpp. Cascaded PID with quaternion attitude error and prioritized motor-mix saturation handling. - ArduPilot
AC_PIDlibrary —https://github.com/ArduPilot/ardupilot, filelibraries/AC_PID/AC_PID.cpp. Production multirotor PID with notch filtering, slew limiting, derivative low-pass at configurable cutoff. - Crazyflie firmware (Bitcraze) —
https://github.com/bitcraze/crazyflie-firmware, filesrc/modules/src/pid.c. Minimal embedded PID (~150 lines C); good reference for what’s actually needed in a 168 MHz STM32F4 nano-drone. - odrive firmware —
https://github.com/odriverobotics/ODrive, current and velocity PID for brushless motor servos. - VESC firmware (Vedder Electronic Speed Controller) —
https://github.com/vedderb/bldc. Production-grade motor PID with field-oriented control. - Industrial PLC blocks — Siemens TIA Portal
PID_CompactandPID_3Step; Rockwell Studio 5000PIDE; Schneider EcoStruxurePID2andPIDFF(with feedforward); ABB AC800MPidCC; Beckhoff TwinCATFB_BasicPID. All implement IEC 61131-3 PID semantics with anti-windup, gain scheduling, and feedforward.
Auto-tuners
- MATLAB Control System Toolbox —
pidtune(), interactivepidTunerGUI. Plant must be modelled astforidtf. - Simulink PID Tuner — auto-tune block from plant model or measured data.
- Honeywell PlantTriage (now Aspen) — DCS historian-driven; recommends tuning for process control loops.
- Yokogawa Exaopc Tune — CENTUM VP companion.
- ABB AutoTune — built into AC800M; relay-feedback method.
- PX4 in-flight autotune —
MC_AT_*parameters; injects sinusoidal excitations on each axis. - ArduPilot Autotune mode — automatic gain discovery via repeated step inputs in flight.
Logging and offline analysis
- ROS 2
rosbag2— records all/joint_states,/cmd_vel,/imu/datatopics for offline replay. - PlotJuggler — interactive time-series visualizer for ROS bags, CSV, MAT files. Standard for diagnosing PID behavior.
- MATLAB / Simulink —
sim(),compare(), parameter sweeps. - Foxglove Studio — modern web-based bag viewer; integrates with ROS 2.
6. Reference data
Two main PID structures
| Form | Equation | Used by |
|---|---|---|
| Parallel (academic) | K_p·e + K_i·∫e + K_d·de/dt | MATLAB pid(), Arduino PID, ros2_control, most textbooks |
| ISA standard | K_c·[e + (1/T_i)·∫e + T_d·de/dt] | Siemens TIA, most DCS, industrial PLC blocks |
| Series / interacting | K_c·(1 + 1/(T_i s))·(1 + T_d s/(1 + T_d s/N)) | Foxboro, older Bailey systems |
Conversion parallel ↔ ISA:
K_p = K_c
K_i = K_c / T_i
K_d = K_c · T_d
Typical cascaded-loop bandwidths in robotics
| Layer | Sample rate | BW (Hz) | Example platform |
|---|---|---|---|
Current (FOC i_d, i_q) | 5–20 kHz | 500–2000 | ODrive, VESC, Maxon EPOS4 |
| Motor velocity | 1–5 kHz | 100–500 | UR servo drive, Yaskawa Sigma-7 |
| Joint position | 250 Hz–1 kHz | 25–100 | ABB IRC5, KUKA KR C5 |
| Cartesian position | 50–250 Hz | 5–25 | ros2_control, OROCOS |
| Operational-space impedance | 100–1 kHz | 10–100 | Franka FR3, Kinova Gen3 |
Drone bandwidth typicals
| Loop | Rate (Hz) | BW (Hz) | Notes |
|---|---|---|---|
| Body rate (P only or PI) | 1 k – 8 k | 100–400 | Inner of cascade |
| Attitude angle (PID) | 250 – 1 k | 30–80 | Quaternion-based |
| Position (PID) | 50 – 250 | 1–5 | Outer |
| Mission / waypoint | 10 | <1 | Above PID stack |
Robot arm joint bandwidth ranges
| Class | BW (Hz) | Example |
|---|---|---|
| Heavy industrial arm | 5–20 | ABB IRB 6700 (200 kg payload) |
| Mid-payload industrial | 10–30 | ABB IRB 1600 (10 kg) |
| Cobot | 10–50 | UR5e, Franka FR3 |
| Direct-drive / parallel | 50–200 | ABB FlexPicker, Quanser |
| Surgical robot | 100–300 | Intuitive da Vinci, Auris Monarch |
Anti-windup methods — comparison
| Method | Code complexity | Smoothness | Tuning needed |
|---|---|---|---|
| No anti-windup | none | catastrophic overshoot | — |
| Clamp output only | 1 line | abrupt | none |
| Conditional integration | ~5 lines | medium | none |
| Back-calculation | ~10 lines | smooth | K_b |
| Tracking (slave integrator) | ~15 lines | smooth | requires actuator position FB |
| Velocity-form PID | inherent | smooth | none |
Auto-tune methods — comparison
| Method | Required infra | Typical outcome |
|---|---|---|
| Ziegler-Nichols ultimate | Drive plant to limit cycle manually | Aggressive, ~25% OS |
| Cohen-Coon (1953) | Open-loop step | High dead-time plants |
| Relay feedback (Åström-Hägglund 1984) | Single relay element | Embedded auto-tune in PLCs |
MATLAB pidtune | Plant model | Optimal for plant; bench tool |
| PX4 in-flight autotune | Drone in flight, safety pilot | Gains for each axis in <1 minute |
| ArduPilot Autotune mode | Drone, large open area | Best for new airframes |
| Honeywell PlantTriage | DCS historian | Process control, slow plants |
Robot-family PID starting gains (parallel form, after gravity comp)
| Robot type | K_p | K_d | K_i | T_s |
|---|---|---|---|---|
| 6-DOF arm joint (mid-size) | 200–800 N·m/rad | 50–200 N·m·s/rad | 100–2000 N·m/(rad·s) | 1–4 ms |
| Cobot joint (UR-class) | 100–500 N·m/rad | 20–100 N·m·s/rad | 50–500 N·m/(rad·s) | 0.5–2 ms |
| Drone attitude (1 kg quad) | 4–10 N·m/rad | 0.1–0.3 N·m·s/rad | 0.5–2 N·m/(rad·s) | 1–4 ms |
| Drone position | 4–10 N/m | 2–6 N·s/m | 0.5–2 N/(m·s) | 4–20 ms |
| Mobile-base wheel velocity | 0.3–1.0 duty/(m/s) | 0 | 0.5–2.0 duty/m | 5 ms |
| Mobile-base position outer | 0.5–2.0 (1/s) | 0.2–0.8 (s/s) | 0 | 20 ms |
7. Failure modes & debugging
The PID debug flowchart is one of the most-used pages in robotics service manuals. Most failures fit one of these patterns:
Symptom → likely cause table
| Symptom | Likely cause | Fix |
|---|---|---|
| Overshoot after long saturation | Integral windup | Add anti-windup back-calc or velocity-form |
| Motor whine, hot driver, jittery output | D-term amplifying sensor noise | Add filtered derivative; lower N from 10 to 5 |
| Sharp output spike on every setpoint change | D acting on error (kick) | Switch to derivative-on-PV |
| Output jump on Auto→Manual mode change | Initialization mismatch | Velocity-form PID or explicit handoff init |
| Slow oscillation after disturbance | I too high | Reduce K_i by 50%; recheck PM |
| Joint “thumps” through zero crossing | Stick-slip friction | Add Coulomb friction feedforward |
| High-freq oscillation at known resonance | K_d exciting flexible mode | Add notch at resonance; reduce K_d |
| Inertia mismatch — works folded, sluggish extended | Plant inertia configuration-dependent | Gain schedule on q, or computed-torque |
| Sensor noise drives output even at rest | High-freq noise above bandwidth | Add IIR low-pass on PV input |
| Discretization-induced phase lag | T_s too long relative to bandwidth | Increase loop rate; aim T_s ≤ 1/(20·BW) |
| Gains tuned at one operating point, unstable elsewhere | Linearization assumption violated | Gain schedule, adaptive, or robust design |
| ”Guessed” gains, never characterized plant | No baseline | Run ZN-relay or step-response identification |
Detailed failure modes
-
Integral windup (the #1 cause of “works on bench, oscillates in field”). Detect: output stuck at limit for >1 second, then big overshoot when error reverses. Fix: anti-windup; verify by running a step test with deliberately undersized actuator.
-
Derivative noise amplification. Detect: motor whine, current ripple at high freq (oscilloscope on current shunt), audible buzzing. Fix: reduce N to 5–8; if that’s not enough, drop D entirely (the joint will be slower but quieter — a fair trade in many production cobots, where the rule-of-thumb gain
K_d ≈ 2·ζ·√(K_p · J_eff)is too aggressive for noisy encoders). -
Setpoint kick (derivative-on-error). Detect: any operator step on the setpoint produces an instantaneous motor torque spike, visible in current logs as a 1-sample blip. Fix: derivative-on-PV (γ=0). Mandatory in modern robotics; no exceptions.
-
Bumpless transfer. Detect: switching from Auto to Manual (or one PID to another) produces a step in u(t). Fix: velocity-form PID; or in positional form, initialize the integrator on handoff so
u_new = u_oldat the switch instant. -
Limit-cycle oscillation from backlash + integrator. Detect: low-amplitude oscillation at a frequency much lower than the natural bandwidth (~0.1–1 Hz on a robot joint with 5-arcmin backlash). Fix: reduce K_i; add backlash feedforward at zero crossings; or preload the gears mechanically.
-
Resonance excitation at structural mode. Detect: high-freq oscillation at a known mode (often 80–300 Hz on industrial arms — beam modes of the link). Fix: notch filter at f_n; or reduce K_d (D excites HF more than P does); see
[[Engineering/classical-control]]§6p Example B for the notch-design pattern. -
Inertia mismatch in load-changing applications. Detect: gain tuned at unloaded condition oscillates when carrying max payload (or vice versa). Fix: gain schedule on payload mass (often available as
M_payloadparameter in robot SDK); or implement computed-torque. -
Sensor noise dominates D term. Detect: at rest, motor current shows white noise at the D-band frequency. Fix: heavier derivative filter (N=5 instead of 10); or eliminate D and use PI; or improve sensor (replace incremental encoder with absolute, add anti-aliasing filter at f_s/4).
-
Discretization too slow. Detect: phase margin worse on real hardware than in continuous-time simulation. Fix: aim for
T_s ≤ 1/(20·BW)(one twentieth of the closed-loop period). Below this, the half-period sample delay (T_s/2) becomes invisible to the loop. -
Joint friction stick-slip at low speed. Detect: motor hums, position twitches by ~1 LSB at very low commanded velocity (<0.05 rad/s). Fix: Coulomb friction feedforward (
τ_ff = sign(q̇)·τ_Coulomb), with sign smoothed bytanh(q̇/ε). -
“Found by guessing” gains. Detect: nobody in the team can answer “why is K_p exactly 423?“. Fix: run a documented tuning procedure (relay-feedback or step test), record gains with date and conditions in a loop log. This is the #1 cause of long-tail maintenance grief in industrial robot deployments.
-
Cascaded inner-outer separation violated. Detect: outer loop oscillates at the inner loop’s bandwidth. Fix: speed up inner loop, or slow down outer loop, until BW_inner ≥ 5·BW_outer.
Tuning a stuck loop — quick procedure
- Set
K_i = K_d = 0. ReduceK_pto half its current value. - Step the setpoint by a small amount. Observe response on a real-time plot.
- Raise
K_pin 30% increments until small sustained oscillation appears. RecordK_uand oscillation periodT_u. - Set
K_p = 0.45·K_u(Tyreus-Luyben — gentler than Ziegler-Nichols). - Add
K_dcorresponding toT_d = T_u/6.3. Verify damping. - Add
K_icorresponding toT_i = 2.2·T_u. Verify steady-state error closes. - Document the result.
8. Case studies
Case A — Universal Robots e-series joint control
The UR3e/UR5e/UR10e/UR16e cobots (released 2018, current production as of 2026-05) use a three-layer cascaded PID with model-based feedforward in each joint:
- Current loop: ~4 kHz on the joint MCU (STM32F4-class), PI on
i_dandi_qfrom field-oriented control. - Velocity loop: ~1 kHz, PI with feedforward of the commanded acceleration multiplied by joint inertia estimate.
- Position loop: ~250 Hz, PID with derivative-on-PV.
- Cartesian admittance (outer): ~250 Hz, runs on the controller’s x86 CPU (Linux Ubuntu, PREEMPT_RT kernel).
Each joint runs gravity and Coriolis feedforward in the joint torque path. The feedforward is computed at the host (x86) side from the full 6-DOF Lagrangian using the manufacturer’s measured inertial parameters; sent over the UR’s proprietary EtherCAT-derived bus at 500 Hz.
The “force-mode” command (operator-controlled compliant interaction) layers a Cartesian admittance controller on top. The gains are exposed via the RTDE (Real-Time Data Exchange) interface; service manuals document only the safety-relevant ones.
Why this matters as a reference case: UR pioneered the consumer-cobot model of “out-of-box working PID” — operators don’t tune anything; the gains are factory-set and adaptive on payload mass. The price is a closed-source controller; if you want similar behavior in open-source, ros2_control’s joint_trajectory_controller plus effort_controllers plus a Python-side gravity computer gets you to ~80% of the same performance.
Case B — PX4 Autopilot quadrotor control
PX4 (https://docs.px4.io/) is the open-source flight control firmware that runs on Pixhawk hardware. Its multirotor control stack is the textbook example of cascaded PID in drones:
- Inner attitude-rate loop: P (or PID with very small I, D=0) at 8 kHz on STM32H7 (Pixhawk 6X). Outputs body torques to the motor mixer. Files:
src/modules/mc_rate_control/RateControl.cpp. - Attitude (quaternion) loop: PID at 250 Hz. Outputs angular rate setpoint. File:
src/modules/mc_att_control/AttitudeControl.cpp. Uses quaternion errorq_err = q_des ⊗ q_meas⁻¹mapped to axis-angle for the P term. - Velocity / position loop: PID at 100 Hz. Outputs attitude setpoint and total thrust. File:
src/modules/mc_pos_control/PositionControl.cpp.
The motor mixer (src/lib/mixer_module/) clips per-motor outputs to [0, 1] and applies prioritized saturation: yaw is sacrificed before roll/pitch, which is sacrificed before thrust. The position-loop integrators are frozen when any motor command saturates.
In-flight tuning: the MC_ROLLRATE_P/I/D, MC_PITCHRATE_*, MC_YAWRATE_*, MC_ROLL_P, etc. parameters are exposed through MAVLink. The PX4 autotune (autotune.cpp) injects sinusoidal excitations on each axis at progressively higher frequencies, identifies the body-rate transfer function, and selects gains for a target bandwidth.
Why this matters: PX4’s design is the reference everyone steals from. The 8 kHz P-only inner rate loop is faster than any joint loop in any production arm — it’s required because aerodynamics couple body rates strongly and the motors have ~1 ms response. The same architectural pattern (cascaded angular rate / attitude / position, gain scheduled on airspeed) reappears in fixed-wing autopilots, helicopter control, and surface-vessel autopilots.
Case C — Boston Dynamics Spot leg PID
Spot (released 2019, current as of 2026) uses a hybrid PID + virtual-model controller for each leg. Boston Dynamics has published partial details via their SDK and conference talks (Bledt et al. 2018 “MIT Cheetah 3: Design and Control of a Robust, Dynamic Quadruped Robot,” IROS — the academic predecessor to Spot’s stack):
- Joint torque PID: ~1 kHz on the leg MCU. Each of the 12 joints (3 per leg × 4 legs) runs a PI on joint torque error, with the torque setpoint coming from the leg-level controller.
- Foot Cartesian PID: ~500 Hz on the body computer. The leg’s commanded foot position translates into joint torques via the Jacobian transpose:
τ = Jᵀ · F_cart, whereF_cartis a virtual stiffness/damping force from the foot PID. - Body-level MPC: ~250 Hz, plans 6-DOF body pose and contact-force distribution. Outputs the per-leg foot-position setpoints that feed the foot PID.
The leg controller is impedance control ([[Robotics/impedance-control]] planned), with the impedance parameters (foot stiffness, damping) themselves tuned for different gaits — walk, trot, gallop — and gain-scheduled on terrain estimate.
Why this matters: Spot is the high-water mark for legged-robot control deployed at scale. The hybrid PID + virtual-model + MPC stack is too computationally heavy for a single MCU (runs on a ~50 W x86 onboard) but the PID layer at the joint level is unchanged from a standard servo drive. The intelligence is in the outer layers; the inner PID is “boring, reliable, fast.”
9. Cross-references
Robotics (this library):
[[Robotics/kinematics-dh]]— forward/inverse kinematics; providesq_desto the joint PID.[[Robotics/dynamics-rigid-body]]— model-based feedforward (gravity, Coriolis, mass-matrix decoupling).- planned
[[Robotics/motors-electric]]— driver-level PI current loop (FOC). - planned
[[Robotics/state-space-lqr]]— when PID alone is insufficient (constrained, unstable plants). - planned
[[Robotics/impedance-control]]— Cartesian compliance; wraps a PID inside. - planned
[[Robotics/trajectory-generation]]— smoothr(t)for the PID to track. - planned
[[Robotics/gears-power-transmission]]— backlash, harmonic drives, planetary reducers.
Engineering (foundations):
[[Engineering/classical-control]]— theory: Laplace, Bode, Nyquist, root locus, IMC, Smith predictor.- planned
[[Engineering/digital-control]]— z-domain, Tustin discretization, anti-aliasing. - planned
[[Engineering/mpc-control]]— model-predictive alternative for constrained robots. [[Engineering/microcontrollers]]— where embedded PID actually runs.[[Engineering/electric-motors]]— current loop dynamics, back-EMF as disturbance.- planned
[[Engineering/realtime-embedded]]— RTOS, loop scheduling, jitter. [[Engineering/op-amps]]— analog PID implementation.
Languages:
- planned
[[Languages/Tier3/robotics-control]]— ros2_control + control_toolbox + ros2_controllers. - planned
[[Languages/Tier3/scientific]]— Control System Toolbox, PID Tuner.
10. Citations
Foundational books
- Åström, K. J. & Hägglund, T. (2006). Advanced PID Control. ISA. The definitive reference on PID; anti-windup, gain scheduling, auto-tuning, dead-time compensation.
- Åström, K. J. & Hägglund, T. (1995). PID Controllers: Theory, Design, and Tuning (2nd ed.). ISA. The canonical earlier edition, still widely cited.
- Åström, K. J. & Murray, R. M. (2021). Feedback Systems: An Introduction for Scientists and Engineers (2nd ed.). Princeton University Press. Free PDF: https://fbswiki.org/. Modern integrated text.
- Spong, M. W., Hutchinson, S. & Vidyasagar, M. (2020). Robot Modeling and Control (2nd ed.). Wiley. Computed-torque, PID for arms, joint-space and operational-space control.
- Siciliano, B., Sciavicco, L., Villani, L. & Oriolo, G. (2009). Robotics: Modelling, Planning and Control. Springer. Chapter 8 on joint-space control and computed-torque.
- Craig, J. J. (2018). Introduction to Robotics: Mechanics and Control (4th ed.). Pearson.
- Shinskey, F. G. (1996). Process Control Systems (4th ed.). McGraw-Hill. Industrial PID culture and gotcha catalogue.
- Visioli, A. (2006). Practical PID Control. Springer. Anti-windup, auto-tuning, real-world implementation issues.
Foundational papers
- Ziegler, J. G. & Nichols, N. B. (1942). “Optimum settings for automatic controllers.” Trans. ASME 64, 759–768. The original PID tuning paper.
- Cohen, G. H. & Coon, G. A. (1953). “Theoretical consideration of retarded control.” Trans. ASME 75, 827–834.
- Tyreus, B. D. & Luyben, W. L. (1992). “Tuning PI controllers for integrator/dead time processes.” Ind. Eng. Chem. Res. 31, 2625–2628.
- Skogestad, S. (2003). “Simple analytic rules for model reduction and PID controller tuning.” J. Process Control 13(4), 291–309. doi:10.1016/S0959-1524(02)00062-8. SIMC paper.
- Rivera, D. E., Morari, M. & Skogestad, S. (1986). “Internal Model Control. 4. PID Controller Design.” Ind. Eng. Chem. Process Des. Dev. 25(1), 252–265. doi:10.1021/i200032a041. IMC-PID paper.
- Åström, K. J. & Hägglund, T. (1984). “Automatic tuning of simple regulators with specifications on phase and amplitude margins.” Automatica 20(5), 645–651. Relay-feedback auto-tuner.
- Åström, K. J. (1989). “Toward intelligent PID control.” Automatica (special issue). Anti-windup back-calculation theory.
- Mahony, R., Hamel, T. & Pflimlin, J.-M. (2008). “Nonlinear Complementary Filters on the Special Orthogonal Group.” IEEE Trans. Automat. Contr. 53(5), 1203–1217. Foundation for quaternion attitude control.
- Khatib, O. (1987). “A unified approach for motion and force control of robot manipulators: The operational space formulation.” IEEE J. Robot. Autom. 3(1), 43–53.
- Armstrong-Hélouvry, B. (1991). Control of Machines with Friction. Kluwer. Stick-slip, stiction-Coulomb-viscous model.
- Maciejowski, J. M. (2002). Predictive Control with Constraints. Prentice Hall. MPC alternative to PID for constrained robots.
- Bledt, G., Powell, M. J., Katz, B., Di Carlo, J., Wensing, P. M. & Kim, S. (2018). “MIT Cheetah 3: Design and Control of a Robust, Dynamic Quadruped Robot.” IROS 2018. Academic predecessor to Spot’s control architecture.
Standards
- ISO 10218-1:2011. Robots and robotic devices — Safety requirements — Part 1: Industrial robots. Includes control-loop response time requirements for safety functions.
- ISO/TS 15066:2016. Collaborative robots — Safety requirements. Power and force limits — relevant to cobot PID gain design.
- IEC 61131-3:2013. Programmable controllers — Part 3: Programming languages. PLC PID function block semantics.
- IEC 61508:2010. Functional safety of electrical/electronic/programmable electronic safety-related systems. SIL ratings affect PID anti-windup and bumpless-transfer requirements.
Online references and source code
- ROS 2 documentation (current LTS as of 2026-05: Lyrical Luth) — https://docs.ros.org/.
- ros2_control documentation — https://control.ros.org/.
ros-controls/control_toolboxGitHub —pid.hpp,pid.cpp.- PX4 Autopilot documentation — https://docs.px4.io/.
- PX4 source — https://github.com/PX4/PX4-Autopilot, modules
mc_att_control,mc_rate_control,mc_pos_control. - ArduPilot documentation — https://ardupilot.org/.
- ArduPilot
AC_PIDsource —libraries/AC_PID/AC_PID.cpp. - Arduino PID Library (Brett Beauregard) — https://github.com/br3ttb/Arduino-PID-Library.
- Crazyflie firmware — https://github.com/bitcraze/crazyflie-firmware.
- ODrive firmware — https://github.com/odriverobotics/ODrive.
- VESC firmware (Vedder) — https://github.com/vedderb/bldc.
- MATLAB Control System Toolbox documentation — https://www.mathworks.com/help/control/.
- Beauregard, B. (2011). “Improving the Beginner’s PID” series — http://brettbeauregard.com/blog/2011/04/improving-the-beginners-pid-introduction/. The pedagogically clearest tour of derivative-on-PV, anti-windup, and tuning in C, written by the Arduino PID library author.