Perfil de movimiento trapezoidal en WPILib
Nota
Este artículo cubre la generación en código de perfil de movimiento trapezoidal. A continuación se presentan más detalles que describen los conceptos involucrados.
Nota
For a guide on implementing the TrapezoidProfile
class in the command-based framework framework, see Motion Profiling in Command-based.
Nota
La clase TrapezoidProfile
, utilizada por su cuenta, es más útil cuando se compone con un controlador personalizado (tales como un controlador de motor «inteligente» con una funcionalidad PID integrada). Para integrarlo con un WPILib ControladorPID
, vea Combinando perfiles de movimiento y control de PID con ProfiledPIDController.
Mientras el avance y el control de la retroalimentación ofrecen maneras convenientes de lograr un punto fijo, estamos constantemente enfrentándonos al problema de la generación de un punto fijo para nuestros mecanismos. Si bien el enfoque ingenuo de comandar inmediatamente un mecanismo a su estado deseado puede funcionar, usualmente no es óptimo. Para mejorar el manejo de nuestros mecanismos, normalmente deseamos ordenar mecanismos a una secuencia de puntos fijos que interpolan suavemente entre su estado actual, y su estado deseado.
To help users do this, WPILib provides a TrapezoidProfile
class (Java, C++, Python
).
Creando un Perfil Trapezoidal
Nota
En C++, la clase TrapezoidProfile
está basada en el tipo de unidad utilizada para medidas de distancia, las cuales pueden ser angulares o lineales. Los valores pasados deben tener unidades consistentes con las unidades de distancia, o se producirá un error de compilación de tiempo. Para más información de las unidades C++, vea Biblioteca de unidades de C++.
Restricciones
Nota
Los diversos feedforward helper classes proporcionan métodos para calcular la máxima velocidad simultáneamente alcanzable y la aceleración de un mecanismo. Esto puede ser muy útil para calcular restricciones de movimientos adecuadas para ``Perfil Trapezoidal`.
In order to create a trapezoidal motion profile, we must first impose some constraints on the desired motion. Namely, we must specify a maximum velocity and acceleration that the mechanism will be expected to achieve during the motion. To do this, we create an instance of the TrapezoidProfile.Constraints
class (Java, C++, Python
):
// Creates a new set of trapezoidal motion profile constraints
// Max velocity of 10 meters per second
// Max acceleration of 20 meters per second squared
new TrapezoidProfile.Constraints(10, 20);
// Creates a new set of trapezoidal motion profile constraints
// Max velocity of 10 meters per second
// Max acceleration of 20 meters per second squared
frc::TrapezoidProfile<units::meters>::Constraints{10_mps, 20_mps_sq};
from wpimath.trajectory import TrapezoidProfile
# Creates a new set of trapezoidal motion profile constraints
# Max velocity of 10 meters per second
# Max acceleration of 20 meters per second squared
TrapezoidProfile.Constraints(10, 20)
Estados de Inicio y Fin
Next, we must specify the desired starting and ending states for our mechanisms using the TrapezoidProfile.State
class (Java, C++, Python
). Each state has a position and a velocity:
// Creates a new state with a position of 5 meters
// and a velocity of 0 meters per second
new TrapezoidProfile.State(5, 0);
// Creates a new state with a position of 5 meters
// and a velocity of 0 meters per second
frc::TrapezoidProfile<units::meters>::State{5_m, 0_mps};
from wpimath.trajectory import TrapezoidProfile
# Creates a new state with a position of 5 meters
# and a velocity of 0 meters per second
TrapezoidProfile.State(5, 0)
Poniéndolo todo junto
Nota
C++ normalmente es capaz de inferir el tipo de las clases internas y, por lo tanto, se puede enviar una lista de inicializadores simple (sin el nombre de clase) como parámetro. Los nombres completos de la clase están incluidos en el siguiente ejemplo.
Now that we know how to create a set of constraints and the desired start/end states, we are ready to create our motion profile. The TrapezoidProfile
constructor takes 1 parameter: the constraints.
// Creates a new TrapezoidProfile
// Profile will have a max vel of 5 meters per second
// Profile will have a max acceleration of 10 meters per second squared
TrapezoidProfile profile = new TrapezoidProfile(new TrapezoidProfile.Constraints(5, 10));
// Creates a new TrapezoidProfile
// Profile will have a max vel of 5 meters per second
// Profile will have a max acceleration of 10 meters per second squared
frc::TrapezoidProfile<units::meters> profile{
frc::TrapezoidProfile<units::meters>::Constraints{5_mps, 10_mps_sq}};
from wpimath.trajectory import TrapezoidProfile
# Creates a new TrapezoidProfile
# Profile will have a max vel of 5 meters per second
# Profile will have a max acceleration of 10 meters per second squared
profile = TrapezoidProfile(TrapezoidProfile.Constraints(5, 10))
Usando un Perfil Trapezoidal
Muestreo del Perfil
Once we’ve created a TrapezoidProfile
, using it is very simple: to get the profile state at the given time after the profile has started, call the calculate()
method with the goal state and initial state:
// Profile will start stationary at zero position
// Profile will end stationary at 5 meters
// Returns the motion profile state after 5 seconds of motion
profile.calculate(5, new TrapezoidProfile.State(0, 0), new TrapezoidProfile.State(5, 0));
// Profile will start stationary at zero position
// Profile will end stationary at 5 meters
// Returns the motion profile state after 5 seconds of motion
profile.Calculate(5_s,
frc::TrapezoidProfile<units::meters>::State{0_m, 0_mps},
frc::TrapezoidProfile<units::meters>::State{5_m, 0_mps});
# Profile will start stationary at zero position
# Profile will end stationary at 5 meters
# Returns the motion profile state after 5 seconds of motion
profile.calculate(5, TrapezoidProfile.State(0, 0), TrapezoidProfile.State(5, 0))
Usando el Estado
The calculate
method returns a TrapezoidProfile.State
class (the same one that was used to specify the initial/end states when calculating the profile state). To use this for actual control, simply pass the contained position and velocity values to whatever controller you wish (for example, a PIDController):
var setpoint = profile.calculate(elapsedTime, initialState, goalState);
controller.calculate(encoder.getDistance(), setpoint.position);
auto setpoint = profile.Calculate(elapsedTime, initialState, goalState);
controller.Calculate(encoder.GetDistance(), setpoint.position.value());
setpoint = profile.calculate(elapsedTime, initialState, goalState)
controller.calculate(encoder.getDistance(), setpoint.position)
Ejemplo de Uso Completo
Nota
In this example, the initial state is re-computed every timestep. This is a somewhat different usage technique than is detailed above, but works according to the same principles - the profile is sampled at a time corresponding to the loop period to get the setpoint for the next loop iteration.
A more complete example of TrapezoidProfile
usage is provided in the ElevatorTrapezoidProfile example project (Java, C++, Python):
5package edu.wpi.first.wpilibj.examples.elevatortrapezoidprofile;
6
7import static edu.wpi.first.units.Units.MetersPerSecond;
8import static edu.wpi.first.units.Units.Volts;
9
10import edu.wpi.first.math.controller.SimpleMotorFeedforward;
11import edu.wpi.first.math.trajectory.TrapezoidProfile;
12import edu.wpi.first.wpilibj.Joystick;
13import edu.wpi.first.wpilibj.TimedRobot;
14
15public class Robot extends TimedRobot {
16 private static double kDt = 0.02;
17
18 private final Joystick m_joystick = new Joystick(1);
19 private final ExampleSmartMotorController m_motor = new ExampleSmartMotorController(1);
20 // Note: These gains are fake, and will have to be tuned for your robot.
21 private final SimpleMotorFeedforward m_feedforward = new SimpleMotorFeedforward(1, 1.5);
22
23 // Create a motion profile with the given maximum velocity and maximum
24 // acceleration constraints for the next setpoint.
25 private final TrapezoidProfile m_profile =
26 new TrapezoidProfile(new TrapezoidProfile.Constraints(1.75, 0.75));
27 private TrapezoidProfile.State m_goal = new TrapezoidProfile.State();
28 private TrapezoidProfile.State m_setpoint = new TrapezoidProfile.State();
29
30 public Robot() {
31 // Note: These gains are fake, and will have to be tuned for your robot.
32 m_motor.setPID(1.3, 0.0, 0.7);
33 }
34
35 @Override
36 public void teleopPeriodic() {
37 if (m_joystick.getRawButtonPressed(2)) {
38 m_goal = new TrapezoidProfile.State(5, 0);
39 } else if (m_joystick.getRawButtonPressed(3)) {
40 m_goal = new TrapezoidProfile.State();
41 }
42
43 // Retrieve the profiled setpoint for the next timestep. This setpoint moves
44 // toward the goal while obeying the constraints.
45 m_setpoint = m_profile.calculate(kDt, m_setpoint, m_goal);
46
47 // Send setpoint to offboard controller PID
48 m_motor.setSetpoint(
49 ExampleSmartMotorController.PIDMode.kPosition,
50 m_setpoint.position,
51 m_feedforward.calculate(MetersPerSecond.of(m_setpoint.velocity)).in(Volts) / 12.0);
52 }
53}
5#include <numbers>
6
7#include <frc/Joystick.h>
8#include <frc/TimedRobot.h>
9#include <frc/controller/SimpleMotorFeedforward.h>
10#include <frc/trajectory/TrapezoidProfile.h>
11#include <units/acceleration.h>
12#include <units/length.h>
13#include <units/time.h>
14#include <units/velocity.h>
15#include <units/voltage.h>
16
17#include "ExampleSmartMotorController.h"
18
19class Robot : public frc::TimedRobot {
20 public:
21 static constexpr units::second_t kDt = 20_ms;
22
23 Robot() {
24 // Note: These gains are fake, and will have to be tuned for your robot.
25 m_motor.SetPID(1.3, 0.0, 0.7);
26 }
27
28 void TeleopPeriodic() override {
29 if (m_joystick.GetRawButtonPressed(2)) {
30 m_goal = {5_m, 0_mps};
31 } else if (m_joystick.GetRawButtonPressed(3)) {
32 m_goal = {0_m, 0_mps};
33 }
34
35 // Retrieve the profiled setpoint for the next timestep. This setpoint moves
36 // toward the goal while obeying the constraints.
37 m_setpoint = m_profile.Calculate(kDt, m_setpoint, m_goal);
38
39 // Send setpoint to offboard controller PID
40 m_motor.SetSetpoint(ExampleSmartMotorController::PIDMode::kPosition,
41 m_setpoint.position.value(),
42 m_feedforward.Calculate(m_setpoint.velocity) / 12_V);
43 }
44
45 private:
46 frc::Joystick m_joystick{1};
47 ExampleSmartMotorController m_motor{1};
48 frc::SimpleMotorFeedforward<units::meters> m_feedforward{
49 // Note: These gains are fake, and will have to be tuned for your robot.
50 1_V, 1.5_V * 1_s / 1_m};
51
52 // Create a motion profile with the given maximum velocity and maximum
53 // acceleration constraints for the next setpoint.
54 frc::TrapezoidProfile<units::meters> m_profile{{1.75_mps, 0.75_mps_sq}};
55 frc::TrapezoidProfile<units::meters>::State m_goal;
56 frc::TrapezoidProfile<units::meters>::State m_setpoint;
57};
58
59#ifndef RUNNING_FRC_TESTS
60int main() {
61 return frc::StartRobot<Robot>();
62}
63#endif
8import wpilib
9import wpimath.controller
10import wpimath.trajectory
11import examplesmartmotorcontroller
12
13
14class MyRobot(wpilib.TimedRobot):
15 kDt = 0.02
16
17 def robotInit(self):
18 self.joystick = wpilib.Joystick(1)
19 self.motor = examplesmartmotorcontroller.ExampleSmartMotorController(1)
20 # Note: These gains are fake, and will have to be tuned for your robot.
21 self.feedforward = wpimath.controller.SimpleMotorFeedforwardMeters(1, 1.5)
22
23 self.constraints = wpimath.trajectory.TrapezoidProfile.Constraints(1.75, 0.75)
24
25 self.goal = wpimath.trajectory.TrapezoidProfile.State()
26 self.setpoint = wpimath.trajectory.TrapezoidProfile.State()
27
28 # Note: These gains are fake, and will have to be tuned for your robot.
29 self.motor.setPID(1.3, 0.0, 0.7)
30
31 def teleopPeriodic(self):
32 if self.joystick.getRawButtonPressed(2):
33 self.goal = wpimath.trajectory.TrapezoidProfile.State(5, 0)
34 elif self.joystick.getRawButtonPressed(3):
35 self.goal = wpimath.trajectory.TrapezoidProfile.State(0, 0)
36
37 # Create a motion profile with the given maximum velocity and maximum
38 # acceleration constraints for the next setpoint, the desired goal, and the
39 # current setpoint.
40 profile = wpimath.trajectory.TrapezoidProfile(
41 self.constraints, self.goal, self.setpoint
42 )
43
44 # Retrieve the profiled setpoint for the next timestep. This setpoint moves
45 # toward the goal while obeying the constraints.
46 self.setpoint = profile.calculate(self.kDt)
47
48 # Send setpoint to offboard controller PID
49 self.motor.setSetPoint(
50 examplesmartmotorcontroller.ExampleSmartMotorController.PIDMode.kPosition,
51 self.setpoint.position,
52 self.feedforward.calculate(self.setpoint.velocity) / 12,
53 )