The SwerveModule Class Lesson

The `SwerveModule` Class

The `SwerveModule` class is the heart of our swerve drive system. It's a self-contained, reusable class that represents one of the four identical swerve modules on our robot, bringing together hardware, state management, and control logic.

Purpose of the `SwerveModule` Class

This class is designed to be the single source of truth for one swerve module. It encapsulates all the complexity of controlling the drive and steering motors and reading from the sensors.

Key Responsibilities

  • Encapsulate Hardware: It creates and manages the motor controller and sensor objects for one module.
  • Provide a Control Interface: It has a primary method, `setDesiredState()`, that takes a target speed and angle from the main drivetrain subsystem.
  • Manage State: It provides getter methods like `getState()` and `getPosition()` so the drivetrain's odometry can know what the module is doing.
  • Handle Control Logic: It contains the PID controller for steering to a precise angle.

A Complete `SwerveModule` Class Example

This code demonstrates how a single `SwerveModule` class brings together all the pieces of hardware and software to control one of our swerve modules.

// Imports for all the WPILib and vendor libraries we need
import com.revrobotics.CANSparkMax;
import edu.wpi.first.math.controller.PIDController;
import edu.wpi.first.math.geometry.Rotation2d;
import edu.wpi.first.math.kinematics.SwerveModulePosition;
import edu.wpi.first.math.kinematics.SwerveModuleState;
import frc.robot.Constants.SwerveModuleConstants;

public class SwerveModule {
    // Hardware objects for a single module
    private final CANSparkMax driveMotor;
    private final CANSparkMax turningMotor;
    // ... other sensors like absolute encoder ...

    // PID controller for the turning motor
    private final PIDController turningPidController;

    // Constructor to set everything up
    public SwerveModule(int driveMotorId, int turningMotorId, ...) {
        driveMotor = new CANSparkMax(driveMotorId, ...);
        turningMotor = new CANSparkMax(turningMotorId, ...);
        
        turningPidController = new PIDController(
            SwerveModuleConstants.kPTurning, 
            SwerveModuleConstants.kITurning, 
            SwerveModuleConstants.kDTurning);
        
        // This is crucial for swerve: it tells the PID controller that the input is circular (0-360 degrees)
        turningPidController.enableContinuousInput(-Math.PI, Math.PI);
    }

    // Getter for the module's current state (speed and angle)
    public SwerveModuleState getState() {
        return new SwerveModuleState(getDriveVelocity(), getTurningRotation());
    }

    // Getter for the module's position (distance and angle)
    public SwerveModulePosition getPosition() {
        return new SwerveModulePosition(getDrivePosition(), getTurningRotation());
    }

    // This is the most important method!
    // It takes a target state and makes the module go there.
    public void setDesiredState(SwerveModuleState desiredState) {
        // Optimize the state to prevent unnecessary 180-degree turns
        SwerveModuleState optimizedState = SwerveModuleState.optimize(desiredState, getTurningRotation());

        // Command the drive motor to the target speed
        driveMotor.set(optimizedState.speedMetersPerSecond / SwerveModuleConstants.kMaxSpeedMetersPerSecond);

        // Use the PID controller to calculate the output for the turning motor
        double pidOutput = turningPidController.calculate(getTurningRotation().getRadians(), optimizedState.angle.getRadians());
        turningMotor.set(pidOutput);
    }
}
    

Test Your Knowledge

Question: What is the primary purpose of the `setDesiredState(SwerveModuleState desiredState)` method in the `SwerveModule` class?