Building a Swerve Drivetrain with the CTRE Swerve Project Generator#
A guide to CTRE's own code-generation workflow for swerve drivetrains, and how to wire the generated code into a command-based FRC robot project.
Overview#
If your entire swerve drivetrain is CTRE hardware — TalonFX (or TalonFXS) drive/steer motors, CANcoders, and a Pigeon 2 — Tuner X can generate a complete, tuned drivetrain subsystem for you directly from Phoenix 6, without any third-party library. This is CTRE's Swerve Project Generator, and it's a different approach from the YAGSL tutorial already on this site.
YAGSL vs. the CTRE Swerve Generator
- YAGSL is a community library that reads JSON files at runtime and works with mixed hardware (REV, CTRE, Redux, etc.). It's a good default if your team's hardware varies year to year, or you mix vendors.
- CTRE's generator writes real, compiled Java (
TunerConstants.java+CommandSwerveDrivetrain.java) directly against the Phoenix 6 API. It only supports CTRE devices, but in exchange you get first-party support and direct access to Phoenix 6/Pro features (advanced closed-loop control, CAN FD odometry rates, etc.) that YAGSL's abstraction layer doesn't expose.
Neither is "better" — Make your choice based on your robot hardware and how much you want direct control over the motors and the Phoenix 6 API versus a simpler, more abstracted JSON config with YAGSL.
This page assumes you're comfortable with the swerve drive concepts (holonomic motion, field- vs. robot-oriented driving, kinematics) already covered on this site — it focuses on what's specific to CTRE's generator.
Prerequisites#
- CTRE Tuner X installed and up to date for the current season.
- Phoenix 6 vendordep added to your robot project (see 3rd Party Libraries):
https://maven.ctr-electronics.com/release/com/ctre/phoenix6/latest/Phoenix6-frc2025-latest.json - All swerve hardware must be CTRE: TalonFX or TalonFXS for both drive and steer motors, CANcoder for absolute position, and a Pigeon 2 for heading.
- Firmware on every device, and your Tuner X version, must match the current season's Phoenix 6 release — mismatched versions are a common source of generator failures.
Exact version requirements
CTRE's requirements change with each season's Phoenix 6 release. Check Swerve System Requirements for the current version matrix rather than relying on this page — it will go stale faster than CTRE's own docs.
Running the Generator#
The Swerve Project Generator lives inside PhoenixTuner X, under the Mechanisms tab. At a high level, it walks you through:
What the generator asks for
- Your swerve module type (e.g. a supported WCP/SDS pre-made module, or a fully custom configuration) — gear ratios are filled in for you if you pick a a WCP or SDS module.
- Per-module CAN IDs for each drive motor, steer/turning motor, and CANcoder. CTRE's convention is Front-Left (1, 2, 3), Front-Right (4, 5, 6), Back-Left (7, 8, 9), Back-Right (10, 11, 12) for (drive, steer/turn, encoder) — This numbering pattern is not required, but IDs must be unique across the whole CAN bus.
- CANcoder offsets, which the generator measures for you via a self-test rather than you guessing them — point every wheel forward, run the self-test, and it reads and stores the offset.
- Track width, wheelbase, and Pigeon 2 CAN ID.
- Motor/encoder inverts, usually determined the same way — verified with the self-test rather than trial and error.
For detailed walkthrough steps and screenshots, follow CTRE's documentation: Creating your Project.
TunerConstants only, vs. a full project
The generator can output just TunerConstants.java, or a full project that also includes CommandSwerveDrivetrain.java. Once you've customized CommandSwerveDrivetrain (e.g. added vision integration), re-run the generator in "TunerConstants only" mode when you re-measure your robot — that updates your constants without overwriting the subsystem code you've since edited.
What the Generator Gives You#
The generated generated/ package (under src/main/java/frc/robot/generated/) contains two files:
TunerConstants.java— every hardware constant: CAN bus name, per-module CAN IDs/offsets/positions, gear ratios, wheel radius, and default PID/feedforward gains. It also exposes acreateDrivetrain()factory method.CommandSwerveDrivetrain.java— aSubsystem(viaTunerSwerveDrivetrain) that wraps Phoenix 6'sSwerveDrivetrainfor command-based use, exposingapplyRequest(...),getState(),seedFieldCentric(), and SysId characterization commands.
Here's an excerpt of what a generated TunerConstants.java looks like, so you recognize the shape when you open the real one — don't hand-type this, the generator writes the actual values from your robot:
// The CAN bus every drivetrain device lives on: "rio" for the roboRIO's native bus,
// or the name assigned to a CANivore.
public static final CANBus kCANBus = new CANBus("canivore", "./logs/example.hoot");
// Front-Left module: drive/steer motor CAN IDs, CANcoder ID, mounting location, and the
// CANcoder offset measured by the generator's self-test — note this is in ROTATIONS, not
// the degrees YAGSL's absoluteEncoderOffset uses.
private static final int kFrontLeftDriveMotorId = 3;
private static final int kFrontLeftSteerMotorId = 2;
private static final int kFrontLeftEncoderId = 1;
private static final Angle kFrontLeftEncoderOffset = Rotations.of(0.152_343_75);
private static final boolean kFrontLeftSteerMotorInverted = true;
private static final Distance kFrontLeftXPos = Inches.of(10);
private static final Distance kFrontLeftYPos = Inches.of(10);
public static final SwerveModuleConstants<TalonFXConfiguration, TalonFXConfiguration, CANcoderConfiguration> FrontLeft =
ConstantCreator.createModuleConstants(
kFrontLeftSteerMotorId, kFrontLeftDriveMotorId, kFrontLeftEncoderId, kFrontLeftEncoderOffset,
kFrontLeftXPos, kFrontLeftYPos, /* driveInverted */ false, kFrontLeftSteerMotorInverted, /* encoderInverted */ false);
/**
* Creates the drivetrain with the generated constants for all four modules.
* This should only be called once, from RobotContainer.
*/
public static CommandSwerveDrivetrain createDrivetrain()
{
return new CommandSwerveDrivetrain(DrivetrainConstants, FrontLeft, FrontRight, BackLeft, BackRight);
}
Rotations, not degrees
Notice kFrontLeftEncoderOffset is expressed in rotations (Angle/Rotations.of(...)), not degrees. If you've also read the YAGSL tutorial, don't carry that library's degree-based absoluteEncoderOffset convention over here — the two systems measure the same physical thing in different units.
For the full generated-file reference, see CTRE's Swerve Builder API docs.
Wiring It Into a Command-Based Robot#
The CTRE generator assumes a freshly created WPILib project. If you need to integrate it into an existing project you can drop the generated code into RobotContainer alongside your other subsystems and commands.
- Copy the generator's
generated/package (andCommandSwerveDrivetrain.java, if you generated the full project) into your existing project'ssrc/main/java/frc/robot/. - Instantiate the drivetrain once from
TunerConstants.createDrivetrain(). - Build a
SwerveRequest.FieldCentricrequest and bind it as the drivetrain's default command, reading joystick axes.
// Top speed and angular rate the joysticks will command. kSpeedAt12Volts comes from the
// generated TunerConstants and reflects your robot's actual measured free speed.
private double MaxSpeed = TunerConstants.kSpeedAt12Volts.in(MetersPerSecond);
private double MaxAngularRate = RotationsPerSecond.of(0.75).in(RadiansPerSecond);
// A SwerveRequest is a reusable, mutable "what should the drivetrain do right now" object.
// FieldCentric drives relative to the field; a 10% deadband ignores joystick noise near zero.
private final SwerveRequest.FieldCentric drive = new SwerveRequest.FieldCentric()
.withDeadband(MaxSpeed * 0.1)
.withRotationalDeadband(MaxAngularRate * 0.1)
.withDriveRequestType(DriveRequestType.OpenLoopVoltage);
// Locks the wheels into an "X" pattern so the robot resists being pushed.
private final SwerveRequest.SwerveDriveBrake brake = new SwerveRequest.SwerveDriveBrake();
// Points every wheel in a given direction without driving — useful for diagnostics.
private final SwerveRequest.PointWheelsAt point = new SwerveRequest.PointWheelsAt();
private final CommandXboxController driverXbox = new CommandXboxController(0);
// Built from the generator's TunerConstants — this one call replaces the entire
// hand-written motor/encoder/kinematics setup a non-generated swerve project needs.
private final CommandSwerveDrivetrain drivetrain = TunerConstants.createDrivetrain();
private void configureBindings()
{
// Default command: continuously drive field-centric off the joystick.
// X is forward, Y is left (WPILib convention) — axes are negated because
// joysticks report negative Y when pushed forward.
drivetrain.setDefaultCommand(
drivetrain.applyRequest(() ->
drive.withVelocityX(-driverXbox.getLeftY() * MaxSpeed)
.withVelocityY(-driverXbox.getLeftX() * MaxSpeed)
.withRotationalRate(-driverXbox.getRightX() * MaxAngularRate)
)
);
// Apply the drivetrain's configured neutral mode while disabled.
final SwerveRequest.Idle idle = new SwerveRequest.Idle();
RobotModeTriggers.disabled().whileTrue(
drivetrain.applyRequest(() -> idle).ignoringDisable(true)
);
// Hold A to brake (lock wheels in an X).
driverXbox.a().whileTrue(drivetrain.applyRequest(() -> brake));
// Hold B to point all wheels toward the left stick's direction.
driverXbox.b().whileTrue(drivetrain.applyRequest(() ->
point.withModuleDirection(new Rotation2d(-driverXbox.getLeftY(), -driverXbox.getLeftX()))
));
// Re-zero field-centric heading — press this when the robot's "forward" drifts from
// the field's forward, e.g. at the start of teleop.
driverXbox.leftBumper().onTrue(drivetrain.runOnce(drivetrain::seedFieldCentric));
}
Why negate the joystick axes?
Same reason as every other drive code on this site: standard joysticks report negative Y when pushed forward, so -driverXbox.getLeftY() corrects it so "forward" on the stick means forward on the field.
Driving Concepts: SwerveRequest#
Instead of calling a drive(...) method directly like YAGSL's SwerveSubsystem, CTRE's generated drivetrain is driven by handing it a SwerveRequest — a small, reusable, mutable object describing "what should the drivetrain do right now." You build one request per behavior, then continuously re-apply it (usually via applyRequest(() -> request) as a default or triggered command) with fresh values each loop.
The handful of request types you'll actually use day-to-day:
SwerveRequest.FieldCentric— drive relative to the field (the normal teleop mode).SwerveRequest.RobotCentric— drive relative to the robot's current facing.SwerveRequest.SwerveDriveBrake— lock the wheels in an X to resist being pushed.SwerveRequest.PointWheelsAt— point every wheel at a given angle without driving (diagnostics, defense).SwerveRequest.Idle— do nothing; useful bound to the disabled trigger so neutral mode still applies.
The full request catalog (including FieldCentricFacingAngle, RobotCentricFacingAngle, and Pro-only requests) is documented in CTRE's Swerve Requests reference — read it once you're comfortable with the pattern above, since most of it is variations on the same idea.
Tuning & Troubleshooting#
Practical notes
- PID/feedforward gains live in
TunerConstantsasSlot0Configsfor the drive and steer motors — tune these the same way you'd tune any Phoenix 6 closed-loop controller. - CANcoder offsets are measured, not guessed. Re-run the generator's self-test (or Tuner X's self-test tool directly) any time you re-mount a module, rather than hand-editing the offset.
- CAN bus name mismatches are the most common first-run failure: every device (TalonFX, CANcoder, Pigeon 2) must agree on whether it's on
"rio"or your CANivore's specific name.TunerConstants.kCANBussets this in one place — check it first if devices aren't responding. - Inverted drive/wrong module ordering shows up as the robot driving diagonally or spinning instead of translating — re-verify CAN IDs and invert flags against what the self-test reported, rather than guessing new values.
CTRE's own tuning guidance (Slot0 gain tuning, current limits, closed-loop output types) is more complete and versioned to the current season, so treat it as the source of truth: Swerve System Requirements and the Swerve Overview.
Links to Relevant Documentation#
- Swerve Project Generator: Tuner X Swerve docs
- Creating your Project (step-by-step): CTRE docs
- System Requirements: CTRE docs
- Swerve Requests API: CTRE docs
- Swerve Overview: CTRE docs
- Full working examples: Phoenix6-Examples repository (see
SwerveWithPathPlannerandSwerveWithChoreofor complete generated projects) - Alternative approach: YAGSL Swerve Tutorial — a mixed-hardware, JSON-config alternative to this generator
Knowledge Check#
Quiz results are saved to your browser's local storage and will persist between sessions.
Your team's swerve drivetrain uses only TalonFX motors, CANcoders, and a Pigeon 2. Based on this page, what's the main advantage of using CTRE's Swerve Project Generator instead of YAGSL?
In the generated TunerConstants.java, what does calling TunerConstants.createDrivetrain() do?
What is a SwerveRequest in the CTRE swerve API?
A generated TunerConstants.java expresses kFrontLeftEncoderOffset as Rotations.of(0.152...). What unit does YAGSL's absoluteEncoderOffset use for the equivalent value, per the YAGSL tutorial on this site?
Quiz Progress
0 / 0 questions answered (0%)
0 correct