Chapter 2: PID Control Fundamentals
What is PID Control?
PID stands for Proportional-Integral-Derivative. It's the most widely used control algorithm in industry.
Where:
- e(t) = error = setpoint - measured value
- Kp = proportional gain
- Ki = integral gain
- Kd = derivative gain
The Three Components
Proportional (P) Control
- Responds proportionally to current error
- Higher Kp ā faster response, but more oscillation
- Cannot eliminate steady-state error
class PController:
def __init__(self, Kp):
self.Kp = Kp
def compute(self, setpoint, measured):
error = setpoint - measured
return self.Kp * error
Integral (I) Control
- Accumulates past errors
- Eliminates steady-state error
- Can cause overshoot and oscillation
class PIController:
def __init__(self, Kp, Ki, dt):
self.Kp = Kp
self.Ki = Ki
self.dt = dt
self.integral = 0
def compute(self, setpoint, measured):
error = setpoint - measured
self.integral += error * self.dt
return self.Kp * error + self.Ki * self.integral
Derivative (D) Control
- Predicts future error based on rate of change
- Reduces overshoot
- Sensitive to noise
class PIDController:
def __init__(self, Kp, Ki, Kd, dt):
self.Kp = Kp
self.Ki = Ki
self.Kd = Kd
self.dt = dt
self.integral = 0
self.prev_error = 0
def compute(self, setpoint, measured):
error = setpoint - measured
# Proportional
P = self.Kp * error
# Integral
self.integral += error * self.dt
I = self.Ki * self.integral
# Derivative
derivative = (error - self.prev_error) / self.dt
D = self.Kd * derivative
self.prev_error = error
return P + I + D
Effect of Each Term
| Term | Response Speed | Overshoot | Steady-State Error | Stability |
|---|---|---|---|---|
| ā Kp | Faster | Increases | Decreases (not zero) | Decreases |
| ā Ki | Slower | Increases | Eliminates | Decreases |
| ā Kd | Faster | Decreases | No effect | Improves |
Complete PID Implementation
import numpy as np
class PID:
def __init__(self, Kp, Ki, Kd, dt, output_limits=None):
self.Kp = Kp
self.Ki = Ki
self.Kd = Kd
self.dt = dt
self.output_limits = output_limits
self.integral = 0
self.prev_error = 0
self.prev_derivative = 0
def reset(self):
self.integral = 0
self.prev_error = 0
def compute(self, setpoint, measured):
error = setpoint - measured
# Proportional term
P = self.Kp * error
# Integral term with anti-windup
self.integral += error * self.dt
I = self.Ki * self.integral
# Derivative term (on measurement to avoid derivative kick)
derivative = (error - self.prev_error) / self.dt
# Low-pass filter on derivative
alpha = 0.1
filtered_derivative = alpha * derivative + (1 - alpha) * self.prev_derivative
D = self.Kd * filtered_derivative
self.prev_error = error
self.prev_derivative = filtered_derivative
output = P + I + D
# Apply output limits
if self.output_limits:
output = np.clip(output, self.output_limits[0], self.output_limits[1])
# Anti-windup: stop integrating if saturated
if output == self.output_limits[0] or output == self.output_limits[1]:
self.integral -= error * self.dt
return output
Simulation Example
import numpy as np
import matplotlib.pyplot as plt
# Simulate a first-order system with PID control
def simulate_pid():
dt = 0.01
time = np.arange(0, 10, dt)
# System: first-order with time constant tau
tau = 1.0
# PID controller
pid = PID(Kp=2.0, Ki=1.0, Kd=0.5, dt=dt, output_limits=(-10, 10))
# Initial conditions
y = 0
setpoint = 1.0
outputs = []
for t in time:
# Compute control signal
u = pid.compute(setpoint, y)
# Simulate system response (Euler method)
dy = (u - y) / tau
y += dy * dt
outputs.append(y)
# Plot
plt.figure(figsize=(10, 6))
plt.plot(time, outputs, 'b-', label='System Output')
plt.axhline(y=setpoint, color='r', linestyle='--', label='Setpoint')
plt.xlabel('Time (s)')
plt.ylabel('Output')
plt.title('PID Control Response')
plt.legend()
plt.grid(True)
plt.show()
simulate_pid()
Key Takeaways
- ā PID combines three control actions
- ā P responds to current error
- ā I eliminates steady-state error
- ā D reduces overshoot
- ā Anti-windup prevents integral saturation
Next: Chapter 3 - PID Tuning Methods!