Swerve Builder API¶
To simplify the API surface, both builder and factory paradigms are used. Users create a SwerveDrivetrain by first defining the global drivetrain characteristics and then each module characteristics.
Note
Phoenix 6 supports the Java units library when applicable.
Defining Drivetrain Characteristics¶
Drivetrain, in this instance, refers to the SwerveDrivetrainConstants class (Java, C++, Python). This class defines characteristics that are not tied to the swerve modules, such as the CAN bus or Pigeon 2 device ID.
Note
All devices in the swerve drivetrain must be on the same CAN bus.
Users can optionally provide a configuration object to apply custom configs to the Pigeon 2, such as mount orientation. Leaving the configuration object null will skip applying configs to the Pigeon 2.
Defining Module Characteristics¶
The typical FRC drivetrain includes 4 identical modules. To simplify module creation, the SwerveModuleConstantsFactory (Java, C++, Python) class is used to set up constants common across all modules, such as the drive/steer gear ratios and the wheel radius.
Some extra steps may be required to determine some constants, described below.
CouplingGearRatioThe ratio at which the output wheel rotates when the azimuth spins. In a traditional swerve module, this is the inverse of the 1st stage of the drive motor.
To manually determine the coupling ratio, lock the drive wheel in-place, then rotate the azimuth three times. Observe the number of rotations reported by the drive motor. The coupling ratio will be \(driveRotations / 3\), or \(driveRotations / azimuthRotations\).
SlipCurrentThis is the amount of stator current the drive motors can apply without slippage. Follow the instructions in Preventing Wheel Slip to find the slip current of the drivetrain.
DriveMotorInitialConfigs/SteerMotorInitialConfigs/EncoderInitialConfigsAn initial configuration object that can be used to apply custom configs to the backing devices for each swerve module. This is useful for situations such as applying supply current limits.
Building the Swerve Module Constants¶
SwerveModuleConstants (Java, C++, Python) can be created from the previous SwerveModuleConstantsFactory. A typical swerve drivetrain consists of four identical modules: Front Left, Front Right, Back Left, Back Right. While these modules can be instantiated directly (only really useful if the modules have different physical characteristics), the modules can also be created by calling createModuleConstants(...) with the aforementioned factory.
Building the SwerveDrivetrain¶
SwerveDrivetrain (Java, C++, Python) is the class that handles odometry, configuration and control of the drivetrain. The constructor for this class takes the previous SwerveDrivetrainConstants and a list of SwerveModuleConstants.
Utilization of SwerveDrivetrain consists of SwerveRequests that define the state of the drivetrain. For full details of using SwerveRequests to control your swerve, see Swerve Requests.
Full Example¶
Note
CommandSwerveDrivetrain is a version created by the Tuner X Swerve Project Generator that implements Subsystem (Java, C++, Python) for easy command-based integration.
1// Generated by the 2026 Tuner X Swerve Project Generator
2// https://v6.docs.ctr-electronics.com/en/stable/docs/tuner/tuner-swerve/index.html
3public class TunerConstants {
4 // Both sets of gains need to be tuned to your individual robot.
5
6 // The steer motor uses any SwerveModule.SteerRequestType control request with the
7 // output type specified by SwerveModuleConstants.SteerMotorClosedLoopOutput
8 private static final Slot0Configs steerGains = new Slot0Configs()
9 .withKP(100).withKI(0).withKD(0.5)
10 .withKS(0.1).withKV(1.91).withKA(0)
11 .withStaticFeedforwardSign(StaticFeedforwardSignValue.UseClosedLoopSign);
12 // When using closed-loop control, the drive motor uses the control
13 // output type specified by SwerveModuleConstants.DriveMotorClosedLoopOutput
14 private static final Slot0Configs driveGains = new Slot0Configs()
15 .withKP(0.1).withKI(0).withKD(0)
16 .withKS(0).withKV(0.124);
17
18 // The closed-loop output type to use for the steer motors;
19 // This affects the PID/FF gains for the steer motors
20 private static final ClosedLoopOutputType kSteerClosedLoopOutput = ClosedLoopOutputType.Voltage;
21 // The closed-loop output type to use for the drive motors;
22 // This affects the PID/FF gains for the drive motors
23 private static final ClosedLoopOutputType kDriveClosedLoopOutput = ClosedLoopOutputType.Voltage;
24
25 // The type of motor used for the drive motor
26 private static final DriveMotorArrangement kDriveMotorType = DriveMotorArrangement.TalonFX_Integrated;
27 // The type of motor used for the steer motor
28 private static final SteerMotorArrangement kSteerMotorType = SteerMotorArrangement.TalonFX_Integrated;
29
30 // The remote sensor feedback type to use for the steer motors;
31 // When not Pro-licensed, Fused*/Sync* automatically fall back to Remote*
32 private static final SteerFeedbackType kSteerFeedbackType = SteerFeedbackType.FusedCANcoder;
33
34 // The stator current at which the wheels start to slip;
35 // This needs to be tuned to your individual robot
36 private static final Current kSlipCurrent = Amps.of(120);
37
38 // Initial configs for the drive and steer motors and the azimuth encoder; these cannot be null.
39 // Some configs will be overwritten; check the `with*InitialConfigs()` API documentation.
40 private static final TalonFXConfiguration driveInitialConfigs = new TalonFXConfiguration()
41 .withCurrentLimits(
42 new CurrentLimitsConfigs()
43 // Default supply current limit is 70 A, but it can be lowered to avoid brownouts.
44 // Supply current limits can be larger than the breaker current rating.
45 .withSupplyCurrentLimit(Amps.of(70))
46 .withSupplyCurrentLimitEnable(true)
47 );
48 private static final TalonFXConfiguration steerInitialConfigs = new TalonFXConfiguration()
49 .withCurrentLimits(
50 new CurrentLimitsConfigs()
51 // Swerve azimuth does not require much torque output, so we can set a relatively low
52 // stator current limit to help avoid brownouts without impacting performance.
53 .withStatorCurrentLimit(Amps.of(60))
54 .withStatorCurrentLimitEnable(true)
55 );
56 private static final CANcoderConfiguration encoderInitialConfigs = new CANcoderConfiguration();
57 // Configs for the Pigeon 2; leave this null to skip applying Pigeon 2 configs
58 private static final Pigeon2Configuration pigeonConfigs = null;
59
60 // CAN bus that the devices are located on;
61 // All swerve devices must share the same CAN bus
62 public static final CANBus kCANBus = new CANBus("canivore", "./logs/example.hoot");
63
64 // Measured robot speed (m/s) at 12 V applied output;
65 // This is NOT the desired max robot speed - see MaxSpeed in RobotContainer instead;
66 // This needs to be tuned to your individual robot
67 public static final LinearVelocity kSpeedAt12Volts = MetersPerSecond.of(4.54);
68
69 // Every 1 rotation of the azimuth results in kCoupleRatio drive motor turns;
70 // This may need to be tuned to your individual robot
71 private static final double kCoupleRatio = 3.8181818181818183;
72
73 private static final double kDriveGearRatio = 7.363636363636365;
74 private static final double kSteerGearRatio = 15.42857142857143;
75 private static final Distance kWheelRadius = Inches.of(2.167);
76
77 private static final boolean kInvertLeftSide = false;
78 private static final boolean kInvertRightSide = true;
79
80 private static final int kPigeonId = 1;
81
82 // These are only used for simulation
83 private static final MomentOfInertia kSteerInertia = KilogramSquareMeters.of(0.01);
84 private static final MomentOfInertia kDriveInertia = KilogramSquareMeters.of(0.035);
85 // Simulated voltage necessary to overcome friction
86 private static final Voltage kSteerFrictionVoltage = Volts.of(0.2);
87 private static final Voltage kDriveFrictionVoltage = Volts.of(0.2);
88
89 public static final SwerveDrivetrainConstants DrivetrainConstants = new SwerveDrivetrainConstants()
90 .withCANBusName(kCANBus.getName())
91 .withPigeon2Id(kPigeonId)
92 .withPigeon2Configs(pigeonConfigs);
93
94 private static final SwerveModuleConstantsFactory<TalonFXConfiguration, TalonFXConfiguration, CANcoderConfiguration> ConstantCreator =
95 new SwerveModuleConstantsFactory<TalonFXConfiguration, TalonFXConfiguration, CANcoderConfiguration>()
96 .withDriveMotorGearRatio(kDriveGearRatio)
97 .withSteerMotorGearRatio(kSteerGearRatio)
98 .withCouplingGearRatio(kCoupleRatio)
99 .withWheelRadius(kWheelRadius)
100 .withSteerMotorGains(steerGains)
101 .withDriveMotorGains(driveGains)
102 .withSteerMotorClosedLoopOutput(kSteerClosedLoopOutput)
103 .withDriveMotorClosedLoopOutput(kDriveClosedLoopOutput)
104 .withSlipCurrent(kSlipCurrent)
105 .withSpeedAt12Volts(kSpeedAt12Volts)
106 .withDriveMotorType(kDriveMotorType)
107 .withSteerMotorType(kSteerMotorType)
108 .withFeedbackSource(kSteerFeedbackType)
109 .withDriveMotorInitialConfigs(driveInitialConfigs)
110 .withSteerMotorInitialConfigs(steerInitialConfigs)
111 .withEncoderInitialConfigs(encoderInitialConfigs)
112 .withSteerInertia(kSteerInertia)
113 .withDriveInertia(kDriveInertia)
114 .withSteerFrictionVoltage(kSteerFrictionVoltage)
115 .withDriveFrictionVoltage(kDriveFrictionVoltage);
116
117
118 // Front Left
119 private static final int kFrontLeftDriveMotorId = 3;
120 private static final int kFrontLeftSteerMotorId = 2;
121 private static final int kFrontLeftEncoderId = 1;
122 private static final Angle kFrontLeftEncoderOffset = Rotations.of(0.15234375);
123 private static final boolean kFrontLeftSteerMotorInverted = true;
124 private static final boolean kFrontLeftEncoderInverted = false;
125
126 private static final Distance kFrontLeftXPos = Inches.of(10);
127 private static final Distance kFrontLeftYPos = Inches.of(10);
128
129 // Front Right
130 private static final int kFrontRightDriveMotorId = 1;
131 private static final int kFrontRightSteerMotorId = 0;
132 private static final int kFrontRightEncoderId = 0;
133 private static final Angle kFrontRightEncoderOffset = Rotations.of(-0.4873046875);
134 private static final boolean kFrontRightSteerMotorInverted = true;
135 private static final boolean kFrontRightEncoderInverted = false;
136
137 private static final Distance kFrontRightXPos = Inches.of(10);
138 private static final Distance kFrontRightYPos = Inches.of(-10);
139
140 // Back Left
141 private static final int kBackLeftDriveMotorId = 7;
142 private static final int kBackLeftSteerMotorId = 6;
143 private static final int kBackLeftEncoderId = 3;
144 private static final Angle kBackLeftEncoderOffset = Rotations.of(-0.219482421875);
145 private static final boolean kBackLeftSteerMotorInverted = true;
146 private static final boolean kBackLeftEncoderInverted = false;
147
148 private static final Distance kBackLeftXPos = Inches.of(-10);
149 private static final Distance kBackLeftYPos = Inches.of(10);
150
151 // Back Right
152 private static final int kBackRightDriveMotorId = 5;
153 private static final int kBackRightSteerMotorId = 4;
154 private static final int kBackRightEncoderId = 2;
155 private static final Angle kBackRightEncoderOffset = Rotations.of(0.17236328125);
156 private static final boolean kBackRightSteerMotorInverted = true;
157 private static final boolean kBackRightEncoderInverted = false;
158
159 private static final Distance kBackRightXPos = Inches.of(-10);
160 private static final Distance kBackRightYPos = Inches.of(-10);
161
162
163 public static final SwerveModuleConstants<TalonFXConfiguration, TalonFXConfiguration, CANcoderConfiguration> FrontLeft =
164 ConstantCreator.createModuleConstants(
165 kFrontLeftSteerMotorId, kFrontLeftDriveMotorId, kFrontLeftEncoderId, kFrontLeftEncoderOffset,
166 kFrontLeftXPos, kFrontLeftYPos, kInvertLeftSide, kFrontLeftSteerMotorInverted, kFrontLeftEncoderInverted
167 );
168 public static final SwerveModuleConstants<TalonFXConfiguration, TalonFXConfiguration, CANcoderConfiguration> FrontRight =
169 ConstantCreator.createModuleConstants(
170 kFrontRightSteerMotorId, kFrontRightDriveMotorId, kFrontRightEncoderId, kFrontRightEncoderOffset,
171 kFrontRightXPos, kFrontRightYPos, kInvertRightSide, kFrontRightSteerMotorInverted, kFrontRightEncoderInverted
172 );
173 public static final SwerveModuleConstants<TalonFXConfiguration, TalonFXConfiguration, CANcoderConfiguration> BackLeft =
174 ConstantCreator.createModuleConstants(
175 kBackLeftSteerMotorId, kBackLeftDriveMotorId, kBackLeftEncoderId, kBackLeftEncoderOffset,
176 kBackLeftXPos, kBackLeftYPos, kInvertLeftSide, kBackLeftSteerMotorInverted, kBackLeftEncoderInverted
177 );
178 public static final SwerveModuleConstants<TalonFXConfiguration, TalonFXConfiguration, CANcoderConfiguration> BackRight =
179 ConstantCreator.createModuleConstants(
180 kBackRightSteerMotorId, kBackRightDriveMotorId, kBackRightEncoderId, kBackRightEncoderOffset,
181 kBackRightXPos, kBackRightYPos, kInvertRightSide, kBackRightSteerMotorInverted, kBackRightEncoderInverted
182 );
183
184 /**
185 * Creates a CommandSwerveDrivetrain instance.
186 * This should only be called once in your robot program,.
187 */
188 public static CommandSwerveDrivetrain createDrivetrain() {
189 return new CommandSwerveDrivetrain(
190 DrivetrainConstants, FrontLeft, FrontRight, BackLeft, BackRight
191 );
192 }
193
194
195 /**
196 * Swerve Drive class utilizing CTR Electronics' Phoenix 6 API with the selected device types.
197 */
198 public static class TunerSwerveDrivetrain extends SwerveDrivetrain<TalonFX, TalonFX, CANcoder> {
199 /**
200 * Constructs a CTRE SwerveDrivetrain using the specified constants.
201 * <p>
202 * This constructs the underlying hardware devices, so users should not construct
203 * the devices themselves. If they need the devices, they can access them through
204 * getters in the classes.
205 *
206 * @param drivetrainConstants Drivetrain-wide constants for the swerve drive
207 * @param modules Constants for each specific module
208 */
209 public TunerSwerveDrivetrain(
210 SwerveDrivetrainConstants drivetrainConstants,
211 SwerveModuleConstants<?, ?, ?>... modules
212 ) {
213 super(
214 TalonFX::new, TalonFX::new, CANcoder::new,
215 drivetrainConstants, modules
216 );
217 }
218
219 /**
220 * Constructs a CTRE SwerveDrivetrain using the specified constants.
221 * <p>
222 * This constructs the underlying hardware devices, so users should not construct
223 * the devices themselves. If they need the devices, they can access them through
224 * getters in the classes.
225 *
226 * @param drivetrainConstants Drivetrain-wide constants for the swerve drive
227 * @param odometryUpdateFrequency The frequency to run the odometry loop. If
228 * unspecified or set to 0 Hz, this is 250 Hz on
229 * CAN FD, and 100 Hz on CAN 2.0.
230 * @param modules Constants for each specific module
231 */
232 public TunerSwerveDrivetrain(
233 SwerveDrivetrainConstants drivetrainConstants,
234 double odometryUpdateFrequency,
235 SwerveModuleConstants<?, ?, ?>... modules
236 ) {
237 super(
238 TalonFX::new, TalonFX::new, CANcoder::new,
239 drivetrainConstants, odometryUpdateFrequency, modules
240 );
241 }
242
243 /**
244 * Constructs a CTRE SwerveDrivetrain using the specified constants.
245 * <p>
246 * This constructs the underlying hardware devices, so users should not construct
247 * the devices themselves. If they need the devices, they can access them through
248 * getters in the classes.
249 *
250 * @param drivetrainConstants Drivetrain-wide constants for the swerve drive
251 * @param odometryUpdateFrequency The frequency to run the odometry loop. If
252 * unspecified or set to 0 Hz, this is 250 Hz on
253 * CAN FD, and 100 Hz on CAN 2.0.
254 * @param odometryStandardDeviation The standard deviation for odometry calculation
255 * in the form [x, y, theta]ᵀ, with units in meters
256 * and radians
257 * @param visionStandardDeviation The standard deviation for vision calculation
258 * in the form [x, y, theta]ᵀ, with units in meters
259 * and radians
260 * @param modules Constants for each specific module
261 */
262 public TunerSwerveDrivetrain(
263 SwerveDrivetrainConstants drivetrainConstants,
264 double odometryUpdateFrequency,
265 Matrix<N3, N1> odometryStandardDeviation,
266 Matrix<N3, N1> visionStandardDeviation,
267 SwerveModuleConstants<?, ?, ?>... modules
268 ) {
269 super(
270 TalonFX::new, TalonFX::new, CANcoder::new,
271 drivetrainConstants, odometryUpdateFrequency,
272 odometryStandardDeviation, visionStandardDeviation, modules
273 );
274 }
275 }
276}
1#include "ctre/phoenix6/swerve/SwerveDrivetrain.hpp"
2#include "ctre/phoenix6/CANcoder.hpp"
3#include "ctre/phoenix6/TalonFX.hpp"
4
5using namespace ctre::phoenix6;
6
7namespace subsystems {
8 /* Forward declaration */
9 class CommandSwerveDrivetrain;
10}
11
12// Generated by the 2026 Tuner X Swerve Project Generator
13// https://v6.docs.ctr-electronics.com/en/stable/docs/tuner/tuner-swerve/index.html
14class TunerConstants {
15 // Both sets of gains need to be tuned to your individual robot.
16
17 // The steer motor uses any SwerveModule.SteerRequestType control request with the
18 // output type specified by SwerveModuleConstants::SteerMotorClosedLoopOutput
19 static constexpr configs::Slot0Configs steerGains = configs::Slot0Configs{}
20 .WithKP(100).WithKI(0).WithKD(0.5)
21 .WithKS(0.1).WithKV(1.91).WithKA(0)
22 .WithStaticFeedforwardSign(signals::StaticFeedforwardSignValue::UseClosedLoopSign);
23 // When using closed-loop control, the drive motor uses the control
24 // output type specified by SwerveModuleConstants::DriveMotorClosedLoopOutput
25 static constexpr configs::Slot0Configs driveGains = configs::Slot0Configs{}
26 .WithKP(0.1).WithKI(0).WithKD(0)
27 .WithKS(0).WithKV(0.124);
28
29 // The closed-loop output type to use for the steer motors;
30 // This affects the PID/FF gains for the steer motors
31 static constexpr swerve::ClosedLoopOutputType kSteerClosedLoopOutput = swerve::ClosedLoopOutputType::Voltage;
32 // The closed-loop output type to use for the drive motors;
33 // This affects the PID/FF gains for the drive motors
34 static constexpr swerve::ClosedLoopOutputType kDriveClosedLoopOutput = swerve::ClosedLoopOutputType::Voltage;
35
36 // The type of motor used for the drive motor
37 static constexpr swerve::DriveMotorArrangement kDriveMotorType = swerve::DriveMotorArrangement::TalonFX_Integrated;
38 // The type of motor used for the steer motor
39 static constexpr swerve::SteerMotorArrangement kSteerMotorType = swerve::SteerMotorArrangement::TalonFX_Integrated;
40
41 // The remote sensor feedback type to use for the steer motors;
42 // When not Pro-licensed, Fused*/Sync* automatically fall back to Remote*
43 static constexpr swerve::SteerFeedbackType kSteerFeedbackType = swerve::SteerFeedbackType::FusedCANcoder;
44
45 // The stator current at which the wheels start to slip;
46 // This needs to be tuned to your individual robot
47 static constexpr units::ampere_t kSlipCurrent = 120_A;
48
49 // Initial configs for the drive and steer motors and the azimuth encoder; these cannot be null.
50 // Some configs will be overwritten; check the `With*InitialConfigs()` API documentation.
51 static constexpr configs::TalonFXConfiguration driveInitialConfigs = configs::TalonFXConfiguration{}
52 .WithCurrentLimits(
53 configs::CurrentLimitsConfigs{}
54 // Default supply current limit is 70 A, but it can be lowered to avoid brownouts.
55 // Supply current limits can be larger than the breaker current rating.
56 .WithSupplyCurrentLimit(70_A)
57 .WithSupplyCurrentLimitEnable(true)
58 );
59 static constexpr configs::TalonFXConfiguration steerInitialConfigs = configs::TalonFXConfiguration{}
60 .WithCurrentLimits(
61 configs::CurrentLimitsConfigs{}
62 // Swerve azimuth does not require much torque output, so we can set a relatively low
63 // stator current limit to help avoid brownouts without impacting performance.
64 .WithStatorCurrentLimit(60_A)
65 .WithStatorCurrentLimitEnable(true)
66 );
67 static constexpr configs::CANcoderConfiguration encoderInitialConfigs{};
68 // Configs for the Pigeon 2; leave this nullopt to skip applying Pigeon 2 configs
69 static constexpr std::optional<configs::Pigeon2Configuration> pigeonConfigs = std::nullopt;
70
71 static constexpr std::string_view kCANBusName = "canivore";
72
73public:
74 // CAN bus that the devices are located on;
75 // All swerve devices must share the same CAN bus
76 static inline const CANBus kCANBus{kCANBusName, "./logs/example.hoot"};
77
78 // Measured robot speed (m/s) at 12 V applied output;
79 // This is NOT the desired max robot speed - see MaxSpeed in RobotContainer instead;
80 // This needs to be tuned to your individual robot
81 static constexpr units::meters_per_second_t kSpeedAt12Volts = 4.54_mps;
82
83private:
84 // Every 1 rotation of the azimuth results in kCoupleRatio drive motor turns;
85 // This may need to be tuned to your individual robot
86 static constexpr units::scalar_t kCoupleRatio = 3.8181818181818183;
87
88 static constexpr units::scalar_t kDriveGearRatio = 7.363636363636365;
89 static constexpr units::scalar_t kSteerGearRatio = 15.42857142857143;
90 static constexpr units::inch_t kWheelRadius = 2.167_in;
91
92 static constexpr bool kInvertLeftSide = false;
93 static constexpr bool kInvertRightSide = true;
94
95 static constexpr int kPigeonId = 1;
96
97 // These are only used for simulation
98 static constexpr units::kilogram_square_meter_t kSteerInertia = 0.01_kg_sq_m;
99 static constexpr units::kilogram_square_meter_t kDriveInertia = 0.035_kg_sq_m;
100 // Simulated voltage necessary to overcome friction
101 static constexpr units::volt_t kSteerFrictionVoltage = 0.2_V;
102 static constexpr units::volt_t kDriveFrictionVoltage = 0.2_V;
103
104public:
105 static constexpr swerve::SwerveDrivetrainConstants DrivetrainConstants = swerve::SwerveDrivetrainConstants{}
106 .WithCANBusName(kCANBusName)
107 .WithPigeon2Id(kPigeonId)
108 .WithPigeon2Configs(pigeonConfigs);
109
110private:
111 static constexpr swerve::SwerveModuleConstantsFactory ConstantCreator =
112 swerve::SwerveModuleConstantsFactory<configs::TalonFXConfiguration, configs::TalonFXConfiguration, configs::CANcoderConfiguration>{}
113 .WithDriveMotorGearRatio(kDriveGearRatio)
114 .WithSteerMotorGearRatio(kSteerGearRatio)
115 .WithCouplingGearRatio(kCoupleRatio)
116 .WithWheelRadius(kWheelRadius)
117 .WithSteerMotorGains(steerGains)
118 .WithDriveMotorGains(driveGains)
119 .WithSteerMotorClosedLoopOutput(kSteerClosedLoopOutput)
120 .WithDriveMotorClosedLoopOutput(kDriveClosedLoopOutput)
121 .WithSlipCurrent(kSlipCurrent)
122 .WithSpeedAt12Volts(kSpeedAt12Volts)
123 .WithDriveMotorType(kDriveMotorType)
124 .WithSteerMotorType(kSteerMotorType)
125 .WithFeedbackSource(kSteerFeedbackType)
126 .WithDriveMotorInitialConfigs(driveInitialConfigs)
127 .WithSteerMotorInitialConfigs(steerInitialConfigs)
128 .WithEncoderInitialConfigs(encoderInitialConfigs)
129 .WithSteerInertia(kSteerInertia)
130 .WithDriveInertia(kDriveInertia)
131 .WithSteerFrictionVoltage(kSteerFrictionVoltage)
132 .WithDriveFrictionVoltage(kDriveFrictionVoltage);
133
134
135 // Front Left
136 static constexpr int kFrontLeftDriveMotorId = 3;
137 static constexpr int kFrontLeftSteerMotorId = 2;
138 static constexpr int kFrontLeftEncoderId = 1;
139 static constexpr units::turn_t kFrontLeftEncoderOffset = 0.15234375_tr;
140 static constexpr bool kFrontLeftSteerMotorInverted = true;
141 static constexpr bool kFrontLeftEncoderInverted = false;
142
143 static constexpr units::inch_t kFrontLeftXPos = 10_in;
144 static constexpr units::inch_t kFrontLeftYPos = 10_in;
145
146 // Front Right
147 static constexpr int kFrontRightDriveMotorId = 1;
148 static constexpr int kFrontRightSteerMotorId = 0;
149 static constexpr int kFrontRightEncoderId = 0;
150 static constexpr units::turn_t kFrontRightEncoderOffset = -0.4873046875_tr;
151 static constexpr bool kFrontRightSteerMotorInverted = true;
152 static constexpr bool kFrontRightEncoderInverted = false;
153
154 static constexpr units::inch_t kFrontRightXPos = 10_in;
155 static constexpr units::inch_t kFrontRightYPos = -10_in;
156
157 // Back Left
158 static constexpr int kBackLeftDriveMotorId = 7;
159 static constexpr int kBackLeftSteerMotorId = 6;
160 static constexpr int kBackLeftEncoderId = 3;
161 static constexpr units::turn_t kBackLeftEncoderOffset = -0.219482421875_tr;
162 static constexpr bool kBackLeftSteerMotorInverted = true;
163 static constexpr bool kBackLeftEncoderInverted = false;
164
165 static constexpr units::inch_t kBackLeftXPos = -10_in;
166 static constexpr units::inch_t kBackLeftYPos = 10_in;
167
168 // Back Right
169 static constexpr int kBackRightDriveMotorId = 5;
170 static constexpr int kBackRightSteerMotorId = 4;
171 static constexpr int kBackRightEncoderId = 2;
172 static constexpr units::turn_t kBackRightEncoderOffset = 0.17236328125_tr;
173 static constexpr bool kBackRightSteerMotorInverted = true;
174 static constexpr bool kBackRightEncoderInverted = false;
175
176 static constexpr units::inch_t kBackRightXPos = -10_in;
177 static constexpr units::inch_t kBackRightYPos = -10_in;
178
179
180public:
181 static constexpr swerve::SwerveModuleConstants FrontLeft = ConstantCreator.CreateModuleConstants(
182 kFrontLeftSteerMotorId, kFrontLeftDriveMotorId, kFrontLeftEncoderId, kFrontLeftEncoderOffset,
183 kFrontLeftXPos, kFrontLeftYPos, kInvertLeftSide, kFrontLeftSteerMotorInverted, kFrontLeftEncoderInverted);
184 static constexpr swerve::SwerveModuleConstants FrontRight = ConstantCreator.CreateModuleConstants(
185 kFrontRightSteerMotorId, kFrontRightDriveMotorId, kFrontRightEncoderId, kFrontRightEncoderOffset,
186 kFrontRightXPos, kFrontRightYPos, kInvertRightSide, kFrontRightSteerMotorInverted, kFrontRightEncoderInverted);
187 static constexpr swerve::SwerveModuleConstants BackLeft = ConstantCreator.CreateModuleConstants(
188 kBackLeftSteerMotorId, kBackLeftDriveMotorId, kBackLeftEncoderId, kBackLeftEncoderOffset,
189 kBackLeftXPos, kBackLeftYPos, kInvertLeftSide, kBackLeftSteerMotorInverted, kBackLeftEncoderInverted);
190 static constexpr swerve::SwerveModuleConstants BackRight = ConstantCreator.CreateModuleConstants(
191 kBackRightSteerMotorId, kBackRightDriveMotorId, kBackRightEncoderId, kBackRightEncoderOffset,
192 kBackRightXPos, kBackRightYPos, kInvertRightSide, kBackRightSteerMotorInverted, kBackRightEncoderInverted);
193
194 /**
195 * Creates a CommandSwerveDrivetrain instance.
196 * This should only be called once in your robot program.
197 */
198 static subsystems::CommandSwerveDrivetrain CreateDrivetrain();
199};
200
201
202/**
203 * \brief Swerve Drive class utilizing CTR Electronics' Phoenix 6 API with the selected device types.
204 */
205class TunerSwerveDrivetrain : public swerve::SwerveDrivetrain<hardware::TalonFX, hardware::TalonFX, hardware::CANcoder> {
206public:
207 using SwerveModuleConstants = swerve::SwerveModuleConstants<configs::TalonFXConfiguration, configs::TalonFXConfiguration, configs::CANcoderConfiguration>;
208
209 /**
210 * \brief Constructs a CTRE SwerveDrivetrain using the specified constants.
211 *
212 * This constructs the underlying hardware devices, so users should not construct
213 * the devices themselves. If they need the devices, they can access them
214 * through getters in the classes.
215 *
216 * \param drivetrainConstants Drivetrain-wide constants for the swerve drive
217 * \param modules Constants for each specific module
218 */
219 template <std::same_as<SwerveModuleConstants>... ModuleConstants>
220 TunerSwerveDrivetrain(swerve::SwerveDrivetrainConstants const &driveTrainConstants, ModuleConstants const &... modules) :
221 SwerveDrivetrain{driveTrainConstants, modules...}
222 {}
223
224 /**
225 * \brief Constructs a CTRE SwerveDrivetrain using the specified constants.
226 *
227 * This constructs the underlying hardware devices, so users should not construct
228 * the devices themselves. If they need the devices, they can access them
229 * through getters in the classes.
230 *
231 * \param drivetrainConstants Drivetrain-wide constants for the swerve drive
232 * \param odometryUpdateFrequency The frequency to run the odometry loop. If
233 * unspecified or set to 0 Hz, this is 250 Hz on
234 * CAN FD, and 100 Hz on CAN 2.0.
235 * \param modules Constants for each specific module
236 */
237 template <std::same_as<SwerveModuleConstants>... ModuleConstants>
238 TunerSwerveDrivetrain(
239 swerve::SwerveDrivetrainConstants const &driveTrainConstants,
240 units::hertz_t odometryUpdateFrequency,
241 ModuleConstants const &... modules
242 ) :
243 SwerveDrivetrain{driveTrainConstants, odometryUpdateFrequency, modules...}
244 {}
245
246 /**
247 * \brief Constructs a CTRE SwerveDrivetrain using the specified constants.
248 *
249 * This constructs the underlying hardware devices, so users should not construct
250 * the devices themselves. If they need the devices, they can access them
251 * through getters in the classes.
252 *
253 * \param drivetrainConstants Drivetrain-wide constants for the swerve drive
254 * \param odometryUpdateFrequency The frequency to run the odometry loop. If
255 * unspecified or set to 0 Hz, this is 250 Hz on
256 * CAN FD, and 100 Hz on CAN 2.0.
257 * \param odometryStandardDeviation The standard deviation for odometry calculation
258 * in the form [x, y, theta]ᵀ, with units in meters
259 * and radians
260 * \param visionStandardDeviation The standard deviation for vision calculation
261 * in the form [x, y, theta]ᵀ, with units in meters
262 * and radians
263 * \param modules Constants for each specific module
264 */
265 template <std::same_as<SwerveModuleConstants>... ModuleConstants>
266 TunerSwerveDrivetrain(
267 swerve::SwerveDrivetrainConstants const &driveTrainConstants,
268 units::hertz_t odometryUpdateFrequency,
269 std::array<double, 3> const &odometryStandardDeviation,
270 std::array<double, 3> const &visionStandardDeviation,
271 ModuleConstants const &... modules
272 ) :
273 SwerveDrivetrain{
274 driveTrainConstants, odometryUpdateFrequency,
275 odometryStandardDeviation, visionStandardDeviation, modules...
276 }
277 {}
278};
1#include "generated/TunerConstants.h"
2#include "subsystems/CommandSwerveDrivetrain.h"
3
4subsystems::CommandSwerveDrivetrain TunerConstants::CreateDrivetrain()
5{
6 return {DrivetrainConstants, FrontLeft, FrontRight, BackLeft, BackRight};
7}
1 from subsystems.command_swerve_drivetrain import CommandSwerveDrivetrain
2
3
4class TunerConstants:
5 """
6 Generated by the 2026 Tuner X Swerve Project Generator
7 https://v6.docs.ctr-electronics.com/en/stable/docs/tuner/tuner-swerve/index.html
8 """
9
10 # Both sets of gains need to be tuned to your individual robot
11
12 # The steer motor uses any SwerveModule.SteerRequestType control request with the
13 # output type specified by SwerveModuleConstants.SteerMotorClosedLoopOutput
14 _steer_gains = (
15 configs.Slot0Configs()
16 .with_k_p(100)
17 .with_k_i(0)
18 .with_k_d(0.5)
19 .with_k_s(0.1)
20 .with_k_v(1.91)
21 .with_k_a(0)
22 .with_static_feedforward_sign(
23 signals.StaticFeedforwardSignValue.USE_CLOSED_LOOP_SIGN
24 )
25 )
26 # When using closed-loop control, the drive motor uses the control
27 # output type specified by SwerveModuleConstants.DriveMotorClosedLoopOutput
28 _drive_gains = (
29 configs.Slot0Configs()
30 .with_k_p(0.1)
31 .with_k_i(0)
32 .with_k_d(0)
33 .with_k_s(0)
34 .with_k_v(0.124)
35 )
36
37 # The closed-loop output type to use for the steer motors;
38 # This affects the PID/FF gains for the steer motors
39 _steer_closed_loop_output = swerve.ClosedLoopOutputType.VOLTAGE
40 # The closed-loop output type to use for the drive motors;
41 # This affects the PID/FF gains for the drive motors
42 _drive_closed_loop_output = swerve.ClosedLoopOutputType.VOLTAGE
43
44 # The type of motor used for the drive motor
45 _drive_motor_type = swerve.DriveMotorArrangement.TALON_FX_INTEGRATED
46 # The type of motor used for the steer motor
47 _steer_motor_type = swerve.SteerMotorArrangement.TALON_FX_INTEGRATED
48
49 # The remote sensor feedback type to use for the steer motors;
50 # When not Pro-licensed, Fused*/Sync* automatically fall back to Remote*
51 _steer_feedback_type = swerve.SteerFeedbackType.FUSED_CANCODER
52
53 # The stator current at which the wheels start to slip;
54 # This needs to be tuned to your individual robot
55 _slip_current: units.ampere = 120.0
56
57 # Initial configs for the drive and steer motors and the azimuth encoder; these cannot be null.
58 # Some configs will be overwritten; check the `with_*_initial_configs()` API documentation.
59 _drive_initial_configs = configs.TalonFXConfiguration().with_current_limits(
60 configs.CurrentLimitsConfigs()
61 # Default supply current limit is 70 A, but it can be lowered to avoid brownouts.
62 # Supply current limits can be larger than the breaker current rating.
63 .with_supply_current_limit(70.0)
64 .with_supply_current_limit_enable(True)
65 )
66 _steer_initial_configs = configs.TalonFXConfiguration().with_current_limits(
67 configs.CurrentLimitsConfigs()
68 # Swerve azimuth does not require much torque output, so we can set a relatively low
69 # stator current limit to help avoid brownouts without impacting performance.
70 .with_stator_current_limit(60.0)
71 .with_stator_current_limit_enable(True)
72 )
73 _encoder_initial_configs = configs.CANcoderConfiguration()
74 # Configs for the Pigeon 2; leave this None to skip applying Pigeon 2 configs
75 _pigeon_configs: configs.Pigeon2Configuration | None = None
76
77 # CAN bus that the devices are located on;
78 # All swerve devices must share the same CAN bus
79 canbus = CANBus("canivore", "./logs/example.hoot")
80
81 # Measured robot speed (m/s) at 12 V applied output;
82 # This is NOT the desired max robot speed - see _max_speed in RobotContainer instead;
83 # This needs to be tuned to your individual robot
84 speed_at_12_volts: units.meters_per_second = 4.54
85
86 # Every 1 rotation of the azimuth results in _couple_ratio drive motor turns;
87 # This may need to be tuned to your individual robot
88 _couple_ratio = 3.8181818181818183
89
90 _drive_gear_ratio = 7.363636363636365
91 _steer_gear_ratio = 15.42857142857143
92 _wheel_radius: units.meter = inchesToMeters(2.167)
93
94 _invert_left_side = False
95 _invert_right_side = True
96
97 _pigeon_id = 1
98
99 # These are only used for simulation
100 _steer_inertia: units.kilogram_square_meter = 0.01
101 _drive_inertia: units.kilogram_square_meter = 0.035
102 # Simulated voltage necessary to overcome friction
103 _steer_friction_voltage: units.volt = 0.2
104 _drive_friction_voltage: units.volt = 0.2
105
106 drivetrain_constants = (
107 swerve.SwerveDrivetrainConstants()
108 .with_can_bus_name(canbus.name)
109 .with_pigeon2_id(_pigeon_id)
110 .with_pigeon2_configs(_pigeon_configs)
111 )
112
113 _constants_creator: swerve.SwerveModuleConstantsFactory[
114 configs.TalonFXConfiguration,
115 configs.TalonFXConfiguration,
116 configs.CANcoderConfiguration,
117 ] = (
118 swerve.SwerveModuleConstantsFactory()
119 .with_drive_motor_gear_ratio(_drive_gear_ratio)
120 .with_steer_motor_gear_ratio(_steer_gear_ratio)
121 .with_coupling_gear_ratio(_couple_ratio)
122 .with_wheel_radius(_wheel_radius)
123 .with_steer_motor_gains(_steer_gains)
124 .with_drive_motor_gains(_drive_gains)
125 .with_steer_motor_closed_loop_output(_steer_closed_loop_output)
126 .with_drive_motor_closed_loop_output(_drive_closed_loop_output)
127 .with_slip_current(_slip_current)
128 .with_speed_at12_volts(speed_at_12_volts)
129 .with_drive_motor_type(_drive_motor_type)
130 .with_steer_motor_type(_steer_motor_type)
131 .with_feedback_source(_steer_feedback_type)
132 .with_drive_motor_initial_configs(_drive_initial_configs)
133 .with_steer_motor_initial_configs(_steer_initial_configs)
134 .with_encoder_initial_configs(_encoder_initial_configs)
135 .with_steer_inertia(_steer_inertia)
136 .with_drive_inertia(_drive_inertia)
137 .with_steer_friction_voltage(_steer_friction_voltage)
138 .with_drive_friction_voltage(_drive_friction_voltage)
139 )
140
141
142 # Front Left
143 _front_left_drive_motor_id = 3
144 _front_left_steer_motor_id = 2
145 _front_left_encoder_id = 1
146 _front_left_encoder_offset: units.rotation = 0.15234375
147 _front_left_steer_motor_inverted = True
148 _front_left_encoder_inverted = False
149
150 _front_left_x_pos: units.meter = inchesToMeters(10)
151 _front_left_y_pos: units.meter = inchesToMeters(10)
152
153 # Front Right
154 _front_right_drive_motor_id = 1
155 _front_right_steer_motor_id = 0
156 _front_right_encoder_id = 0
157 _front_right_encoder_offset: units.rotation = -0.4873046875
158 _front_right_steer_motor_inverted = True
159 _front_right_encoder_inverted = False
160
161 _front_right_x_pos: units.meter = inchesToMeters(10)
162 _front_right_y_pos: units.meter = inchesToMeters(-10)
163
164 # Back Left
165 _back_left_drive_motor_id = 7
166 _back_left_steer_motor_id = 6
167 _back_left_encoder_id = 3
168 _back_left_encoder_offset: units.rotation = -0.219482421875
169 _back_left_steer_motor_inverted = True
170 _back_left_encoder_inverted = False
171
172 _back_left_x_pos: units.meter = inchesToMeters(-10)
173 _back_left_y_pos: units.meter = inchesToMeters(10)
174
175 # Back Right
176 _back_right_drive_motor_id = 5
177 _back_right_steer_motor_id = 4
178 _back_right_encoder_id = 2
179 _back_right_encoder_offset: units.rotation = 0.17236328125
180 _back_right_steer_motor_inverted = True
181 _back_right_encoder_inverted = False
182
183 _back_right_x_pos: units.meter = inchesToMeters(-10)
184 _back_right_y_pos: units.meter = inchesToMeters(-10)
185
186
187 front_left = _constants_creator.create_module_constants(
188 _front_left_steer_motor_id,
189 _front_left_drive_motor_id,
190 _front_left_encoder_id,
191 _front_left_encoder_offset,
192 _front_left_x_pos,
193 _front_left_y_pos,
194 _invert_left_side,
195 _front_left_steer_motor_inverted,
196 _front_left_encoder_inverted,
197 )
198 front_right = _constants_creator.create_module_constants(
199 _front_right_steer_motor_id,
200 _front_right_drive_motor_id,
201 _front_right_encoder_id,
202 _front_right_encoder_offset,
203 _front_right_x_pos,
204 _front_right_y_pos,
205 _invert_right_side,
206 _front_right_steer_motor_inverted,
207 _front_right_encoder_inverted,
208 )
209 back_left = _constants_creator.create_module_constants(
210 _back_left_steer_motor_id,
211 _back_left_drive_motor_id,
212 _back_left_encoder_id,
213 _back_left_encoder_offset,
214 _back_left_x_pos,
215 _back_left_y_pos,
216 _invert_left_side,
217 _back_left_steer_motor_inverted,
218 _back_left_encoder_inverted,
219 )
220 back_right = _constants_creator.create_module_constants(
221 _back_right_steer_motor_id,
222 _back_right_drive_motor_id,
223 _back_right_encoder_id,
224 _back_right_encoder_offset,
225 _back_right_x_pos,
226 _back_right_y_pos,
227 _invert_right_side,
228 _back_right_steer_motor_inverted,
229 _back_right_encoder_inverted,
230 )
231
232 @classmethod
233 def create_drivetrain(cls) -> "CommandSwerveDrivetrain":
234 """
235 Creates a CommandSwerveDrivetrain instance.
236 This should only be called once in your robot program.
237 """
238 from subsystems.command_swerve_drivetrain import CommandSwerveDrivetrain
239
240 return CommandSwerveDrivetrain(
241 cls.drivetrain_constants,
242 [
243 cls.front_left,
244 cls.front_right,
245 cls.back_left,
246 cls.back_right,
247 ],
248 )
249
250
251class TunerSwerveDrivetrain(
252 swerve.SwerveDrivetrain[hardware.TalonFX, hardware.TalonFX, hardware.CANcoder]
253):
254 """Swerve Drive class utilizing CTR Electronics' Phoenix 6 API with the selected device types."""
255
256 @overload
257 def __init__(
258 self,
259 drivetrain_constants: swerve.SwerveDrivetrainConstants,
260 modules: list[swerve.SwerveModuleConstants],
261 /,
262 ) -> None:
263 """
264 Constructs a CTRE SwerveDrivetrain using the specified constants.
265
266 This constructs the underlying hardware devices, so users should not construct
267 the devices themselves. If they need the devices, they can access them through
268 getters in the classes.
269
270 :param drivetrain_constants: Drivetrain-wide constants for the swerve drive
271 :type drivetrain_constants: swerve.SwerveDrivetrainConstants
272 :param modules: Constants for each specific module
273 :type modules: list[swerve.SwerveModuleConstants]
274 """
275 ...
276
277 @overload
278 def __init__(
279 self,
280 drivetrain_constants: swerve.SwerveDrivetrainConstants,
281 odometry_update_frequency: units.hertz,
282 modules: list[swerve.SwerveModuleConstants],
283 /,
284 ) -> None:
285 """
286 Constructs a CTRE SwerveDrivetrain using the specified constants.
287
288 This constructs the underlying hardware devices, so users should not construct
289 the devices themselves. If they need the devices, they can access them through
290 getters in the classes.
291
292 :param drivetrain_constants: Drivetrain-wide constants for the swerve drive
293 :type drivetrain_constants: swerve.SwerveDrivetrainConstants
294 :param odometry_update_frequency: The frequency to run the odometry loop. If
295 unspecified or set to 0 Hz, this is 250 Hz on
296 CAN FD, and 100 Hz on CAN 2.0.
297 :type odometry_update_frequency: units.hertz
298 :param modules: Constants for each specific module
299 :type modules: list[swerve.SwerveModuleConstants]
300 """
301 ...
302
303 @overload
304 def __init__(
305 self,
306 drivetrain_constants: swerve.SwerveDrivetrainConstants,
307 odometry_update_frequency: units.hertz,
308 odometry_standard_deviation: tuple[float, float, float],
309 vision_standard_deviation: tuple[float, float, float],
310 modules: list[swerve.SwerveModuleConstants],
311 /,
312 ) -> None:
313 """
314 Constructs a CTRE SwerveDrivetrain using the specified constants.
315
316 This constructs the underlying hardware devices, so users should not construct
317 the devices themselves. If they need the devices, they can access them through
318 getters in the classes.
319
320 :param drivetrain_constants: Drivetrain-wide constants for the swerve drive
321 :type drivetrain_constants: swerve.SwerveDrivetrainConstants
322 :param odometry_update_frequency: The frequency to run the odometry loop. If
323 unspecified or set to 0 Hz, this is 250 Hz on
324 CAN FD, and 100 Hz on CAN 2.0.
325 :type odometry_update_frequency: units.hertz
326 :param odometry_standard_deviation: The standard deviation for odometry calculation
327 in the form [x, y, theta]ᵀ, with units in meters
328 and radians
329 :type odometry_standard_deviation: tuple[float, float, float]
330 :param vision_standard_deviation: The standard deviation for vision calculation
331 in the form [x, y, theta]ᵀ, with units in meters
332 and radians
333 :type vision_standard_deviation: tuple[float, float, float]
334 :param modules: Constants for each specific module
335 :type modules: list[swerve.SwerveModuleConstants]
336 """
337 ...
338
339 @overload
340 def __init__(
341 self,
342 drivetrain_constants: swerve.SwerveDrivetrainConstants,
343 arg0: None,
344 arg1: None,
345 arg2: None,
346 arg3: None,
347 /,
348 ) -> None: ...
349
350 def __init__(
351 self,
352 drivetrain_constants: swerve.SwerveDrivetrainConstants,
353 arg0=None,
354 arg1=None,
355 arg2=None,
356 arg3=None,
357 ):
358 swerve.SwerveDrivetrain.__init__(
359 self,
360 hardware.TalonFX,
361 hardware.TalonFX,
362 hardware.CANcoder,
363 drivetrain_constants,
364 arg0,
365 arg1,
366 arg2,
367 arg3,
368 )