在基于指令中将运动分析和PID结合

备注

有关这些基于指令的包装程序使用的WPILib PID控制功能的描述,请参阅:ref:docs/software/advanced-controls/controllers/pidcontroller:PID Control in WPILib

常见的FRC | reg |控制解决方案是将用于设定值生成的梯形运动曲线与用于设定值跟踪的PID控制器配对。为此,WPILib包含了自己的:ref:ProfiledPIDController <docs/software/advanced-controls/controllers/profiled-pidcontroller:Combining Motion Profiling and PID Control with ProfiledPIDController> 类。为了进一步帮助团队将该功能集成到他们的机器人中,基于指令的框架为“ProfiledPIDController”类提供了两个便捷包装:“ProfiledPIDSubsystem”将控制器集成到子系统中以及“ProfiledPIDCommand”,它将控制器集成到指令中。

ProfiledPIDSubsystem

备注

在C ++中,“ProfiledPIDSubsystem”类在用于距离测量的单位类型(可能是角度的或线性的)上模板化。传入的值*必须*具有与距离单位一致的单位,否则将引发编译时错误。有关C ++单元的更多信息,请参阅:ref:docs/software/basic-programming/cpp-units:The C++ Units Library

The ProfiledPIDSubsystem class (Java, C++) allows users to conveniently create a subsystem with a built-in PIDController. In order to use the ProfiledPIDSubsystem class, users must create a subclass of it.

创建一个ProfiledPIDSubsystem

备注

If periodic is overridden when inheriting from ProfiledPIDSubsystem, make sure to call super.periodic()! Otherwise, control functionality will not work properly.

子类化”ProfiledPIDSubsystem”时,用户必须重写两个抽象方法以提供该类将在其常规操作中使用的功能:

getMeasurement()

  protected abstract double getMeasurement();
  virtual Distance_t GetMeasurement() = 0;

“getMeasurement”方法返回过程变量的当前测量值。 “PIDSubsystem”将自动从其“periodic()”块中调用此方法,并将其值传递给控制循环。

用户应重写此方法,以返回他们希望用作过程变量测量的任何传感器读数。

useOutput()

  protected abstract void useOutput(double output, State setpoint);
  virtual void UseOutput(double output, State setpoint) = 0;

The useOutput() method consumes the output of the Profiled PID controller, and the current setpoint state (which is often useful for computing a feedforward). The PIDSubsystem will automatically call this method from its periodic() block, and pass it the computed output of the control loop.

用户应重写此方法,以将最终计算出的控制输出传递给其子系统的电机。

传递至控制器

用户还必须通过子类的超类构造函数调用将“ProfiledPIDController”传递给“ProfiledPIDSubsystem”基类。这用于指定PID增益,运动曲线约束和周期(如果用户使用的是非标准主机器人循环周期)。

可以通过调用“getController()”在构造函数主体中对控制器进行其他修改(例如启用连续输入)。

使用ProfiledPIDSubsystem

一旦创建了“PIDSubsystem”子类的实例,命令就可以通过以下方法使用它:

setGoal()

备注

如果希望将目标设置为隐式目标速度为零的简单距离,则存在“setGoal()”重载,该重载采用单个距离值而不是完整的运动轮廓状态。

“setGoal()”方法可用于设置“PIDSubsystem”的设定值。子系统将使用定义的输出自动跟踪到设定点:

// The subsystem will track to a goal of 5 meters and velocity of 3 meters per second.
examplePIDSubsystem.setGoal(5, 3);
// The subsystem will track to a goal of 5 meters and velocity of 3 meters per second.
examplePIDSubsystem.SetGoal({5_m, 3_mps});

enable()和disable()

“enable()”和“disable()”方法启用和禁用“ProfiledPIDSubsystem’”的自动控制。启用子系统后,它将自动运行运动曲线和控制循环并跟踪目标。禁用时,不执行任何控制。

另外,“enable()”方法重置内部ProfileDPIDController,“disable()”方法调用用户定义的`useOutput()`_方法,将output和setpoint都设置为“0”。

完整ProfiledPIDSubsystem示例

