Unit 7: Advanced FRC Coding
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?