title: "PID Controller" description: "Closed-loop control for drones, robotics, and industrial automation." order: 4

PID Controller

Vibe Prompt

"Simulate PID temperature control. Target 100C. Tune Kp, Ki, Kd."

import numpy as np

class PID:
    def __init__(self, Kp, Ki, Kd, setpoint):
        self.Kp, self.Ki, self.Kd = Kp, Ki, Kd
        self.setpoint = setpoint
        self.prev_error = 0
        self.integral = 0

    def update(self, measurement, dt):
        error = self.setpoint - measurement
        self.integral += error * dt
        derivative = (error - self.prev_error) / dt
        self.prev_error = error
        return self.Kp*error + self.Ki*self.integral + self.Kd*derivative

pid = PID(Kp=2.0, Ki=0.5, Kd=1.0, setpoint=100)
temp = 25.0
dt = 0.01
for _ in range(1000):
    output = pid.update(temp, dt)
    temp += output * dt * 10
    temp -= (temp - 25) * 0.01 * dt
print(f"Final temperature: {temp:.1f}C")

PID Control Principles

Three terms:

$$u(t) = K_p \cdot e(t) + K_i \cdot \int_0^t e(\tau) d\tau + K_d \cdot \frac{de(t)}{dt}$$

| Term | Name | Intuition | Effect | |:----:|:----:|-----------|--------| | P | Proportional | "How far?" | Speeds response, may overshoot | | I | Integral | "Accumulated?" | Eliminates steady-state error | | D | Derivative | "Future trend?" | Reduces overshoot, improves stability |

Parameter Impact

| Effect | Kp Up | Ki Up | Kd Up | |:------:|:----:|:----:|:----:| | Rise time | Shorter | Minor | Minor change | | Overshoot | Increases | Increases | Decreases | | Settling time | Minor | Increases | Decreases | | Steady error | Decreases | Eliminates | No effect | | Stability | Degrades | Degrades | Improves |

Ziegler-Nichols Tuning

  1. Set Ki=0, Kd=0
  2. Increase Kp until stable oscillation (record Ku)
  3. Measure oscillation period Pu
  4. Use table:

| Controller | Kp | Ki | Kd | |:----------:|:--:|:--:|:--:| | P only | 0.5 x Ku | - | - | | PI | 0.45 x Ku | 0.54 x Ku/Pu | - | | PID | 0.60 x Ku | 1.20 x Ku/Pu | 0.075 x Ku x Pu |

Applications

| Domain | Application | |:------:|-------------| | Drones | Flight stabilization, hover, waypoint tracking | | Robotics | Motor position/speed, arm precision | | Industry | Temperature/pressure/flow control | | Automotive | Cruise control, stability program | | Appliances | AC temperature, oven thermostat |

Advanced Variants

| Variant | Description | Use Case | |:-------:|-------------|----------| | PI | No derivative term | Noisy systems | | PD | No integral term | No steady-error needed | | Cascade | Two PID nested | Drone flight control | | Fuzzy PID | Dynamic parameter adjustment | Non-linear systems | | Adaptive PID | Auto-tune parameters | Time-varying systems |


Key Takeaways

  • P: correction based on current error
  • I: eliminates accumulated steady-state error
  • D: predicts error trend, reduces overshoot
  • Ziegler-Nichols: classic tuning method
  • Real implementations need anti-windup and filtering
  • PID requires no system model - works for most control scenarios

What Is PID Control?

PID (Proportional-Integral-Derivative) control is a feedback mechanism that continuously calculates an error value and applies corrections.

The Three Components

| Component | Effect | Formula | |-----------|--------|---------| | Proportional (P) | Reacts to current error | $K_p e(t)$ | | Integral (I) | Corrects accumulated past error | $K_i \int e(t) dt$ | | Derivative (D) | Predicts future error trend | $K_d \frac{de(t)}{dt}$ |

$$u(t) = K_p e(t) + K_i \int_0^t e(\tau) d\tau + K_d \frac{de(t)}{dt}$$

