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_d and i_q (field-oriented control).

Why PID and not the modern alternatives? Three reasons that stack:

  1. 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.
  2. 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.
  3. 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:

LoopyuK_p units
Joint positionradN·mN·m / rad
Joint velocityrad/sN·mN·m·s / rad
Motor current (FOC)AVV / A = Ω
Drone altitudemN (thrust)N/m
Mobile-base velocitym/sPWM duty (0–1)duty·s / m
Cartesian positionmN (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:

  1. Bumpless transfer is automatic — at handover, just set u[k−1] to the current actuator value and resume.
  2. 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.
  3. 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).
  4. 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)

  1. P first. Set K_i = K_d = 0. Raise K_p until the plant shows small sustained oscillation. Drop back by ~30%.
  2. D next. Add K_d to damp the residual oscillation. Stop when noise starts to dominate the actuator (audible whine on motors, jittery valve on a pneumatic).
  3. I last. Add K_i just enough to remove steady-state error within the spec’s settling time. Too much K_i brings 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)

MethodUse whenK_pT_iT_d
Ziegler-Nichols ultimateBench testing, you can drive plant to oscillation0.6·K_u0.5·T_u0.125·T_u
Tyreus-LuybenRobust tuning, plant uncertainty0.45·K_u2.2·T_uT_u / 6.3
Skogestad SIMCFOPDT model knownτ/(K·(λ+θ))min(τ, 8θ)
IMC (lambda)FOPDT model, want one tuning knobτ/(K·(λ+θ))τ
Astrom relay-feedback autoEmbedded auto-tuner(1.4/π)·K_u0.5·T_uT_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 layerSample rateClosed-loop bandwidth
Motor current (FOC inner)5–20 kHz500–2000 Hz
Motor velocity1–5 kHz100–500 Hz
Joint position250 Hz–1 kHz25–100 Hz
Cartesian position50–250 Hz5–25 Hz
Operational-space / impedance50–250 Hz5–25 Hz
Outer planner / waypoint10–50 Hz1–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:

  1. Lookup-table gain schedule. Discretise q into 4–16 regions; store K_p, K_i, K_d per region; interpolate. Used in ABB, KUKA, Fanuc industrial arm firmware (proprietary; reverse-engineered from KSS, RobotWare service logs).
  2. 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 accurate M(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̇_des reverses 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

PlatformLoop rate (typical)Use case
ATmega328 (Arduino Uno)100 Hz–1 kHzHobby robots, education
RP20401–10 kHzHobby drones, mobile bots; dual core, 133 MHz
STM32F4 (84 MHz, FPU)1–10 kHzServo drives, light arms
STM32G4 (170 MHz, math accel)10–50 kHzModern servo drives, motor control
STM32H7 (480 MHz, dual core)10–100 kHzHigh-end flight controllers (PX4 Pixhawk 6X)
TI C2000 F2837x / F28004x50–200 kHzHigh-end industrial motor drives
Teensy 4.x (Cortex-M7 600 MHz)50 kHzEducation, prototype servos
Jetson Orin (ARM Cortex-A78 + GPU)250 Hz–1 kHzHigh-level control, perception fusion
PLC + RTOS (Beckhoff CX, Siemens S7-1500)1–10 kHzIndustrial 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::Pidhttps://github.com/ros-controls/control_toolbox, file src/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 controllershttps://github.com/PX4/PX4-Autopilot, files src/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_PID libraryhttps://github.com/ArduPilot/ardupilot, file libraries/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, file src/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 firmwarehttps://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_Compact and PID_3Step; Rockwell Studio 5000 PIDE; Schneider EcoStruxure PID2 and PIDFF (with feedforward); ABB AC800M PidCC; Beckhoff TwinCAT FB_BasicPID. All implement IEC 61131-3 PID semantics with anti-windup, gain scheduling, and feedforward.

Auto-tuners

  • MATLAB Control System Toolboxpidtune(), interactive pidTuner GUI. Plant must be modelled as tf or idtf.
  • 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 autotuneMC_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/data topics for offline replay.
  • PlotJuggler — interactive time-series visualizer for ROS bags, CSV, MAT files. Standard for diagnosing PID behavior.
  • MATLAB / Simulinksim(), compare(), parameter sweeps.
  • Foxglove Studio — modern web-based bag viewer; integrates with ROS 2.

6. Reference data

Two main PID structures

FormEquationUsed by
Parallel (academic)K_p·e + K_i·∫e + K_d·de/dtMATLAB pid(), Arduino PID, ros2_control, most textbooks
ISA standardK_c·[e + (1/T_i)·∫e + T_d·de/dt]Siemens TIA, most DCS, industrial PLC blocks
Series / interactingK_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

LayerSample rateBW (Hz)Example platform
Current (FOC i_d, i_q)5–20 kHz500–2000ODrive, VESC, Maxon EPOS4
Motor velocity1–5 kHz100–500UR servo drive, Yaskawa Sigma-7
Joint position250 Hz–1 kHz25–100ABB IRC5, KUKA KR C5
Cartesian position50–250 Hz5–25ros2_control, OROCOS
Operational-space impedance100–1 kHz10–100Franka FR3, Kinova Gen3

Drone bandwidth typicals

LoopRate (Hz)BW (Hz)Notes
Body rate (P only or PI)1 k – 8 k100–400Inner of cascade
Attitude angle (PID)250 – 1 k30–80Quaternion-based
Position (PID)50 – 2501–5Outer
Mission / waypoint10<1Above PID stack

Robot arm joint bandwidth ranges

ClassBW (Hz)Example
Heavy industrial arm5–20ABB IRB 6700 (200 kg payload)
Mid-payload industrial10–30ABB IRB 1600 (10 kg)
Cobot10–50UR5e, Franka FR3
Direct-drive / parallel50–200ABB FlexPicker, Quanser
Surgical robot100–300Intuitive da Vinci, Auris Monarch

Anti-windup methods — comparison

MethodCode complexitySmoothnessTuning needed
No anti-windupnonecatastrophic overshoot
Clamp output only1 lineabruptnone
Conditional integration~5 linesmediumnone
Back-calculation~10 linessmoothK_b
Tracking (slave integrator)~15 linessmoothrequires actuator position FB
Velocity-form PIDinherentsmoothnone

Auto-tune methods — comparison

MethodRequired infraTypical outcome
Ziegler-Nichols ultimateDrive plant to limit cycle manuallyAggressive, ~25% OS
Cohen-Coon (1953)Open-loop stepHigh dead-time plants
Relay feedback (Åström-Hägglund 1984)Single relay elementEmbedded auto-tune in PLCs
MATLAB pidtunePlant modelOptimal for plant; bench tool
PX4 in-flight autotuneDrone in flight, safety pilotGains for each axis in <1 minute
ArduPilot Autotune modeDrone, large open areaBest for new airframes
Honeywell PlantTriageDCS historianProcess control, slow plants

Robot-family PID starting gains (parallel form, after gravity comp)

Robot typeK_pK_dK_iT_s
6-DOF arm joint (mid-size)200–800 N·m/rad50–200 N·m·s/rad100–2000 N·m/(rad·s)1–4 ms
Cobot joint (UR-class)100–500 N·m/rad20–100 N·m·s/rad50–500 N·m/(rad·s)0.5–2 ms
Drone attitude (1 kg quad)4–10 N·m/rad0.1–0.3 N·m·s/rad0.5–2 N·m/(rad·s)1–4 ms
Drone position4–10 N/m2–6 N·s/m0.5–2 N/(m·s)4–20 ms
Mobile-base wheel velocity0.3–1.0 duty/(m/s)00.5–2.0 duty/m5 ms
Mobile-base position outer0.5–2.0 (1/s)0.2–0.8 (s/s)020 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

SymptomLikely causeFix
Overshoot after long saturationIntegral windupAdd anti-windup back-calc or velocity-form
Motor whine, hot driver, jittery outputD-term amplifying sensor noiseAdd filtered derivative; lower N from 10 to 5
Sharp output spike on every setpoint changeD acting on error (kick)Switch to derivative-on-PV
Output jump on Auto→Manual mode changeInitialization mismatchVelocity-form PID or explicit handoff init
Slow oscillation after disturbanceI too highReduce K_i by 50%; recheck PM
Joint “thumps” through zero crossingStick-slip frictionAdd Coulomb friction feedforward
High-freq oscillation at known resonanceK_d exciting flexible modeAdd notch at resonance; reduce K_d
Inertia mismatch — works folded, sluggish extendedPlant inertia configuration-dependentGain schedule on q, or computed-torque
Sensor noise drives output even at restHigh-freq noise above bandwidthAdd IIR low-pass on PV input
Discretization-induced phase lagT_s too long relative to bandwidthIncrease loop rate; aim T_s ≤ 1/(20·BW)
Gains tuned at one operating point, unstable elsewhereLinearization assumption violatedGain schedule, adaptive, or robust design
”Guessed” gains, never characterized plantNo baselineRun ZN-relay or step-response identification

Detailed failure modes

  1. 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.

  2. 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).

  3. 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.

  4. 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_old at the switch instant.

  5. 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.

  6. 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.

  7. 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_payload parameter in robot SDK); or implement computed-torque.

  8. 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).

  9. 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.

  10. 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 by tanh(q̇/ε).

  11. “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.

  12. 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

  1. Set K_i = K_d = 0. Reduce K_p to half its current value.
  2. Step the setpoint by a small amount. Observe response on a real-time plot.
  3. Raise K_p in 30% increments until small sustained oscillation appears. Record K_u and oscillation period T_u.
  4. Set K_p = 0.45·K_u (Tyreus-Luyben — gentler than Ziegler-Nichols).
  5. Add K_d corresponding to T_d = T_u/6.3. Verify damping.
  6. Add K_i corresponding to T_i = 2.2·T_u. Verify steady-state error closes.
  7. 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_d and i_q from 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 error q_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, where F_cart is 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; provides q_des to 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]] — smooth r(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