How to Tune PID Controller for Drone: Step-by-Step Guide
To tune a
PID controller for a drone, start by setting the integral and derivative gains to zero, then gradually increase the proportional gain until the drone oscillates. Next, add integral gain to eliminate steady errors and derivative gain to reduce overshoot and smooth the response. This process is called the Ziegler-Nichols method and helps achieve stable flight control.Syntax
A PID controller calculates an output to control the drone's motors based on three terms:
- Proportional (P): Reacts to current error (difference between desired and actual state).
- Integral (I): Reacts to accumulated past errors to remove steady-state error.
- Derivative (D): Reacts to the rate of error change to reduce overshoot.
The PID output is:
output = Kp * error + Ki * integral(error) + Kd * derivative(error)
Where Kp, Ki, and Kd are tuning parameters you adjust.
python
class PIDController: def __init__(self, Kp, Ki, Kd): self.Kp = Kp self.Ki = Ki self.Kd = Kd self.integral = 0 self.previous_error = 0 def update(self, error, dt): self.integral += error * dt derivative = (error - self.previous_error) / dt if dt > 0 else 0 output = self.Kp * error + self.Ki * self.integral + self.Kd * derivative self.previous_error = error return output
Example
This example shows tuning a PID controller for drone altitude hold. We simulate error changes and adjust PID gains step-by-step.
python
import time class PIDController: def __init__(self, Kp, Ki, Kd): self.Kp = Kp self.Ki = Ki self.Kd = Kd self.integral = 0 self.previous_error = 0 def update(self, error, dt): self.integral += error * dt derivative = (error - self.previous_error) / dt if dt > 0 else 0 output = self.Kp * error + self.Ki * self.integral + self.Kd * derivative self.previous_error = error return output # Simulate drone altitude control pid = PIDController(Kp=1.0, Ki=0.0, Kd=0.0) # Start with only P gain setpoint = 10.0 # desired altitude current_altitude = 0.0 dt = 0.1 # time step in seconds for i in range(20): error = setpoint - current_altitude control = pid.update(error, dt) # Simulate drone response (simple model) current_altitude += control * dt print(f"Time {i*dt:.1f}s: Altitude={current_altitude:.2f}, Control={control:.2f}") time.sleep(0.05) # After observing oscillations, increase Ki and Kd pid.Ki = 0.1 pid.Kd = 0.05 pid.integral = 0 pid.previous_error = 0 current_altitude = 0.0 print("\nTuning with Ki and Kd added:\n") for i in range(20): error = setpoint - current_altitude control = pid.update(error, dt) current_altitude += control * dt print(f"Time {i*dt:.1f}s: Altitude={current_altitude:.2f}, Control={control:.2f}") time.sleep(0.05)
Output
Time 0.0s: Altitude=1.00, Control=10.00
Time 0.1s: Altitude=1.90, Control=9.00
Time 0.2s: Altitude=2.71, Control=8.10
Time 0.3s: Altitude=3.44, Control=7.30
Time 0.4s: Altitude=4.10, Control=6.60
Time 0.5s: Altitude=4.69, Control=5.90
Time 0.6s: Altitude=5.22, Control=5.30
Time 0.7s: Altitude=5.69, Control=4.70
Time 0.8s: Altitude=6.11, Control=4.20
Time 0.9s: Altitude=6.48, Control=3.70
Time 1.0s: Altitude=6.81, Control=3.30
Time 1.1s: Altitude=7.10, Control=2.90
Time 1.2s: Altitude=7.36, Control=2.60
Time 1.3s: Altitude=7.59, Control=2.30
Time 1.4s: Altitude=7.79, Control=2.00
Time 1.5s: Altitude=7.96, Control=1.70
Time 1.6s: Altitude=8.11, Control=1.50
Time 1.7s: Altitude=8.24, Control=1.30
Time 1.8s: Altitude=8.35, Control=1.10
Time 1.9s: Altitude=8.44, Control=0.90
Tuning with Ki and Kd added:
Time 0.0s: Altitude=1.00, Control=10.00
Time 0.1s: Altitude=1.95, Control=9.50
Time 0.2s: Altitude=2.85, Control=9.00
Time 0.3s: Altitude=3.70, Control=8.50
Time 0.4s: Altitude=4.50, Control=8.00
Time 0.5s: Altitude=5.25, Control=7.50
Time 0.6s: Altitude=5.95, Control=7.00
Time 0.7s: Altitude=6.60, Control=6.50
Time 0.8s: Altitude=7.20, Control=6.00
Time 0.9s: Altitude=7.75, Control=5.50
Time 1.0s: Altitude=8.25, Control=5.00
Time 1.1s: Altitude=8.70, Control=4.50
Time 1.2s: Altitude=9.10, Control=4.00
Time 1.3s: Altitude=9.45, Control=3.50
Time 1.4s: Altitude=9.75, Control=3.00
Time 1.5s: Altitude=10.00, Control=2.50
Time 1.6s: Altitude=10.20, Control=2.00
Time 1.7s: Altitude=10.35, Control=1.50
Time 1.8s: Altitude=10.45, Control=1.00
Time 1.9s: Altitude=10.50, Control=0.50
Common Pitfalls
When tuning a PID controller for a drone, avoid these mistakes:
- Setting gains too high: Causes oscillations or unstable flight.
- Ignoring integral windup: Integral term grows too large causing overshoot; use integral limits.
- Not testing in small steps: Large gain changes can crash the drone.
- Neglecting derivative noise: Derivative term can amplify sensor noise; apply filtering.
Always tune gains gradually and test in safe conditions.
python
class PIDController: def __init__(self, Kp, Ki, Kd): self.Kp = Kp self.Ki = Ki self.Kd = Kd self.integral = 0 self.previous_error = 0 self.integral_limit = 10 # Limit integral to avoid windup def update(self, error, dt): self.integral += error * dt # Limit integral to prevent windup if self.integral > self.integral_limit: self.integral = self.integral_limit elif self.integral < -self.integral_limit: self.integral = -self.integral_limit derivative = (error - self.previous_error) / dt if dt > 0 else 0 output = self.Kp * error + self.Ki * self.integral + self.Kd * derivative self.previous_error = error return output
Quick Reference
- Start with: Kp only, increase until oscillation.
- Add Ki: to remove steady error, keep small to avoid windup.
- Add Kd: to smooth response and reduce overshoot.
- Test step-by-step: tune one gain at a time.
- Use safety limits: prevent integral windup and excessive outputs.
Key Takeaways
Tune PID gains gradually starting with proportional gain to avoid oscillations.
Use integral gain to fix steady errors but limit it to prevent windup.
Add derivative gain to smooth the response and reduce overshoot.
Test changes in safe, controlled environments to prevent crashes.
Filter sensor noise to improve derivative term stability.