Skip to content

Commit b8f5ed2

Browse files
Add Rotational Snapping & Auto Driving (#127)
* Begin implementing rotational snapping * Better utilize Phoenix 6 Configurations * Implement auto drive and rotate commands
1 parent 6edf94e commit b8f5ed2

File tree

3 files changed

+182
-141
lines changed

3 files changed

+182
-141
lines changed

src/main/java/com/frcteam3255/components/swerve/SN_SuperSwerve.java

Lines changed: 174 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -5,19 +5,21 @@
55
package com.frcteam3255.components.swerve;
66

77
import java.util.HashMap;
8+
import java.util.List;
89
import java.util.function.BooleanSupplier;
910

11+
import com.ctre.phoenix6.configs.CANcoderConfiguration;
12+
import com.ctre.phoenix6.configs.TalonFXConfiguration;
1013
import com.ctre.phoenix6.hardware.Pigeon2;
11-
import com.ctre.phoenix6.signals.InvertedValue;
12-
import com.ctre.phoenix6.signals.NeutralModeValue;
13-
import com.ctre.phoenix6.signals.SensorDirectionValue;
1414
import com.pathplanner.lib.auto.AutoBuilder;
1515
import com.pathplanner.lib.config.PIDConstants;
1616
import com.pathplanner.lib.config.RobotConfig;
1717
import com.pathplanner.lib.controllers.PPHolonomicDriveController;
1818
import com.pathplanner.lib.trajectory.PathPlannerTrajectory;
1919

20+
import edu.wpi.first.math.MathUtil;
2021
import edu.wpi.first.math.Matrix;
22+
import edu.wpi.first.math.controller.HolonomicDriveController;
2123
import edu.wpi.first.math.estimator.SwerveDrivePoseEstimator;
2224
import edu.wpi.first.math.geometry.Pose2d;
2325
import edu.wpi.first.math.geometry.Rotation2d;
@@ -29,6 +31,10 @@
2931
import edu.wpi.first.math.numbers.N1;
3032
import edu.wpi.first.math.numbers.N3;
3133
import edu.wpi.first.units.Units;
34+
import edu.wpi.first.units.measure.Angle;
35+
import edu.wpi.first.units.measure.AngularVelocity;
36+
import edu.wpi.first.units.measure.Distance;
37+
import edu.wpi.first.units.measure.LinearVelocity;
3238
import edu.wpi.first.wpilibj.Timer;
3339
import edu.wpi.first.wpilibj.smartdashboard.Field2d;
3440
import edu.wpi.first.wpilibj2.command.Command;
@@ -58,6 +64,10 @@ public class SN_SuperSwerve extends SubsystemBase {
5864
public double timeFromLastUpdate = 0;
5965
public double lastSimTime = Timer.getFPGATimestamp();
6066
public Field2d field;
67+
public TalonFXConfiguration driveConfig, steerConfig;
68+
public CANcoderConfiguration cancoderConfig;
69+
public HolonomicDriveController teleopAutoDriveController;
70+
public AngularVelocity turnSpeed;
6171

6272
/**
6373
* <p>
@@ -84,22 +94,21 @@ public class SN_SuperSwerve extends SubsystemBase {
8494
* Physically measured distance (center to center) between the Front
8595
* and Back wheels in meters
8696
* @param CANBusName
87-
* The name of the CANBus that all of the swerve components are on
97+
* The name of the CANBus that the Pigeon is on.
8898
* @param pigeonCANId
89-
* The CAN id of the Pigeon. The Pigeon MUST be on the same CANBus as
90-
* the modules
99+
* The CAN id of the Pigeon.
91100
* @param minimumSteerPercent
92101
* The minimum PercentOutput required to make the steer motor move
93-
* @param driveInversion
94-
* The direction that is positive for drive motors
95-
* @param steerInversion
96-
* The direction that is positive for steer motors
97-
* @param cancoderInversion
98-
* The direction that is positive for Cancoders
99-
* @param driveNeutralMode
100-
* The behavior of every drive motor when set to neutral-output
101-
* @param steerNeutralMode
102-
* The behavior of every steer motor when set to neutral-output
102+
* @param driveConfig
103+
* The configuration for each drive motor. Make sure you set the
104+
* inversion, neutral mode, and sensor to mechanism ratio!
105+
* @param steerConfig
106+
* The configuration for each steer motor. Make sure you set the
107+
* inversion, neutral mode, sensor to mechanism ratio, and continuous
108+
* wrap!
109+
* @param cancoderConfig
110+
* The configuration for each CANCoder. Make sure you set the sensor
111+
* direction!
103112
* @param stateStdDevs
104113
* Standard deviations of the pose estimate (x position in meters, y
105114
* position in meters, and heading in radians). Increase these
@@ -114,6 +123,11 @@ public class SN_SuperSwerve extends SubsystemBase {
114123
* @param autoSteerPID
115124
* The rotational PID constants applied to the entire Drivetrain
116125
* during autonomous in order to reach the correct pose
126+
* @param teleopAutoDriveController
127+
* The PID controller used to control rotational snapping and auto
128+
* driving during Teleoperated.
129+
* @param turnSpeed
130+
* The turning speed of the robot.
117131
* @param robotConfig
118132
* The robot configuration used by PathPlanner.
119133
* @param autoFlipPaths
@@ -126,10 +140,10 @@ public class SN_SuperSwerve extends SubsystemBase {
126140
*/
127141
public SN_SuperSwerve(SN_SwerveConstants swerveConstants, SN_SwerveModule[] modules, double wheelbase,
128142
double trackWidth, String CANBusName, int pigeonCANId, double minimumSteerPercent,
129-
InvertedValue driveInversion, InvertedValue steerInversion, SensorDirectionValue cancoderInversion,
130-
NeutralModeValue driveNeutralMode, NeutralModeValue steerNeutralMode, Matrix<N3, N1> stateStdDevs,
131-
Matrix<N3, N1> visionStdDevs, PIDConstants autoDrivePID, PIDConstants autoSteerPID, RobotConfig robotConfig,
132-
BooleanSupplier autoFlipPaths, boolean isSimulation) {
143+
TalonFXConfiguration driveConfig, TalonFXConfiguration steerConfig, CANcoderConfiguration cancoderConfig,
144+
Matrix<N3, N1> stateStdDevs, Matrix<N3, N1> visionStdDevs, PIDConstants autoDrivePID,
145+
PIDConstants autoSteerPID, HolonomicDriveController teleopAutoDriveController, AngularVelocity turnSpeed,
146+
RobotConfig robotConfig, BooleanSupplier autoFlipPaths, boolean isSimulation) {
133147

134148
isFieldRelative = true;
135149
field = new Field2d();
@@ -148,22 +162,15 @@ public SN_SuperSwerve(SN_SwerveConstants swerveConstants, SN_SwerveModule[] modu
148162
this.autoSteerPID = autoSteerPID;
149163
this.isSimulation = isSimulation;
150164
this.autoFlipPaths = autoFlipPaths;
165+
this.teleopAutoDriveController = teleopAutoDriveController;
166+
this.turnSpeed = turnSpeed;
151167

152168
SN_SwerveModule.isSimulation = isSimulation;
153169
SN_SwerveModule.wheelCircumference = swerveConstants.wheelCircumference;
154170
SN_SwerveModule.maxModuleSpeedMeters = swerveConstants.maxSpeedMeters;
155-
SN_SwerveModule.driveGearRatio = swerveConstants.driveGearRatio;
156-
SN_SwerveModule.steerGearRatio = swerveConstants.steerGearRatio;
157171

158172
SN_SwerveModule.minimumSteerSpeedPercent = minimumSteerPercent;
159173

160-
SN_SwerveModule.driveInversion = driveInversion;
161-
SN_SwerveModule.driveNeutralMode = driveNeutralMode;
162-
163-
SN_SwerveModule.steerInversion = steerInversion;
164-
SN_SwerveModule.steerNeutralMode = steerNeutralMode;
165-
SN_SwerveModule.cancoderInversion = cancoderInversion;
166-
167174
pigeon = new Pigeon2(pigeonCANId, CANBusName);
168175

169176
// The absolute encoders need time to initialize
@@ -176,6 +183,9 @@ public SN_SuperSwerve(SN_SwerveConstants swerveConstants, SN_SwerveModule[] modu
176183
}
177184

178185
public void configure() {
186+
SN_SwerveModule.driveConfiguration = driveConfig;
187+
SN_SwerveModule.steerConfiguration = steerConfig;
188+
SN_SwerveModule.cancoderConfiguration = cancoderConfig;
179189
for (SN_SwerveModule mod : modules) {
180190
mod.configure();
181191
}
@@ -297,6 +307,22 @@ public void drive(Translation2d translation, double rotation, boolean isOpenLoop
297307
setModuleStates(desiredModuleStates, isOpenLoop);
298308
}
299309

310+
/**
311+
* Drive the drivetrain with pre-calculated ChassisSpeeds
312+
*
313+
* @param chassisSpeeds
314+
* Desired ChassisSpeeds
315+
* @param isOpenLoop
316+
* Are the modules being set based on open loop or closed loop
317+
* control
318+
*
319+
*/
320+
public void drive(ChassisSpeeds chassisSpeeds, boolean isOpenLoop) {
321+
SwerveModuleState[] desiredModuleStates = swerveKinematics
322+
.toSwerveModuleStates(ChassisSpeeds.discretize(chassisSpeeds, timeFromLastUpdate));
323+
setModuleStates(desiredModuleStates, isOpenLoop);
324+
}
325+
300326
/**
301327
* Drive the drivetrain in autonomous. Autonomous driving is always closed loop.
302328
*
@@ -305,9 +331,7 @@ public void drive(Translation2d translation, double rotation, boolean isOpenLoop
305331
*
306332
*/
307333
public void driveAutonomous(ChassisSpeeds chassisSpeeds) {
308-
SwerveModuleState[] desiredModuleStates = swerveKinematics
309-
.toSwerveModuleStates(ChassisSpeeds.discretize(chassisSpeeds, timeFromLastUpdate));
310-
setModuleStates(desiredModuleStates, false);
334+
drive(chassisSpeeds, false);
311335
}
312336

313337
/**
@@ -398,7 +422,7 @@ private Rotation2d getGyroRotation() {
398422
* per Second
399423
*/
400424
public double getGyroRate() {
401-
return pigeon.getRate();
425+
return pigeon.getAngularVelocityZWorld().getValueAsDouble();
402426
}
403427

404428
/**
@@ -442,6 +466,123 @@ public void updateTimer() {
442466
lastSimTime = Timer.getFPGATimestamp();
443467
}
444468

469+
/**
470+
* Returns the rotational velocity calculated with PID control to reach the
471+
* given rotation. This must be called every loop until you reach the given
472+
* rotation.
473+
*
474+
* @param desiredYaw
475+
* The desired yaw to rotate to
476+
* @return The desired velocity needed to rotate to that position.
477+
*/
478+
public AngularVelocity getVelocityToRotate(Rotation2d desiredYaw) {
479+
double yawSetpoint = teleopAutoDriveController.getThetaController().calculate(getRotation().getRadians(),
480+
desiredYaw.getRadians());
481+
482+
// limit the PID output to our maximum rotational speed
483+
yawSetpoint = MathUtil.clamp(yawSetpoint, -turnSpeed.in(Units.RadiansPerSecond),
484+
turnSpeed.in(Units.RadiansPerSecond));
485+
486+
return Units.RadiansPerSecond.of(yawSetpoint);
487+
}
488+
489+
// I would write "field relative" or "robot relative" somewhere here but I
490+
// genuinely don't know if its robot relative (i think it is?)
491+
/**
492+
* Aligns the drivetrain rotationally while still allowing for translational
493+
* inputs from the driver.
494+
*
495+
* @param isRedAlliance
496+
* If we are a red robot this match
497+
* @param desiredTarget
498+
* The desired rotation to reach
499+
* @param xVelocity
500+
* The manual translational velocity on the X axis
501+
* @param yVelocity
502+
* The manual translational velocity on the Y axis
503+
* @param isOpenLoop
504+
* If we are driving using OpenLoop control
505+
*/
506+
public void rotationalAlign(boolean isRedAlliance, Pose2d desiredTarget, LinearVelocity xVelocity,
507+
LinearVelocity yVelocity, boolean isOpenLoop) {
508+
int redAllianceMultiplier = isRedAlliance ? -1 : 1;
509+
// Rotational-only auto-align
510+
drive(new Translation2d(xVelocity.times(redAllianceMultiplier).in(Units.MetersPerSecond),
511+
yVelocity.times(redAllianceMultiplier).in(Units.MetersPerSecond)),
512+
getVelocityToRotate(desiredTarget.getRotation()).in(Units.RadiansPerSecond), isOpenLoop);
513+
}
514+
515+
/**
516+
*
517+
* Automatically drives to a pose during the teleoperated period. If an axis is
518+
* locked, it will override the automatically calculated input with the driver's
519+
* input.
520+
*
521+
* @param isRedAlliance
522+
* If we are a red robot this match
523+
* @param desiredTarget
524+
* The desired pose to drive to
525+
* @param xVelocity
526+
* The manual translational velocity on the X axis
527+
*
528+
* @param yVelocity
529+
* The manual translational velocity on the Y axis
530+
* @param isOpenLoop
531+
* If we are driving using OpenLoop control
532+
* @param lockX
533+
* If manual velocities should be used on the X axis
534+
* @param lockY
535+
* If manual velocities should be used on the Y axis
536+
*/
537+
public void autoAlign(boolean isRedAlliance, Pose2d desiredTarget, LinearVelocity xVelocity,
538+
LinearVelocity yVelocity, boolean isOpenLoop, boolean lockX, boolean lockY) {
539+
540+
int redAllianceMultiplier = isRedAlliance ? -1 : 1;
541+
double manualXVelocity = xVelocity.times(redAllianceMultiplier).in(Units.MetersPerSecond);
542+
double manualYVelocity = yVelocity.times(redAllianceMultiplier).in(Units.MetersPerSecond);
543+
// Full auto-align
544+
ChassisSpeeds automatedDTVelocity = teleopAutoDriveController.calculate(getPose(), desiredTarget, 0,
545+
desiredTarget.getRotation());
546+
547+
if (lockX) {
548+
automatedDTVelocity.vxMetersPerSecond = manualXVelocity;
549+
}
550+
if (lockY) {
551+
automatedDTVelocity.vyMetersPerSecond = manualYVelocity;
552+
}
553+
drive(automatedDTVelocity, isOpenLoop);
554+
}
555+
556+
public boolean isAtRotation(Rotation2d desiredRotation, Angle tolerance) {
557+
return (getRotation().getMeasure().compareTo(desiredRotation.getMeasure().minus(tolerance)) > 0)
558+
&& getRotation().getMeasure().compareTo(desiredRotation.getMeasure().plus(tolerance)) < 0;
559+
}
560+
561+
public boolean isAtPosition(Pose2d desiredPose2d, Distance tolerance) {
562+
return Units.Meters.of(getPose().getTranslation().getDistance(desiredPose2d.getTranslation())).lte(tolerance);
563+
}
564+
/**
565+
* Calculate which pose from an array has the closest rotation to the robot's
566+
* current pose. If multiple poses have the same rotation, the last one in the
567+
* list will be returned.
568+
*
569+
* @param poses
570+
* The list of poses to check
571+
* @return The last pose in the list with the closest rotation
572+
*/
573+
public Pose2d getClosestPoseByRotation(List<Pose2d> poses) {
574+
Pose2d closestPose = poses.get(0);
575+
double smallestDifference = Math.abs(getRotation().minus(closestPose.getRotation()).getRadians());
576+
for (Pose2d pose : poses) {
577+
double difference = Math.abs(getRotation().minus(pose.getRotation()).getRadians());
578+
if (difference < smallestDifference) {
579+
smallestDifference = difference;
580+
closestPose = pose;
581+
}
582+
}
583+
return closestPose;
584+
}
585+
445586
@Override
446587
public void periodic() {
447588
updateTimer();

0 commit comments

Comments
 (0)