Skip to content

Commit a382762

Browse files
committed
Update documentation
1 parent 2b00785 commit a382762

File tree

15 files changed

+1703
-895
lines changed

15 files changed

+1703
-895
lines changed

README.md

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -48,10 +48,12 @@ Custom library for 418 Purple Haze
4848
* Module must be calibrated using calibration tool provided by module manufacturer
4949
* Mixing motor controller vendors within a module is NOT supported (ex. TalonFX for drive, Spark Max for rotation)
5050
* More info [here](src/main/java/org/lasarobotics/drive/swerve/README.md)
51-
* Robot rotation PID
52-
* Automatic module "locking"
53-
* Traction control
54-
* Swerve second order kinematics correction
51+
* Swerve drive parent class
52+
* Robot rotation PID
53+
* Automatic module "locking"
54+
* Traction control
55+
* Swerve second order kinematics correction
56+
* More info [here](src/main/java/org/lasarobotics/drive/swerve/README.md)
5557
* Health monitoring and automatic recovery
5658
* Available in the hardware wrappers and for subsystems
5759
* Configurable input maps

src/main/java/org/lasarobotics/drive/RotatePIDController.java

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
import java.util.HashMap;
88

99
import org.apache.commons.math3.analysis.polynomials.PolynomialSplineFunction;
10-
import org.lasarobotics.utils.GlobalConstants;
1110
import org.lasarobotics.utils.PIDConstants;
1211

1312
import edu.wpi.first.math.MathUtil;
@@ -16,17 +15,19 @@
1615
import edu.wpi.first.units.measure.Angle;
1716
import edu.wpi.first.units.measure.AngularVelocity;
1817
import edu.wpi.first.units.measure.Dimensionless;
18+
import edu.wpi.first.units.measure.Time;
1919