How Each Component Affects Response

| Component | Rise Time | Overshoot | Settling Time | Steady-State Error | |-----------|-----------|-----------|---------------|-------------------| | P | Decreases | Increases | Small change | Decreases | | I | Decreases | Increases | Increases | Eliminates | | D | Minor change | Decreases | Decreases | Minor change |

PID Tuning Methods

Manual Tuning

def tune_pid(Kp_start=1.0, Ki_start=0.0, Kd_start=0.0):
    """Manual PID tuning procedure."""
    print("Step 1: Set Ki=0, Kd=0. Increase Kp until oscillation.")
    print("Step 2: Reduce Kp by half (stable Kp).")
    print("Step 3: Increase Ki to eliminate steady-state error.")
    print("Step 4: Increase Kd to reduce overshoot.")
    print("Step 5: Fine-tune all three together.")
    
    return {
        "Kp": Kp_start * 0.5,   # Half of ultimate gain
        "Ki": Ki_start + 0.1,    # Slowly increase
        "Kd": Kd_start + 0.05    # Slowly increase
    }

Ziegler-Nichols Method

| Control Type | $K_p$ | $T_i$ | $T_d$ | |-------------|-------|-------|-------| | P | $0.5 K_u$ | — | — | | PI | $0.45 K_u$ | $0.83 T_u$ | — | | PID | $0.6 K_u$ | $0.50 T_u$ | $0.125 T_u$ |

Where:

  • $K_u$ = ultimate gain (when system oscillates steadily)
  • $T_u$ = oscillation period at ultimate gain

Python PID Implementation

class PID:
    def __init__(self, Kp, Ki, Kd, setpoint=0):
        self.Kp = Kp
        self.Ki = Ki
        self.Kd = Kd
        self.setpoint = setpoint
        self.last_error = 0
        self.integral = 0
        self.last_time = None
    
    def update(self, measurement, current_time=None):
        if current_time is None:
            current_time = time.time()
        
        # Calculate error
        error = self.setpoint - measurement
        
        # Delta time
        if self.last_time is None:
            dt = 0.01
        else:
            dt = current_time - self.last_time
        
        # Proportional
        P = self.Kp * error
        
        # Integral (with anti-windup)
        self.integral += error * dt
        self.integral = max(-10, min(10, self.integral))  # Clamp
        I = self.Ki * self.integral
        
        # Derivative
        derivative = (error - self.last_error) / dt if dt > 0 else 0
        D = self.Kd * derivative
        
        # Save state
        self.last_error = error
        self.last_time = current_time
        
        return P + I + D

# Temperature control example
pid = PID(Kp=2.0, Ki=0.1, Kd=0.5, setpoint=37.0)  # Target: 37°C
temperature = 25.0  # Starting temp

for minute in range(20):
    output = pid.update(temperature)
    # Heater power = output (clamped to 0-100%)
    heater_power = max(0, min(100, output))
    # Simulate heating
    temperature += heater_power * 0.1 - 0.2  # Heat + cooling loss
    print(f"Minute {minute}: Temp={temperature:.1f}°C, Heater={heater_power:.0f}%")

Summary

PID control combines proportional (current error), integral (past error), and derivative (future trend) terms. It's the most widely used control algorithm in industry.

Key takeaways: | P: reacts to current error — reduces rise time but causes overshoot | | I: eliminates steady-state error — accumulates past errors | | D: predicts future error — dampens overshoot and oscillation | | Anti-windup: clamp integral term to prevent excessive buildup | | Ziegler-Nichols: systematic tuning from ultimate gain and period | | PID requires no system model — works for most real-world control | | Applications: temperature, speed, position, pressure, flow |

Next Chapter: Kalman Filter

The next chapter covers Kalman filters for state estimation from noisy measurements.

Unlock Full Tutorial

This chapter is paid content. Join the project to unlock over 5000 words of deep analysis, including 10+ god-tier Prompts and real Source Code examples!