Gray Matter
WorkshopSwerve Project Generator
WPILib 2027 is still in alpha: these pages change as the APIs settle.
LESSON 17

Swerve Project Generator

Tuner X measures your drivetrain and writes one file. The rest of the swerve project is already written, so you swap that file in and drive. Have the robot assembled and up on blocks.

Branch1-Swerve14 minutes
You’ll need
  • An assembled drivetrain: eight TalonFX, four CANcoders, one Pigeon 2, one CANivore.
  • Phoenix Tuner X connected, with firmware current on every device.
  • Module anatomy and field-centric driving from Swerve Drive Prerequisites.
  • A tape measure, and the robot up on blocks.

Nobody on this team writes swerve kinematics by hand. Everything specific to your robot gets measured: which motor sits at which corner, how far apart the modules are, where each wheel reads zero.

Note

Start from the workshop project

Download the swerve project below. It is the code on this page, and steps 5 and 6 are the only two edits it needs.

Download Swerve Project (v3.0, Commands v3)

What the generator writes

The generator can write a whole project. Only one file comes back with you: src/main/java/frc/robot/generated/TunerConstants.java.

It carries thirteen device IDs, kDriveGearRatio, kSteerGearRatio and kWheelRadius. Per module it holds an X and Y offset from the robot's center, which is what kinematics runs on. There is a CANcoder offset per module as well, measured with that wheel straight. steerGains, driveGains, kSlipCurrent and kSpeedAt12Volts are estimates you replace on Swerve Calibration.

The copy checked into the workshop project belongs to somebody else's robot: fake IDs, fake gains, and a comment saying so. It describes a square robot with the modules 10 inches out in each direction, 7.36:1 on the drive, and a 2.167-inch wheel radius. Deploy it unchanged and the code looks for motors that are not on your bus.

ABOUT TUNER X

Look for the state, not the button

Tuner X moves its controls between releases, so a labeled screenshot goes stale within a season. The state you are aiming for does not move. Every step below says what the screen should show once you have it right.

Six steps, in order

  1. Put every device on one CAN bus with a unique ID, and write the IDs down corner by corner. You want thirteen listed, no duplicate-ID warning, no red firmware badge. A missing device is a wiring fault.
  2. Open the swerve project generator, under Mechanisms, behind a New Project button. It opens by asking for dimensions rather than for code.
  3. Measure the robot and enter four numbers.
    • Wheelbase, labeled FL to BL: front-to-back distance between module centers.
    • Trackwidth, labeled FL to FR: side-to-side distance between module centers.
    • Wheel radius: half the tread width, on a wheel already driven on. The field asks for radius, not diameter.
    • Drive gear ratio: motor rotations per wheel rotation, off the module's spec sheet.

    Take them off the real robot, not the CAD you meant to build. Wheelbase and trackwidth are the pair people swap, and on a square robot that stays hidden until it turns.

  4. With the robot on blocks, the wizard drives the modules one at a time. Hold each wheel straight when it asks: that measurement becomes the corner's CANcoder offset. Half a degree of error per module walks the robot sideways over a long drive. If the corner moving is not the one the wizard named, two CAN IDs are swapped.
  5. Generate the constants and replace src/main/java/frc/robot/generated/TunerConstants.java with the file it writes. Replace the whole file, since offsets, inversions and IDs all come from the same run. You should see your own CAN IDs and a kCANBus line naming your CANivore.
  6. Set your team number in .wpilib/wpilib_preferences.json, which ships as 5712, and deploy the way Running Your Code showed. The driver station lists Teleop as a selectable mode. Modules point straight when you enable, and nothing spins on its own.
CTRE: Tuner X Swerve Project Generator

Three files, one drivetrain

Every other mechanism you wrote was one class. The drivetrain is three, because of a Java rule from The Java You Need: a class extends only one other class.

CommandSwerveDrivetrainalready extends CTRE's generated swerve class, so it cannot also extend Mechanism. DriveMechanism therefore owns a drivetrain instead of being one, and hands out its commands.

FileWho wrote itWhat it holds
TunerConstants.javaTuner X, from your robotEvery number about your robot. Hand-edited only when calibration says to.
CommandSwerveDrivetrain.javaTuner X, then lightly editedMotors, odometry, the simulation thread, and forward matched to your alliance color.
DriveMechanism.javaThe workshop, by handThe Mechanism: commands, pose readings, telemetry. What an OpMode talks to.

