Skip to content

PID Control

Conceptual

PID Control is the most widely used feedback control algorithm in robotics and automation. It continuously calculates an error between a desired setpoint and a measured process variable, then applies a correction based on proportional, integral, and derivative terms.

u(t) = Kp·e(t) + Ki·∫e(t)dt + Kd·de(t)/dt

Where:

  • u(t) = control output
  • e(t) = error (setpoint - measured value)
  • Kp = proportional gain
  • Ki = integral gain
  • Kd = derivative gain

Reacts to the current error. Larger error → larger correction.

  • Effect: Reduces rise time, but can’t eliminate steady-state error alone
  • Too high: Oscillation, instability
  • Too low: Slow response

Reacts to accumulated error over time. Eliminates steady-state error.

  • Effect: Drives error to zero over time
  • Too high: Overshoot, slow oscillations (integral windup)
  • Too low: Steady-state error persists

Reacts to rate of change of error. Predicts future error.

  • Effect: Reduces overshoot, dampens oscillations
  • Too high: Amplifies noise, jerky response
  • Too low: More overshoot
Setpoint
│
Error = ──────────►├◄────── Measured Value
│
▼
┌────────────────┐
│ PID Control │
│ │
│ P: "React" │
│ I: "Remember" │
│ D: "Predict" │
└───────┬────────┘
│
▼
Control Output
(motor speed, valve position, etc.)
  1. Set Ki = Kd = 0
  2. Increase Kp until system oscillates at constant amplitude
  3. Note the critical gain (Ku) and oscillation period (Tu)
  4. Calculate gains from lookup table
Control TypeKpKiKd
P0.5·Ku00
PI0.45·Ku1.2·Kp/Tu0
PID0.6·Ku2·Kp/TuKp·Tu/8

Note: Ziegler-Nichols tuning yields aggressive response with ~25% overshoot. Often requires refinement for production use.

  1. Start with Kp only, increase until fast response with some overshoot
  2. Add Kd to reduce overshoot
  3. Add Ki to eliminate steady-state error
  4. Fine-tune all three
class PIDController:
def __init__(self, kp, ki, kd, setpoint=0):
self.kp = kp
self.ki = ki
self.kd = kd
self.setpoint = setpoint
self.integral = 0
self.prev_error = 0
def update(self, measured, dt):
error = self.setpoint - measured
# Proportional
p_term = self.kp * error
# Integral (with anti-windup)
self.integral += error * dt
self.integral = max(-100, min(100, self.integral))
i_term = self.ki * self.integral
# Derivative
derivative = (error - self.prev_error) / dt
d_term = self.kd * derivative
self.prev_error = error
return p_term + i_term + d_term
# Usage: motor speed control
pid = PIDController(kp=2.0, ki=0.5, kd=0.1, setpoint=100)
current_speed = 0
dt = 0.01 # 10ms loop
for _ in range(1000):
output = pid.update(current_speed, dt)
# Apply output to motor...

When the actuator saturates, the integral term keeps growing. Common anti-windup strategies:

  • Clamping: Limit integral term to bounds (simple, shown in code example)
  • Back-calculation: Adjust integral based on saturation feedback (better for frequent saturation)
  • Conditional integration: Freeze integration when saturated

High-frequency noise gets amplified by the D term. Solution: low-pass filter on derivative or measured value.

Sudden setpoint changes cause large derivative spikes. Solution: apply D term only to measured value, not error.

ApplicationTypical Tuning Priority
Motor positionHigh P, moderate D, low I
TemperatureHigh I, low P and D
Drone altitudeBalanced P, I, D
Line followingHigh P, moderate D
Balancing robotHigh P and D, low I

PID works well for linear, single-input/single-output (SISO) systems. For complex robots, consider:

  • Model Predictive Control (MPC) — handles constraints and multi-variable systems
  • LQR — optimal control for linear systems
  • Adaptive control — adjusts gains based on system changes
  • Neural network controllers — learns complex nonlinear dynamics