-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathDriveSubsystem.java
More file actions
486 lines (416 loc) · 16.5 KB
/
Copy pathDriveSubsystem.java
File metadata and controls
486 lines (416 loc) · 16.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2019 FIRST. All Rights Reserved. */
/* Open Source Software - may be modified and shared by FRC teams. The code */
/* must be accompanied by the FIRST BSD license file in the root directory of */
/* the project. */
/*----------------------------------------------------------------------------*/
package frc.robot.subsystems;
import com.ctre.phoenix.motorcontrol.ControlMode;
import com.ctre.phoenix.motorcontrol.DemandType;
import com.ctre.phoenix.motorcontrol.FeedbackDevice;
import com.ctre.phoenix.motorcontrol.NeutralMode;
import com.ctre.phoenix.motorcontrol.StatorCurrentLimitConfiguration;
import com.ctre.phoenix.motorcontrol.can.WPI_TalonFX;
import com.kauailabs.navx.frc.AHRS;
import org.apache.commons.math3.analysis.polynomials.PolynomialSplineFunction;
import edu.wpi.first.math.MathUtil;
import edu.wpi.first.math.geometry.Pose2d;
import edu.wpi.first.math.geometry.Rotation2d;
import edu.wpi.first.math.kinematics.DifferentialDriveOdometry;
import edu.wpi.first.math.kinematics.DifferentialDriveWheelSpeeds;
import edu.wpi.first.wpilibj.SPI;
import edu.wpi.first.wpilibj.shuffleboard.Shuffleboard;
import edu.wpi.first.wpilibj.shuffleboard.ShuffleboardTab;
import edu.wpi.first.wpilibj2.command.SubsystemBase;
import frc.robot.Constants;
import frc.robot.utils.TractionControlController;
import frc.robot.utils.TurnPIDController;
public class DriveSubsystem extends SubsystemBase implements AutoCloseable {
public static class Hardware {
private WPI_TalonFX lMasterMotor, rMasterMotor;
private WPI_TalonFX lSlaveMotor, rSlaveMotor;
private AHRS navx;
public Hardware(WPI_TalonFX lMasterMotor,
WPI_TalonFX rMasterMotor,
WPI_TalonFX lSlaveMotor,
WPI_TalonFX rSlaveMotor,
AHRS navx) {
this.lMasterMotor = lMasterMotor;
this.rMasterMotor = rMasterMotor;
this.lSlaveMotor = lSlaveMotor;
this.rSlaveMotor = rSlaveMotor;
this.navx = navx;
}
}
private String SUBSYSTEM_NAME = "Drive Subsystem";
private TurnPIDController m_turnPIDController;
private TractionControlController m_tractionControlController;
private DifferentialDriveOdometry m_odometry;
private WPI_TalonFX m_lMasterMotor;
private WPI_TalonFX m_lSlaveMotor;
private WPI_TalonFX m_rMasterMotor;
private WPI_TalonFX m_rSlaveMotor;
private AHRS m_navx;
private final double TOLERANCE = 0.125;
private final double MOTOR_DEADBAND = 0.04;
private final double MAX_VOLTAGE = 12.0;
private final double VISION_AIM_DAMPENER = 0.9;
private double m_metersPerTick = 0.0;
private double m_deadband = 0.0;
/**
* Create an instance of DriveSubsystem
* <p>
* NOTE: ONLY ONE INSTANCE SHOULD EXIST AT ANY TIME!
* <p>
* @param drivetrainHardware Hardware devices required by drivetrain
* @param kP Proportional gain
* @param kD Derivative gain
* @param turnScalar Scalar for turn input (degrees)
* @param deadband Deadband for controller input [+0.001, +0.1]
* @param lookAhead Turn PID lookahead, in number of loops
* @param metersPerTick Meters traveled per encoder tick (meters)
* @param maxLinearSpeed Maximum linear speed of the robot (m/s)
* @param tractionControlCurve Spline function characterising traction of the robot
* @param throttleInputCurve Spline function characterising throttle input
* @param turnInputCurve Spline function characterising turn input
* @param currentLimitConfiguration Drive current limit
*/
public DriveSubsystem(Hardware drivetrainHardware, double kP, double kD, double turnScalar, double deadband, double lookAhead, double metersPerTick, double maxLinearSpeed,
PolynomialSplineFunction tractionControlCurve, PolynomialSplineFunction throttleInputCurve, PolynomialSplineFunction turnInputCurve,
StatorCurrentLimitConfiguration currentLimitConfiguration) {
m_turnPIDController = new TurnPIDController(kP, kD, turnScalar, deadband, lookAhead, turnInputCurve);
m_tractionControlController = new TractionControlController(deadband, maxLinearSpeed, tractionControlCurve, throttleInputCurve);
this.m_lMasterMotor = drivetrainHardware.lMasterMotor;
this.m_rMasterMotor = drivetrainHardware.rMasterMotor;
this.m_lSlaveMotor = drivetrainHardware.lSlaveMotor;
this.m_rSlaveMotor = drivetrainHardware.rSlaveMotor;
this.m_navx = drivetrainHardware.navx;
this.m_deadband = deadband;
this.m_metersPerTick = metersPerTick;
// Reset TalonFX settings
m_lMasterMotor.configFactoryDefault();
m_lSlaveMotor.configFactoryDefault();
m_rMasterMotor.configFactoryDefault();
m_rSlaveMotor.configFactoryDefault();
// Set all drive motors to brake
m_lMasterMotor.setNeutralMode(NeutralMode.Brake);
m_lSlaveMotor.setNeutralMode(NeutralMode.Brake);
m_rMasterMotor.setNeutralMode(NeutralMode.Brake);
m_rSlaveMotor.setNeutralMode(NeutralMode.Brake);
// Make rear left motor controllers follow left master
m_lSlaveMotor.set(ControlMode.Follower, m_lMasterMotor.getDeviceID());
// Make rear right motor controllers follow right master
m_rSlaveMotor.set(ControlMode.Follower, m_rMasterMotor.getDeviceID());
// Make motors use integrated encoder
m_lMasterMotor.configSelectedFeedbackSensor(FeedbackDevice.IntegratedSensor);
m_rMasterMotor.configSelectedFeedbackSensor(FeedbackDevice.IntegratedSensor);
// Reduce motor deadband
m_lMasterMotor.configNeutralDeadband(MOTOR_DEADBAND);
m_lSlaveMotor.configNeutralDeadband(MOTOR_DEADBAND);
m_rMasterMotor.configNeutralDeadband(MOTOR_DEADBAND);
m_rSlaveMotor.configNeutralDeadband(MOTOR_DEADBAND);
// Enable voltage compensation
m_lMasterMotor.configVoltageCompSaturation(MAX_VOLTAGE);
m_lMasterMotor.enableVoltageCompensation(true);
m_lSlaveMotor.configVoltageCompSaturation(MAX_VOLTAGE);
m_lSlaveMotor.enableVoltageCompensation(true);
m_rMasterMotor.configVoltageCompSaturation(MAX_VOLTAGE);
m_rMasterMotor.enableVoltageCompensation(true);
m_rSlaveMotor.configVoltageCompSaturation(MAX_VOLTAGE);
m_rSlaveMotor.enableVoltageCompensation(true);
// Enable current limits
m_lMasterMotor.configStatorCurrentLimit(currentLimitConfiguration);
m_lSlaveMotor.configStatorCurrentLimit(currentLimitConfiguration);
m_rMasterMotor.configStatorCurrentLimit(currentLimitConfiguration);
m_rSlaveMotor.configStatorCurrentLimit(currentLimitConfiguration);
// Initialise PID subsystem setpoint and input
m_navx.calibrate();
resetAngle();
m_turnPIDController.setSetpoint(0.0);
// Set drive PID tolerance
m_turnPIDController.setTolerance(TOLERANCE, 1);
// Initialise odometry
m_odometry = new DifferentialDriveOdometry(Rotation2d.fromDegrees(0));
}
/**
* Initialize hardware devices for drive subsystem
* @return hardware object containing all necessary devices for this subsystem
*/
public static Hardware initializeHardware() {
Hardware drivetrainHardware = new Hardware(new WPI_TalonFX(Constants.FRONT_LEFT_MOTOR_PORT),
new WPI_TalonFX(Constants.FRONT_RIGHT_MOTOR_PORT),
new WPI_TalonFX(Constants.REAR_LEFT_MOTOR_PORT),
new WPI_TalonFX(Constants.REAR_RIGHT_MOTOR_PORT),
new AHRS(SPI.Port.kMXP));
return drivetrainHardware;
}
/**
* Initialize drive subsystem for autonomous
*/
public void autonomousInit() {
// Invert only left side
m_lMasterMotor.setInverted(true);
m_lSlaveMotor.setInverted(true);
m_rMasterMotor.setInverted(false);
m_rSlaveMotor.setInverted(false);
}
/**
* Initialize drive subsystem for teleop
*/
public void teleopInit() {
resetDrivePID();
// Invert only right side
m_lMasterMotor.setInverted(false);
m_lSlaveMotor.setInverted(false);
m_rMasterMotor.setInverted(true);
m_rSlaveMotor.setInverted(true);
}
/**
* Create Shuffleboard tab for this subsystem and display values
*/
public void shuffleboard() {
ShuffleboardTab tab = Shuffleboard.getTab(SUBSYSTEM_NAME);
tab.addBoolean("Is on target?", () -> isOnTarget());
tab.addNumber("Drive Angle (degrees)", () -> getAngle());
tab.addNumber("Right master current (amps)", () -> m_rMasterMotor.getSupplyCurrent());
tab.addNumber("Left master current (amps)", () -> m_lMasterMotor.getSupplyCurrent());
tab.addNumber("Right slave current (amps)", () -> m_rSlaveMotor.getSupplyCurrent());
tab.addNumber("Left slave current (amps)", () -> m_lSlaveMotor.getSupplyCurrent());
}
/**
* Create SmartDashboard indicators
*/
public void smartDashboard() {}
@Override
public void periodic() {
smartDashboard();
}
/**
* Call this repeatedly to drive without PID during teleoperation
* @param speed Desired speed [-1.0, +1.0]
* @param turn Turn input [-1.0, +1.0]
* @param power exponent for drive response curve. 1 is linear response
*/
public void teleop(double speed, double turn, int power) {
speed = Math.copySign(Math.pow(speed, power), speed);
turn = Math.copySign(Math.pow(turn, power), turn);
speed = MathUtil.applyDeadband(speed, m_deadband);
turn = MathUtil.applyDeadband(turn, m_deadband);
m_lMasterMotor.set(ControlMode.PercentOutput, speed, DemandType.ArbitraryFeedForward, -turn);
m_rMasterMotor.set(ControlMode.PercentOutput, speed, DemandType.ArbitraryFeedForward, +turn);
}
/**
* Call this repeatedly to drive using PID during teleoperation
* @param speedRequest Desired speed [-1.0, +1.0]
* @param turnRequest Turn input [-1.0, +1.0]
*/
public void teleopPID(double speedRequest, double turnRequest) {
// Calculate next speed output
double speedOutput = m_tractionControlController.calculate(getInertialVelocity(), speedRequest);
// Calculate next PID turn output
double turnOutput = m_turnPIDController.calculate(getAngle(), getTurnRate(), turnRequest);
// Run motors with appropriate values
m_lMasterMotor.set(ControlMode.PercentOutput, speedOutput, DemandType.ArbitraryFeedForward, -turnOutput);
m_rMasterMotor.set(ControlMode.PercentOutput, speedOutput, DemandType.ArbitraryFeedForward, +turnOutput);
}
/**
* Turn robot by angleDelta
* @param angleDelta Degrees to turn robot by
*/
public void aimToAngle(double angleDelta) {
aimToAngle(angleDelta, 0.0);
}
/**
* Turn robot by angleDelta
* @param angleDelta Degrees to turn robot by
* @param speedRequest Desired speed [-1.0, +1.0]
*/
public void aimToAngle(double angleDelta, double speedRequest) {
angleDelta *= VISION_AIM_DAMPENER;
m_turnPIDController.setSetpoint(getAngle() + angleDelta);
// Calculate next speed output
double speedOutput = m_tractionControlController.calculate(getInertialVelocity(), speedRequest);
double turnOutput = m_turnPIDController.calculate(getAngle());
m_lMasterMotor.set(ControlMode.PercentOutput, speedOutput, DemandType.ArbitraryFeedForward, -turnOutput);
m_rMasterMotor.set(ControlMode.PercentOutput, speedOutput, DemandType.ArbitraryFeedForward, +turnOutput);
}
/**
* Whether robot is aimed at target
* @param tolerance tolerance in degrees
* @return true if robot is aimed at target within tolerance
*/
public boolean isOnTarget() {
return m_turnPIDController.atSetpoint();
}
/**
* Maintain setpoint angle
*/
public void maintainAngle() {
double turnOutput = m_turnPIDController.calculate(getAngle());
m_lMasterMotor.set(ControlMode.PercentOutput, 0.0, DemandType.ArbitraryFeedForward, -turnOutput);
m_rMasterMotor.set(ControlMode.PercentOutput, 0.0, DemandType.ArbitraryFeedForward, +turnOutput);
}
/**
* Controls the left and right sides of the drive directly with voltages.
* <p>
* Only use this method to drive during autonomous!
* @param leftVolts Left voltage [-12, +12]
* @param rightVolts Right voltage [-12, +12]
*/
public void autoTankDriveVolts(double leftVolts, double rightVolts) {
m_lMasterMotor.setVoltage(leftVolts);
m_rMasterMotor.setVoltage(rightVolts);
}
/**
* Set speed of left and right drive seperately
* @param leftSpeed speed [-1, 1]
* @param rightSpeed speed [-1, 1]
*/
public void tankDrive(double leftSpeed, double rightSpeed) {
m_lMasterMotor.set(ControlMode.PercentOutput, leftSpeed, DemandType.ArbitraryFeedForward, 0.0);
m_rMasterMotor.set(ControlMode.PercentOutput, rightSpeed, DemandType.ArbitraryFeedForward, 0.0);
}
/**
* Returns the current wheel speeds of the robot.
* @return The current wheel speeds.
*/
public DifferentialDriveWheelSpeeds getWheelSpeeds() {
return new DifferentialDriveWheelSpeeds(m_lMasterMotor.getSelectedSensorVelocity() * 10 * m_metersPerTick,
m_rMasterMotor.getSelectedSensorVelocity() * 10 * m_metersPerTick);
}
/**
* Resets the odometry
*/
public void resetOdometry(Pose2d pose) {
resetAngle();
resetEncoders();
m_odometry.resetPosition(pose, m_navx.getRotation2d());
}
/**
* Update robot odometry
* <p>
* Repeatedly call this method at a steady rate to keep track of robot position
*/
public void updateOdometry() {
m_odometry.update(m_navx.getRotation2d(),
m_lMasterMotor.getSelectedSensorPosition() * m_metersPerTick,
m_rMasterMotor.getSelectedSensorPosition() * m_metersPerTick);
}
/**
* Returns the currently estimated pose of the robot
* <p>
* This method is called periodically by the Ramsete command to update and obtain the latest pose
* @return The pose
*/
public Pose2d getPose() {
updateOdometry();
return m_odometry.getPoseMeters();
}
/**
* Returns the heading of the robot.
* @return the robot's heading in degrees, from 180 to 180
*/
public double getHeading() {
return m_navx.getYaw();
}
/**
* Zeros the heading of the robot
*/
public void zeroHeading() {
resetAngle();
}
/**
* Reset left and right drive
*/
public void resetEncoders() {
m_lMasterMotor.setSelectedSensorPosition(0.0);
m_rMasterMotor.setSelectedSensorPosition(0.0);
}
/**
* Returns the turn rate of the robot.
* @return The turn rate of the robot, in degrees per second
*/
public double getTurnRate() {
return m_navx.getRate();
}
/**
* Returns inertial velocity of the robot.
* @return Velocity of the robot as measured by the NAVX
*/
public double getInertialVelocity() {
return m_navx.getVelocityY();
}
/**
* Converts encoder position to meters
* @param ticks Encoder position
* @return Return distance in meters
*/
public double getDistance(double ticks) {
return ticks * m_metersPerTick;
}
/**
* Gets the average distance of the two encoders.
* @return the average of the two encoder readings
*/
public double getAverageEncoderDistance() {
return (((m_lMasterMotor.getSensorCollection().getIntegratedSensorPosition() * m_metersPerTick) +
(m_lMasterMotor.getSensorCollection().getIntegratedSensorPosition() * m_metersPerTick)) / 2);
}
/**
* Stop drivetrain
*/
public void stop() {
m_lMasterMotor.stopMotor();
m_rMasterMotor.stopMotor();
}
/**
* Get whether robot is turning or not
* @return true if robot is turning
*/
public boolean isTurning() {
return m_turnPIDController.isTurning();
}
/**
* Get DriveSubsystem angle as detected by the navX MXP
* @return Total accumulated yaw angle
*/
public double getAngle() {
return m_navx.getAngle();
}
/**
* Reset Drivesubsystem navX MXP yaw angle
*/
public void resetAngle() {
m_navx.reset();
}
/**
* Reset DriveSubsystem PID
*/
public void resetDrivePID() {
resetAngle();
m_turnPIDController.setSetpoint(0.0);
m_turnPIDController.reset();
}
/**
* Set setpoint for drive PID
* @param setpoint setpoint in degrees
*/
public void setDrivePIDSetpoint(double setpoint) {
m_turnPIDController.setSetpoint(setpoint);
}
/**
* Get setpoint for drive PID
* @return current setpoint in degrees
*/
public double getDrivePIDSetpoint() {
return m_turnPIDController.getSetpoint();
}
@Override
public void close() {
m_lMasterMotor = null;
m_rMasterMotor = null;
m_lSlaveMotor = null;
m_rSlaveMotor = null;
m_navx = null;
}
}