Closed-Loop Control: PID + Feedforward for a Consistent Shot
Replace open-loop guessing with characterized feedforward and PID so your Fuel launcher hits target RPM every shot.
Sign in to track progress, earn XP, and save lessons.
In Project 4 you ran the launcher open-loop at 85%. The problem: after each ball, the wheel slows down and the next shot is weaker until it recovers. Closed-loop control fixes this by measuring the wheel's speed and correcting toward a target. For REBUILT's Hub scoring, shot consistency is what turns a good launcher into a ranking-point machine.
The two halves. WPILib's recommended approach combines feedforward and feedback:
- Feedforward predicts the voltage needed to hold a speed, using a physics model. For a flywheel:
voltage = kS + kV*velocity + kA*acceleration. TheSimpleMotorFeedforwardclass implements exactly this. - Feedback (PID) corrects the small remaining error.
PIDControllercomputes a correction from the difference between the target and the measured velocity.
You add their outputs together — feedforward does the heavy lifting, PID cleans up.
Getting the gains with SysId. Do not guess kS/kV/kA. WPILib's SysId tool does this rigorously: you add a characterization routine that drives the mechanism through quasi-static and dynamic tests while logging voltage and velocity. After the run, you load the log into the SysId app, which fits a model and outputs kS, kV, kA, plus suggested PID gains. (For a drivetrain, SysId needs clear travel space; a flywheel needs none.)
private final SimpleMotorFeedforward m_ff =
new SimpleMotorFeedforward(kS, kV, kA); // from SysId
private final PIDController m_pid =
new PIDController(kP, 0, 0); // from SysId
public void runFlywheel(double targetRPS) {
double measured = m_encoder.getVelocity(); // rotations/sec
double ff = m_ff.calculate(targetRPS);
// PIDController.calculate(measurement, setpoint): measured first
double fb = m_pid.calculate(measured, targetRPS);
m_motor.setVoltage(ff + fb);
}
Note the argument order: PIDController.calculate(measurement, setpoint) takes the measured value first and the setpoint second — getting these backwards is a common bug.
Tuning intuition. With good feedforward, the wheel reaches and holds target with very little PID effort. Raise kP until the wheel recovers quickly after a shot without oscillating. If it oscillates, lower kP. The WPILib flywheel-tuning guide walks through this exact loop. Velocity control like this is forgiving — flywheels are nearly the textbook case for feedforward.
The payoff: a characterized launcher returns to target RPM quickly between shots, so every Fuel leaves at the same speed and lands in the Hub. That repeatability is what lets a team chain enough scores to reach the Energized (100 Fuel) and Supercharged (360 Fuel) ranking-point thresholds reliably instead of hoping.
Key takeaways
- Combine SimpleMotorFeedforward (kS+kV*v+kA*a) with a PIDController and add their outputs — feedforward leads, PID corrects
- PIDController.calculate takes (measurement, setpoint) — measured first; use SysId to measure kS/kV/kA instead of guessing
- A characterized flywheel recovers to target RPM between shots, giving the shot consistency needed for the Energized/Supercharged RPs
Go deeper
Lesson quiz
RequiredAnswer all 3 questions correctly to complete this lesson.
1.In WPILib's SimpleMotorFeedforward, what does the kV (velocity) gain primarily account for?
2.Per WPILib, what is the correct way to combine a feedforward controller with a PID feedback controller for a flywheel?
3.Why is a flywheel shooter considered an ideal candidate for feedforward control?
Answer every question to submit.