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.

CouplingGearRatio

The 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\).

SlipCurrent

This 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/EncoderInitialConfigs

An 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.

Note

The X and Y position of the modules is measured from the center point of the robot along the X and Y axes, respectively. These values use the same coordinate system as Translation2d (Java, C++, Python), where forward is positive X and left is positive Y.

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        )