Paso 3: creación de un subsistema de desplazamiento o drive
Ahora que nuestra unidad está caracterizada, es hora de comenzar a escribir nuestro código de robot adecuadamente. Como se mencionó anteriormente, utilizaremos el Framework command-based para nuestro código de robot. En consecuencia, nuestro primer paso es escribir una clase adecuada subsistema .
La clase de drive completa del RamseteCommand Example Project (Java, C++) se puede ver a continuación. El resto del artículo describirá los pasos necesarios para escribir esta clase.
5package edu.wpi.first.wpilibj.examples.ramsetecommand.subsystems;
6
7import edu.wpi.first.math.geometry.Pose2d;
8import edu.wpi.first.math.kinematics.DifferentialDriveOdometry;
9import edu.wpi.first.math.kinematics.DifferentialDriveWheelSpeeds;
10import edu.wpi.first.wpilibj.ADXRS450_Gyro;
11import edu.wpi.first.wpilibj.Encoder;
12import edu.wpi.first.wpilibj.drive.DifferentialDrive;
13import edu.wpi.first.wpilibj.examples.ramsetecommand.Constants.DriveConstants;
14import edu.wpi.first.wpilibj.interfaces.Gyro;
15import edu.wpi.first.wpilibj.motorcontrol.MotorControllerGroup;
16import edu.wpi.first.wpilibj.motorcontrol.PWMSparkMax;
17import edu.wpi.first.wpilibj2.command.SubsystemBase;
18
19public class DriveSubsystem extends SubsystemBase {
20 // The motors on the left side of the drive.
21 private final MotorControllerGroup m_leftMotors =
22 new MotorControllerGroup(
23 new PWMSparkMax(DriveConstants.kLeftMotor1Port),
24 new PWMSparkMax(DriveConstants.kLeftMotor2Port));
25
26 // The motors on the right side of the drive.
27 private final MotorControllerGroup m_rightMotors =
28 new MotorControllerGroup(
29 new PWMSparkMax(DriveConstants.kRightMotor1Port),
30 new PWMSparkMax(DriveConstants.kRightMotor2Port));
31
32 // The robot's drive
33 private final DifferentialDrive m_drive = new DifferentialDrive(m_leftMotors, m_rightMotors);
34
35 // The left-side drive encoder
36 private final Encoder m_leftEncoder =
37 new Encoder(
38 DriveConstants.kLeftEncoderPorts[0],
39 DriveConstants.kLeftEncoderPorts[1],
40 DriveConstants.kLeftEncoderReversed);
41
42 // The right-side drive encoder
43 private final Encoder m_rightEncoder =
44 new Encoder(
45 DriveConstants.kRightEncoderPorts[0],
46 DriveConstants.kRightEncoderPorts[1],
47 DriveConstants.kRightEncoderReversed);
48
49 // The gyro sensor
50 private final Gyro m_gyro = new ADXRS450_Gyro();
51
52 // Odometry class for tracking robot pose
53 private final DifferentialDriveOdometry m_odometry;
54
55 /** Creates a new DriveSubsystem. */
56 public DriveSubsystem() {
57 // We need to invert one side of the drivetrain so that positive voltages
58 // result in both sides moving forward. Depending on how your robot's
59 // gearbox is constructed, you might have to invert the left side instead.
60 m_rightMotors.setInverted(true);
61
62 // Sets the distance per pulse for the encoders
63 m_leftEncoder.setDistancePerPulse(DriveConstants.kEncoderDistancePerPulse);
64 m_rightEncoder.setDistancePerPulse(DriveConstants.kEncoderDistancePerPulse);
65
66 resetEncoders();
67 m_odometry =
68 new DifferentialDriveOdometry(
69 m_gyro.getRotation2d(), m_leftEncoder.getDistance(), m_rightEncoder.getDistance());
70 }
71
72 @Override
73 public void periodic() {
74 // Update the odometry in the periodic block
75 m_odometry.update(
76 m_gyro.getRotation2d(), m_leftEncoder.getDistance(), m_rightEncoder.getDistance());
77 }
78
79 /**
80 * Returns the currently-estimated pose of the robot.
81 *
82 * @return The pose.
83 */
84 public Pose2d getPose() {
85 return m_odometry.getPoseMeters();
86 }
87
88 /**
89 * Returns the current wheel speeds of the robot.
90 *
91 * @return The current wheel speeds.
92 */
93 public DifferentialDriveWheelSpeeds getWheelSpeeds() {
94 return new DifferentialDriveWheelSpeeds(m_leftEncoder.getRate(), m_rightEncoder.getRate());
95 }
96
97 /**
98 * Resets the odometry to the specified pose.
99 *
100 * @param pose The pose to which to set the odometry.
101 */
102 public void resetOdometry(Pose2d pose) {
103 resetEncoders();
104 m_odometry.resetPosition(
105 m_gyro.getRotation2d(), m_leftEncoder.getDistance(), m_rightEncoder.getDistance(), pose);
106 }
107
108 /**
109 * Drives the robot using arcade controls.
110 *
111 * @param fwd the commanded forward movement
112 * @param rot the commanded rotation
113 */
114 public void arcadeDrive(double fwd, double rot) {
115 m_drive.arcadeDrive(fwd, rot);
116 }
117
118 /**
119 * Controls the left and right sides of the drive directly with voltages.
120 *
121 * @param leftVolts the commanded left output
122 * @param rightVolts the commanded right output
123 */
124 public void tankDriveVolts(double leftVolts, double rightVolts) {
125 m_leftMotors.setVoltage(leftVolts);
126 m_rightMotors.setVoltage(rightVolts);
127 m_drive.feed();
128 }
129
130 /** Resets the drive encoders to currently read a position of 0. */
131 public void resetEncoders() {
132 m_leftEncoder.reset();
133 m_rightEncoder.reset();
134 }
135
136 /**
137 * Gets the average distance of the two encoders.
138 *
139 * @return the average of the two encoder readings
140 */
141 public double getAverageEncoderDistance() {
142 return (m_leftEncoder.getDistance() + m_rightEncoder.getDistance()) / 2.0;
143 }
144
145 /**
146 * Gets the left drive encoder.
147 *
148 * @return the left drive encoder
149 */
150 public Encoder getLeftEncoder() {
151 return m_leftEncoder;
152 }
153
154 /**
155 * Gets the right drive encoder.
156 *
157 * @return the right drive encoder
158 */
159 public Encoder getRightEncoder() {
160 return m_rightEncoder;
161 }
162
163 /**
164 * Sets the max output of the drive. Useful for scaling the drive to drive more slowly.
165 *
166 * @param maxOutput the maximum output to which the drive will be constrained
167 */
168 public void setMaxOutput(double maxOutput) {
169 m_drive.setMaxOutput(maxOutput);
170 }
171
172 /** Zeroes the heading of the robot. */
173 public void zeroHeading() {
174 m_gyro.reset();
175 }
176
177 /**
178 * Returns the heading of the robot.
179 *
180 * @return the robot's heading in degrees, from -180 to 180
181 */
182 public double getHeading() {
183 return m_gyro.getRotation2d().getDegrees();
184 }
185
186 /**
187 * Returns the turn rate of the robot.
188 *
189 * @return The turn rate of the robot, in degrees per second
190 */
191 public double getTurnRate() {
192 return -m_gyro.getRate();
193 }
194}
5#pragma once
6
7#include <frc/ADXRS450_Gyro.h>
8#include <frc/Encoder.h>
9#include <frc/drive/DifferentialDrive.h>
10#include <frc/geometry/Pose2d.h>
11#include <frc/kinematics/DifferentialDriveOdometry.h>
12#include <frc/motorcontrol/MotorControllerGroup.h>
13#include <frc/motorcontrol/PWMSparkMax.h>
14#include <frc2/command/SubsystemBase.h>
15#include <units/voltage.h>
16
17#include "Constants.h"
18
19class DriveSubsystem : public frc2::SubsystemBase {
20 public:
21 DriveSubsystem();
22
23 /**
24 * Will be called periodically whenever the CommandScheduler runs.
25 */
26 void Periodic() override;
27
28 // Subsystem methods go here.
29
30 /**
31 * Drives the robot using arcade controls.
32 *
33 * @param fwd the commanded forward movement
34 * @param rot the commanded rotation
35 */
36 void ArcadeDrive(double fwd, double rot);
37
38 /**
39 * Controls each side of the drive directly with a voltage.
40 *
41 * @param left the commanded left output
42 * @param right the commanded right output
43 */
44 void TankDriveVolts(units::volt_t left, units::volt_t right);
45
46 /**
47 * Resets the drive encoders to currently read a position of 0.
48 */
49 void ResetEncoders();
50
51 /**
52 * Gets the average distance of the TWO encoders.
53 *
54 * @return the average of the TWO encoder readings
55 */
56 double GetAverageEncoderDistance();
57
58 /**
59 * Gets the left drive encoder.
60 *
61 * @return the left drive encoder
62 */
63 frc::Encoder& GetLeftEncoder();
64
65 /**
66 * Gets the right drive encoder.
67 *
68 * @return the right drive encoder
69 */
70 frc::Encoder& GetRightEncoder();
71
72 /**
73 * Sets the max output of the drive. Useful for scaling the drive to drive
74 * more slowly.
75 *
76 * @param maxOutput the maximum output to which the drive will be constrained
77 */
78 void SetMaxOutput(double maxOutput);
79
80 /**
81 * Returns the heading of the robot.
82 *
83 * @return the robot's heading in degrees, from -180 to 180
84 */
85 units::degree_t GetHeading() const;
86
87 /**
88 * Returns the turn rate of the robot.
89 *
90 * @return The turn rate of the robot, in degrees per second
91 */
92 double GetTurnRate();
93
94 /**
95 * Returns the currently-estimated pose of the robot.
96 *
97 * @return The pose.
98 */
99 frc::Pose2d GetPose();
100
101 /**
102 * Returns the current wheel speeds of the robot.
103 *
104 * @return The current wheel speeds.
105 */
106 frc::DifferentialDriveWheelSpeeds GetWheelSpeeds();
107
108 /**
109 * Resets the odometry to the specified pose.
110 *
111 * @param pose The pose to which to set the odometry.
112 */
113 void ResetOdometry(frc::Pose2d pose);
114
115 private:
116 // Components (e.g. motor controllers and sensors) should generally be
117 // declared private and exposed only through public methods.
118
119 // The motor controllers
120 frc::PWMSparkMax m_left1;
121 frc::PWMSparkMax m_left2;
122 frc::PWMSparkMax m_right1;
123 frc::PWMSparkMax m_right2;
124
125 // The motors on the left side of the drive
126 frc::MotorControllerGroup m_leftMotors{m_left1, m_left2};
127
128 // The motors on the right side of the drive
129 frc::MotorControllerGroup m_rightMotors{m_right1, m_right2};
130
131 // The robot's drive
132 frc::DifferentialDrive m_drive{m_leftMotors, m_rightMotors};
133
134 // The left-side drive encoder
135 frc::Encoder m_leftEncoder;
136
137 // The right-side drive encoder
138 frc::Encoder m_rightEncoder;
139
140 // The gyro sensor
141 frc::ADXRS450_Gyro m_gyro;
142
143 // Odometry class for tracking robot pose
144 frc::DifferentialDriveOdometry m_odometry;
145};
5#include "subsystems/DriveSubsystem.h"
6
7#include <frc/geometry/Rotation2d.h>
8#include <frc/kinematics/DifferentialDriveWheelSpeeds.h>
9
10using namespace DriveConstants;
11
12DriveSubsystem::DriveSubsystem()
13 : m_left1{kLeftMotor1Port},
14 m_left2{kLeftMotor2Port},
15 m_right1{kRightMotor1Port},
16 m_right2{kRightMotor2Port},
17 m_leftEncoder{kLeftEncoderPorts[0], kLeftEncoderPorts[1]},
18 m_rightEncoder{kRightEncoderPorts[0], kRightEncoderPorts[1]},
19 m_odometry{m_gyro.GetRotation2d(), units::meter_t{0}, units::meter_t{0}} {
20 // We need to invert one side of the drivetrain so that positive voltages
21 // result in both sides moving forward. Depending on how your robot's
22 // gearbox is constructed, you might have to invert the left side instead.
23 m_rightMotors.SetInverted(true);
24
25 // Set the distance per pulse for the encoders
26 m_leftEncoder.SetDistancePerPulse(kEncoderDistancePerPulse.value());
27 m_rightEncoder.SetDistancePerPulse(kEncoderDistancePerPulse.value());
28
29 ResetEncoders();
30}
31
32void DriveSubsystem::Periodic() {
33 // Implementation of subsystem periodic method goes here.
34 m_odometry.Update(m_gyro.GetRotation2d(),
35 units::meter_t{m_leftEncoder.GetDistance()},
36 units::meter_t{m_rightEncoder.GetDistance()});
37}
38
39void DriveSubsystem::ArcadeDrive(double fwd, double rot) {
40 m_drive.ArcadeDrive(fwd, rot);
41}
42
43void DriveSubsystem::TankDriveVolts(units::volt_t left, units::volt_t right) {
44 m_leftMotors.SetVoltage(left);
45 m_rightMotors.SetVoltage(right);
46 m_drive.Feed();
47}
48
49void DriveSubsystem::ResetEncoders() {
50 m_leftEncoder.Reset();
51 m_rightEncoder.Reset();
52}
53
54double DriveSubsystem::GetAverageEncoderDistance() {
55 return (m_leftEncoder.GetDistance() + m_rightEncoder.GetDistance()) / 2.0;
56}
57
58frc::Encoder& DriveSubsystem::GetLeftEncoder() {
59 return m_leftEncoder;
60}
61
62frc::Encoder& DriveSubsystem::GetRightEncoder() {
63 return m_rightEncoder;
64}
65
66void DriveSubsystem::SetMaxOutput(double maxOutput) {
67 m_drive.SetMaxOutput(maxOutput);
68}
69
70units::degree_t DriveSubsystem::GetHeading() const {
71 return m_gyro.GetRotation2d().Degrees();
72}
73
74double DriveSubsystem::GetTurnRate() {
75 return -m_gyro.GetRate();
76}
77
78frc::Pose2d DriveSubsystem::GetPose() {
79 return m_odometry.GetPose();
80}
81
82frc::DifferentialDriveWheelSpeeds DriveSubsystem::GetWheelSpeeds() {
83 return {units::meters_per_second_t{m_leftEncoder.GetRate()},
84 units::meters_per_second_t{m_rightEncoder.GetRate()}};
85}
86
87void DriveSubsystem::ResetOdometry(frc::Pose2d pose) {
88 ResetEncoders();
89 m_odometry.ResetPosition(m_gyro.GetRotation2d(),
90 units::meter_t{m_leftEncoder.GetDistance()},
91 units::meter_t{m_rightEncoder.GetDistance()}, pose);
92}
Configuración de los codificadores de unidad
Los codificadores de transmisión miden la rotación de las ruedas en cada lado de la transmisión. Para configurar correctamente los codificadores, necesitamos especificar dos cosas: los puertos a los que están conectados los codificadores y la distancia por pulso del codificador. Luego, necesitamos escribir métodos que permitan el acceso a los valores del codificador desde el código que usa el subsistema.
Puertos del codificador
Los puertos del codificador se especifican en el constructor del codificador, así:
35 // The left-side drive encoder
36 private final Encoder m_leftEncoder =
37 new Encoder(
38 DriveConstants.kLeftEncoderPorts[0],
39 DriveConstants.kLeftEncoderPorts[1],
40 DriveConstants.kLeftEncoderReversed);
41
42 // The right-side drive encoder
43 private final Encoder m_rightEncoder =
44 new Encoder(
45 DriveConstants.kRightEncoderPorts[0],
46 DriveConstants.kRightEncoderPorts[1],
47 DriveConstants.kRightEncoderReversed);
17 m_leftEncoder{kLeftEncoderPorts[0], kLeftEncoderPorts[1]},
18 m_rightEncoder{kRightEncoderPorts[0], kRightEncoderPorts[1]},
Distancia del codificador por pulso
The distance per pulse is specified by calling the encoder’s setDistancePerPulse
method. Note that for the WPILib Encoder class, «pulse» refers to a full encoder cycle (i.e. four edges), and thus will be 1/4 the value that was specified in the SysId config. Remember, as well, that the distance should be measured in meters!
63 m_leftEncoder.setDistancePerPulse(DriveConstants.kEncoderDistancePerPulse);
64 m_rightEncoder.setDistancePerPulse(DriveConstants.kEncoderDistancePerPulse);
26 m_leftEncoder.SetDistancePerPulse(kEncoderDistancePerPulse.value());
27 m_rightEncoder.SetDistancePerPulse(kEncoderDistancePerPulse.value());
Método de acceso del codificador
Para acceder a los valores medidos por los codificadores, incluimos el siguiente método:
Importante
¡Las velocidades devueltas deben estar en metros! Dado que hemos configurado la distancia por pulso en los codificadores anteriormente, al llamar a getRate()
se aplicará automáticamente el factor de conversión de las unidades del codificador a metros. Si no estás usando la clase Encoder
de WPILib, debes realizar esta conversión a través de la API del proveedor respectivo o multiplicando manualmente por un factor de conversión.
88 /**
89 * Returns the current wheel speeds of the robot.
90 *
91 * @return The current wheel speeds.
92 */
93 public DifferentialDriveWheelSpeeds getWheelSpeeds() {
94 return new DifferentialDriveWheelSpeeds(m_leftEncoder.getRate(), m_rightEncoder.getRate());
95 }
82frc::DifferentialDriveWheelSpeeds DriveSubsystem::GetWheelSpeeds() {
83 return {units::meters_per_second_t{m_leftEncoder.GetRate()},
84 units::meters_per_second_t{m_rightEncoder.GetRate()}};
85}
Envolvemos los valores medidos del codificador en un objeto DifferentialDriveWheelSpeeds
para facilitar la integración con la clase RamseteCommand
más adelante.
Configurar el giroscopio
El giroscopio mide la tasa de cambio del rumbo del robot (que luego se puede integrar para proporcionar una medida del rumbo del robot en relación con la primera vez que se encendió). En nuestro ejemplo, utilizamos el Analog Devices ADXRS450 FRC Gyro Board, que se ha incluido en el kit de piezas durante varios años:
50 private final Gyro m_gyro = new ADXRS450_Gyro();
140 // The gyro sensor
141 frc::ADXRS450_Gyro m_gyro;
Método de acceso al giroscopio
Para acceder al rumbo actual medido por el giroscopio, incluimos el siguiente método:
177 /**
178 * Returns the heading of the robot.
179 *
180 * @return the robot's heading in degrees, from -180 to 180
181 */
182 public double getHeading() {
183 return m_gyro.getRotation2d().getDegrees();
184 }
70units::degree_t DriveSubsystem::GetHeading() const {
71 return m_gyro.GetRotation2d().Degrees();
72}
Configuración de la odometría
Ahora que tenemos nuestros codificadores y giroscopio configurados, es hora de configurar nuestro subsistema de transmisión para calcular automáticamente su posición a partir de las lecturas del codificador y del giroscopio.
Primero, creamos una instancia de miembro de la clase DifferentialDriveOdometry
:
53 // Odometry class for tracking robot pose
54 private final DifferentialDriveOdometry m_odometry;
143 // Odometry class for tracking robot pose
144 frc::DifferentialDriveOdometry m_odometry;
Actualización de la odometría
La clase de odometría debe actualizarse periódicamente para incorporar nuevas lecturas del codificador y el giroscopio. Logramos esto dentro del método periódico
del subsistema, que se llama automáticamente una vez por iteración del ciclo principal:
70 @Override
71 public void periodic() {
72 // Update the odometry in the periodic block
73 m_odometry.update(
74 m_gyro.getRotation2d(), m_leftEncoder.getDistance(), m_rightEncoder.getDistance());
75 }
32void DriveSubsystem::Periodic() {
33 // Implementation of subsystem periodic method goes here.
34 m_odometry.Update(m_gyro.GetRotation2d(),
35 units::meter_t{m_leftEncoder.GetDistance()},
36 units::meter_t{m_rightEncoder.GetDistance()});
37}
Método de acceso de odometría
Para acceder a la pose calculada actual del robot, incluimos el siguiente método:
79 /**
80 * Returns the currently-estimated pose of the robot.
81 *
82 * @return The pose.
83 */
84 public Pose2d getPose() {
85 return m_odometry.getPoseMeters();
86 }
78frc::Pose2d DriveSubsystem::GetPose() {
79 return m_odometry.GetPose();
80}
Método de accionamiento basado en voltaje
Finally, we must include one additional method - a method that allows us to set the voltage to each side of the drive using the setVoltage()
method of the MotorController
interface. The default WPILib drive class does not include this functionality, so we must write it ourselves:
118 /**
119 * Controls the left and right sides of the drive directly with voltages.
120 *
121 * @param leftVolts the commanded left output
122 * @param rightVolts the commanded right output
123 */
124 public void tankDriveVolts(double leftVolts, double rightVolts) {
125 m_leftMotors.setVoltage(leftVolts);
126 m_rightMotors.setVoltage(rightVolts);
127 m_drive.feed();
128 }
43void DriveSubsystem::TankDriveVolts(units::volt_t left, units::volt_t right) {
44 m_leftMotors.SetVoltage(left);
45 m_rightMotors.SetVoltage(right);
46 m_drive.Feed();
47}
It is very important to use the setVoltage()
method rather than the ordinary set()
method, as this will automatically compensate for battery «voltage sag» during operation. Since our feedforward voltages are physically-meaningful (as they are based on measured identification data), this is essential to ensuring their accuracy.