2020
/** Rotate PID controller */
2121
public class RotatePIDController {
2222
private final double MIN_DEADBAND = 0.001;
2323
private final double MAX_DEADBAND = 0.2;
2424
private final double FILTER_FACTOR = 1.0 / 3.0;
25+
private final Time MAX_LOOKAHEAD = Units.Seconds.of(2);
2526

2627
private HashMap<Double, Double> m_rotateInputMap = new HashMap<Double, Double>();
2728
private PIDController m_pidController;
2829
private Angle m_rotateScalar;
29-
private int m_lookAhead;
30+
private Time m_lookAhead;
3031
private double m_deadband;
3132
private double m_rotateRequest;
3233
private boolean m_isRotating;
@@ -39,11 +40,15 @@ public class RotatePIDController {
3940
* @param deadband Controller deadband
4041
* @param lookAhead Number of loops to look ahead by
4142
*/
42-
public RotatePIDController(PolynomialSplineFunction rotateInputCurve, PIDConstants pidf, Angle rotateScalar, Dimensionless deadband, int lookAhead) {
43+
public RotatePIDController(PolynomialSplineFunction rotateInputCurve,
44+
PIDConstants pidf,
45+
Angle rotateScalar,
46+
Dimensionless deadband,
47+
Time lookAhead) {
4348
this.m_pidController = new PIDController(pidf.kP, 0.0, pidf.kD, pidf.period.in(Units.Seconds));
4449
this.m_rotateScalar = rotateScalar;
4550
this.m_deadband = MathUtil.clamp(deadband.in(Units.Percent), MIN_DEADBAND, MAX_DEADBAND);
46-
this.m_lookAhead = lookAhead;
51+
this.m_lookAhead = Units.Seconds.of(MathUtil.clamp(lookAhead.in(Units.Seconds), 0.0, MAX_LOOKAHEAD.in(Units.Seconds)));
4752
this.m_isRotating = false;
4853

4954
// Enable PID position wrapping
@@ -84,7 +89,7 @@ public AngularVelocity calculate(Angle currentAngle, AngularVelocity rotateRate,
8489
} else {
8590
// When rotation is complete, set setpoint to current angle
8691
if (m_isRotating) {
87-
m_pidController.setSetpoint(currentAngle.in(Units.Degrees) + (rotateRate.in(Units.DegreesPerSecond) * m_lookAhead * GlobalConstants.ROBOT_LOOP_HZ.asPeriod().in(Units.Seconds)));
92+
m_pidController.setSetpoint(currentAngle.in(Units.Degrees) + (rotateRate.in(Units.DegreesPerSecond) * m_lookAhead.in(Units.Seconds)));
8893
m_isRotating = false;
8994
}
9095
}

src/main/java/org/lasarobotics/drive/SwervePoseEstimatorService.java

Lines changed: 10 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,8 @@
1313
import java.util.function.Supplier;
1414

1515
import org.lasarobotics.drive.swerve.SwerveModule;
16+
import org.lasarobotics.hardware.IMU;
1617
import org.lasarobotics.hardware.PurpleManager;
17-
import org.lasarobotics.hardware.ctre.Pigeon2;
18-
import org.lasarobotics.hardware.kauailabs.NavX2;
19-
import org.lasarobotics.hardware.reduxrobotics.Canandgyro;
2018
import org.lasarobotics.utils.GlobalConstants;
2119
import org.lasarobotics.vision.AprilTagCamera;
2220
import org.littletonrobotics.junction.AutoLog;
@@ -55,9 +53,7 @@ public static class SwervePoseEstimatorServiceInputs {
5553
private static final String ESTIMATED_POSES_LOG_ENTRY = "/Vision/EstimatedPoses";
5654

5755
private boolean m_running;
58-
private Supplier<Rotation2d> m_rotation2dSupplier;
59-
private Supplier<AngularVelocity> m_yawRateSupplier;
60-
private Supplier<Boolean> m_imuConnectedSupplier;
56+
private IMU m_imu;
6157
private Supplier<SwerveModulePosition[]> m_swerveModulePositionSupplier;
6258
private Consumer<Pose2d> m_poseResetMethod;
6359
private SwerveDriveKinematics m_kinematics;
@@ -81,68 +77,13 @@ public static class SwervePoseEstimatorServiceInputs {
8177
* <p>
8278
* This service runs in the background and keeps track of where the robot is on the field
8379
* @param odometryStdDev Standard deviation of wheel odometry measurements
84-
* @param imu NavX2 MXP IMU
80+
* @param imu IMU installed on robot
8581
* @param modules Swerve modules
8682
*/
87-
public SwervePoseEstimatorService(Matrix<N3,N1> odometryStdDev, NavX2 imu, SwerveModule... modules) {
88-
this(
89-
odometryStdDev,
90-
() -> imu.getInputs().rotation2d,
91-
() -> imu.getInputs().yawRate,
92-
() -> imu.getInputs().isConnected,
93-
modules
94-
);
95-
}
96-
97-
/**
98-
* Create Swerve Pose Estimator Service
99-
* <p>
100-
* This service runs in the background and keeps track of where the robot is on the field
101-
* @param odometryStdDev Standard deviation of wheel odometry measurements
102-
* @param imu CTRE Pigeon 2.0 IMU
103-
* @param modules Swerve modules
104-
*/
105-
public SwervePoseEstimatorService(Matrix<N3,N1> odometryStdDev, Pigeon2 imu, SwerveModule... modules) {
106-
this(
107-
odometryStdDev,
108-
() -> imu.getInputs().rotation2d,
109-
() -> imu.getInputs().yawRate,
110-
() -> true,
111-
modules
112-
);
113-
}
114-
115-
/**
116-
* Create Swerve Pose Estimator Service
117-
* <p>
118-
* This service runs in the background and keeps track of where the robot is on the field
119-
* @param odometryStdDev Standard deviation of wheel odometry measurements
120-
* @param imu Redux Canandgyro IMU
121-
* @param modules Swerve modules
122-
*/
123-
public SwervePoseEstimatorService(Matrix<N3,N1> odometryStdDev, Canandgyro imu, SwerveModule... modules) {
124-
this(
125-
odometryStdDev,
126-
() -> imu.getInputs().rotation2d,
127-
() -> imu.getInputs().yawRate,
128-
() -> true,
129-
modules
130-
);
131-
}
132-
133-
private SwervePoseEstimatorService(Matrix<N3,N1> odometryStdDev,
134-
Supplier<Rotation2d> rotation2dSupplier,
135-
Supplier<AngularVelocity> yawRateSupplier,
136-
Supplier<Boolean> imuConnectedSupplier,
137-
SwerveModule... modules) {
83+
public SwervePoseEstimatorService(Matrix<N3,N1> odometryStdDev, IMU imu, SwerveModule... modules) {
13884
if (modules.length != 4) throw new IllegalArgumentException("Four (4) modules must be used!");
85+
this.m_imu = imu;
13986
this.m_running = false;
140-
// Remember how to get rotation2d from IMU
141-
this.m_rotation2dSupplier = rotation2dSupplier;
142-
// Remember how to get yaw rate from IMU
143-
this.m_yawRateSupplier = yawRateSupplier;
144-
// Remember how to check if IMU is connected
145-
this.m_imuConnectedSupplier = imuConnectedSupplier;
14687

14788
// Get each individual module
14889
var moduleList = Arrays.asList(modules);
@@ -182,7 +123,7 @@ private SwervePoseEstimatorService(Matrix<N3,N1> odometryStdDev,
182123
// Initialise pose estimator
183124
this.m_poseEstimator = new SwerveDrivePoseEstimator(
184125
m_kinematics,
185-
m_rotation2dSupplier.get(),
126+
m_imu.getRotation2d(),
186127
m_swerveModulePositionSupplier.get(),
187128
new Pose2d(),
188129
odometryStdDev,
@@ -207,7 +148,7 @@ private SwervePoseEstimatorService(Matrix<N3,N1> odometryStdDev,
207148
m_currentTimestamp = Logger.getRealTimestamp();
208149

209150
// Check if IMU is connected
210-
boolean isIMUConnected = m_imuConnectedSupplier.get();
151+
boolean isIMUConnected = m_imu.isConnected();
211152

212153
// Get synchronized swerve module positions
213154
var currentModulePositions = m_swerveModulePositionSupplier.get();
@@ -222,9 +163,9 @@ private SwervePoseEstimatorService(Matrix<N3,N1> odometryStdDev,
222163
m_previousModulePositions[i] = currentModulePositions[i];
223164
}
224165
var previousYawAngle = m_yawAngle;
225-
m_yawAngle = (isIMUConnected) ? m_rotation2dSupplier.get() :
166+
m_yawAngle = (isIMUConnected) ? m_imu.getRotation2d() :
226167
m_yawAngle.plus(new Rotation2d(m_kinematics.toTwist2d(moduleDeltas).dtheta));
227-
var yawRate = (isIMUConnected) ? m_yawRateSupplier.get() :
168+
var yawRate = (isIMUConnected) ? m_imu.getYawRate() :
228169
Units.Radians.of(m_yawAngle.minus(previousYawAngle).getRadians()).divide(Units.Microseconds.of(m_currentTimestamp - m_previousTimestamp));
229170

230171
// If no cameras or yaw rate is too high, just update pose based on odometry and exit
@@ -272,7 +213,7 @@ private SwervePoseEstimatorService(Matrix<N3,N1> odometryStdDev,
272213
m_thread.setName(NAME);
273214

274215
// Remember how to reset pose
275-
this.m_poseResetMethod = pose -> m_poseEstimator.resetPosition(m_rotation2dSupplier.get(), m_swerveModulePositionSupplier.get(), pose);
216+
this.m_poseResetMethod = pose -> m_poseEstimator.resetPosition(m_imu.getRotation2d(), m_swerveModulePositionSupplier.get(), pose);
276217

277218
// Register service as pose supplier with PurpleManager for simulation
278219
PurpleManager.setPoseSupplier(this::getPose);

src/main/java/org/lasarobotics/drive/TractionControlController.java

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ private State(int value) {
5252

5353
private double m_optimalSlipRatio;
5454
private double m_mass;
55-
private double m_maxLinearSpeed;
55+
private double m_maxLinearVelocity;
5656
private double m_maxAcceleration;
5757
private double m_staticCoF;
5858
private double m_dynamicCoF;
@@ -94,7 +94,7 @@ public TractionControlController(Dimensionless staticCoF,
9494
this.m_dynamicCoF = dynamicCoF.in(Units.Value);
9595
this.m_optimalSlipRatio = MathUtil.clamp(optimalSlipRatio.in(Units.Value), MIN_SLIP_RATIO, MAX_SLIP_RATIO);
9696
this.m_mass = mass.divide(4).in(Units.Kilograms);
97-
this.m_maxLinearSpeed = Math.floor(maxLinearSpeed.in(Units.MetersPerSecond) * 1000) / 1000;
97+
this.m_maxLinearVelocity = Math.floor(maxLinearSpeed.in(Units.MetersPerSecond) * 1000) / 1000;
9898
this.m_maxAcceleration = m_staticCoF * Units.Gs.one().in(Units.MetersPerSecondPerSecond);
9999
this.m_maxPredictedSlipRatio = (m_maxAcceleration * GlobalConstants.ROBOT_LOOP_HZ.in(Units.Hertz))
100100
/ (m_staticCoF * m_mass * Units.Gs.one().in(Units.MetersPerSecondPerSecond));
@@ -143,12 +143,12 @@ public LinearVelocity calculate(LinearVelocity velocityRequest,
143143
// Get current slip ratio, and check if slipping
144144
double currentSlipRatio = Math.abs(
145145
inertialVelocity.lte(INERTIAL_VELOCITY_THRESHOLD)
146-
? wheelSpeed.in(Units.MetersPerSecond) / m_maxLinearSpeed
146+
? wheelSpeed.in(Units.MetersPerSecond) / m_maxLinearVelocity
147147
: (Math.abs(wheelSpeed.in(Units.MetersPerSecond)) - inertialVelocity.in(Units.MetersPerSecond)) / inertialVelocity.in(Units.MetersPerSecond)
148148
);
149149
m_isSlipping = m_slippingDebouncer.calculate(
150150
currentSlipRatio > m_optimalSlipRatio
151-
& Math.abs(wheelSpeed.in(Units.MetersPerSecond)) > m_maxLinearSpeed * m_optimalSlipRatio
151+
& Math.abs(wheelSpeed.in(Units.MetersPerSecond)) > m_maxLinearVelocity * m_optimalSlipRatio
152152
& isEnabled()
153153
);
154154

@@ -178,8 +178,8 @@ & isEnabled()
178178
// Update output, clamping to max linear speed
179179
velocityOutput = Units.MetersPerSecond.of(MathUtil.clamp(
180180
velocityOutput.plus(velocityCorrection).in(Units.MetersPerSecond),
181-
-m_maxLinearSpeed,
182-
+m_maxLinearSpeed
181+
-m_maxLinearVelocity,
182+
+m_maxLinearVelocity
183183
));
184184

185185
return velocityOutput;
@@ -193,6 +193,14 @@ public boolean isSlipping() {
193193
return m_isSlipping;
194194
}
195195

196+
/**
197+
* Get max linear velocity
198+
* @return Maximum linear velocity
199+
*/
200+
public LinearVelocity getMaxLinearVelocity() {
201+
return Units.MetersPerSecond.of(m_maxLinearVelocity);
202+
}
203+
196204
/**
197205
* Toggle traction control
198206
*/

src/main/java/org/lasarobotics/drive/swerve/README.md

Lines changed: 52 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,10 @@
1-
# REV Swerve
1+
# Swerve Drive template
2+
3+
Simply extend the [SwerveDrive](src/main/java/org/lasarobotics/drive/swerve/SwerveDrive.java) for your drive subsystem, and call the `super()` constructor with the appropriate arguments.
4+
5+
Use the [PurpleSwerve](https://github.com/lasarobotics/PurpleSwerve) repo as an example
6+
7+
## REV Swerve
28

39
In order to use Spark motor controllers, you MUST have a PWM absolute encoder directly connected to the Spark that controls the rotation motor.
410

@@ -13,8 +19,8 @@ Options include:
1319
```
1420
REVSwerveModule lFrontModule = MAXSwerveModule.create(
1521
REVSwerveModule.initializeHardware(
16-
Constants.DriveHardware.LEFT_FRONT_DRIVE_MOTOR_ID,
17-
Constants.DriveHardware.LEFT_FRONT_ROTATE_MOTOR_ID,
22+
new Spark.ID("DriveHardware/Swerve/LeftFront/Drive", 2);
23+
new Spark.ID("DriveHardware/Swerve/LeftFront/Rotate", 3);
1824
MotorKind.NEO_VORTEX,
1925
MotorKind.NEO_550
2026
),
@@ -34,16 +40,50 @@ REVSwerveModule lFrontModule = MAXSwerveModule.create(
3440
);
3541
```
3642

37-
# CTRE Swerve
43+
## CTRE Swerve
3844

3945
At the current time, the CTRE CANCoder is the ONLY absolute encoder supported.
4046

41-
## MAXSwerve
47+
Use of CANivore is HIGHLY recommended, as adjustment of status frame rates isn't easily possible while protecting the underlying sensor getter methods.
48+
49+
CANivore must be named "canivore" using Phoenix Tuner X.
50+
51+
To create an instance of a module, use the helper class for your module, and call the `create()` method for CTRE.
52+
53+
For example, here's how to create a Swerve X2i module:
54+
```
55+
CTRESwerveModule lFrontModule = SwerveX2Module.create(
56+
CTRESwerveModule.initializeHardware(
57+
new TalonFX.ID("DriveHardware/Swerve/LeftFront/Drive", PhoenixCANBus.CANIVORE, 2);
58+
new TalonFX.ID("DriveHardware/Swerve/LeftFront/Rotate", PhoenixCANBus.CANIVORE, 3);
59+
new CANcoder.ID("DriveHardware/Swerve/LeftFront/Encoder", PhoenixCANBus.CANIVORE, 10);
60+
),
61+
SwerveModule.Location.LeftFront,
62+
SwerveModule.MountOrientation.INVERTED
63+
SwerveX2Module.GearRatio.X4_3,
64+
DriveWheel.create(Units.Inches.of(3.0), Units.Value.of(0.9), Units.Value.of(0.8)),
65+
PIDConstants.of(0.3, 0.0, 0.001, 0.0, 0.0),
66+
FFConstants.of(0.2, 0.0, 0.0, 0.5),
67+
PIDConstants.of(2.0, 0.0, 0.1, 0.0, 0.0),
68+
FFConstants.of(0.2, 0.0, 0.0, 0.01),
69+
Units.Percent.of(8.0),
70+
Units.Pounds.of(135.0),
71+
Units.Meters.of(0.5588),
72+
Units.Meters.of(0.5588),
73+
Units.Seconds.of(3.0),
74+
Units.Amps.of(60.0)
75+
);
76+
```
77+
78+
### MAXSwerve
79+
80+
Only REV Spark motor controllers are currently supported with MAXSwerve.
4281

43-
### Calibration
44-
Modules must be calibrated as per manufacturer instructions: https://docs.revrobotics.com/brushless/spark-max/encoders/maxswerve
82+
#### Calibration
83+
Modules must be calibrated as per manufacturer instructions:
84+
https://docs.revrobotics.com/brushless/spark-max/encoders/maxswerve
4585

46-
### Sample PID values
86+
#### Sample PID values
4787
These values can be a good starting point for MAXSwerve.
4888
Note that rotate feed forward values are only used for simulation.
4989

@@ -61,14 +101,14 @@ Rotate kS: 0.2
61101

62102
Rotate kA: 0.01
63103

64-
## Swerve X
104+
### Swerve X
65105

66-
### Calibration
106+
#### Calibration
67107
Module must be calibrated as per the manufacturer instructions for each respective module:
68108
* [Swerve X/Xi](https://docs.wcproducts.com/wcp-swervex/overview-and-features/zeroing-module)
69109
* [Swerve X2](https://docs.wcproducts.com/wcp-swerve-x2/overview-and-features/zeroing-module)
70110

71-
## SDS
111+
### SDS
72112

73-
### Calibration
113+
#### Calibration
74114
SDS does not use a "calibration tool" or jig like MAXSwerve or WCP. As such, I have assumed that users will zero the modules to be straight ahead, aligned with the chassis. Therefore the "zero offset" for these modules is zero.

0 commit comments

Comments
 (0)