TalonFX Swerve Template
AdvantageKit includes two swerve project templates with built-in support for advanced features:
- Easy setup with Tuner X swerve project generator
- High-frequency odometry
- CANivore time sync (for Phoenix Pro users)
- On-controller feedback loops, including Motion Magic for turn control
- Physics simulation
- Automated characterization routines
- Dashboard alerts for disconnected devices
- Pose estimator integration (not including vision)
- Deterministic replay with a guarantee of accuracy
By default, the TalonFX version of the swerve template is configured for robots with four TalonFX drive motors, four TalonFX turn motors, four CANcoder, and a NavX or Pigeon 2 gyro. See the Spark Swerve Template for swerve robots using Spark Max/Flex.
The AdvantageKit swerve templates are open-source and fully customizable:
- No black boxes: Users can view and adjust all layers of the swerve control stack.
- Customizable: IO implementations can be adjusted to support any hardware configuration (see the customization section).
- Replayable: Every aspect of the swerve control logic, pose estimator, etc. can be replayed and logged in simulation using AdvantageKit's deterministic replay features with guaranteed accuracy.
Setup
This example project is part of the 2025 AdvantageKit beta release. If you encounter any issues during setup, please open an issue.
- Swerve Project Generator
- Manual
CTRE only permits the swerve project generator to be used on swerve robots with exclusively CTRE hardware (eight TalonFX controllers, four CANcoders, and a Pigeon 2). Otherwise, switch to the "Manual" tab for standard setup instructions.
-
Download the TalonFX swerve template project from the AdvantageKit release on GitHub and open it in VSCode.
-
Click the WPILib icon in the VSCode toolbar and find the task
WPILib: Set Team Number
. Enter your team number and press enter. -
Follow the instructions in the Phoenix documentation for the Tuner X Swerve Project Generator.
-
On the final screen in Tuner X, choose "Generate only TunerConstants" and overwrite the file located at
src/main/java/frc/robot/generated/TunerConstants.java
. -
In
TunerConstants.java
, comment out the last import and last method. Before removing them, both lines will be marked as errors in VSCode. -
In
TunerConstants.java
, changekSteerInertia
to 0.004 andkDriveInertia
to 0.025.
-
Download the TalonFX swerve template project from the AdvantageKit release on GitHub and open it in VSCode.
-
Click the WPILib icon in the VSCode toolbar and find the task
WPILib: Set Team Number
. Enter your team number and press enter. -
Navigate to
src/main/java/frc/robot/generated/TunerConstants.java
in the AdvantageKit project. -
Update the values of
kDriveGearRatio
andkSteerGearRatio
based on the robot's module type and configuration. This information can typically be found on the product page for the swerve module. These values represent reductions and should generally be greater than one. -
Update the value of
kWheelRadius
to the theoretical radius on each wheel. This value can be further refined as described in the "Tuning" section below. -
Update the value of
kSpeedAt12Volts
to the theoretical max speed of the robot. This value can be further refined as described in the "Tuning" section below. -
Update the value of
kCANBus
based on the CAN bus used by the drive devices. Check theCANBus
API documentation for details on possible values. -
Set the value of
kPigeonId
to the correct CAN ID of the Pigeon 2 (as configured using Tuner X). If using a NavX instead of a Pigeon 2, see the customization section below. -
For each module, set the values of
k...DriveMotorId
,k...SteerMotorId
, andk...EncoderId
to the correct CAN IDs of the drive TalonFX, turn TalonFX, and CANcoder (as configured in Tuner X). -
For each module, set the values of
k...XPos
andk...YPos
based on the distance from each module to the center of the robot. Positive X values are closer to the front of the robot and positive Y values are closer to the left side of the robot. -
For each module, set the value of
k...EncoderOffset
toRadians.of(0.0)
. -
Deploy the project to the robot and connect using AdvantageScope.
-
Check that there are no dashboard alerts or errors in the Driver Station console. If any errors appear, verify tha CAN IDs, firmware versions, and configurations of all devices.
-
Manually rotate the turning position each module such that the position in AdvantageScope (
/Drive/Module.../TurnPosition
) is increasing. The module should be rotating counter-clockwise as viewed from above the robot. Verify that the units visible in AdvantageScope (radians) match the physical motion of the module. If necessary, change the value ofk...SteerMotorInverted
orkSteerGearRatio
. -
Manually rotate each drive wheel and view that the position in AdvantageScope (
/Drive/Module.../DrivePositionRad
). Verify that the units visible in AdvantageScope (radians) match the physical motion of the module. If necessary, change the value ofkDriveGearRatio
. -
Manually rotate each module to align it directly forwards. Verify using AdvantageScope that the drive position increases when the wheel rotates such that the robot would be propelled forwards. We recommend pressing a straight object such as aluminum tubing against the pairs of left and right modules to ensure accurate alignment.
-
Record the value of
/Drive/Module.../TurnPosition
for each aligned module. Update the value ofk...EncoderOffset
for each module toRadians.of(<insert value>)
. The value saved inTunerConstants
must be the negative of the value displayed in AdvantageScope (i.e. positive values become negative and vice versa).
Tuning
Torque-Current Control
The project defaults to voltage control for both the drive and turn motors. Phoenix Pro subscribers can optionally switch to torque-current control, as described in the Phoenix documentation. This can be configured by changing the values of kSteerClosedLoopOutput
and/or kDriveClosedLoopOutput
in TunerConstants.java
to ClosedLoopOutputType.TorqueCurrentFOC
.
Torque-current control requires different gains than voltage control. We recommend following the steps below to tune feedforward and PID gains.
Feedforward Characterization
The project includes default feedforward gains for velocity control of the drive motors (kS
and kV
), acceleration control of the drive motors (kA
), and velocity control of the turn motors (kS
and kV
).
The drive kS
and kV
gains should always be characterized (as described below). The drive/turn kA
gains and turn kS
and kV
gains are unnecessary in most cases, but can be tuned by advanced users.
The project includes a simple feedforward routine that can be used to quicly measure the drive kS
and kV
values without requiring SysId:
-
Tune turning PID gains as described here.
-
Place the robot in an open space.
-
Select the "Drive Simple FF Characterization" auto routine.
-
Enable the robot in autonomous. The robot will slowly accelerate forwards, similar to a SysId quasistic test.
-
Disable the robot after at least ~5-10 seconds.
-
Check the console output for the measured
kS
andkV
values, and copy them to thedriveGains
config inTunerConstants.java
.
The feedforward model used in simulation can be characterized using the same method. Simulation gains are stored in ModuleIOSim.java
instead of TunerConstants.java
.
Users who wish to characterize acceleration gains (kA
) or turn gains can choose to use the full SysId application. The project includes auto routines for each of the four required SysId tests. Two options are available to load data in SysId:
- Export the Hoot log file as described here.
- Export the AdvantageKit log file as described here. Note that AdvantageKit values are logged in radians while Phoenix requires rotation to be used. Gains must be converted appropriately.
The built-in SysId routines can be easily adapted to characterize the turn motor feedforward or the angular motion of the robot (for example, to estimate the robot's moment of inertia). The code below shows how the runCharacterization
method can be adapted for these use cases.
/** Characterize turn motor feedforward. */
public void runCharacterization(double output) {
io.setDriveOpenLoop(0.0);
io.setTurnOpenLoop(output);
}
/** Characterize robot angular motion. */
public void runCharacterization(double output) {
io.setDriveOpenLoop(output);
io.setTurnPosition(new Rotation2d(constants.LocationX, constants.LocationY).plus(Rotation2d.kCCW_Pi_2));
}
Wheel Radius Characterization
The effective wheel radius of a robot tends to change over time as wheels are worn down, swapped, or compress into the carpet. This can have significant impacts on odometry accuracy. We recommend regularly recharacterizing wheel radius to combat these issues.
The project includes an automated wheel radius characterization routine, which only requires enough space for the robot to rotate in place.
-
Place the robot on carpet. Characterizing on a hard floor may produce errors in the measurement, as the robot's effective wheel radius is affected by carpet compression.
-
Select the "Drive Wheel Radius Characterization" auto routine.
-
Enable the robot is autonomous. The robot will slowly rotate in place.
-
Disable the robot after at least one full rotation.
-
Check the console output for the measured wheel radius, and copy the value to
kWheelRadius
inTunerConstants.java
.
Drive/Turn PID Tuning
The project includes default gains for the drive velocity PID controllers and turn position PID controllers, which can be found in the steerGains
and driveGains
configs in TunerConstants.java
. These gains should be tuned for each robot.
More information about PID tuning can be found in the WPILib documentation.
We recommend using AdvantageScope to plot the measured and setpoint values while tuning. Measured values are published to the /RealOutputs/SwerveStates/Measured
field and setpoint values are published to the /RealOutputs/SwerveStates/SetpointsOptimized
field.
The PID gains used in simulation can be tuned using the same method. Simulation gains are stored in ModuleIOSim.java
instead of TunerConstants.java
.
Max Speed Measurement
The effective maximum speed of a robot is typically slightly less than the theroetically max speed based on motor free speed and gearing. To ensure that the robot remains controllable at high speeds, we recommend measuring the effective maximum speed of the robot.
-
Set
kSpeedAt12Volts
inTunerConstants.java
to the theoretical max speed of the robot based on motor free speed and gearing. This value can typically be found on the product page for your chosen swerve modules. -
Place the robot in a open space.
-
Plot the measured robot speed in AdvantageScope using the
/RealOutputs/SwerveChassisSpeeds/Measured
field. -
In teleop, drive forwards at full speed until the robot velocity is no longer increasing.
-
Record the maximum velocity achieved and update the value of
kSpeedAt12Volts
.
Slip Current Measurement
The value of kSlipCurrent
can be tuned to avoid slipping the wheels.
-
Place the robot against the solid wall.
-
Using AdvantageScope, plot the current of a drive motor from the
/Drive/Module.../DriveCurrentAmps
key, and the velocity of the motor from the/Drive/Module.../DriveVelocityRadPerSec
key. -
Accelerate forwards until the drive velocity increases (the wheel slips). Note the current at this time.
-
Update the value of
kSlipCurrent
to this value.
PathPlanner Configuration
The project includes a built-in configuration for PathPlanner, located in the constructor of Drive.java
. You may wish to manually adjust the following values:
- Robot mass, MOI, and wheel coefficient as configured at the top of
Drive.java
- Drive PID constants as configured in
AutoBuilder
. - Turn PID constants as configured in
AutoBuilder
.
Customization
Setting Odometry Frequency
By default, the project runs at 100Hz on the RIO CAN bus and 250Hz on CAN FD buses. These values are stored at the top of Drive.java
and can be freely customized. The project configures all devices to minimize CAN bus utilization, but we recommend monitoring utilization carefully when increasing frequency.
Custom Gyro Implementations
The project defaults to the Pigeon 2 gyro, but can be integrated with any standard gyro. An example implementation for a NavX is included.
To change the gyro implementation, switch new GyroIOPigeon2()
in the RobotContainer
constructor to any other implementation. For example, the GyroIONavX
implementation is pre-configured to use a NavX connected to the MXP SPI port. See the page on IO interfaces for more details on how hardware abstraction works.
The PhoenixOdometryThread
class reads high-frequency gyro data for odometry alongside samples from drive encoders. This class supports both Phoenix signals and generic signals. Note that the gyro should be configured to publish signals at the same frequency as odometry. Call registerSignal
with a double supplier to create a queue, as shown in the GyroIONavX
implementation:
Queue<Double> yawPositionQueue = SparkOdometryThread.getInstance().registerSignal(navX::getAngle);
Reference the full GyroIONavX
implementation for an example of how to create a timestamp queue and update the odometry inputs for the gyro.
Custom Module Implementations
The implementation of ModuleIOTalonFX
can be freely customized to support alternative hardware configurations, such as using a Spark Max as a turn controller. When integrating with Spark devices, we recommend referencing the implementation found in the ModuleIOSpark
class of the Spark Swerve Template.
As described in the previous section, the PhoenixOdometryThread
supports non-Phoenix signals through the registerSignal
method. This allows devices from different vendors to be freely mixed.
By default, the project uses a CANcoder in remote/fused/sync mode. When using another absolute encoder (such as a duty cycle encoder or HELIUM Canandmag), we recommend reseting the relative encoder based on the absolute encoder; the relative encoder can then be used for PID control. In this case, the following changes are required:
-
Create the encoder object in
ModuleIOTalonFX
and configure it appropriately. -
Change the feedback sensor source of the turn controller:
turnConfig.Feedback.FeedbackSensorSource = FeedbackSensorSourceValue.RotorSensor;
- Reset the relative encoder position at startup:
tryUntilOk(5, () -> turnTalon.setPosition(customEncoder.getPositionRotations(), 0.25));
Exponential Turning Profile
By default, the project uses a standard trapezoidal profile for turn control. Users may choose to replace the trapezoidal profile with an exponential version using the Phoenix Motion Magic Expo API. To implement this, simply replace the position request in ModuleIOTalonFX
with the new request type, as shown below. The kV
and kA
constraints are already configured in the ModuleIOTalonFX
constructor, but can be adjusted.
private final MotionMagicExpoVoltage positionVoltageRequest = new MotionMagicExpoVoltage(0.0);
private final MotionMagicExpoTorqueCurrentFOC positionTorqueCurrentRequest = new MotionMagicExpoTorqueCurrentFOC(0.0);
Vision Integration
The Drive
subsystem uses WPILib's SwerveDrivePoseEstimator
class for odometry updates. The subsystem exposes the addVisionMeasurement
method to enable vision systems to publish samples. Additional methods can be easily exposed as desired.
Alternatively, other pose estimation systems can be easily integrated in place of the WPILib solution. The periodic
method includes a call to poseEstimator.updateWithTime
that includes the sample timestamp, gyro rotation, and module positions. This call can be replaced to integrate with any other odometry or pose estimation system.
Swerve Setpoint Generator
The project already includes basic mechanisms to reduce skidding, such as drive current limits and cosine optimization. Users who prefer more control over module skidding may wish to utilize Team 254's swerve setpoint generator. Documentation for using the version of this algorithm bundled with PathPlanner can be found here. The SwerveSetpointGenerator
should be instantiated in the Drive
subsystem and used in the runVelocity
method, as shown below:
private final SwerveSetpointGenerator setpointGenerator;
private SwerveSetpoint previousSetpoint;
public Drive(...) {
// ...
setpointGenerator = new SwerveSetpointGenerator(...);
previousSetpoint = new SwerveSetpoint(getChassisSpeeds(), getModuleStates(), DriveFeedforwards.zeroes(4));
// ...
}
public void runVelocity(ChassisSpeeds speeds) {
previousSetpoint = setpointGenerator.generateSetpoint(previousSetpoint, speeds, 0.02);
SwerveModuleStatep[] setpointStates = previousSetpoint.moduleStates();
// ...
}
Advanced Physics Simulation
The project can be easily adapted to utilize Team 5516's maple-sim library for simulation, which provides a full rigid-body simulation of the swerve drive and its interactions with the field. Check the documentation for more details on how to install and use the library.