Combinaison du profilage de mouvement et du PID dans les commandes

Note

Pour une description des fonctionnalités de contrôle PID WPILib utilisées par ces wrappers basés sur des commandes, voir: Contrôle PID dans WPILib.

A common FRC® controls solution is to pair a trapezoidal motion profile for setpoint generation with a PID controller for setpoint tracking. To facilitate this, WPILib includes its own ProfiledPIDController class. The following example is from the RapidReactCommandBot example project (Java, C++) and shows how ProfiledPIDController can be used within the command-based framework to turn a drivetrain to a specified angle:

  5package edu.wpi.first.wpilibj.examples.rapidreactcommandbot.subsystems;
  6
  7import static edu.wpi.first.units.Units.DegreesPerSecond;
  8import static edu.wpi.first.units.Units.Volts;
  9
 10import edu.wpi.first.epilogue.Logged;
 11import edu.wpi.first.epilogue.NotLogged;
 12import edu.wpi.first.math.controller.ProfiledPIDController;
 13import edu.wpi.first.math.controller.SimpleMotorFeedforward;
 14import edu.wpi.first.math.trajectory.TrapezoidProfile;
 15import edu.wpi.first.util.sendable.SendableRegistry;
 16import edu.wpi.first.wpilibj.ADXRS450_Gyro;
 17import edu.wpi.first.wpilibj.Encoder;
 18import edu.wpi.first.wpilibj.RobotController;
 19import edu.wpi.first.wpilibj.drive.DifferentialDrive;
 20import edu.wpi.first.wpilibj.examples.rapidreactcommandbot.Constants.DriveConstants;
 21import edu.wpi.first.wpilibj.motorcontrol.PWMSparkMax;
 22import edu.wpi.first.wpilibj2.command.Command;
 23import edu.wpi.first.wpilibj2.command.SubsystemBase;
 24import java.util.function.DoubleSupplier;
 25
 26@Logged
 27public class Drive extends SubsystemBase {
 28  // The motors on the left side of the drive.
 29  private final PWMSparkMax m_leftLeader = new PWMSparkMax(DriveConstants.kLeftMotor1Port);
 30  private final PWMSparkMax m_leftFollower = new PWMSparkMax(DriveConstants.kLeftMotor2Port);
 31
 32  // The motors on the right side of the drive.
 33  private final PWMSparkMax m_rightLeader = new PWMSparkMax(DriveConstants.kRightMotor1Port);
 34  private final PWMSparkMax m_rightFollower = new PWMSparkMax(DriveConstants.kRightMotor2Port);
 35
 36  // The robot's drive
 37  @NotLogged // Would duplicate motor data, there's no point sending it twice
 38  private final DifferentialDrive m_drive =
 39      new DifferentialDrive(m_leftLeader::set, m_rightLeader::set);
 40
 41  // The left-side drive encoder
 42  private final Encoder m_leftEncoder =
 43      new Encoder(
 44          DriveConstants.kLeftEncoderPorts[0],
 45          DriveConstants.kLeftEncoderPorts[1],
 46          DriveConstants.kLeftEncoderReversed);
 47
 48  // The right-side drive encoder
 49  private final Encoder m_rightEncoder =
 50      new Encoder(
 51          DriveConstants.kRightEncoderPorts[0],
 52          DriveConstants.kRightEncoderPorts[1],
 53          DriveConstants.kRightEncoderReversed);
 54
 55  private final ADXRS450_Gyro m_gyro = new ADXRS450_Gyro();
 56  private final ProfiledPIDController m_controller =
 57      new ProfiledPIDController(
 58          DriveConstants.kTurnP,
 59          DriveConstants.kTurnI,
 60          DriveConstants.kTurnD,
 61          new TrapezoidProfile.Constraints(
 62              DriveConstants.kMaxTurnRateDegPerS,
 63              DriveConstants.kMaxTurnAccelerationDegPerSSquared));
 64  private final SimpleMotorFeedforward m_feedforward =
 65      new SimpleMotorFeedforward(
 66          DriveConstants.ksVolts,
 67          DriveConstants.kvVoltSecondsPerDegree,
 68          DriveConstants.kaVoltSecondsSquaredPerDegree);
 69
 70  /** Creates a new Drive subsystem. */
 71  public Drive() {
 72    SendableRegistry.addChild(m_drive, m_leftLeader);
 73    SendableRegistry.addChild(m_drive, m_rightLeader);
 74
 75    m_leftLeader.addFollower(m_leftFollower);
 76    m_rightLeader.addFollower(m_rightFollower);
 77
 78    // We need to invert one side of the drivetrain so that positive voltages
 79    // result in both sides moving forward. Depending on how your robot's
 80    // gearbox is constructed, you might have to invert the left side instead.
 81    m_rightLeader.setInverted(true);
 82
 83    // Sets the distance per pulse for the encoders
 84    m_leftEncoder.setDistancePerPulse(DriveConstants.kEncoderDistancePerPulse);
 85    m_rightEncoder.setDistancePerPulse(DriveConstants.kEncoderDistancePerPulse);
 86
 87    // Set the controller to be continuous (because it is an angle controller)
 88    m_controller.enableContinuousInput(-180, 180);
 89    // Set the controller tolerance - the delta tolerance ensures the robot is stationary at the
 90    // setpoint before it is considered as having reached the reference
 91    m_controller.setTolerance(
 92        DriveConstants.kTurnToleranceDeg, DriveConstants.kTurnRateToleranceDegPerS);
 93  }
 94
 95  /**
 96   * Returns a command that drives the robot with arcade controls.
 97   *
 98   * @param fwd the commanded forward movement
 99   * @param rot the commanded rotation
100   */
101  public Command arcadeDriveCommand(DoubleSupplier fwd, DoubleSupplier rot) {
102    // A split-stick arcade command, with forward/backward controlled by the left
103    // hand, and turning controlled by the right.
104    return run(() -> m_drive.arcadeDrive(fwd.getAsDouble(), rot.getAsDouble()))
105        .withName("arcadeDrive");
106  }
107
108  /**
109   * Returns a command that drives the robot forward a specified distance at a specified speed.
110   *
111   * @param distanceMeters The distance to drive forward in meters
112   * @param speed The fraction of max speed at which to drive
113   */
114  public Command driveDistanceCommand(double distanceMeters, double speed) {
115    return runOnce(
116            () -> {
117              // Reset encoders at the start of the command
118              m_leftEncoder.reset();
119              m_rightEncoder.reset();
120            })
121        // Drive forward at specified speed
122        .andThen(run(() -> m_drive.arcadeDrive(speed, 0)))
123        // End command when we've traveled the specified distance
124        .until(
125            () ->
126                Math.max(m_leftEncoder.getDistance(), m_rightEncoder.getDistance())
127                    >= distanceMeters)
128        // Stop the drive when the command ends
129        .finallyDo(interrupted -> m_drive.stopMotor());
130  }
131
132  /**
133   * Returns a command that turns to robot to the specified angle using a motion profile and PID
134   * controller.
135   *
136   * @param angleDeg The angle to turn to
137   */
138  public Command turnToAngleCommand(double angleDeg) {
139    return startRun(
140            () -> m_controller.reset(m_gyro.getRotation2d().getDegrees()),
141            () ->
142                m_drive.arcadeDrive(
143                    0,
144                    m_controller.calculate(m_gyro.getRotation2d().getDegrees(), angleDeg)
145                        // Divide feedforward voltage by battery voltage to normalize it to [-1, 1]
146                        + m_feedforward
147                                .calculate(DegreesPerSecond.of(m_controller.getSetpoint().velocity))
148                                .in(Volts)
149                            / RobotController.getBatteryVoltage()))
150        .until(m_controller::atGoal)
151        .finallyDo(() -> m_drive.arcadeDrive(0, 0));
152  }
153}
 5#pragma once
 6
 7#include <functional>
 8
 9#include <frc/ADXRS450_Gyro.h>