在实际使用中,PIDSubsystem是什么样的?以下示例摘自ArmBot示例项目(Java <https://github.com/wpilibsuite/allwpilib/tree/main/wpilibjExamples/src/main/java/edu/wpi/first/wpilibj/examples/armbot>`__,C ++):

 5package edu.wpi.first.wpilibj.examples.armbot.subsystems;
 6
 7import edu.wpi.first.math.controller.ArmFeedforward;
 8import edu.wpi.first.math.controller.ProfiledPIDController;
 9import edu.wpi.first.math.trajectory.TrapezoidProfile;
10import edu.wpi.first.wpilibj.Encoder;
11import edu.wpi.first.wpilibj.examples.armbot.Constants.ArmConstants;
12import edu.wpi.first.wpilibj.motorcontrol.PWMSparkMax;
13import edu.wpi.first.wpilibj2.command.ProfiledPIDSubsystem;
14
15/** A robot arm subsystem that moves with a motion profile. */
16public class ArmSubsystem extends ProfiledPIDSubsystem {
17  private final PWMSparkMax m_motor = new PWMSparkMax(ArmConstants.kMotorPort);
18  private final Encoder m_encoder =
19      new Encoder(ArmConstants.kEncoderPorts[0], ArmConstants.kEncoderPorts[1]);
20  private final ArmFeedforward m_feedforward =
21      new ArmFeedforward(
22          ArmConstants.kSVolts, ArmConstants.kGVolts,
23          ArmConstants.kVVoltSecondPerRad, ArmConstants.kAVoltSecondSquaredPerRad);
24
25  /** Create a new ArmSubsystem. */
26  public ArmSubsystem() {
27    super(
28        new ProfiledPIDController(
29            ArmConstants.kP,
30            0,
31            0,
32            new TrapezoidProfile.Constraints(
33                ArmConstants.kMaxVelocityRadPerSecond,
34                ArmConstants.kMaxAccelerationRadPerSecSquared)),
35        0);
36    m_encoder.setDistancePerPulse(ArmConstants.kEncoderDistancePerPulse);
37    // Start arm at rest in neutral position
38    setGoal(ArmConstants.kArmOffsetRads);
39  }
40
41  @Override
42  public void useOutput(double output, TrapezoidProfile.State setpoint) {
43    // Calculate the feedforward from the sepoint
44    double feedforward = m_feedforward.calculate(setpoint.position, setpoint.velocity);
45    // Add the feedforward to the PID output to get the motor output
46    m_motor.setVoltage(output + feedforward);
47  }
48
49  @Override
50  public double getMeasurement() {
51    return m_encoder.getDistance() + ArmConstants.kArmOffsetRads;
52  }
53}
 5#pragma once
 6
 7#include <frc/Encoder.h>
 8#include <frc/controller/ArmFeedforward.h>
 9#include <frc/motorcontrol/PWMSparkMax.h>
10#include <frc2/command/ProfiledPIDSubsystem.h>
11#include <units/angle.h>
12
13/**
14 * A robot arm subsystem that moves with a motion profile.
15 */
16class ArmSubsystem : public frc2::ProfiledPIDSubsystem<units::radians> {
17  using State = frc::TrapezoidProfile<units::radians>::State;
18
19 public:
20  ArmSubsystem();
21
22  void UseOutput(double output, State setpoint) override;
23
24  units::radian_t GetMeasurement() override;
25
26 private:
27  frc::PWMSparkMax m_motor;
28  frc::Encoder m_encoder;
29  frc::ArmFeedforward m_feedforward;
30};
 5#include "subsystems/ArmSubsystem.h"
 6
 7#include "Constants.h"
 8
 9using namespace ArmConstants;
10using State = frc::TrapezoidProfile<units::radians>::State;
11
12ArmSubsystem::ArmSubsystem()
13    : frc2::ProfiledPIDSubsystem<units::radians>(
14          frc::ProfiledPIDController<units::radians>(
15              kP, 0, 0, {kMaxVelocity, kMaxAcceleration})),
16      m_motor(kMotorPort),
17      m_encoder(kEncoderPorts[0], kEncoderPorts[1]),
18      m_feedforward(kS, kG, kV, kA) {
19  m_encoder.SetDistancePerPulse(kEncoderDistancePerPulse.value());
20  // Start arm in neutral position
21  SetGoal(State{kArmOffset, 0_rad_per_s});
22}
23
24void ArmSubsystem::UseOutput(double output, State setpoint) {
25  // Calculate the feedforward from the sepoint
26  units::volt_t feedforward =
27      m_feedforward.Calculate(setpoint.position, setpoint.velocity);
28  // Add the feedforward to the PID output to get the motor output
29  m_motor.SetVoltage(units::volt_t{output} + feedforward);
30}
31
32units::radian_t ArmSubsystem::GetMeasurement() {
33  return units::radian_t{m_encoder.GetDistance()} + kArmOffset;
34}

通过命令使用“ProfiledPIDSubsystem”可以非常简单:

55    // Move the arm to 2 radians above horizontal when the 'A' button is pressed.
56    m_driverController
57        .a()
58        .onTrue(
59            Commands.runOnce(
60                () -> {
61                  m_robotArm.setGoal(2);
62                  m_robotArm.enable();
63                },
64                m_robotArm));
32  // Move the arm to 2 radians above horizontal when the 'A' button is pressed.
33  m_driverController.A().OnTrue(frc2::cmd::RunOnce(
34      [this] {
35        m_arm.SetGoal(2_rad);
36        m_arm.Enable();
37      },
38      {&m_arm}));

ProfiledPIDCommand

备注

在C ++中,“ProfiledPIDCommand”类以用于距离测量的单位类型为模板,该单位类型可以是角度或线性的。传入的值*必须*具有与距离单位一致的单位,否则将引发编译时错误。有关C ++单元的更多信息,请参阅:ref:docs/software/basic-programming/cpp-units:The C++ Units Library

The ProfiledPIDCommand class (Java, C++) allows users to easily create commands with a built-in ProfiledPIDController.

创建一个PIDCommand

A ProfiledPIDCommand can be created two ways - by subclassing the ProfiledPIDCommand class, or by defining the command inline. Both methods ultimately extremely similar, and ultimately the choice of which to use comes down to where the user desires that the relevant code be located.

备注

If subclassing ProfiledPIDCommand and overriding any methods, make sure to call the super version of those methods! Otherwise, control functionality will not work properly.

无论哪种情况,都通过向构造函数传递必要的参数来创建“ProfileDPIDCommand”(如果定义了子类,则可以通过调用“super()”来完成):

29  /**
30   * Creates a new PIDCommand, which controls the given output with a ProfiledPIDController. Goal
31   * velocity is specified.
32   *
33   * @param controller the controller that controls the output.
34   * @param measurementSource the measurement of the process variable
35   * @param goalSource the controller's goal
36   * @param useOutput the controller's output
37   * @param requirements the subsystems required by this command
38   */
39  public ProfiledPIDCommand(
40      ProfiledPIDController controller,
41      DoubleSupplier measurementSource,
42      Supplier<State> goalSource,
43      BiConsumer<Double, State> useOutput,
44      Subsystem... requirements) {
38  /**
39   * Creates a new PIDCommand, which controls the given output with a
40   * ProfiledPIDController.
41   *
42   * @param controller        the controller that controls the output.
43   * @param measurementSource the measurement of the process variable
44   * @param goalSource   the controller's goal
45   * @param useOutput         the controller's output
46   * @param requirements      the subsystems required by this command
47   */
48  ProfiledPIDCommand(frc::ProfiledPIDController<Distance> controller,
49                     std::function<Distance_t()> measurementSource,
50                     std::function<State()> goalSource,
51                     std::function<void(double, State)> useOutput,
52                     Requirements requirements = {})

controller

“controller”参数是在指令中将使用的“ProfiledPIDController”对象。通过传递此参数,用户可以指定PID增益,运动曲线约束和控制器的周期(如果用户使用的是非标准的主机器人循环周期)。

当将“ProfiledPIDCommand”子类化时,可以通过调用“getController()”对构造函数主体中的控制器进行其他修改(例如,启用连续输入)。

measurementSource

The measurementSource parameter is a function (usually passed as a lambda) that returns the measurement of the process variable. Passing in the measurementSource function in ProfiledPIDCommand is functionally analogous to overriding the getMeasurement() function in ProfiledPIDSubsystem.

在子类化“ProfiledPIDCommand”时,高级用户可以通过修改类的“m_measurement”字段来进一步修改度量提供者。

goalSource

The goalSource parameter is a function (usually passed as a lambda) that returns the current goal state for the mechanism. If only a constant goal is needed, an overload exists that takes a constant goal rather than a supplier. Additionally, if goal velocities are desired to be zero, overloads exist that take a constant distance rather than a full profile state.

当子类化“ProfiledPIDCommand”时,高级用户可以通过修改类的“m_goal”字段来进一步修改设定值供应商。

useOutput

The useOutput parameter is a function (usually passed as a lambda) that consumes the output and setpoint state of the control loop. Passing in the useOutput function in ProfiledPIDCommand is functionally analogous to overriding the useOutput() function in ProfiledPIDSubsystem.

在子类化“ProfiledPIDCommand”时,高级用户可以通过修改类的“m_useOutput”字段来进一步修改输出使用者。

requirements

像所有内联命令一样,“ProfiledPIDCommand”允许用户将其子系统要求指定为构造函数参数。

完整ProfiledPIDCommand示例

在实际使用中,“ ProfiledPIDCommand”是什么样的?以下示例来自GyroDriveCommands示例项目(JavaC ++):

 5package edu.wpi.first.wpilibj.examples.gyrodrivecommands.commands;
 6
 7import edu.wpi.first.math.controller.ProfiledPIDController;
 8import edu.wpi.first.math.trajectory.TrapezoidProfile;
 9import edu.wpi.first.wpilibj.examples.gyrodrivecommands.Constants.DriveConstants;
10import edu.wpi.first.wpilibj.examples.gyrodrivecommands.subsystems.DriveSubsystem;
11import edu.wpi.first.wpilibj2.command.ProfiledPIDCommand;
12
13/** A command that will turn the robot to the specified angle using a motion profile. */
14public class TurnToAngleProfiled extends ProfiledPIDCommand {
15  /**
16   * Turns to robot to the specified angle using a motion profile.
17   *
18   * @param targetAngleDegrees The angle to turn to
19   * @param drive The drive subsystem to use
20   */
21  public TurnToAngleProfiled(double targetAngleDegrees, DriveSubsystem drive) {
22    super(
23        new ProfiledPIDController(
24            DriveConstants.kTurnP,
25            DriveConstants.kTurnI,
26            DriveConstants.kTurnD,
27            new TrapezoidProfile.Constraints(
28                DriveConstants.kMaxTurnRateDegPerS,
29                DriveConstants.kMaxTurnAccelerationDegPerSSquared)),
30        // Close loop on heading
31        drive::getHeading,
32        // Set reference to target
33        targetAngleDegrees,
34        // Pipe output to turn robot
35        (output, setpoint) -> drive.arcadeDrive(0, output),
36        // Require the drive
37        drive);
38
39    // Set the controller to be continuous (because it is an angle controller)
40    getController().enableContinuousInput(-180, 180);
41    // Set the controller tolerance - the delta tolerance ensures the robot is stationary at the
42    // setpoint before it is considered as having reached the reference
43    getController()
44        .setTolerance(DriveConstants.kTurnToleranceDeg, DriveConstants.kTurnRateToleranceDegPerS);
45  }
46
47  @Override
48  public boolean isFinished() {
49    // End when the controller is at the reference.
50    return getController().atGoal();
51  }
52}
 5#pragma once
 6
 7#include <frc2/command/CommandHelper.h>
 8#include <frc2/command/ProfiledPIDCommand.h>
 9
10#include "subsystems/DriveSubsystem.h"
11
12/**
13 * A command that will turn the robot to the specified angle using a motion
14 * profile.
15 */
16class TurnToAngleProfiled
17    : public frc2::CommandHelper<frc2::ProfiledPIDCommand<units::radians>,
18                                 TurnToAngleProfiled> {
19 public:
20  /**
21   * Turns to robot to the specified angle using a motion profile.
22   *
23   * @param targetAngleDegrees The angle to turn to
24   * @param drive              The drive subsystem to use
25   */
26  TurnToAngleProfiled(units::degree_t targetAngleDegrees,
27                      DriveSubsystem* drive);
28
29  bool IsFinished() override;
30};
 5#include "commands/TurnToAngleProfiled.h"
 6
 7#include <frc/controller/ProfiledPIDController.h>
 8
 9using namespace DriveConstants;
10
11TurnToAngleProfiled::TurnToAngleProfiled(units::degree_t target,
12                                         DriveSubsystem* drive)
13    : CommandHelper{
14          frc::ProfiledPIDController<units::radians>{
15              kTurnP, kTurnI, kTurnD, {kMaxTurnRate, kMaxTurnAcceleration}},
16          // Close loop on heading
17          [drive] { return drive->GetHeading(); },
18          // Set reference to target
19          target,
20          // Pipe output to turn robot
21          [drive](double output, auto setpointState) {
22            drive->ArcadeDrive(0, output);
23          },
24          // Require the drive
25          {drive}} {
26  // Set the controller to be continuous (because it is an angle controller)
27  GetController().EnableContinuousInput(-180_deg, 180_deg);
28  // Set the controller tolerance - the delta tolerance ensures the robot is
29  // stationary at the setpoint before it is considered as having reached the
30  // reference
31  GetController().SetTolerance(kTurnTolerance, kTurnRateTolerance);
32
33  AddRequirements(drive);
34}
35
36bool TurnToAngleProfiled::IsFinished() {
37  return GetController().AtGoal();
38}