Skip to content

Setting Up a Swerve Drive with YAGSL for FRC#

This tutorial provides a comprehensive, step-by-step guide to setting up a swerve drive using Yet Another Generic Swerve Library (YAGSL) for FIRST Robotics Competition (FRC) projects. YAGSL is designed to simplify swerve drive implementation by providing a generic, well-documented library that works with various motor controllers and sensors, eliminating the need for custom code for each robot configuration.

1. Introduction to YAGSL#

YAGSL (Yet Another Generic Swerve Library) is a swerve drive library developed by current and former BroncBotz mentors for FRC teams. Its primary goal is to make swerve drive programming as straightforward as using a DifferentialDrive, while supporting a wide range of hardware combinations.

Key Features#

  • Generic Design: Works with mixed hardware (e.g., REV SparkMax with CTRE CANCoder, TalonFX with Pigeon2, etc.)
  • JSON-Based Configuration: Robot-specific settings are stored in JSON files, allowing the same code to work across different robots
  • Active Maintenance: Regularly updated and community-supported
  • Comprehensive Documentation: Extensive guides, examples, and troubleshooting resources

Why YAGSL?#

Unlike many swerve templates that require extensive modification, YAGSL abstracts hardware differences, so teams can focus on robot logic rather than drive code. It's particularly useful for teams with multiple robots or those using non-standard hardware combinations.

Two hardware paths, one tutorial

This tutorial walks through both of the most common FRC swerve hardware combinations side-by-side, using tabs like the ones below wherever the configuration differs:

  • REVLib: REV SparkMax controllers driving NEO motors, with a CTRE CANcoder for absolute position.
  • TalonFX: CTRE TalonFX controllers driving Kraken X60 / Falcon 500 motors, with a CTRE CANcoder and Pigeon 2 IMU.

Every Java class YAGSL gives you (SwerveSubsystem, drive commands, odometry, etc.) is identical regardless of which hardware you use — YAGSL abstracts that difference away entirely into the JSON configuration files. The only thing that ever changes in your Java code is which deploy folder you point YAGSL at.

If you're using all-CTRE hardware and want CTRE's own first-party generator instead of YAGSL's JSON abstraction, see the CTRE Swerve Project Generator tutorial for the alternative approach.

For more details, see the YAGSL Overview.

2. Prerequisites and Dependencies#

Before starting, ensure you have the following installed and configured.

Software Requirements#

  • WPILib: Latest stable version for your season (2025 recommended)
  • Installation guide: WPILib Setup
  • REV Hardware Client 2: For configuring REV devices
  • Download: REV Hardware Client
  • CTRE Tuner X: For configuring CTRE devices (Phoenix 6)
  • Installation: Phoenix 6 Installation

Vendor Dependencies (Vendordeps)#

YAGSL requires vendor libraries for all supported hardware, even if not used on your robot. Install these via WPILib's vendor dependency system:

  • REVLib: https://software-metadata.revrobotics.com/REVLib.json
  • Phoenix 6: https://maven.ctr-electronics.com/release/com/ctre/phoenix6/latest/Phoenix6-frc2025-latest.json
  • ReduxLib: https://frcsdk.reduxrobotics.com/ReduxLib.json
  • PhotonVision (optional, for vision): https://maven.photonvision.org/repository/internal/org/photonvision/PhotonLib-json/1.0/PhotonLib-json-1.0.json
  • YAGSL: https://yet-another-software-suite.github.io/YAGSL/yagsl.json

You only strictly need the vendordep for your own hardware

YAGSL's JSON parser only touches the vendor library for the hardware types referenced in your config files. Installing REVLib (NEO) and Phoenix 6 (CTRE) side by side is harmless (and required if you ever mix hardware), but if your robot is purely CTRE (TalonFX) based, for example, you don't need to worry about configuring REV devices in the REV Hardware Client.

Installation steps: 3rd Party Libraries

Hardware Knowledge#

You should know your robot's physical characteristics while configuring YAGSL. (See section 3 below for details).

3. Hardware Requirements and Getting to Know Your Robot#

A swerve drive consists of: - Gyroscope/IMU: For heading tracking (e.g., Pigeon2, NavX, or built-in IMU) - Swerve Modules: Each containing: - Drive motor (e.g., NEO, Falcon500, Kraken) - Angle/steering motor (e.g., NEO 550, TalonFXS) - Absolute encoder (e.g., CANCoder, Canandmag, Thrifty Encoder) - CAN Bus: For communication (required for most modern FRC hardware)