10#include <frc/Encoder.h>
11#include <frc/controller/ProfiledPIDController.h>
12#include <frc/controller/SimpleMotorFeedforward.h>
13#include <frc/drive/DifferentialDrive.h>
14#include <frc/motorcontrol/PWMSparkMax.h>
15#include <frc2/command/CommandPtr.h>
16#include <frc2/command/SubsystemBase.h>
17#include <units/angle.h>
18#include <units/length.h>
19
20#include "Constants.h"
21
22class Drive : public frc2::SubsystemBase {
23 public:
24  Drive();
25  /**
26   * Returns a command that drives the robot with arcade controls.
27   *
28   * @param fwd the commanded forward movement
29   * @param rot the commanded rotation
30   */
31  [[nodiscard]]
32  frc2::CommandPtr ArcadeDriveCommand(std::function<double()> fwd,
33                                      std::function<double()> rot);
34
35  /**
36   * Returns a command that drives the robot forward a specified distance at a
37   * specified speed.
38   *
39   * @param distance The distance to drive forward in meters
40   * @param speed The fraction of max speed at which to drive
41   */
42  [[nodiscard]]
43  frc2::CommandPtr DriveDistanceCommand(units::meter_t distance, double speed);
44
45  /**
46   * Returns a command that turns to robot to the specified angle using a motion
47   * profile and PID controller.
48   *
49   * @param angle The angle to turn to
50   */
51  [[nodiscard]]
52  frc2::CommandPtr TurnToAngleCommand(units::degree_t angle);
53
54 private:
55  frc::PWMSparkMax m_leftLeader{DriveConstants::kLeftMotor1Port};
56  frc::PWMSparkMax m_leftFollower{DriveConstants::kLeftMotor2Port};
57  frc::PWMSparkMax m_rightLeader{DriveConstants::kRightMotor1Port};
58  frc::PWMSparkMax m_rightFollower{DriveConstants::kRightMotor2Port};
59
60  frc::DifferentialDrive m_drive{
61      [&](double output) { m_leftLeader.Set(output); },
62      [&](double output) { m_rightLeader.Set(output); }};
63
64  frc::Encoder m_leftEncoder{DriveConstants::kLeftEncoderPorts[0],
65                             DriveConstants::kLeftEncoderPorts[1],
66                             DriveConstants::kLeftEncoderReversed};
67  frc::Encoder m_rightEncoder{DriveConstants::kRightEncoderPorts[0],
68                              DriveConstants::kRightEncoderPorts[1],
69                              DriveConstants::kRightEncoderReversed};
70
71  frc::ADXRS450_Gyro m_gyro;
72
73  frc::ProfiledPIDController<units::radians> m_controller{
74      DriveConstants::kTurnP,
75      DriveConstants::kTurnI,
76      DriveConstants::kTurnD,
77      {DriveConstants::kMaxTurnRate, DriveConstants::kMaxTurnAcceleration}};
78  frc::SimpleMotorFeedforward<units::radians> m_feedforward{
79      DriveConstants::ks, DriveConstants::kv, DriveConstants::ka};
80};
 5#include "subsystems/Drive.h"
 6
 7#include <utility>
 8
 9#include <frc/RobotController.h>
