001/* 002 * Copyright (C) Cross The Road Electronics. All rights reserved. 003 * License information can be found in CTRE_LICENSE.txt 004 * For support and suggestions contact support@ctr-electronics.com or file 005 * an issue tracker at https://github.com/CrossTheRoadElec/Phoenix-Releases 006 */ 007package com.ctre.phoenix6.swerve; 008 009import static edu.wpi.first.units.Units.Newtons; 010 011import java.util.HashMap; 012import java.util.concurrent.locks.Lock; 013import java.util.concurrent.locks.ReentrantLock; 014 015import com.ctre.phoenix6.StatusCode; 016import com.ctre.phoenix6.configs.CANcoderConfiguration; 017import com.ctre.phoenix6.configs.CANdiConfiguration; 018import com.ctre.phoenix6.configs.TalonFXConfiguration; 019import com.ctre.phoenix6.configs.TalonFXSConfiguration; 020import com.ctre.phoenix6.controls.ControlRequest; 021import com.ctre.phoenix6.hardware.CANcoder; 022import com.ctre.phoenix6.hardware.CANdi; 023import com.ctre.phoenix6.hardware.ParentDevice; 024import com.ctre.phoenix6.hardware.TalonFX; 025import com.ctre.phoenix6.hardware.TalonFXS; 026import com.ctre.phoenix6.hardware.traits.CommonTalon; 027import com.ctre.phoenix6.signals.BrushedMotorWiringValue; 028import com.ctre.phoenix6.signals.ExternalFeedbackSensorSourceValue; 029import com.ctre.phoenix6.signals.FeedbackSensorSourceValue; 030import com.ctre.phoenix6.signals.InvertedValue; 031import com.ctre.phoenix6.signals.MotorArrangementValue; 032import com.ctre.phoenix6.signals.NeutralModeValue; 033import com.ctre.phoenix6.signals.SensorDirectionValue; 034import com.ctre.phoenix6.signals.SensorPhaseValue; 035import com.ctre.phoenix6.swerve.SwerveDrivetrain.DeviceConstructor; 036import com.ctre.phoenix6.swerve.SwerveModuleConstants.ClosedLoopOutputType; 037import com.ctre.phoenix6.swerve.SwerveModuleConstants.DriveMotorArrangement; 038import com.ctre.phoenix6.swerve.SwerveModuleConstants.SteerMotorArrangement; 039import com.ctre.phoenix6.swerve.jni.SwerveJNI; 040 041import edu.wpi.first.math.geometry.Rotation2d; 042import edu.wpi.first.math.kinematics.SwerveModulePosition; 043import edu.wpi.first.math.kinematics.SwerveModuleState; 044import edu.wpi.first.units.measure.Force; 045import edu.wpi.first.wpilibj.DriverStation; 046 047/** 048 * Swerve Module class that encapsulates a swerve module powered by CTR 049 * Electronics devices. 050 * <p> 051 * This class handles the hardware devices and configures them for 052 * swerve module operation using the Phoenix 6 API. 053 * <p> 054 * This class constructs hardware devices internally, so the user 055 * only specifies the constants (IDs, PID gains, gear ratios, etc). 056 * Getters for these hardware devices are available. 057 */ 058public class SwerveModule< 059 DriveMotorT extends CommonTalon, 060 SteerMotorT extends CommonTalon, 061 EncoderT extends ParentDevice 062> { 063 /** 064 * All possible control requests for the module steer motor. 065 */ 066 public enum SteerRequestType { 067 /** 068 * Control the drive motor using a Motion Magic® Expo request. 069 * The control output type is determined by {@link SwerveModuleConstants#SteerMotorClosedLoopOutput} 070 */ 071 MotionMagicExpo(0), 072 /** 073 * Control the drive motor using an unprofiled position request. 074 * The control output type is determined by {@link SwerveModuleConstants#SteerMotorClosedLoopOutput} 075 */ 076 Position(1); 077 078 public final int value; 079 080 SteerRequestType(int initValue) { 081 this.value = initValue; 082 } 083 084 private static HashMap<Integer, SteerRequestType> _map = null; 085 static { 086 _map = new HashMap<Integer, SteerRequestType>(); 087 for (SteerRequestType type : SteerRequestType.values()) { 088 _map.put(type.value, type); 089 } 090 } 091 092 /** 093 * Gets SteerRequestType from specified value 094 * 095 * @param value Value of SteerRequestType 096 * @return SteerRequestType of specified value 097 */ 098 public static SteerRequestType valueOf(int value) { 099 SteerRequestType retval = _map.get(value); 100 if (retval != null) 101 return retval; 102 return SteerRequestType.values()[0]; 103 } 104 } 105 106 /** 107 * All possible control requests for the module drive motor. 108 */ 109 public enum DriveRequestType { 110 /** 111 * Control the drive motor using an open-loop voltage request. 112 */ 113 OpenLoopVoltage(0), 114 /** 115 * Control the drive motor using a velocity closed-loop request. 116 * The control output type is determined by {@link SwerveModuleConstants#DriveMotorClosedLoopOutput} 117 */ 118 Velocity(1); 119 120 public final int value; 121 122 DriveRequestType(int initValue) { 123 this.value = initValue; 124 } 125 126 private static HashMap<Integer, DriveRequestType> _map = null; 127 static { 128 _map = new HashMap<Integer, DriveRequestType>(); 129 for (DriveRequestType type : DriveRequestType.values()) { 130 _map.put(type.value, type); 131 } 132 } 133 134 /** 135 * Gets DriveRequestType from specified value 136 * 137 * @param value Value of DriveRequestType 138 * @return DriveRequestType of specified value 139 */ 140 public static DriveRequestType valueOf(int value) { 141 DriveRequestType retval = _map.get(value); 142 if (retval != null) 143 return retval; 144 return DriveRequestType.values()[0]; 145 } 146 } 147 148 /** 149 * Contains everything the swerve module needs to apply a request. 150 */ 151 public static class ModuleRequest { 152 /** 153 * Unoptimized speed and direction the module should target. 154 */ 155 public SwerveModuleState State = new SwerveModuleState(); 156 157 /** 158 * Robot-centric wheel force feedforward to apply in the 159 * X direction. X is defined as forward according to WPILib 160 * convention, so this determines the forward force to apply. 161 * <p> 162 * This force should include friction. 163 */ 164 public double WheelForceFeedforwardX = 0.0; 165 /** 166 * Robot-centric wheel force feedforward to apply in the 167 * Y direction. Y is defined as to the left according to WPILib 168 * convention, so this determines the force to apply to the left. 169 * <p> 170 * This force should include friction. 171 */ 172 public double WheelForceFeedforwardY = 0.0; 173 174 /** 175 * The type of control request to use for the drive motor. 176 */ 177 public DriveRequestType DriveRequest = DriveRequestType.OpenLoopVoltage; 178 /** 179 * The type of control request to use for the steer motor. 180 */ 181 public SteerRequestType SteerRequest = SteerRequestType.Position; 182 183 /** 184 * The update period of the module request. Setting this to a 185 * non-zero value adds a velocity feedforward to the steer motor. 186 */ 187 public double UpdatePeriod = 0.0; 188 189 /** 190 * When using Voltage-based control, set to true (default) to use FOC commutation 191 * (requires Phoenix Pro), which increases peak power by ~15%. Set to false to 192 * use trapezoidal commutation. This is ignored when using Torque-based control, 193 * which always uses FOC. 194 * <p> 195 * FOC improves motor performance by leveraging torque (current) control. 196 * However, this may be inconvenient for applications that require specifying 197 * duty cycle or voltage. CTR-Electronics has developed a hybrid method that 198 * combines the performances gains of FOC while still allowing applications to 199 * provide duty cycle or voltage demand. This not to be confused with simple 200 * sinusoidal control or phase voltage control which lacks the performance 201 * gains. 202 */ 203 public boolean EnableFOC = true; 204 205 /** 206 * Modifies the State parameter and returns itself. 207 * <p> 208 * Unoptimized speed and direction the module should target. 209 * 210 * @param newState Parameter to modify 211 * @return Itself 212 */ 213 public ModuleRequest withState(SwerveModuleState newState) { 214 this.State = newState; 215 return this; 216 } 217 218 /** 219 * Modifies the WheelForceFeedforwardX parameter and returns itself. 220 * <p> 221 * Robot-centric wheel force feedforward to apply in the 222 * X direction. X is defined as forward according to WPILib 223 * convention, so this determines the forward force to apply. 224 * <p> 225 * This force should include friction applied to the ground. 226 * 227 * @param newWheelForceFeedforwardX Parameter to modify 228 * @return Itself 229 */ 230 public ModuleRequest withWheelForceFeedforwardX(double newWheelForceFeedforwardX) { 231 this.WheelForceFeedforwardX = newWheelForceFeedforwardX; 232 return this; 233 } 234 /** 235 * Modifies the WheelForceFeedforwardX parameter and returns itself. 236 * <p> 237 * Robot-centric wheel force feedforward to apply in the 238 * X direction. X is defined as forward according to WPILib 239 * convention, so this determines the forward force to apply. 240 * <p> 241 * This force should include friction applied to the ground. 242 * 243 * @param newWheelForceFeedforwardX Parameter to modify 244 * @return Itself 245 */ 246 public ModuleRequest withWheelForceFeedforwardX(Force newWheelForceFeedforwardX) { 247 this.WheelForceFeedforwardX = newWheelForceFeedforwardX.in(Newtons); 248 return this; 249 } 250 /** 251 * Helper method to get the WheelForceFeedforwardX parameter 252 * as a unit type. If not using the Java units library, 253 * {@link #WheelForceFeedforwardX} can be accessed directly instead. 254 * <p> 255 * Robot-centric wheel force feedforward to apply in the 256 * X direction. X is defined as forward according to WPILib 257 * convention, so this determines the forward force to apply. 258 * <p> 259 * This force should include friction applied to the ground. 260 * 261 * @return WheelForceFeedforwardX 262 */ 263 public Force getWheelForceFeedforwardXMeasure() { 264 return Newtons.of(WheelForceFeedforwardX); 265 } 266 267 /** 268 * Modifies the WheelForceFeedforwardY parameter and returns itself. 269 * <p> 270 * Robot-centric wheel force feedforward to apply in the 271 * Y direction. Y is defined as to the left according to WPILib 272 * convention, so this determines the force to apply to the left. 273 * <p> 274 * This force should include friction applied to the ground. 275 * 276 * @param newWheelForceFeedforwardY Parameter to modify 277 * @return Itself 278 */ 279 public ModuleRequest withWheelForceFeedforwardY(double newWheelForceFeedforwardY) { 280 this.WheelForceFeedforwardY = newWheelForceFeedforwardY; 281 return this; 282 } 283 /** 284 * Modifies the WheelForceFeedforwardY parameter and returns itself. 285 * <p> 286 * Robot-centric wheel force feedforward to apply in the 287 * Y direction. Y is defined as to the left according to WPILib 288 * convention, so this determines the force to apply to the left. 289 * <p> 290 * This force should include friction applied to the ground. 291 * 292 * @param newWheelForceFeedforwardY Parameter to modify 293 * @return Itself 294 */ 295 public ModuleRequest withWheelForceFeedforwardY(Force newWheelForceFeedforwardY) { 296 this.WheelForceFeedforwardY = newWheelForceFeedforwardY.in(Newtons); 297 return this; 298 } 299 /** 300 * Helper method to get the WheelForceFeedforwardY parameter 301 * as a unit type. If not using the Java units library, 302 * {@link #WheelForceFeedforwardY} can be accessed directly instead. 303 * <p> 304 * Robot-centric wheel force feedforward to apply in the 305 * Y direction. Y is defined as to the left according to WPILib 306 * convention, so this determines the force to apply to the left. 307 * <p> 308 * This force should include friction applied to the ground. 309 * 310 * @return WheelForceFeedforwardY 311 */ 312 public Force getWheelForceFeedforwardYMeasure() { 313 return Newtons.of(WheelForceFeedforwardY); 314 } 315 316 /** 317 * Modifies the DriveRequest parameter and returns itself. 318 * <p> 319 * The type of control request to use for the drive motor. 320 * 321 * @param newDriveRequest Parameter to modify 322 * @return Itself 323 */ 324 public ModuleRequest withDriveRequest(DriveRequestType newDriveRequest) { 325 this.DriveRequest = newDriveRequest; 326 return this; 327 } 328 329 /** 330 * Modifies the SteerRequest parameter and returns itself. 331 * <p> 332 * The type of control request to use for the steer motor. 333 * 334 * @param newSteerRequest Parameter to modify 335 * @return Itself 336 */ 337 public ModuleRequest withSteerRequest(SteerRequestType newSteerRequest) { 338 this.SteerRequest = newSteerRequest; 339 return this; 340 } 341 342 /** 343 * Modifies the UpdatePeriod parameter and returns itself. 344 * <p> 345 * The update period of the module request. Setting this to a 346 * non-zero value adds a velocity feedforward to the steer motor. 347 * 348 * @param newUpdatePeriod Parameter to modify 349 * @return Itself 350 */ 351 public ModuleRequest withUpdatePeriod(double newUpdatePeriod) { 352 this.UpdatePeriod = newUpdatePeriod; 353 return this; 354 } 355 356 /** 357 * Modifies the EnableFOC parameter and returns itself. 358 * <p> 359 * When using Voltage-based control, set to true (default) to use FOC commutation 360 * (requires Phoenix Pro), which increases peak power by ~15%. Set to false to 361 * use trapezoidal commutation. This is ignored when using Torque-based control, 362 * which always uses FOC. 363 * <p> 364 * FOC improves motor performance by leveraging torque (current) control. 365 * However, this may be inconvenient for applications that require specifying 366 * duty cycle or voltage. CTR-Electronics has developed a hybrid method that 367 * combines the performances gains of FOC while still allowing applications to 368 * provide duty cycle or voltage demand. This not to be confused with simple 369 * sinusoidal control or phase voltage control which lacks the performance 370 * gains. 371 * 372 * @param newEnableFOC Parameter to modify 373 * @return Itself 374 */ 375 public ModuleRequest withEnableFOC(boolean newEnableFOC) { 376 this.EnableFOC = newEnableFOC; 377 return this; 378 } 379 } 380 381 /** Number of times to attempt config applies. */ 382 protected static final int kNumConfigAttempts = 2; 383 384 /** ID of the native drivetrain instance, used for JNI calls. */ 385 protected final int m_drivetrainId; 386 /** Index of this module in the native drivetrain, used for JNI calls. */ 387 protected final int m_moduleIdx; 388 /** JNI instance to use for non-static JNI calls. This object is not thread-safe. */ 389 protected final SwerveJNI m_jni = new SwerveJNI(); 390 391 private final DriveMotorT m_driveMotor; 392 private final SteerMotorT m_steerMotor; 393 private final EncoderT m_encoder; 394 395 private final ClosedLoopOutputType m_driveClosedLoopOutput; 396 private final ClosedLoopOutputType m_steerClosedLoopOutput; 397 398 private final SwerveModulePosition m_currentPosition = new SwerveModulePosition(); 399 private final SwerveModuleState m_currentState = new SwerveModuleState(); 400 private final SwerveModuleState m_targetState = new SwerveModuleState(); 401 /** Lock protecting the swerve module state. */ 402 private final Lock m_stateLock = new ReentrantLock(); 403 404 boolean m_isValid = true; 405 406 /** 407 * Construct a SwerveModule with the specified constants. 408 * 409 * @param driveMotorConstructor Constructor for the drive motor, such as {@code TalonFX::new} 410 * @param steerMotorConstructor Constructor for the steer motor, such as {@code TalonFX::new} 411 * @param encoderConstructor Constructor for the azimuth encoder, such as {@code CANcoder::new} 412 * @param constants Constants used to construct the module 413 * @param canbusName The name of the CAN bus this module is on 414 * @param drivetrainId ID of the swerve drivetrain 415 * @param index Index of this swerve module 416 */ 417 public SwerveModule( 418 DeviceConstructor<DriveMotorT> driveMotorConstructor, 419 DeviceConstructor<SteerMotorT> steerMotorConstructor, 420 DeviceConstructor<EncoderT> encoderConstructor, 421 SwerveModuleConstants<?, ?, ?> constants, 422 String canbusName, 423 int drivetrainId, 424 int index 425 ) { 426 m_drivetrainId = drivetrainId; 427 m_moduleIdx = index; 428 429 m_driveMotor = driveMotorConstructor.create(constants.DriveMotorId, canbusName); 430 m_steerMotor = steerMotorConstructor.create(constants.SteerMotorId, canbusName); 431 m_encoder = encoderConstructor.create(constants.EncoderId, canbusName); 432 433 m_driveClosedLoopOutput = constants.DriveMotorClosedLoopOutput; 434 m_steerClosedLoopOutput = constants.SteerMotorClosedLoopOutput; 435 436 StatusCode response = StatusCode.OK; 437 438 if ( 439 m_driveMotor instanceof TalonFX driveMotor && 440 (constants.DriveMotorInitialConfigs == null || constants.DriveMotorInitialConfigs instanceof TalonFXConfiguration) 441 ) { 442 TalonFXConfiguration driveConfigs = (TalonFXConfiguration)constants.DriveMotorInitialConfigs; 443 if (driveConfigs == null) { 444 driveConfigs = new TalonFXConfiguration(); 445 } 446 447 driveConfigs.MotorOutput.NeutralMode = NeutralModeValue.Brake; 448 449 driveConfigs.Slot0 = constants.DriveMotorGains; 450 driveConfigs.TorqueCurrent.PeakForwardTorqueCurrent = constants.SlipCurrent; 451 driveConfigs.TorqueCurrent.PeakReverseTorqueCurrent = -constants.SlipCurrent; 452 driveConfigs.CurrentLimits.StatorCurrentLimit = constants.SlipCurrent; 453 driveConfigs.CurrentLimits.StatorCurrentLimitEnable = true; 454 455 if (constants.DriveMotorType != DriveMotorArrangement.TalonFX_Integrated) { 456 System.out.println( 457 "Drive motor Talon FX ID " + driveMotor.getDeviceID() + " only supports TalonFX_Integrated." 458 ); 459 } 460 461 driveConfigs.MotorOutput.Inverted = constants.DriveMotorInverted 462 ? InvertedValue.Clockwise_Positive 463 : InvertedValue.CounterClockwise_Positive; 464 for (int i = 0; i < kNumConfigAttempts; ++i) { 465 response = driveMotor.getConfigurator().apply(driveConfigs); 466 if (response.isOK()) break; 467 } 468 if (!response.isOK()) { 469 System.out.println( 470 "Talon ID " + driveMotor.getDeviceID() + " failed config with error " + response.toString() 471 ); 472 } 473 } else if ( 474 m_driveMotor instanceof TalonFXS driveMotor && 475 (constants.DriveMotorInitialConfigs == null || constants.DriveMotorInitialConfigs instanceof TalonFXSConfiguration) 476 ) { 477 TalonFXSConfiguration driveConfigs = (TalonFXSConfiguration)constants.DriveMotorInitialConfigs; 478 if (driveConfigs == null) { 479 driveConfigs = new TalonFXSConfiguration(); 480 } 481 482 driveConfigs.MotorOutput.NeutralMode = NeutralModeValue.Brake; 483 484 driveConfigs.Slot0 = constants.DriveMotorGains; 485 driveConfigs.CurrentLimits.StatorCurrentLimit = constants.SlipCurrent; 486 driveConfigs.CurrentLimits.StatorCurrentLimitEnable = true; 487 488 switch (constants.DriveMotorType) { 489 case TalonFX_Integrated: 490 System.out.println( 491 "Cannot use TalonFX_Integrated drive motor type on Talon FXS ID " + driveMotor.getDeviceID() + ". TalonFX_Integrated is only supported on Talon FX." 492 ); 493 break; 494 495 case TalonFXS_NEO_JST: 496 driveConfigs.Commutation.MotorArrangement = MotorArrangementValue.NEO_JST; 497 break; 498 case TalonFXS_VORTEX_JST: 499 driveConfigs.Commutation.MotorArrangement = MotorArrangementValue.VORTEX_JST; 500 break; 501 } 502 503 driveConfigs.MotorOutput.Inverted = constants.DriveMotorInverted 504 ? InvertedValue.Clockwise_Positive 505 : InvertedValue.CounterClockwise_Positive; 506 for (int i = 0; i < kNumConfigAttempts; ++i) { 507 response = driveMotor.getConfigurator().apply(driveConfigs); 508 if (response.isOK()) break; 509 } 510 if (!response.isOK()) { 511 System.out.println( 512 "Talon ID " + driveMotor.getDeviceID() + " failed config with error " + response.toString() 513 ); 514 } 515 } else { 516 DriverStation.reportError( 517 "[phoenix] SwerveDrivetrain drive motor type does not match the SwerveModuleConstants drive initial configs type. Ensure that the SwerveDrivetrain and SwerveModuleConstants instances are both constructed with the same drive motor type (TalonFX/TalonFXConfiguration, TalonFXS/TalonFXSConfiguration, etc.).", 518 true 519 ); 520 m_isValid = false; 521 } 522 523 if ( 524 m_steerMotor instanceof TalonFX steerMotor && 525 (constants.SteerMotorInitialConfigs == null || constants.SteerMotorInitialConfigs instanceof TalonFXConfiguration) 526 ) { 527 TalonFXConfiguration steerConfigs = (TalonFXConfiguration)constants.SteerMotorInitialConfigs; 528 if (steerConfigs == null) { 529 steerConfigs = new TalonFXConfiguration(); 530 } 531 532 steerConfigs.MotorOutput.NeutralMode = NeutralModeValue.Brake; 533 534 steerConfigs.Slot0 = constants.SteerMotorGains; 535 536 if (constants.SteerMotorType != SteerMotorArrangement.TalonFX_Integrated) { 537 System.out.println( 538 "Steer motor Talon FX ID " + steerMotor.getDeviceID() + " only supports TalonFX_Integrated." 539 ); 540 } 541 542 /* Modify configuration to use remote encoder setting */ 543 steerConfigs.Feedback.FeedbackRemoteSensorID = constants.EncoderId; 544 switch (constants.FeedbackSource) { 545 case FusedCANcoder: 546 steerConfigs.Feedback.FeedbackSensorSource = FeedbackSensorSourceValue.FusedCANcoder; 547 break; 548 case SyncCANcoder: 549 steerConfigs.Feedback.FeedbackSensorSource = FeedbackSensorSourceValue.SyncCANcoder; 550 break; 551 case RemoteCANcoder: 552 steerConfigs.Feedback.FeedbackSensorSource = FeedbackSensorSourceValue.RemoteCANcoder; 553 break; 554 555 case FusedCANdiPWM1: 556 steerConfigs.Feedback.FeedbackSensorSource = FeedbackSensorSourceValue.FusedCANdiPWM1; 557 break; 558 case FusedCANdiPWM2: 559 steerConfigs.Feedback.FeedbackSensorSource = FeedbackSensorSourceValue.FusedCANdiPWM2; 560 break; 561 case SyncCANdiPWM1: 562 steerConfigs.Feedback.FeedbackSensorSource = FeedbackSensorSourceValue.SyncCANdiPWM1; 563 break; 564 case SyncCANdiPWM2: 565 steerConfigs.Feedback.FeedbackSensorSource = FeedbackSensorSourceValue.SyncCANdiPWM2; 566 break; 567 case RemoteCANdiPWM1: 568 steerConfigs.Feedback.FeedbackSensorSource = FeedbackSensorSourceValue.RemoteCANdiPWM1; 569 break; 570 case RemoteCANdiPWM2: 571 steerConfigs.Feedback.FeedbackSensorSource = FeedbackSensorSourceValue.RemoteCANdiPWM2; 572 break; 573 574 case TalonFXS_PulseWidth: 575 System.out.println( 576 "Cannot use Pulse Width steer feedback type on Talon FX ID " + steerMotor.getDeviceID() + ". Pulse Width is only supported on Talon FXS." 577 ); 578 break; 579 } 580 steerConfigs.Feedback.RotorToSensorRatio = constants.SteerMotorGearRatio; 581 582 steerConfigs.MotionMagic.MotionMagicExpo_kV = 0.12 * constants.SteerMotorGearRatio; 583 steerConfigs.MotionMagic.MotionMagicExpo_kA = 0.8 / constants.SteerMotorGearRatio; 584 585 steerConfigs.ClosedLoopGeneral.ContinuousWrap = true; // Enable continuous wrap for swerve modules 586 587 steerConfigs.MotorOutput.Inverted = constants.SteerMotorInverted 588 ? InvertedValue.Clockwise_Positive 589 : InvertedValue.CounterClockwise_Positive; 590 for (int i = 0; i < kNumConfigAttempts; ++i) { 591 response = steerMotor.getConfigurator().apply(steerConfigs); 592 if (response.isOK()) break; 593 } 594 if (!response.isOK()) { 595 System.out.println( 596 "Talon ID " + steerMotor.getDeviceID() + " failed config with error: " + response.toString() 597 ); 598 } 599 } else if ( 600 m_steerMotor instanceof TalonFXS steerMotor && 601 (constants.SteerMotorInitialConfigs == null || constants.SteerMotorInitialConfigs instanceof TalonFXSConfiguration) 602 ) { 603 TalonFXSConfiguration steerConfigs = (TalonFXSConfiguration)constants.SteerMotorInitialConfigs; 604 if (steerConfigs == null) { 605 steerConfigs = new TalonFXSConfiguration(); 606 } 607 608 steerConfigs.MotorOutput.NeutralMode = NeutralModeValue.Brake; 609 610 steerConfigs.Slot0 = constants.SteerMotorGains; 611 612 switch (constants.SteerMotorType) { 613 case TalonFX_Integrated: 614 System.out.println( 615 "Cannot use TalonFX_Integrated steer motor type on Talon FXS ID " + steerMotor.getDeviceID() + ". TalonFX_Integrated is only supported on Talon FX." 616 ); 617 break; 618 619 case TalonFXS_Minion_JST: 620 steerConfigs.Commutation.MotorArrangement = MotorArrangementValue.Minion_JST; 621 break; 622 case TalonFXS_NEO_JST: 623 steerConfigs.Commutation.MotorArrangement = MotorArrangementValue.NEO_JST; 624 break; 625 case TalonFXS_VORTEX_JST: 626 steerConfigs.Commutation.MotorArrangement = MotorArrangementValue.VORTEX_JST; 627 break; 628 case TalonFXS_NEO550_JST: 629 steerConfigs.Commutation.MotorArrangement = MotorArrangementValue.NEO550_JST; 630 break; 631 case TalonFXS_Brushed_AB: 632 steerConfigs.Commutation.MotorArrangement = MotorArrangementValue.Brushed_DC; 633 steerConfigs.Commutation.BrushedMotorWiring = BrushedMotorWiringValue.Leads_A_and_B; 634 break; 635 case TalonFXS_Brushed_AC: 636 steerConfigs.Commutation.MotorArrangement = MotorArrangementValue.Brushed_DC; 637 steerConfigs.Commutation.BrushedMotorWiring = BrushedMotorWiringValue.Leads_A_and_C; 638 break; 639 case TalonFXS_Brushed_BC: 640 steerConfigs.Commutation.MotorArrangement = MotorArrangementValue.Brushed_DC; 641 steerConfigs.Commutation.BrushedMotorWiring = BrushedMotorWiringValue.Leads_B_and_C; 642 break; 643 } 644 645 /* Modify configuration to use remote encoder setting */ 646 steerConfigs.ExternalFeedback.FeedbackRemoteSensorID = constants.EncoderId; 647 switch (constants.FeedbackSource) { 648 case FusedCANcoder: 649 steerConfigs.ExternalFeedback.ExternalFeedbackSensorSource = ExternalFeedbackSensorSourceValue.FusedCANcoder; 650 break; 651 case SyncCANcoder: 652 steerConfigs.ExternalFeedback.ExternalFeedbackSensorSource = ExternalFeedbackSensorSourceValue.SyncCANcoder; 653 break; 654 case RemoteCANcoder: 655 steerConfigs.ExternalFeedback.ExternalFeedbackSensorSource = ExternalFeedbackSensorSourceValue.RemoteCANcoder; 656 break; 657 658 case FusedCANdiPWM1: 659 steerConfigs.ExternalFeedback.ExternalFeedbackSensorSource = ExternalFeedbackSensorSourceValue.FusedCANdiPWM1; 660 break; 661 case FusedCANdiPWM2: 662 steerConfigs.ExternalFeedback.ExternalFeedbackSensorSource = ExternalFeedbackSensorSourceValue.FusedCANdiPWM2; 663 break; 664 case SyncCANdiPWM1: 665 steerConfigs.ExternalFeedback.ExternalFeedbackSensorSource = ExternalFeedbackSensorSourceValue.SyncCANdiPWM1; 666 break; 667 case SyncCANdiPWM2: 668 steerConfigs.ExternalFeedback.ExternalFeedbackSensorSource = ExternalFeedbackSensorSourceValue.SyncCANdiPWM2; 669 break; 670 case RemoteCANdiPWM1: 671 steerConfigs.ExternalFeedback.ExternalFeedbackSensorSource = ExternalFeedbackSensorSourceValue.RemoteCANdiPWM1; 672 break; 673 case RemoteCANdiPWM2: 674 steerConfigs.ExternalFeedback.ExternalFeedbackSensorSource = ExternalFeedbackSensorSourceValue.RemoteCANdiPWM2; 675 break; 676 677 case TalonFXS_PulseWidth: 678 steerConfigs.ExternalFeedback.ExternalFeedbackSensorSource = ExternalFeedbackSensorSourceValue.PulseWidth; 679 steerConfigs.ExternalFeedback.AbsoluteSensorOffset = constants.EncoderOffset; 680 steerConfigs.ExternalFeedback.SensorPhase = constants.EncoderInverted 681 ? SensorPhaseValue.Opposed 682 : SensorPhaseValue.Aligned; 683 break; 684 } 685 steerConfigs.ExternalFeedback.RotorToSensorRatio = constants.SteerMotorGearRatio; 686 687 steerConfigs.MotionMagic.MotionMagicExpo_kV = 0.12 * constants.SteerMotorGearRatio; 688 steerConfigs.MotionMagic.MotionMagicExpo_kA = 0.8 / constants.SteerMotorGearRatio; 689 690 steerConfigs.ClosedLoopGeneral.ContinuousWrap = true; // Enable continuous wrap for swerve modules 691 692 steerConfigs.MotorOutput.Inverted = constants.SteerMotorInverted 693 ? InvertedValue.Clockwise_Positive 694 : InvertedValue.CounterClockwise_Positive; 695 for (int i = 0; i < kNumConfigAttempts; ++i) { 696 response = steerMotor.getConfigurator().apply(steerConfigs); 697 if (response.isOK()) break; 698 } 699 if (!response.isOK()) { 700 System.out.println( 701 "Talon ID " + steerMotor.getDeviceID() + " failed config with error: " + response.toString() 702 ); 703 } 704 } else { 705 DriverStation.reportError( 706 "[phoenix] SwerveDrivetrain steer motor type does not match the SwerveModuleConstants steer initial configs type. Ensure that the SwerveDrivetrain and SwerveModuleConstants instances are both constructed with the same steer motor type (TalonFX/TalonFXConfiguration, TalonFXS/TalonFXSConfiguration, etc.).", 707 true 708 ); 709 m_isValid = false; 710 } 711 712 if ( 713 m_encoder instanceof CANcoder encoder && 714 (constants.EncoderInitialConfigs == null || constants.EncoderInitialConfigs instanceof CANcoderConfiguration) 715 ) { 716 CANcoderConfiguration encoderConfigs = (CANcoderConfiguration)constants.EncoderInitialConfigs; 717 if (encoderConfigs == null) { 718 encoderConfigs = new CANcoderConfiguration(); 719 } 720 721 encoderConfigs.MagnetSensor.MagnetOffset = constants.EncoderOffset; 722 encoderConfigs.MagnetSensor.SensorDirection = constants.EncoderInverted 723 ? SensorDirectionValue.Clockwise_Positive 724 : SensorDirectionValue.CounterClockwise_Positive; 725 for (int i = 0; i < kNumConfigAttempts; ++i) { 726 response = encoder.getConfigurator().apply(encoderConfigs); 727 if (response.isOK()) break; 728 } 729 if (!response.isOK()) { 730 System.out.println( 731 "Encoder ID " + encoder.getDeviceID() + " failed config with error: " + response.toString() 732 ); 733 } 734 } else if ( 735 m_encoder instanceof CANdi encoder && 736 (constants.EncoderInitialConfigs == null || constants.EncoderInitialConfigs instanceof CANdiConfiguration) 737 ) { 738 CANdiConfiguration encoderConfigs = (CANdiConfiguration)constants.EncoderInitialConfigs; 739 if (encoderConfigs == null) { 740 encoderConfigs = new CANdiConfiguration(); 741 } 742 743 for (int i = 0; i < kNumConfigAttempts; ++i) { 744 response = encoder.getConfigurator().apply(encoderConfigs.DigitalInputs); 745 if (response.isOK()) break; 746 } 747 if (!response.isOK()) { 748 System.out.println( 749 "Encoder ID " + encoder.getDeviceID() + " failed config with error: " + response.toString() 750 ); 751 } 752 753 for (int i = 0; i < kNumConfigAttempts; ++i) { 754 response = encoder.getConfigurator().apply(encoderConfigs.CustomParams); 755 if (response.isOK()) break; 756 } 757 if (!response.isOK()) { 758 System.out.println( 759 "Encoder ID " + encoder.getDeviceID() + " failed config with error: " + response.toString() 760 ); 761 } 762 763 switch (constants.FeedbackSource) { 764 case FusedCANdiPWM1: 765 case SyncCANdiPWM1: 766 case RemoteCANdiPWM1: 767 encoderConfigs.PWM1.AbsoluteSensorOffset = constants.EncoderOffset; 768 encoderConfigs.PWM1.SensorDirection = constants.EncoderInverted; 769 770 for (int i = 0; i < kNumConfigAttempts; ++i) { 771 response = encoder.getConfigurator().apply(encoderConfigs.PWM1); 772 if (response.isOK()) break; 773 } 774 if (!response.isOK()) { 775 System.out.println( 776 "Encoder ID " + encoder.getDeviceID() + " failed config with error: " + response.toString() 777 ); 778 } 779 break; 780 781 case FusedCANdiPWM2: 782 case SyncCANdiPWM2: 783 case RemoteCANdiPWM2: 784 encoderConfigs.PWM2.AbsoluteSensorOffset = constants.EncoderOffset; 785 encoderConfigs.PWM2.SensorDirection = constants.EncoderInverted; 786 787 for (int i = 0; i < kNumConfigAttempts; ++i) { 788 response = encoder.getConfigurator().apply(encoderConfigs.PWM2); 789 if (response.isOK()) break; 790 } 791 if (!response.isOK()) { 792 System.out.println( 793 "Encoder ID " + encoder.getDeviceID() + " failed config with error: " + response.toString() 794 ); 795 } 796 break; 797 798 default: 799 break; 800 } 801 } else if ( 802 m_encoder instanceof TalonFXS && 803 (constants.EncoderInitialConfigs == null || constants.EncoderInitialConfigs instanceof TalonFXSConfiguration) 804 ) { 805 /* do nothing if using encoder on a Talon FXS, configs are applied when initializing the steer motor */ 806 } else { 807 DriverStation.reportError( 808 "[phoenix] SwerveDrivetrain encoder type does not match the SwerveModuleConstants encoder initial configs type. Ensure that the SwerveDrivetrain and SwerveModuleConstants instances are both constructed with the same encoder type (CANcoder/CANcoderConfiguration, CANdi/CANdiConfiguration, etc.).", 809 true 810 ); 811 m_isValid = false; 812 } 813 } 814 815 /** 816 * Applies the desired SwerveModuleState to this module. 817 * 818 * @param moduleRequest The request to apply to this module 819 */ 820 public void apply(ModuleRequest moduleRequest) { 821 m_jni.moduleApplyParams.state.speed = moduleRequest.State.speedMetersPerSecond; 822 m_jni.moduleApplyParams.state.angle = moduleRequest.State.angle.getRadians(); 823 m_jni.moduleApplyParams.wheelForceFeedforwardX = moduleRequest.WheelForceFeedforwardX; 824 m_jni.moduleApplyParams.wheelForceFeedforwardY = moduleRequest.WheelForceFeedforwardY; 825 m_jni.moduleApplyParams.driveRequest = moduleRequest.DriveRequest.value; 826 m_jni.moduleApplyParams.steerRequest = moduleRequest.SteerRequest.value; 827 m_jni.moduleApplyParams.updatePeriod = moduleRequest.UpdatePeriod; 828 m_jni.moduleApplyParams.enableFOC = moduleRequest.EnableFOC; 829 830 m_jni.JNI_Module_Apply(m_drivetrainId, m_moduleIdx); 831 } 832 833 /** 834 * Controls this module using the specified drive and steer control requests. 835 * 836 * This is intended only to be used for characterization of the robot; do not use this for normal use. 837 * 838 * @param driveRequest The control request to apply to the drive motor 839 * @param steerRequest The control request to apply to the steer motor 840 */ 841 public void apply(ControlRequest driveRequest, ControlRequest steerRequest) { 842 m_driveMotor.setControl(driveRequest.withUpdateFreqHz(0)); 843 m_steerMotor.setControl(steerRequest.withUpdateFreqHz(0)); 844 } 845 846 /** 847 * Gets the state of this module and passes it back as a 848 * SwerveModulePosition object with latency compensated values. 849 * <p> 850 * This function is blocking when it performs a refresh. 851 * 852 * @param refresh True if the signals should be refreshed 853 * @return SwerveModulePosition containing this module's state 854 */ 855 public final SwerveModulePosition getPosition(boolean refresh) { 856 try { 857 m_stateLock.lock(); 858 859 m_jni.JNI_Module_GetPosition(m_drivetrainId, m_moduleIdx, refresh); 860 861 m_currentPosition.distanceMeters = m_jni.modulePosition.distance; 862 if (m_currentPosition.angle.getRadians() != m_jni.modulePosition.angle) { 863 m_currentPosition.angle = Rotation2d.fromRadians(m_jni.modulePosition.angle); 864 } 865 return m_currentPosition; 866 } finally { 867 m_stateLock.unlock(); 868 } 869 } 870 871 /** 872 * Gets the last cached swerve module position. 873 * This differs from {@link getPosition} in that it will not 874 * perform any latency compensation or refresh the signals. 875 * 876 * @return Last cached SwerveModulePosition 877 */ 878 public final SwerveModulePosition getCachedPosition() { 879 try { 880 m_stateLock.lock(); 881 882 m_jni.JNI_Module_GetCachedPosition(m_drivetrainId, m_moduleIdx); 883 884 m_currentPosition.distanceMeters = m_jni.modulePosition.distance; 885 if (m_currentPosition.angle.getRadians() != m_jni.modulePosition.angle) { 886 m_currentPosition.angle = Rotation2d.fromRadians(m_jni.modulePosition.angle); 887 } 888 return m_currentPosition; 889 } finally { 890 m_stateLock.unlock(); 891 } 892 } 893 894 /** 895 * Get the current state of the module. 896 * <p> 897 * This is typically used for telemetry, as the SwerveModulePosition 898 * is used for odometry. 899 * 900 * @return Current state of the module 901 */ 902 public final SwerveModuleState getCurrentState() { 903 try { 904 m_stateLock.lock(); 905 906 m_jni.JNI_Module_GetCurrentState(m_drivetrainId, m_moduleIdx); 907 908 m_currentState.speedMetersPerSecond = m_jni.moduleState.speed; 909 if (m_currentState.angle.getRadians() != m_jni.moduleState.angle) { 910 m_currentState.angle = Rotation2d.fromRadians(m_jni.moduleState.angle); 911 } 912 return m_currentState; 913 } finally { 914 m_stateLock.unlock(); 915 } 916 } 917 918 /** 919 * Get the target state of the module. 920 * <p> 921 * This is typically used for telemetry. 922 * 923 * @return Target state of the module 924 */ 925 public final SwerveModuleState getTargetState() { 926 try { 927 m_stateLock.lock(); 928 929 m_jni.JNI_Module_GetTargetState(m_drivetrainId, m_moduleIdx); 930 931 m_targetState.speedMetersPerSecond = m_jni.moduleState.speed; 932 if (m_targetState.angle.getRadians() != m_jni.moduleState.angle) { 933 m_targetState.angle = Rotation2d.fromRadians(m_jni.moduleState.angle); 934 } 935 return m_targetState; 936 } finally { 937 m_stateLock.unlock(); 938 } 939 } 940 941 /** 942 * Resets this module's drive motor position to 0 rotations. 943 */ 944 public void resetPosition() { 945 SwerveJNI.JNI_Module_ResetPosition(m_drivetrainId, m_moduleIdx); 946 } 947 948 /** 949 * Gets the closed-loop output type to use for the drive motor. 950 * 951 * @return Drive motor closed-loop output type 952 */ 953 public final ClosedLoopOutputType getDriveClosedLoopOutputType() { 954 return m_driveClosedLoopOutput; 955 } 956 957 /** 958 * Gets the closed-loop output type to use for the steer motor. 959 * 960 * @return Steer motor closed-loop output type 961 */ 962 public final ClosedLoopOutputType getSteerClosedLoopOutputType() { 963 return m_steerClosedLoopOutput; 964 } 965 966 /** 967 * Gets this module's Drive Motor reference. 968 * <p> 969 * This should be used only to access signals and change configurations that the 970 * swerve drivetrain does not configure itself. 971 * 972 * @return This module's Drive Motor reference 973 */ 974 public final DriveMotorT getDriveMotor() { 975 return m_driveMotor; 976 } 977 978 /** 979 * Gets this module's Steer Motor reference. 980 * <p> 981 * This should be used only to access signals and change configurations that the 982 * swerve drivetrain does not configure itself. 983 * 984 * @return This module's Steer Motor reference 985 */ 986 public final SteerMotorT getSteerMotor() { 987 return m_steerMotor; 988 } 989 990 /** 991 * Gets this module's azimuth encoder reference. 992 * <p> 993 * This should be used only to access signals and change configurations that the 994 * swerve drivetrain does not configure itself. 995 * 996 * @return This module's azimuth encoder reference 997 */ 998 public final EncoderT getEncoder() { 999 return m_encoder; 1000 } 1001}