DriveMechanism is the only one of the three you would add to, and its surface is small. Both applyRequest(...) and seedFieldCentric() return commands, and setControl(...) sends one request straight through. Two more read the drivetrain back: getPose() and getFieldVelocity(). The camera comes in later, through addVisionMeasurement(...).

The drivetrain default command

Every Mechanism starts out with idle() as its default. Since idle() parks at the lowest priority and sends nothing at all, canceling an arm command leaves the arm pushing. That is what arm.stop() is for. Teleop gives the drivetrain a better default, and it is the only setDefaultCommand call in the workshop code. The call sits in TeleopOpMode rather than Robot because the default needs that mode's controller.

TeleopOpMode.java: the joystick drive, set as the default
public TeleopOpMode(Robot robot) {
final DriveMechanism drivetrain = robot.drivetrain;
 
// In WPILib, X points forward and Y points left. The sticks read the other way around, so
// each axis below gets a minus sign.
drivetrain.setDefaultCommand(
drivetrain.applyRequest(
() ->
drive
.withVelocityX(-driver.getLeftY() * maxSpeed) // left stick up = forward
.withVelocityY(-driver.getLeftX() * maxSpeed) // left stick left = left
.withRotationalRate(
-driver.getRightX() * maxAngularRate))); // right stick left = turn left
 
// Left bumper: make the robot's current facing the new "forward".
driver.leftBumper().onTrue(drivetrain.seedFieldCentric());
}

applyRequest(...) is an ordinary command factory built on runRepeatedly. It re-reads the sticks and re-sends a fresh request every loop. Let go of the sticks and the command is still running, asking for zero speed. Full stick asks for the top speed the constants claim your robot has: maxSpeed comes straight out of TunerConstants.kSpeedAt12Volts. The request ignores the bottom 10 percent of each stick.

The scheduler checks two things when that line runs. A default command has to require its own mechanism, and it must not require a second one. Break either and you get an IllegalArgumentException at runtime, not a compile error. Commands from applyRequest(...) pass both; a hand-built group that also requires the arm would not.

The other binding is the left bumper, wired to seedFieldCentric(). Press it and whatever way the robot faces becomes the new forward for the sticks. It changes a heading reference and nothing else, so it never says where the robot is on the field. That job belongs to resetPose(Pose2d), which nothing in the workshop code calls yet. Swerve Calibration lines both up next to applyOperatorPerspective().

Check your work

Robot on blocks for the first two checks, then on the floor with room around it. Keep a hand on disable.

  1. Select Teleop and enable, hands off the controller. All four wheels stay still and each module holds the angle it had. Creeping means an off-center stick or a wrong deadband.
  2. Push the left stick forward: four modules pointing the same way, four wheels turning the same direction. A wheel turning backwards is an inversion; a module facing sideways is a CANcoder offset. Push the right stick sideways and the modules splay into a rotation pattern.
  3. On the floor, point the robot away from you and push forward. It drives away in a straight line. Turn it 90 degrees in place, push forward again, and it goes the same direction across the floor. Press the left bumper and forward becomes whatever way it is facing now.
  4. Let go of everything. The robot stops and stays stopped, because its default command is asking for zero.
IF IT DIDN'T WORK

Three ways this goes wrong

  • A device the driver station cannot find. You deployed with the example file, or yours landed outside src/main/java/frc/robot/generated/. The kCANBus name has to match your CANivore.
  • One module fighting the other three. Its inversion or its offset is wrong. Re-run the module test for that corner rather than editing the file: the two are measured together.
  • Nothing moves and no error appears. Check that Teleop is selected and the robot enabled. If both are right, the setDefaultCommand line is missing, and idle() underneath looks like broken wiring.

Swerve Calibrationis next. It replaces the generator's estimates with numbers measured off your robot.

Check yourself

You finish the Tuner X wizard. Which file do you take back to the workshop project?

The wizard says it is testing the front-left module, and the back-right wheel turns. What is wrong?

Why does DriveMechanism own a CommandSwerveDrivetrain instead of extending it?

An arm command and a drivetrain command are both canceled. How does the hardware behave differently?

You press the left bumper, which runs seedFieldCentric(). What changed?

One module drives the wrong way while the other three go straight. What do you do?

Pick an answer for each.