Combinando Motion Profiling y PID Basado en Comandos
Nota
Para obtener una descripción de las funciones de control de WPILib PID utilizadas por estos contenedores basados en comandos, consulte Control PID en WPILib.
Una solución común de controles FRC® es emparejar un perfil de movimiento trapezoidal para la generación de puntos de ajuste con un controlador PID para el seguimiento de puntos de ajuste. Para facilitar esto, WPILib incluye su propia clase ProfiledPIDController. Para ayudar aún más a los equipos a integrar esta funcionalidad en sus robots, el marco basado en comandos contiene dos envoltorios de conveniencia para la clase ProfiledPIDController
: ProfiledPIDSubsystem
, que integra el controlador en un subsistema, y ProfiledPIDCommand
, que integra el controlador en un comando.
Subsistema ProfiledPIDS
Nota
En C++, la clase ProfiledPIDSubsystem
se basa en el tipo de unidad utilizada para las mediciones de distancia, que puede ser angular o lineal. Los valores pasados deben tener unidades consistentes con las unidades de distancia, o se lanzará un error en tiempo de compilación. Para obtener más información sobre las unidades C++, consulte Biblioteca de unidades de C++.
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.
Creación de un subsistema ProfiledPIDS
Nota
If periodic
is overridden when inheriting from ProfiledPIDSubsystem
, make sure to call super.periodic()
! Otherwise, control functionality will not work properly.
Al subclasificar ProfiledPIDSubsystem
, los usuarios deben anular dos métodos abstractos para proporcionar la funcionalidad que la clase utilizará en su operación ordinaria:
getMeasurement()
protected abstract double getMeasurement();
virtual Distance_t GetMeasurement() = 0;
El método getMeasurement
devuelve la medición actual de la variable de proceso. El PIDSubsystem
llamará automáticamente a este método desde su bloque periodic()
y pasará su valor al bucle de control.
Los usuarios deben anular este método para devolver cualquier lectura del sensor que deseen utilizar como medida de la variable de proceso.
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.
Los usuarios deben anular este método para pasar la salida de control calculada final a los motores de su subsistema.
Pasando el controlador
Los usuarios también deben pasar un ProfiledPIDController
a la clase base ProfiledPIDSubsystem
a través de la llamada al constructor de superclase de su subclase. Esto sirve para especificar las ganancias de PID, las restricciones del perfil de movimiento y el período (si el usuario está utilizando un período de bucle de robot principal no estándar).
Se pueden realizar modificaciones adicionales (por ejemplo, habilitar la entrada continua) al controlador en el cuerpo del constructor llamando a getController()
.
Uso de un subsistema ProfiledPIDS
Una vez que se ha creado una instancia de una subclase PIDSubsystem
, los comandos pueden usarla a través de los siguientes métodos:
setGoal()
Nota
Si desea establecer el objetivo en una distancia simple con una velocidad objetivo implícita de cero, existe una sobrecarga de setGoal()
que toma un valor de distancia único, en lugar de un estado de perfil de movimiento completo.
El método setGoal()
se puede utilizar para establecer el punto de ajuste del PIDSubsystem
. El subsistema rastreará automáticamente el punto de ajuste usando la salida definida:
// 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() y disable()
Los métodos enable()
y disable()
habilitan y deshabilitan el control automático del ProfiledPIDSubsystem
. Cuando el subsistema está habilitado, ejecutará automáticamente el perfil de movimiento y el bucle de control y seguirá hasta la meta. Cuando está deshabilitado, no se realiza ningún control.
Además, el método enable()
restablece el ProfiledPIDController
interno, y el método``disable()`` llama al método useOutput() definido por el usuario con la salida y el punto de ajuste establecidos en 0
.
Ejemplo completo de subsistema ProfiledPIDS
¿Qué aspecto tiene un subsistema PIDS cuando se utiliza en la práctica? Los siguientes ejemplos están tomados del proyecto de ejemplo ArmBot (Java, 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}
Usar un ProfiledPIDSubsystem
con comandos puede ser muy simple:
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}));
Comando ProfiledPID
Nota
En C++, la clase ProfiledPIDCommand
se basa en el tipo de unidad utilizado para las mediciones de distancia, que puede ser angular o lineal. Los valores pasados deben tener unidades consistentes con las unidades de distancia, o se lanzará un error en tiempo de compilación. Para obtener más información sobre las unidades C++, consulte Biblioteca de unidades de C++.
The ProfiledPIDCommand
class (Java, C++) allows users to easily create commands with a built-in ProfiledPIDController.
Creación de un comando PID
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.
Nota
If subclassing ProfiledPIDCommand
and overriding any methods, make sure to call the super
version of those methods! Otherwise, control functionality will not work properly.
En cualquier caso, se crea un ProfiledPIDCommand
pasando los parámetros necesarios a su constructor (si se define una subclase, esto se puede hacer con una llamada 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 = {})
controlador
El parámetro controller
es el objeto ProfiledPIDController
que será utilizado por el comando. Al pasar esto, los usuarios pueden especificar las ganancias de PID, las restricciones del perfil de movimiento y el período para el controlador (si el usuario está utilizando un período de bucle de robot principal no estándar).
Al subclasificar ProfiledPIDCommand
, se pueden realizar modificaciones adicionales (por ejemplo, habilitar la entrada continua) al controlador en el cuerpo del constructor llamando a 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
.
Al subclasificar ProfiledPIDCommand
, los usuarios avanzados pueden modificar aún más el proveedor de medidas modificando el campo m_measurement
de la clase.
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.
Al subclasificar ProfiledPIDCommand
, los usuarios avanzados pueden modificar aún más el proveedor del punto de ajuste modificando el campo m_goal
de la clase.
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
.
Al subclasificar ProfiledPIDCommand
, los usuarios avanzados pueden modificar aún más el consumidor de salida modificando el campo m_useOutput
de la clase.
requisitos
Como todos los comandos en línea, ProfiledPIDCommand
permite al usuario especificar los requisitos de su subsistema como un parámetro de constructor.
Ejemplo completo de ProfiledPIDCommand
¿Qué aspecto tiene un ProfiledPIDCommand
cuando se utiliza en la práctica? Los siguientes ejemplos proceden del proyecto de ejemplo GyroDriveCommands (Java, C++):
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}