PID Control
Overview
PID (Proportional-Integral-Derivative) is a feedback loop that corrects errors. It drives a mechanism to a target setpoint (like an elevator height or shooter speed) by adjusting motor output.
The Terms
P (Proportional): Reacts to current error. "I'm far away, go fast." Main driver of motion.
I (Integral): Reacts to accumulated error. "I've been close for a while, push harder." Fixes steady-state error (e.g., gravity).
D (Derivative): Reacts to rate of change. "I'm closing in fast, slow down." Dampens oscillation.
I (Integral): Reacts to accumulated error. "I've been close for a while, push harder." Fixes steady-state error (e.g., gravity).
D (Derivative): Reacts to rate of change. "I'm closing in fast, slow down." Dampens oscillation.
Implementation Methods
Where to run PID:
- On-RIO (WPILib PIDController): Runs in user code (20ms loop). Flexible, easy to debug.
- On-Controller (Spark/Talon): Runs on motor controller (1ms loop). Faster, frees up RIO CPU.
WPILib PIDController
Best for learning and complex logic.
RIO PID Example
package frc.robot.subsystems;
import edu.wpi.first.wpilibj2.command.SubsystemBase;
import edu.wpi.first.math.controller.PIDController;
public class Elevator extends SubsystemBase {
// Create PID controller: kP, kI, kD
private PIDController pid = new PIDController(0.5, 0.0, 0.0);
// Example: encoder and motor would be defined elsewhere
// private Encoder encoder;
// private Motor motor;
private double targetHeight = 0.0;
// ... (other methods and fields)
@Override
public void periodic() {
// Calculate PID output: current position vs target
double currentPosition = encoder.getPosition();
double output = pid.calculate(currentPosition, targetHeight);
// Apply output to motor
motor.set(output);
}
// ... (rest of class)On-Controller PID
Best for performance.
Hardware PID
Configuration:
package frc.robot.subsystems;
import com.ctre.phoenix6.configs.Slot0Configs;
import com.ctre.phoenix6.controls.PositionVoltage;
import com.ctre.phoenix6.hardware.TalonFX;
public class Example {
private TalonFX motor = new TalonFX(1);
public void configurePID() {
// Configure PID gains for Slot 0
Slot0Configs config = new Slot0Configs();
config.kP = 0.1; // Proportional gain
config.kI = 0.0; // Integral gain
config.kD = 0.01; // Derivative gain
motor.getConfigurator().apply(config);
}
public void setPosition(double targetRotations) {
// Use position control with configured PID
motor.setControl(new PositionVoltage(targetRotations));
}
}Tuning Process
Tune feedforward first, then PID. Use Phoenix Tuner or similar tools for quick constant changes. Log desired vs actual position using a vendor-specific tool (Phoenix Tuner, REV Hardware Client) or an external tool (AdvantageScope, Elastic) to see system behavior. (Credits to SuperNURDS FRC 3255 for the original method)
Feedforward Tuning
Steps:
- Set all PID constants to 0.
- Command the mechanism to a non-zero position.
- Manually raise to that position and release.
- Observe if it holds, raises, or falls.
- Adjust kG (gravity feedforward) and repeat until it holds position.
PID Tuning
Steps:
- Log desired position and actual position.
- Slowly increase kP until you see overshoot, then set kP slightly below that value.
- (For a riskier strategy, double kP until you see overshoot, then set kP to the midpoint between the old and current kP values until oscillation mostly disappears)
- If steady-state error appears (doesn't reach target), increase kS (static feedforward) slightly.
- This makes kP temporarily too large (overshoot), but your final kP will be close.
- Repeat adjusting kS and kP until properly tuned.