Pre-Configuration Checklist#

Before configuring YAGSL, gather these details about your robot:

  • IMU Type and ID: What gyroscope are you using and what is its CAN ID?
  • Module Configuration: For each swerve module:
  • Drive motor type, CAN ID, and canbus name (if using multiple CAN buses)
  • Angle motor type, CAN ID, and canbus name.
  • Encoder type, CAN ID.
  • Physical location relative to robot center (X, Y coordinates in inches)
  • Physical Properties:
  • Wheel diameter
  • Drive gear ratio (motor rotations per wheel rotation) this can be found on the swerve module spec sheet for your specific module
  • Angle gear ratio (motor rotations per 360° module rotation)
  • Robot coefficient of friction (for feedforward calculations) (optional, can be estimated)
  • Maximum speed (feet per second) (Generally, this is the free speed of your drive motors divided by the drive gear ratio, multiplied by wheel circumference)
  • CAN Bus Configuration: Ensure all devices have unique IDs.

For a complete list and more detailed explanations, see Getting to Know Your Robot.

4. Configuration Steps (JSON Files, Module Setup)#

YAGSL uses JSON configuration files to define your robot's swerve drive. These files are placed in the deploy/swerve/ directory of your robot project.

Directory Structure#

deploy/
└── swerve/
    ├── controllerproperties.json
    ├── modules/
    │   ├── frontleft.json
    │   ├── frontright.json
    │   ├── backleft.json
    │   └── backright.json
    ├── physicalproperties.json
    ├── pidfproperties.json
    └── swervedrive.json

Configuration Files Overview#

swervedrive.json - Global Drive Configuration#

This file defines the overall swerve drive configuration, including the IMU (gyroscope) settings and references to the individual module configuration files.

