Skip to content

Commit ae85963

Browse files
authored
Merge pull request #8 from REVrobotics/rate_limiting
Rate limiting
2 parents 05c308f + 2430a7f commit ae85963

File tree

6 files changed

+187
-13
lines changed

6 files changed

+187
-13
lines changed

CHANGELOG.md

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# MAXSwerve Java Template Changelog
2+
3+
## [2023.1] - 2023-02-03
4+
5+
### Added
6+
7+
- Added a configurable rate limiting system to prevent excessive loads from causing premature wheel failure.
8+
9+
## [2023.0] - 2023-01-18
10+
11+
Initial release of MAXSwerve robot project.

README.md

+5-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
1-
# MAXSwerve Java Template
1+
# MAXSwerve Java Template v2023.1
2+
3+
See [the online changelog](https://github.com/REVrobotics/MAXSwerve-Java-Template/blob/main/CHANGELOG.md) for information about updates to the template that may have been released since you created your project.
4+
5+
## Description
26

37
A template project for an FRC swerve drivetrain that uses REV MAXSwerve Modules.
48

src/main/java/frc/robot/Constants.java

+5
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,10 @@ public static final class DriveConstants {
3030
public static final double kMaxSpeedMetersPerSecond = 4.8;
3131
public static final double kMaxAngularSpeed = 2 * Math.PI; // radians per second
3232

33+
public static final double kDirectionSlewRate = 1.2; // radians per second
34+
public static final double kMagnitudeSlewRate = 1.8; // percent per second (1 = 100%)
35+
public static final double kRotationalSlewRate = 2.0; // percent per second (1 = 100%)
36+
3337
// Chassis configuration
3438
public static final double kTrackWidth = Units.inchesToMeters(26.5);
3539
// Distance between centers of right and left wheels on robot
@@ -114,6 +118,7 @@ public static final class ModuleConstants {
114118

115119
public static final class OIConstants {
116120
public static final int kDriverControllerPort = 0;
121+
public static final double kDriveDeadband = 0.05;
117122
}
118123

119124
public static final class AutoConstants {

src/main/java/frc/robot/RobotContainer.java

+5-5
Original file line numberDiff line numberDiff line change
@@ -51,10 +51,10 @@ public RobotContainer() {
5151
// Turning is controlled by the X axis of the right stick.
5252
new RunCommand(
5353
() -> m_robotDrive.drive(
54-
MathUtil.applyDeadband(-m_driverController.getLeftY(), 0.06),
55-
MathUtil.applyDeadband(-m_driverController.getLeftX(), 0.06),
56-
MathUtil.applyDeadband(-m_driverController.getRightX(), 0.06),
57-
true),
54+
-MathUtil.applyDeadband(m_driverController.getLeftY(), OIConstants.kDriveDeadband),
55+
-MathUtil.applyDeadband(m_driverController.getLeftX(), OIConstants.kDriveDeadband),
56+
-MathUtil.applyDeadband(m_driverController.getRightX(), OIConstants.kDriveDeadband),
57+
true, true),
5858
m_robotDrive));
5959
}
6060

@@ -117,6 +117,6 @@ public Command getAutonomousCommand() {
117117
m_robotDrive.resetOdometry(exampleTrajectory.getInitialPose());
118118

119119
// Run path following command, then stop at the end.
120-
return swerveControllerCommand.andThen(() -> m_robotDrive.drive(0, 0, 0, false));
120+
return swerveControllerCommand.andThen(() -> m_robotDrive.drive(0, 0, 0, false, false));
121121
}
122122
}

src/main/java/frc/robot/subsystems/DriveSubsystem.java

+72-7
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,18 @@
44

55
package frc.robot.subsystems;
66

7+
import edu.wpi.first.math.filter.SlewRateLimiter;
78
import edu.wpi.first.math.geometry.Pose2d;
89
import edu.wpi.first.math.geometry.Rotation2d;
910
import edu.wpi.first.math.kinematics.ChassisSpeeds;
1011
import edu.wpi.first.math.kinematics.SwerveDriveKinematics;
1112
import edu.wpi.first.math.kinematics.SwerveDriveOdometry;
1213
import edu.wpi.first.math.kinematics.SwerveModulePosition;
1314
import edu.wpi.first.math.kinematics.SwerveModuleState;
15+
import edu.wpi.first.util.WPIUtilJNI;
1416
import edu.wpi.first.wpilibj.ADIS16470_IMU;
1517
import frc.robot.Constants.DriveConstants;
18+
import frc.utils.SwerveUtils;
1619
import edu.wpi.first.wpilibj2.command.SubsystemBase;
1720

1821
public class DriveSubsystem extends SubsystemBase {
@@ -40,6 +43,15 @@ public class DriveSubsystem extends SubsystemBase {
4043
// The gyro sensor
4144
private final ADIS16470_IMU m_gyro = new ADIS16470_IMU();
4245

46+
// Slew rate filter variables for controlling lateral acceleration
47+
private double m_currentRotation = 0.0;
48+
private double m_currentTranslationDir = 0.0;
49+
private double m_currentTranslationMag = 0.0;
50+
51+
private SlewRateLimiter m_magLimiter = new SlewRateLimiter(DriveConstants.kMagnitudeSlewRate);
52+
private SlewRateLimiter m_rotLimiter = new SlewRateLimiter(DriveConstants.kRotationalSlewRate);
53+
private double m_prevTime = WPIUtilJNI.now() * 1e-6;
54+
4355
// Odometry class for tracking robot pose
4456
SwerveDriveOdometry m_odometry = new SwerveDriveOdometry(
4557
DriveConstants.kDriveKinematics,
@@ -102,17 +114,70 @@ public void resetOdometry(Pose2d pose) {
102114
* @param rot Angular rate of the robot.
103115
* @param fieldRelative Whether the provided x and y speeds are relative to the
104116
* field.
117+
* @param rateLimit Whether to enable rate limiting for smoother control.
105118
*/
106-
public void drive(double xSpeed, double ySpeed, double rot, boolean fieldRelative) {
107-
// Adjust input based on max speed
108-
xSpeed *= DriveConstants.kMaxSpeedMetersPerSecond;
109-
ySpeed *= DriveConstants.kMaxSpeedMetersPerSecond;
110-
rot *= DriveConstants.kMaxAngularSpeed;
119+
public void drive(double xSpeed, double ySpeed, double rot, boolean fieldRelative, boolean rateLimit) {
120+
121+
double xSpeedCommanded;
122+
double ySpeedCommanded;
123+
124+
if (rateLimit) {
125+
// Convert XY to polar for rate limiting
126+
double inputTranslationDir = Math.atan2(ySpeed, xSpeed);
127+
double inputTranslationMag = Math.sqrt(Math.pow(xSpeed, 2) + Math.pow(ySpeed, 2));
128+
129+
// Calculate the direction slew rate based on an estimate of the lateral acceleration
130+
double directionSlewRate;
131+
if (m_currentTranslationMag != 0.0) {
132+
directionSlewRate = Math.abs(DriveConstants.kDirectionSlewRate / m_currentTranslationMag);
133+
} else {
134+
directionSlewRate = 500.0; //some high number that means the slew rate is effectively instantaneous
135+
}
136+
137+
138+
double currentTime = WPIUtilJNI.now() * 1e-6;
139+
double elapsedTime = currentTime - m_prevTime;
140+
double angleDif = SwerveUtils.AngleDifference(inputTranslationDir, m_currentTranslationDir);
141+
if (angleDif < 0.45*Math.PI) {
142+
m_currentTranslationDir = SwerveUtils.StepTowardsCircular(m_currentTranslationDir, inputTranslationDir, directionSlewRate * elapsedTime);
143+
m_currentTranslationMag = m_magLimiter.calculate(inputTranslationMag);
144+
}
145+
else if (angleDif > 0.85*Math.PI) {
146+
if (m_currentTranslationMag > 1e-4) { //some small number to avoid floating-point errors with equality checking
147+
// keep currentTranslationDir unchanged
148+
m_currentTranslationMag = m_magLimiter.calculate(0.0);
149+
}
150+
else {
151+
m_currentTranslationDir = SwerveUtils.WrapAngle(m_currentTranslationDir + Math.PI);
152+
m_currentTranslationMag = m_magLimiter.calculate(inputTranslationMag);
153+
}
154+
}
155+
else {
156+
m_currentTranslationDir = SwerveUtils.StepTowardsCircular(m_currentTranslationDir, inputTranslationDir, directionSlewRate * elapsedTime);
157+
m_currentTranslationMag = m_magLimiter.calculate(0.0);
158+
}
159+
m_prevTime = currentTime;
160+
161+
xSpeedCommanded = m_currentTranslationMag * Math.cos(m_currentTranslationDir);
162+
ySpeedCommanded = m_currentTranslationMag * Math.sin(m_currentTranslationDir);
163+
m_currentRotation = m_rotLimiter.calculate(rot);
164+
165+
166+
} else {
167+
xSpeedCommanded = xSpeed;
168+
ySpeedCommanded = ySpeed;
169+
m_currentRotation = rot;
170+
}
171+
172+
// Convert the commanded speeds into the correct units for the drivetrain
173+
double xSpeedDelivered = xSpeedCommanded * DriveConstants.kMaxSpeedMetersPerSecond;
174+
double ySpeedDelivered = ySpeedCommanded * DriveConstants.kMaxSpeedMetersPerSecond;
175+
double rotDelivered = m_currentRotation * DriveConstants.kMaxAngularSpeed;
111176

112177
var swerveModuleStates = DriveConstants.kDriveKinematics.toSwerveModuleStates(
113178
fieldRelative
114-
? ChassisSpeeds.fromFieldRelativeSpeeds(xSpeed, ySpeed, rot, Rotation2d.fromDegrees(m_gyro.getAngle()))
115-
: new ChassisSpeeds(xSpeed, ySpeed, rot));
179+
? ChassisSpeeds.fromFieldRelativeSpeeds(xSpeedDelivered, ySpeedDelivered, rotDelivered, Rotation2d.fromDegrees(m_gyro.getAngle()))
180+
: new ChassisSpeeds(xSpeedDelivered, ySpeedDelivered, rotDelivered));
116181
SwerveDriveKinematics.desaturateWheelSpeeds(
117182
swerveModuleStates, DriveConstants.kMaxSpeedMetersPerSecond);
118183
m_frontLeft.setDesiredState(swerveModuleStates[0]);
+89
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
package frc.utils;
2+
3+
public class SwerveUtils {
4+
5+
/**
6+
* Steps a value towards a target with a specified step size.
7+
* @param _current The current or starting value. Can be positive or negative.
8+
* @param _target The target value the algorithm will step towards. Can be positive or negative.
9+
* @param _stepsize The maximum step size that can be taken.
10+
* @return The new value for {@code _current} after performing the specified step towards the specified target.
11+
*/
12+
public static double StepTowards(double _current, double _target, double _stepsize) {
13+
if (Math.abs(_current - _target) <= _stepsize) {
14+
return _target;
15+
}
16+
else if (_target < _current) {
17+
return _current - _stepsize;
18+
}
19+
else {
20+
return _current + _stepsize;
21+
}
22+
}
23+
24+
/**
25+
* Steps a value (angle) towards a target (angle) taking the shortest path with a specified step size.
26+
* @param _current The current or starting angle (in radians). Can lie outside the 0 to 2*PI range.
27+
* @param _target The target angle (in radians) the algorithm will step towards. Can lie outside the 0 to 2*PI range.
28+
* @param _stepsize The maximum step size that can be taken (in radians).
29+
* @return The new angle (in radians) for {@code _current} after performing the specified step towards the specified target.
30+
* This value will always lie in the range 0 to 2*PI (exclusive).
31+
*/
32+
public static double StepTowardsCircular(double _current, double _target, double _stepsize) {
33+
_current = WrapAngle(_current);
34+
_target = WrapAngle(_target);
35+
36+
double stepDirection = Math.signum(_target - _current);
37+
double difference = Math.abs(_current - _target);
38+
39+
if (difference <= _stepsize) {
40+
return _target;
41+
}
42+
else if (difference > Math.PI) { //does the system need to wrap over eventually?
43+
//handle the special case where you can reach the target in one step while also wrapping
44+
if (_current + 2*Math.PI - _target < _stepsize || _target + 2*Math.PI - _current < _stepsize) {
45+
return _target;
46+
}
47+
else {
48+
return WrapAngle(_current - stepDirection * _stepsize); //this will handle wrapping gracefully
49+
}
50+
51+
}
52+
else {
53+
return _current + stepDirection * _stepsize;
54+
}
55+
}
56+
57+
/**
58+
* Finds the (unsigned) minimum difference between two angles including calculating across 0.
59+
* @param _angleA An angle (in radians).
60+
* @param _angleB An angle (in radians).
61+
* @return The (unsigned) minimum difference between the two angles (in radians).
62+
*/
63+
public static double AngleDifference(double _angleA, double _angleB) {
64+
double difference = Math.abs(_angleA - _angleB);
65+
return difference > Math.PI? (2 * Math.PI) - difference : difference;
66+
}
67+
68+
/**
69+
* Wraps an angle until it lies within the range from 0 to 2*PI (exclusive).
70+
* @param _angle The angle (in radians) to wrap. Can be positive or negative and can lie multiple wraps outside the output range.
71+
* @return An angle (in radians) from 0 and 2*PI (exclusive).
72+
*/
73+
public static double WrapAngle(double _angle) {
74+
double twoPi = 2*Math.PI;
75+
76+
if (_angle == twoPi) { // Handle this case separately to avoid floating point errors with the floor after the division in the case below
77+
return 0.0;
78+
}
79+
else if (_angle > twoPi) {
80+
return _angle - twoPi*Math.floor(_angle / twoPi);
81+
}
82+
else if (_angle < 0.0) {
83+
return _angle + twoPi*(Math.floor((-_angle) / twoPi)+1);
84+
}
85+
else {
86+
return _angle;
87+
}
88+
}
89+
}

0 commit comments

Comments
 (0)