Spark Swerve Template
AdvantageKit includes two swerve project templates with built-in support for advanced features:
- High-frequency odometry
- On-controller feedback loops
- 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 Spark version of the swerve template is configured for robots with MAXSwerve modules, four NEO Vortex drive motors, four NEO 550 turn motors, four duty cycle absolute encoders, and a NavX or Pigeon 2 gyro. See the TalonFX Swerve Template for swerve robots using Talon FX.
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.
-
Download the Spark 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/subsystems/drive/DriveConstants.java
in the AdvantageKit project. -
Update the values of
driveMotorReduction
andturnMotorReduction
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 values of
trackWidth
andwheelBase
based on the distance between the left-right and front-back modules (respectively). -
Update the value of
wheelRadiusMeters
to the theoretical radius on each wheel. This value can be further refined as described in the "Tuning" section below. -
Update the value of
maxSpeedMetersPerSec
to the theoretical max speed of the robot. This value can be further refined as described in the "Tuning" section below. -
Set the value of
pigeonCanId
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
...DriveMotorId
and...TurnMotorId
to the correct CAN IDs of the drive Spark FLex and turn Spark Max (as configured in the REV Hardware Client). -
For each module, set the value of
...ZeroRotation
tonew Rotation2d(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 that 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 ofturnInverted
,turnEncoderInverted
, orturnMotorReduction
. -
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 ofdriveMotorReduction
. -
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 of...ZeroRotation
for each module tonew Rotation2d(<insert value>)
.
Tuning
Feedforward Characterization
The project includes default feedforward gains for velocity control of the drive motors (kS
and kV
).
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 thedriveKs
anddriveKv
constants inDriveConstants.java
.
The feedforward model used in simulation can be characterized using the same method. Simulation gains are stored in the driveSimKs
and driveSimKv
constants.
Users who wish to characterize acceleration gains (kA
) 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:
- The project is configured to use URCL by default. This data can be exported as described here.
- Export the AdvantageKit log file as described here.
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(
Rotation2d.fromDegrees(
switch (index) {
case 0 -> 135.0;
case 1 -> 45.0;
case 2 -> -135.0;
case 3 -> -45.0;
default -> 0.0;
}));
}
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
wheelRadiusMeters
inDriveConstants.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 "Drive PID configuration" and "Turn PID configuration" sections of DriveConstants.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 separately from "real" gains in DriveConstants.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
maxSpeedMetersPerSec
inDriveConstants.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
maxSpeedMetersPerSec
.
Slip Current Measurement
The value of driveMotorCurrentLimit
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
driveMotorCurrentLimit
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 bottom of
DriveConstants.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. This value is stored as odometryFrequency
at the top of DriveConstants.java
and can be changed. The project configures all devices to minimize CAN bus utilization, but we recommend monitoring utilization carefully when increasing frequency.
Switching Between Spark Max and Flex
Switching between the Spark Max and Spark Max for drive and turn motors is very simple. In the constructor of ModuleIOSpark
, change the call instantiating the Spark object to use CANSparkMax
or CANSparkFlex
. The configuration object must also be changed to the corresponding SparkMaxConfig
or SparkFlexConfig
class.
When switching between motor types, the driveGearbox
and turnGearbox
constants in DriveConstants
should be updated accordingly.
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 SparkOdometryThread
class reads high-frequency gyro data for odometry alongside samples from drive encoders. This class supports both Spark devices 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 ModuleIOSpark
can be freely customized to support alternative hardware configurations, such as using a TalonFX-based drive motor. When integrating with TalonFX devices, we recommend referencing the implementation found in the ModuleIOTalonFX
class of the TalonFX Swerve Template.
As described in the previous section, the SparkOdometryThread
supports non-Spark signals through the registerSignal
method. This allows devices from different vendors to be freely mixed.
By default, the project uses a duty cycle encoder connected to a turn Spark Max. When using another absolute encoder (such as a CANcoder 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
ModuleIOSpark
and configure it appropriately. -
Change the feedback sensor source of the turn controller:
turnEncoder = turnSpark.getEncoder(); // Change the type of turnEncoder to RelativeEncoder
turnConfig.closedLoopConfig.feedbackSensor(FeedbackSensor.kPrimaryEncoder); // Was: kAbsoluteEncoder
-
Replace
turnConfig.absoluteEncoder...
withturnConfig.encoder...
, andaverageDepth
withuvwAverageDepth
. Remove the setter forinverted(...)
. -
In the signal config for the turn motor, change all instances of
absoluteEncoderPosition...
andabsoluteEncoderVelocity...
toprimaryEncoderPosition...
andprimaryEncoderVelocity...
. -
Incorporate the turn motor reduction in the encoder position and velocity factors:
public static final double turnEncoderPositionFactor = 2 * Math.PI / turnMotorReduction; // Rotor Rotations -> Wheel Radians
public static final double turnEncoderVelocityFactor = (2 * Math.PI) / 60.0 / turnMotorReduction; // Rotor RPM -> Wheel Rad/Sec
- Reset the relative encoder position at startup:
tryUntilOk(turnSpark, 5, () -> turnEncoder.setPosition(customEncoder.getPositionRadians()));
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.