Key Properties

  • imu: Configures the gyroscope/IMU used for heading tracking
  • type: The type of IMU ("pigeon2", "navx", etc.)
  • id: CAN ID of the IMU device
  • canbus: CAN bus name (usually "rio" for roboRIO bus, or your CANivore's name)
  • invertedIMU: Whether to invert the IMU reading (used for orientation correction)
  • modules: Array of module configuration file names

Example - Pigeon2 IMU:

swervedrive.json - Pigeon2 Configuration
{
  "imu": {
    "type": "pigeon2",
    "id": 13,
    "canbus": "rio"
  },
  "invertedIMU": false,
  "modules": [
    "frontleft.json",
    "frontright.json",
    "backleft.json",
    "backright.json"
  ]
}

Example - NavX IMU:

swervedrive.json - NavX Configuration
{
  "imu": {
    "type": "navx",
    "id": 0,
    "canbus": null
  },
  "invertedIMU": false,
  "modules": [
    "frontleft.json",
    "frontright.json",
    "backleft.json",
    "backright.json"
  ]
}

IMU choice is independent of motor vendor

A Pigeon2 works fine on a SparkMax/NEO robot, and a NavX works fine on a TalonFX robot — the IMU is a separate hardware choice from your drive/angle motor controllers. The complete examples below use a Pigeon2 for both hardware sets to keep the comparison focused on the motor/encoder differences.

Module JSON Files - Individual Swerve Module Configuration#

Each swerve module (wheel) has its own configuration file defining the drive motor, angle motor, encoder, and physical location.

Key Properties

  • drive: Configuration for the drive (translation) motor
  • type: Motor controller type — e.g. "sparkmax_neo" for a SparkMax driving a NEO, "talonfx" for a TalonFX driving a Kraken X60/Falcon 500
  • id: CAN ID of the motor controller
  • canbus: CAN bus name
  • angle: Configuration for the angle (steering) motor, same type options as drive
  • encoder: Configuration for the absolute encoder
  • type: Encoder type ("cancoder", "canandmag", "thrifty", "throughbore", etc.)
  • inverted: Motor inversion settings
  • drive: Whether to invert drive motor direction
  • angle: Whether to invert angle motor direction
  • absoluteEncoderOffset: Encoder offset in degrees from 0°. May be negative.
  • location: Physical location relative to robot center
  • front: Distance forward from center (inches)
  • left: Distance left from center (inches, negative for right side)

Measuring absoluteEncoderOffset

Point every wheel straight forward, read each CANcoder/encoder's raw position, and set absoluteEncoderOffset to the negative of that reading (in degrees) so the module reports 0° when facing forward.

Example - Front-Left Module (frontleft.json):

frontleft.json - SparkMax NEO with CANCoder
{
  "drive": {
    "type": "sparkmax_neo",
    "id": 4,
    "canbus": null
  },
  "angle": {
    "type": "sparkmax_neo",
    "id": 3,
    "canbus": null
  },
  "encoder": {
    "type": "cancoder",
    "id": 9,
    "canbus": null
  },
  "inverted": {
    "drive": false,
    "angle": false
  },
  "absoluteEncoderOffset": -114.609,
  "location": {
    "front": 12,
    "left": 12
  }
}
frontleft.json - TalonFX with CANcoder
{
  "drive": {
    "type": "talonfx",
    "id": 4,
    "canbus": "canivore"
  },
  "angle": {
    "type": "talonfx",
    "id": 3,
    "canbus": "canivore"
  },
  "encoder": {
    "type": "cancoder",
    "id": 9,
    "canbus": "canivore"
  },
  "inverted": {
    "drive": false,
    "angle": false
  },
  "absoluteEncoderOffset": -114.609,
  "location": {
    "front": 12,
    "left": 12
  }
}

Example - Front-Right Module (frontright.json):

frontright.json
{
  "drive": {
    "type": "sparkmax_neo",
    "id": 2,
    "canbus": null
  },
  "angle": {
    "type": "sparkmax_neo",
    "id": 1,
    "canbus": null
  },
  "encoder": {
    "type": "cancoder",
    "id": 10,
    "canbus": null
  },
  "inverted": {
    "drive": false,
    "angle": false
  },
  "absoluteEncoderOffset": -50.977,
  "location": {
    "front": 12,
    "left": -12
  }
}
frontright.json
{
  "drive": {
    "type": "talonfx",
    "id": 2,
    "canbus": "canivore"
  },
  "angle": {
    "type": "talonfx",
    "id": 1,
    "canbus": "canivore"
  },
  "encoder": {
    "type": "cancoder",
    "id": 10,
    "canbus": "canivore"
  },
  "inverted": {
    "drive": false,
    "angle": false
  },
  "absoluteEncoderOffset": -50.977,
  "location": {
    "front": 12,
    "left": -12
  }
}

Example - Back-Left Module (backleft.json):

backleft.json
{
  "drive": {
    "type": "sparkmax_neo",
    "id": 7,
    "canbus": null
  },
  "angle": {
    "type": "sparkmax_neo",
    "id": 8,
    "canbus": null
  },
  "encoder": {
    "type": "cancoder",
    "id": 12,
    "canbus": null
  },
  "inverted": {
    "drive": false,
    "angle": false
  },
  "absoluteEncoderOffset": 6.504,
  "location": {
    "front": -12,
    "left": 12
  }
}
backleft.json
{
  "drive": {
    "type": "talonfx",
    "id": 7,
    "canbus": "canivore"
  },
  "angle": {
    "type": "talonfx",
    "id": 8,
    "canbus": "canivore"
  },
  "encoder": {
    "type": "cancoder",
    "id": 12,
    "canbus": "canivore"
  },
  "inverted": {
    "drive": false,
    "angle": false
  },
  "absoluteEncoderOffset": 6.504,
  "location": {
    "front": -12,
    "left": 12
  }
}

Example - Back-Right Module (backright.json):

backright.json
{
  "drive": {
    "type": "sparkmax_neo",
    "id": 5,
    "canbus": null
  },
  "angle": {
    "type": "sparkmax_neo",
    "id": 6,
    "canbus": null
  },
  "encoder": {
    "type": "cancoder",
    "id": 11,
    "canbus": null
  },
  "inverted": {
    "drive": false,
    "angle": false
  },
  "absoluteEncoderOffset": -18.281,
  "location": {
    "front": -12,
    "left": -12
  }
}
backright.json
{
  "drive": {
    "type": "talonfx",
    "id": 5,
    "canbus": "canivore"
  },
  "angle": {
    "type": "talonfx",
    "id": 6,
    "canbus": "canivore"
  },
  "encoder": {
    "type": "cancoder",
    "id": 11,
    "canbus": "canivore"
  },
  "inverted": {
    "drive": false,
    "angle": false
  },
  "absoluteEncoderOffset": -18.281,
  "location": {
    "front": -12,
    "left": -12
  }
}

physicalproperties.json - Physical Robot Parameters#

This file defines the physical characteristics of your robot and swerve modules that affect calculations.

Key Properties

  • optimalVoltage: Battery voltage for calculations (usually 12.0V)
  • conversionFactors.drive.diameter: Diameter of drive wheels in inches
  • conversionFactors.drive.gearRatio: Gear ratio from motor to wheel (motor rotations per wheel rotation)
  • conversionFactors.angle.gearRatio: Gear ratio from motor to module rotation (motor rotations per 360° module turn)
  • currentLimit: Maximum current for each motor in amps (protects from stalling)
  • rampRate: How quickly motors accelerate (0.0-1.0; lower = slower acceleration)

Complete physicalproperties.json Example:

physicalproperties.json - Complete Example from swerve/neo
{
  "conversionFactors": {
    "angle": {
      "gearRatio": 12.8,
      "factor": 0
    },
    "drive": {
      "gearRatio": 8.14,
      "diameter": 4,
      "factor": 0
    }
  },
  "currentLimit": {
    "drive": 40,
    "angle": 20
  },
  "rampRate": {
    "drive": 0.25,
    "angle": 0.25
  },
  "wheelGripCoefficientOfFriction": 1.19,
  "optimalVoltage": 12
}
physicalproperties.json - Complete Example from swerve/talonfx
{
  "conversionFactors": {
    "angle": {
      "gearRatio": 12.8,
      "factor": 0
    },
    "drive": {
      "gearRatio": 6.75,
      "diameter": 4,
      "factor": 0
    }
  },
  "currentLimit": {
    "drive": 60,
    "angle": 20
  },
  "rampRate": {
    "drive": 0.25,
    "angle": 0.25
  },
  "wheelGripCoefficientOfFriction": 1.19,
  "optimalVoltage": 12
}

Current limits differ by hardware

SparkMax uses a single smart current limit per motor. TalonFX has more current-limit options (supply vs. stator current) but YAGSL's currentLimit field maps to a sane default for either — Kraken X60 drive motors can typically handle a higher limit (60A above) than a NEO (40A) before you risk browning out.

pidfproperties.json - Motor Control Tuning#

This file contains PIDF (Proportional, Integral, Derivative, Feedforward) tuning values for both drive and angle motors.

Key Properties

  • drive: PIDF values for drive motors (translation)
  • p: Proportional gain
  • i: Integral gain
  • d: Derivative gain
  • f: Feedforward gain
  • iz: Integral zone (error threshold for integral accumulation)
  • angle: PIDF values for angle motors (steering)

These gains are not portable between vendors

SparkMax and TalonFX use different internal units and closed-loop scaling, so a PIDF value tuned for one will not behave the same on the other. Always start from the vendor-appropriate values below and re-tune for your own robot — see section 7.

Complete pidfproperties.json Example:

pidfproperties.json - Complete Example from swerve/neo
{
  "drive": {
    "p": 0.00023,
    "i": 0.0000002,
    "d": 1,
    "f": 0,
    "iz": 0
  },
  "angle": {
    "p": 0.01,
    "i": 0,
    "d": 0,
    "f": 0,
    "iz": 0
  }
}
pidfproperties.json - Complete Example from swerve/talonfx
{
  "drive": {
    "p": 1,
    "i": 0,
    "d": 0,
    "f": 0,
    "iz": 0
  },
  "angle": {
    "p": 50,
    "i": 0,
    "d": 0.32,
    "f": 0,
    "iz": 0
  }
}

Complete swervedrive.json Example:

swervedrive.json - Complete Example from swerve/neo
{
  "imu": {
    "type": "pigeon2",
    "id": 13,
    "canbus": "canivore"
  },
  "invertedIMU": true,
  "modules": [
    "frontleft.json",
    "frontright.json",
    "backleft.json",
    "backright.json"
  ]
}
swervedrive.json - Complete Example from swerve/talonfx
{
  "imu": {
    "type": "pigeon2",
    "id": 13,
    "canbus": "canivore"
  },
  "invertedIMU": true,
  "modules": [
    "frontleft.json",
    "frontright.json",
    "backleft.json",
    "backright.json"
  ]
}

controllerproperties.json - Advanced Control Settings#

This file configures advanced control parameters for heading correction (usually left at defaults). It has just two fields, and — unlike the module/physical/PIDF files above — it does not vary by motor vendor, since it configures the drivetrain-level heading controller rather than any individual motor.

Key Properties

  • angleJoystickRadiusDeadband: Minimum radius of the angle-control joystick input before a heading adjustment is applied
  • heading: PID values for heading correction (used when driving with a target heading, e.g. driveCommand(x, y, headingX, headingY))
  • p: Proportional gain for heading control
  • i: Integral gain
  • d: Derivative gain

Controllerproperties.json Example:

controllerproperties.json - Complete Example from swerve/neo
{
  "angleJoystickRadiusDeadband": 0.5,
  "heading": {
    "p": 0.4,
    "i": 0,
    "d": 0.01
  }
}

Using the Configuration Tool#

YAGSL provides an online configuration generator: YAGSL Config Tool

  1. Input your robot's physical parameters
  2. Select hardware types and IDs for each module — choose SparkMax (NEO/NEO 550/Vortex) or TalonFX (Falcon 500/Kraken X60) as appropriate per motor, and your absolute encoder type
  3. Download the generated configuration files
  4. Place them in src/main/deploy/swerve/

For manual configuration details, see Configuration Documentation.

5. Code Setup and Integration#

Importing YAGSL#

Add YAGSL as a vendor dependency (see section 2), then import in your code:

SwerveSubsystem.java - YAGSL Imports
import static edu.wpi.first.units.Units.Meter;

import edu.wpi.first.math.controller.SimpleMotorFeedforward;
import edu.wpi.first.math.geometry.Pose2d;
import edu.wpi.first.math.geometry.Rotation2d;
import edu.wpi.first.math.geometry.Translation2d;
import edu.wpi.first.math.kinematics.ChassisSpeeds;
import edu.wpi.first.math.kinematics.SwerveDriveKinematics;
import edu.wpi.first.math.trajectory.Trajectory;
import edu.wpi.first.wpilibj.DriverStation;
import edu.wpi.first.wpilibj.DriverStation.Alliance;
import edu.wpi.first.wpilibj2.command.Command;
import edu.wpi.first.wpilibj2.command.SubsystemBase;
import edu.wpi.first.wpilibj2.command.sysid.SysIdRoutine.Config;
import frc.robot.Constants;
import java.io.File;
import java.util.Arrays;
import java.util.function.DoubleSupplier;
import java.util.function.Supplier;
import swervelib.SwerveController;
import swervelib.SwerveDrive;
import swervelib.SwerveDriveTest;
import swervelib.math.SwerveMath;
import swervelib.parser.SwerveControllerConfiguration;
import swervelib.parser.SwerveDriveConfiguration;
import swervelib.parser.SwerveParser;
import swervelib.telemetry.SwerveDriveTelemetry;
import swervelib.telemetry.SwerveDriveTelemetry.TelemetryVerbosity;

Creating the SwerveDrive Object#

In your subsystem constructor, initialize the swerve drive from your JSON configuration files:

SwerveSubsystem.java - Constructor
   public SwerveSubsystem(File directory)
  {
    boolean blueAlliance = DriverStation.getAlliance().isPresent() && DriverStation.getAlliance().get() == Alliance.Blue;
    Pose2d startingPose = blueAlliance ? new Pose2d(new Translation2d(Meter.of(1),
                                                                      Meter.of(4)),
                                                    Rotation2d.fromDegrees(0))
                                       : new Pose2d(new Translation2d(Meter.of(16),
                                                                      Meter.of(4)),
                                                    Rotation2d.fromDegrees(180));
    // Configure the Telemetry before creating the SwerveDrive to avoid unnecessary objects being created.
    SwerveDriveTelemetry.verbosity = TelemetryVerbosity.HIGH;
    try
    {
      swerveDrive = new SwerveParser(directory).createSwerveDrive(Constants.MAX_SPEED, startingPose);
      // Alternative method if you don't want to supply the conversion factor via JSON files.
      // swerveDrive = new SwerveParser(directory).createSwerveDrive(maximumSpeed, angleConversionFactor, driveConversionFactor);
    } catch (Exception e)
    {
      throw new RuntimeException(e);
    }
    swerveDrive.setHeadingCorrection(false); // Heading correction should only be used while controlling the robot via angle.
    swerveDrive.setCosineCompensator(false);//!SwerveDriveTelemetry.isSimulation); // Disables cosine compensation for simulations since it causes discrepancies not seen in real life.
    swerveDrive.setAngularVelocityCompensation(true,
                                               true,
                                               0.1); //Correct for skew that gets worse as angular velocity increases. Start with a coefficient of 0.1.
    swerveDrive.setModuleEncoderAutoSynchronize(false,
                                                1); // Enable if you want to resynchronize your absolute encoders and motor encoders periodically when they are not moving.
    // swerveDrive.pushOffsetsToEncoders(); // Set the absolute encoder to be used over the internal encoder and push the offsets onto it. Throws warning if not possible
  }

This is instantiated in RobotContainer by pointing a SwerveSubsystem at the deploy folder that matches your hardware — this deploy-folder name is the only line of Java that differs between the REVLib and TalonFX paths; every other class in this tutorial is identical either way:

private final SwerveSubsystem drivebase = new SwerveSubsystem(
    new File(Filesystem.getDeployDirectory(), "swerve/neo"));
private final SwerveSubsystem drivebase = new SwerveSubsystem(
    new File(Filesystem.getDeployDirectory(), "swerve/talonfx"));

Telemetry Setup#

YAGSL provides extensive telemetry for debugging. Configure verbosity before creating the SwerveDrive:

Telemetry Configuration
// Configure telemetry verbosity before creating SwerveDrive
SwerveDriveTelemetry.verbosity = TelemetryVerbosity.HIGH; // Options: NONE, LOW, HIGH

This adds NetworkTables entries under /SwerveDrive/ for monitoring module states, IMU data, and odometry.

Telemetry Levels

  • NONE: No telemetry output
  • LOW: Basic telemetry (odometry position)
  • HIGH: Comprehensive telemetry (module states, IMU data, velocities, raw encoder readings)

For more code setup details, see Code Setup Documentation.

6. Basic Driving Code Examples#

Field-Oriented Drive Command#

Tip

Field-oriented drive means the robot moves relative to the field coordinate system, not its own orientation. Forward on the joystick always moves the robot toward the same direction on the field (e.g., toward the opponent's goal), regardless of how the robot is currently rotated. This is the most intuitive and commonly used drive mode for FRC competition robots.

SwerveSubsystem.java - Field-Oriented Drive Command
  /**
   * Command to drive the robot using translative values and heading as angular velocity.
   *
   * @param translationX     Translation in the X direction. Cubed for smoother controls.
   * @param translationY     Translation in the Y direction. Cubed for smoother controls.
   * @param angularRotationX Angular velocity of the robot to set. Cubed for smoother controls.
   * @return Drive command.
   */
  public Command driveCommand(DoubleSupplier translationX, DoubleSupplier translationY, DoubleSupplier angularRotationX)
  {
    return run(() -> {
      // Make the robot move
      swerveDrive.drive(SwerveMath.scaleTranslation(new Translation2d(
                            translationX.getAsDouble() * swerveDrive.getMaximumChassisVelocity(),
                            translationY.getAsDouble() * swerveDrive.getMaximumChassisVelocity()), 0.8),
                        Math.pow(angularRotationX.getAsDouble(), 3) * swerveDrive.getMaximumChassisAngularVelocity(),
                        true,
                        false);
    });
  }

Input Scaling

  • SwerveMath.scaleTranslation() applies a scaling factor (0.8) to smooth translation
  • Math.pow(..., 3) cubes the rotation input for smoother rotation control
  • Joystick values are multiplied by maximum chassis velocity to convert from [-1, 1] to actual m/s

Robot-Oriented Drive#

Tip

Robot-oriented drive means the robot moves relative to its own orientation. Forward on the joystick always moves the robot in the direction it's currently facing. This mode is useful for precise movements or when field orientation isn't important, but can be confusing for drivers during competition.

SwerveSubsystem.java - Robot-Oriented Drive
  /**
   * The primary method for controlling the drivebase.  Takes a {@link Translation2d} and a rotation rate, and
   * calculates and commands module states accordingly.  Can use either open-loop or closed-loop velocity control for
   * the wheel velocities.  Also has field- and robot-relative modes, which affect how the translation vector is used.
   *
   * @param translation   {@link Translation2d} that is the commanded linear velocity of the robot, in meters per
   *                      second. In robot-relative mode, positive x is torwards the bow (front) and positive y is
   *                      torwards port (left).  In field-relative mode, positive x is away from the alliance wall
   *                      (field North) and positive y is torwards the left wall when looking through the driver station
   *                      glass (field West).
   * @param rotation      Robot angular rate, in radians per second. CCW positive.  Unaffected by field/robot
   *                      relativity.
   * @param fieldRelative Drive mode.  True for field-relative, false for robot-relative.
   */
  public void drive(Translation2d translation, double rotation, boolean fieldRelative)
  {
    swerveDrive.drive(translation,
                      rotation,
                      fieldRelative,
                      false); // Open loop is disabled since it shouldn't be used most of the time.
  }

ChassisSpeeds Drive#

Tip

ChassisSpeeds drive accepts a WPILib ChassisSpeeds object, which represents the desired velocity of the robot chassis. This is useful when integrating with path planning libraries like PathPlanner or when you have calculated velocities from other sources. It provides the most control over robot motion.

SwerveSubsystem.java - driveFieldOriented (void)
  /**
   * Drive the robot given a chassis field oriented velocity.
   *
   * @param velocity Velocity according to the field.
   */
  public void driveFieldOriented(ChassisSpeeds velocity)
  {
    swerveDrive.driveFieldOriented(velocity);
  }
SwerveSubsystem.java - driveFieldOriented (Command)
  /**
   * Drive the robot given a chassis field oriented velocity.
   *
   * @param velocity Velocity according to the field.
   */
  public Command driveFieldOriented(Supplier<ChassisSpeeds> velocity)
  {
    return run(() -> {
      swerveDrive.driveFieldOriented(velocity.get());
    });
  }
SwerveSubsystem.java - drive (ChassisSpeeds)
  /**
   * Drive according to the chassis robot oriented velocity.
   *
   * @param velocity Robot oriented {@link ChassisSpeeds}
   */
  public void drive(ChassisSpeeds velocity)
  {
    swerveDrive.drive(velocity);
  }

Joystick Integration#

Tip

SwerveInputStream chains joystick reads, deadband filtering, scaling, and alliance-relative control into a single reusable supplier of ChassisSpeeds. Pass it directly to driveFieldOriented() as the subsystem's default command. Axes are negated because standard joysticks return negative Y when pushed forward.

RobotContainer.java - SwerveInputStream
  SwerveInputStream driveAngularVelocity = SwerveInputStream.of(drivebase.getSwerveDrive(),
                                                                () -> driverXbox.getLeftY() * -1,
                                                                () -> driverXbox.getLeftX() * -1)
                                                            .withControllerRotationAxis(driverXbox::getRightX)
                                                            .deadband(OperatorConstants.DEADBAND)
                                                            .scaleTranslation(0.8)
                                                            .allianceRelativeControl(true);
RobotContainer.java - Binding to Default Command
  private void configureBindings()
  {
    Command driveFieldOrientedAnglularVelocity = drivebase.driveFieldOriented(driveAngularVelocity);
      drivebase.setDefaultCommand(driveFieldOrientedAnglularVelocity);


  }

Why are axes inverted?

Standard game controller joysticks return negative values when pushed forward (Y-axis inverted convention). Multiplying by -1 corrects this so that pushing forward on the joystick actually moves the robot forward.

Odometry and Pose Reset#

Tip

Odometry tracks the robot's position and orientation on the field using wheel encoders and IMU data. Pose reset is useful for correcting odometry drift, often done at the start of autonomous or when vision systems provide accurate position data.

SwerveSubsystem.java - getPose
  /**
   * Gets the current pose (position and rotation) of the robot, as reported by odometry.
   *
   * @return The robot's pose
   */
  public Pose2d getPose()
  {
    return swerveDrive.getPose();
  }
SwerveSubsystem.java - resetOdometry
  /**
   * Resets odometry to the given pose. Gyro angle and module positions do not need to be reset when calling this
   * method.  However, if either gyro angle or module position is reset, this must be called in order for odometry to
   * keep working.
   *
   * @param initialHolonomicPose The pose to set the odometry to
   */
  public void resetOdometry(Pose2d initialHolonomicPose)
  {
    swerveDrive.resetOdometry(initialHolonomicPose);
  }
SwerveSubsystem.java - zeroGyro
  /**
   * Resets the gyro angle to zero and resets odometry to the same position, but facing toward 0.
   */
  public void zeroGyro()
  {
    swerveDrive.zeroGyro();
  }
SwerveSubsystem.java - zeroGyroWithAlliance
  /**
   * This will zero (calibrate) the robot to assume the current position is facing forward
   * <p>
   * If red alliance rotate the robot 180 after the drviebase zero command
   */
  public void zeroGyroWithAlliance()
  {
    if (isRedAlliance())
    {
      zeroGyro();
      //Set the pose 180 degrees
      resetOdometry(new Pose2d(getPose().getTranslation(), Rotation2d.fromDegrees(180)));
    } else
    {
      zeroGyro();
    }
  }
SwerveSubsystem.java - getHeading
  /**
   * Gets the current yaw angle of the robot, as reported by the swerve pose estimator in the underlying drivebase.
   * Note, this is not the raw gyro reading, this may be corrected from calls to resetOdometry().
   *
   * @return The yaw angle
   */
  public Rotation2d getHeading()
  {
    return getPose().getRotation();
  }

For more examples, see the YAGSL Examples Repository.

7. Tuning and Debugging#

PIDF Tuning#

YAGSL uses PIDF controllers for both drive and angle motors. Start with these values, then tune from there:

pidfproperties.json - SparkMax Starting Point
{
  "drive": {
    "p": 0.00023,
    "i": 0.0000002,
    "d": 1,
    "f": 0,
    "iz": 0
  },
  "angle": {
    "p": 0.01,
    "i": 0,
    "d": 0,
    "f": 0,
    "iz": 0
  }
}
pidfproperties.json - TalonFX Starting Point
{
  "drive": {
    "p": 1,
    "i": 0,
    "d": 0,
    "f": 0,
    "iz": 0
  },
  "angle": {
    "p": 50,
    "i": 0,
    "d": 0.32,
    "f": 0,
    "iz": 0
  }
}

Tuning process: 1. Set P, I, D, F to 0 2. Increase P until oscillation occurs 3. Increase D to reduce jitter 4. Fine-tune as needed

For detailed tuning guides, see WPILib PID Tuning and YAGSL PIDF Tuning.

The Eight Steps for Inversion#

If your swerve drive spins out of control or has incorrect field orientation, use these systematic steps to fix inversion issues:

  1. Set invertIMU to false in swervedrive.json and all drive motor inverted to false in module JSONs
  2. Set invertIMU to true
  3. Invert all drive motors ("drive": {"inverted": true})
  4. Set invertIMU to false
  5. Flip module locations (swap front/back or left/right in configuration)
  6. Uninvert drive motors ("drive": {"inverted": false})
  7. Set invertIMU to true
  8. Invert drive motors again ("drive": {"inverted": true})

Test after each step. Most robots work after step 1, 3, or 7.

For complete details, see When to Invert and The Eight Steps.

Common Issues#

  • Modules not facing correct direction: Check absolute encoder offsets (remember: degrees, not rotations)
  • Robot drifting in odometry: Verify IMU orientation and module locations
  • Gears grinding: PID tuning issue, not inversion
  • Inconsistent behavior: Ensure all modules have same hardware configuration
  • TalonFX-specific: double-check the CAN bus name ("rio" vs. your CANivore's name) matches on every device — a mismatched canbus field is a common first-time TalonFX/CANcoder/Pigeon2 setup mistake

Additional Resources#

This tutorial covers the essentials for getting started with YAGSL. For advanced features like vision integration or custom control algorithms, explore the examples and documentation further. Remember to test thoroughly on a test bench before field use!


Knowledge Check#

Quiz results are saved to your browser's local storage and will persist between sessions.

#

What does "holonomic" mean for a drivetrain?

#

A team switches their swerve robot from SparkMax/NEO to TalonFX/Kraken motors. According to this tutorial, what Java code needs to change in SwerveSubsystem.java or RobotContainer.java?

#

What unit is absoluteEncoderOffset measured in in a YAGSL module JSON file?

Quiz Progress

0 / 0 questions answered (0%)

0 correct