10#include <frc2/command/Commands.h>
11
12Drive::Drive() {
13  wpi::SendableRegistry::AddChild(&m_drive, &m_leftLeader);
14  wpi::SendableRegistry::AddChild(&m_drive, &m_rightLeader);
15
16  m_leftLeader.AddFollower(m_leftFollower);
17  m_rightLeader.AddFollower(m_rightFollower);
18
19  // We need to invert one side of the drivetrain so that positive voltages
20  // result in both sides moving forward. Depending on how your robot's
21  // gearbox is constructed, you might have to invert the left side instead.
22  m_rightLeader.SetInverted(true);
23
24  // Sets the distance per pulse for the encoders
25  m_leftEncoder.SetDistancePerPulse(DriveConstants::kEncoderDistancePerPulse);
26  m_rightEncoder.SetDistancePerPulse(DriveConstants::kEncoderDistancePerPulse);
27
28  // Set the controller to be continuous (because it is an angle controller)
29  m_controller.EnableContinuousInput(-180_deg, 180_deg);
30  // Set the controller tolerance - the delta tolerance ensures the robot is
31  // stationary at the setpoint before it is considered as having reached the
32  // reference
33  m_controller.SetTolerance(DriveConstants::kTurnTolerance,
34                            DriveConstants::kTurnRateTolerance);
35}
36
37frc2::CommandPtr Drive::ArcadeDriveCommand(std::function<double()> fwd,
38                                           std::function<double()> rot) {
39  return Run([this, fwd = std::move(fwd), rot = std::move(rot)] {
40           m_drive.ArcadeDrive(fwd(), rot());
41         })
42      .WithName("ArcadeDrive");
43}
44
45frc2::CommandPtr Drive::DriveDistanceCommand(units::meter_t distance,
46                                             double speed) {
47  return RunOnce([this] {
48           // Reset encoders at the start of the command
49           m_leftEncoder.Reset();
50           m_rightEncoder.Reset();
51         })
52      // Drive forward at specified speed
53      .AndThen(Run([this, speed] { m_drive.ArcadeDrive(speed, 0.0); }))
54      .Until([this, distance] {
55        return units::math::max(units::meter_t(m_leftEncoder.GetDistance()),
56                                units::meter_t(m_rightEncoder.GetDistance())) >=
57               distance;
58      })
59      // Stop the drive when the command ends
60      .FinallyDo([this](bool interrupted) { m_drive.StopMotor(); });
61}
62
63frc2::CommandPtr Drive::TurnToAngleCommand(units::degree_t angle) {
64  return StartRun(
65             [this] { m_controller.Reset(m_gyro.GetRotation2d().Degrees()); },
66             [this, angle] {
67               m_drive.ArcadeDrive(
68                   0, m_controller.Calculate(m_gyro.GetRotation2d().Degrees(),
69                                             angle) +
70                          // Divide feedforward voltage by battery voltage to
71                          // normalize it to [-1, 1]
72                          m_feedforward.Calculate(
73                              m_controller.GetSetpoint().velocity) /
74                              frc::RobotController::GetBatteryVoltage());
75             })
76      .Until([this] { return m_controller.AtGoal(); })
77      .FinallyDo([this] { m_drive.ArcadeDrive(0, 0); });
78}

turnToAngleCommand uses a ProfiledPIDController to smoothly turn the drivetrain. The startRun command factory is used to reset the ProfiledPIDController when the command is scheduled to avoid unwanted behavior, and to calculate PID and feedforward outputs to pass into the arcadeDrive method in order to drive the robot. The command is decorated using the until decorator to end the command when the ProfiledPIDController is finished with the profile. To ensure the drivetrain stops when the command ends, the finallyDo decorator is used to stop the drivetrain by setting the speed to zero.