Combining Feedforward and PID Control


This article covers the in-code implementation of combined feedforward/PID control with WPILib’s provided library classes. Documentation describing the involved concepts in more detail is forthcoming.

Feedforward and feedback controllers can each be used in isolation, but are most effective when combined together. Thankfully, combining these two control methods is exceedingly straightforward - one simply adds their outputs together.

Using Feedforward with a PIDController

Users familiar with the old PIDController class may notice the lack of any feedforward gain in the new controller. As users are expected to use the controller output themselves, there is no longer any need for the PIDController to implement feedforward - users may simply add any feedforward they like to the output of the controller before sending it to their motors:

// Adds a feedforward to the loop output before sending it to the motor
motor.setVoltage(pid.calculate(encoder.getDistance(), setpoint) + feedforward);

Moreover, feedforward is a separate feature entirely from feedback, and thus has no reason to be handled in the same controller object, as this violates separation of concerns. WPILib comes with several helper classes to compute accurate feedforward voltages for common FRC® mechanisms - for more information, see Feedforward Control in WPILib.

Using Feedforward Components with PID


Since feedforward voltages are physically meaningful, it is best to use the setVoltage() (Java, C++) method when applying them to motors to compensate for “voltage sag” from the battery.

What might a more complete example of combined feedforward/PID control look like? Consider the drive example from the feedforward page. We can easily modify this to include feedback control (with a SimpleMotorFeedforward component):

public void tankDriveWithFeedforwardPID(double leftVelocitySetpoint, double rightVelocitySetpoint) {
      + leftPID.calculate(leftEncoder.getRate(), leftVelocitySetpoint));
      + rightPID.calculate(rightEncoder.getRate(), rightVelocitySetpoint));

Other mechanism types can be handled similarly.