PID Control in Command-based

Note

For a description of the WPILib PID control features used by these command-based wrappers, see PID Control in WPILib.

One of the most common control algorithms used in FRC® and FTC® is the PID controller. WPILib offers its own PIDController class to help teams implement this functionality on their robots. The following example is from the RapidReactCommandBot example project (Java, C++) and shows how PIDControllers can be used within the command-based framework:

 5package org.wpilib.examples.rapidreactcommandbot.subsystems;
 6
 7import static org.wpilib.command2.Commands.parallel;
 8import static org.wpilib.command2.Commands.waitUntil;
 9
10import org.wpilib.command2.Command;
11import org.wpilib.command2.SubsystemBase;
12import org.wpilib.epilogue.Logged;
13import org.wpilib.examples.rapidreactcommandbot.Constants.ShooterConstants;
14import org.wpilib.hardware.motor.PWMSparkMax;
15import org.wpilib.hardware.rotation.Encoder;
16import org.wpilib.math.controller.PIDController;
17import org.wpilib.math.controller.SimpleMotorFeedforward;
18
19@Logged
20public class Shooter extends SubsystemBase {
21  private final PWMSparkMax shooterMotor = new PWMSparkMax(ShooterConstants.kShooterMotorPort);
22  private final PWMSparkMax feederMotor = new PWMSparkMax(ShooterConstants.kFeederMotorPort);
23  private final Encoder shooterEncoder =
24      new Encoder(
25          ShooterConstants.kEncoderPorts[0],
26          ShooterConstants.kEncoderPorts[1],
27          ShooterConstants.kEncoderReversed);
28  private final SimpleMotorFeedforward shooterFeedforward =
29      new SimpleMotorFeedforward(ShooterConstants.kS, ShooterConstants.kV);
30  private final PIDController shooterFeedback = new PIDController(ShooterConstants.kP, 0.0, 0.0);
31
32  /** The shooter subsystem for the robot. */
33  public Shooter() {
34    shooterFeedback.setTolerance(ShooterConstants.kShooterToleranceRPS);
35    shooterEncoder.setDistancePerPulse(ShooterConstants.kEncoderDistancePerPulse);
36
37    // Set default command to turn off both the shooter and feeder motors, and then idle
38    setDefaultCommand(
39        runOnce(
40                () -> {
41                  shooterMotor.disable();
42                  feederMotor.disable();
43                })
44            .andThen(run(() -> {}))
45            .withName("Idle"));
46  }
47
48  /**
49   * Returns a command to shoot the balls currently stored in the robot. Spins the shooter flywheel
50   * up to the specified setpoint, and then runs the feeder motor.
51   *
52   * @param setpointRotationsPerSecond The desired shooter velocity
53   */
54  public Command shootCommand(double setpointRotationsPerSecond) {
55    return parallel(
56            // Run the shooter flywheel at the desired setpoint using feedforward and feedback
57            run(
58                () -> {
59                  shooterMotor.setThrottle(
60                      shooterFeedforward.calculate(setpointRotationsPerSecond)
61                          + shooterFeedback.calculate(
62                              shooterEncoder.getRate(), setpointRotationsPerSecond));
63                }),
64
65            // Wait until the shooter has reached the setpoint, and then run the feeder
66            waitUntil(shooterFeedback::atSetpoint).andThen(() -> feederMotor.setThrottle(1)))
67        .withName("Shoot");
68  }
69}
 5#pragma once
 6
 7#include <functional>
 8
 9#include "Constants.hpp"
10#include "wpi/commands2/CommandPtr.hpp"
11#include "wpi/commands2/SubsystemBase.hpp"
12#include "wpi/hardware/motor/PWMSparkMax.hpp"
13#include "wpi/hardware/rotation/Encoder.hpp"
14#include "wpi/math/controller/PIDController.hpp"
15#include "wpi/math/controller/SimpleMotorFeedforward.hpp"
16#include "wpi/units/angle.hpp"
17#include "wpi/units/angular_velocity.hpp"
18
19class Shooter : public wpi::cmd::SubsystemBase {
20 public:
21  Shooter();
22
23  /**
24   * Returns a command to shoot the balls currently stored in the robot. Spins
25   * the shooter flywheel up to the specified setpoint, and then runs the feeder
26   * motor.
27   *
28   * @param setpointRotationsPerSecond The desired shooter velocity
29   */
30  wpi::cmd::CommandPtr ShootCommand(wpi::units::turns_per_second_t setpoint);
31
32 private:
33  wpi::PWMSparkMax shooterMotor{ShooterConstants::kShooterMotorPort};
34  wpi::PWMSparkMax feederMotor{ShooterConstants::kFeederMotorPort};
35
36  wpi::Encoder shooterEncoder{ShooterConstants::kEncoderPorts[0],
37                              ShooterConstants::kEncoderPorts[1],
38                              ShooterConstants::kEncoderReversed};
39  wpi::math::SimpleMotorFeedforward<wpi::units::radians> shooterFeedforward{
40      ShooterConstants::kS, ShooterConstants::kV};
41  wpi::math::PIDController shooterFeedback{ShooterConstants::kP, 0.0, 0.0};
42};
 5#include "subsystems/Shooter.hpp"
 6
 7#include "wpi/commands2/Commands.hpp"
 8
 9Shooter::Shooter() {
10  shooterFeedback.SetTolerance(ShooterConstants::kShooterTolerance.value());
11  shooterEncoder.SetDistancePerPulse(
12      ShooterConstants::kEncoderDistancePerPulse);
13
14  SetDefaultCommand(RunOnce([this] {
15                      shooterMotor.Disable();
16                      feederMotor.Disable();
17                    })
18                        .AndThen(Run([] {}))
19                        .WithName("Idle"));
20}
21
22wpi::cmd::CommandPtr Shooter::ShootCommand(
23    wpi::units::turns_per_second_t setpoint) {
24  return wpi::cmd::Parallel(
25             // Run the shooter flywheel at the desired setpoint using
26             // feedforward and feedback
27             Run([this, setpoint] {
28               shooterMotor.SetVoltage(
29                   shooterFeedforward.Calculate(setpoint) +
30                   wpi::units::volt_t(shooterFeedback.Calculate(
31                       shooterEncoder.GetRate(), setpoint.value())));
32             }),
33             // Wait until the shooter has reached the setpoint, and then
34             // run the feeder
35             wpi::cmd::WaitUntil([this] {
36               return shooterFeedback.AtSetpoint();
37             }).AndThen([this] { feederMotor.SetThrottle(1.0); }))
38      .WithName("Shoot");
39}

A PIDController is declared inside the Shooter subsystem. It is used by ShootCommand alongside a feedforward to spin the shooter flywheel to the specified velocity. Once the PIDController reaches the specified velocity, the ShootCommand runs the feeder.