Skip to content

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 a createDrivetrain() factory method.
  • CommandSwerveDrivetrain.java — a Subsystem (via TunerSwerveDrivetrain) that wraps Phoenix 6's SwerveDrivetrain for command-based use, exposing applyRequest(...), 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:

TunerConstants.java (illustrative excerpt — generator output, not hand-written)
  // 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);
TunerConstants.java - createDrivetrain()
  /**
   * 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.

  1. Copy the generator's generated/ package (and CommandSwerveDrivetrain.java, if you generated the full project) into your existing project's src/main/java/frc/robot/.
  2. Instantiate the drivetrain once from TunerConstants.createDrivetrain().
  3. Build a SwerveRequest.FieldCentric request and bind it as the drivetrain's default command, reading joystick axes.
RobotContainer.java - Fields
  // 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();
RobotContainer.java - configureBindings()
  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 TunerConstants as Slot0Configs for 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.kCANBus sets 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.



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