-
Notifications
You must be signed in to change notification settings - Fork 1
Lesson 6: Drivetrain
Understand the current drivetrain architecture well enough to trace driver input, assisted drive modes, autonomous path requests, CTRE swerve requests, simulation, and drivetrain logs.
Drivetrain code is different from a simple motor subsystem. It mixes driver feel, field coordinates, robot pose, vendor swerve code, autonomous path following, simulation, and hardware configuration. The goal of this lesson is not to memorize every CTRE class. The goal is to learn the layers and know where to inspect behavior before editing.
- Finish Lessons 1-3.
- Read the software overview, especially the Driver Station, roboRIO, CAN, NetworkTables, and logging sections.
- Understand the basic Lesson 3 pattern: high-level robot behavior should not directly own every hardware detail.
- Have
971-second-robot-2026cloned locally.
1-2 hours
By the end of this lesson, you should be able to:
- Explain the role of the drivetrain in the robot.
- Trace manual drive from controller input to CTRE
SwerveRequest. - Explain the responsibilities of
Drive,Manual,ThetaLock,AutoAlign, andCommandSwerveDrivetrain. - Understand how autonomous path following writes drivetrain requests.
- Identify where field-relative pose, heading, module state, and simulation behavior are handled.
- Know what to inspect before changing drive constants or generated swerve configuration.
Keep these files open while reading:
- Robot.java
- RobotContainer.java
- Drive.java
- Manual.java
- ThetaLock.java
- AutoAlign.java
- CommandSwerveDrivetrain.java
- TunerConstants.java
- Controllers.java
- Autos.java
The drivetrain is the system that moves the robot chassis. On this robot, the drivetrain is a swerve drive.
A swerve drive has four modules. Each module has:
- a drive motor for wheel speed,
- a steer motor for wheel angle,
- an encoder for module angle.
The driver thinks in chassis motion:
drive forward/backward
drive left/right
rotate clockwise/counterclockwise
The CTRE swerve library turns those chassis requests into module states:
front-left wheel speed and angle
front-right wheel speed and angle
back-left wheel speed and angle
back-right wheel speed and angle
Our code is responsible for choosing the right request, shaping inputs, selecting assisted modes, handling autonomous ownership, and logging enough data to debug behavior.
The drivetrain code is split into clear layers:
Robot
owns robot lifecycle and mode transitions
RobotContainer
constructs drivetrain objects and calls periodic methods
Drive
selects the active drive mode
Manual
turns driver sticks into field-centric swerve requests
ThetaLock
combines driver translation with a controlled heading
AutoAlign
drives toward a target field pose
Autos
sends path-following speeds into the drivetrain request path
CommandSwerveDrivetrain
stores and applies the active CTRE SwerveRequest
TunerConstants
stores generated swerve hardware configuration
The important design choice is that RobotContainer does not own drive behavior. It wires the objects together. Drive behavior lives in the drive package.
Here is the normal teleop drive path:
flowchart TD
A[Driver moves controller sticks] --> B[Controllers.TROY axes and triggers]
B --> C[Robot.robotPeriodic]
C --> D[RobotContainer.periodic]
D --> E[Drive.periodic]
E --> F{Drive.Mode}
F -->|MANUAL| G[Manual.periodic]
F -->|THETA_LOCK| H[ThetaLock.periodic]
F -->|AUTO_ALIGN| I[AutoAlign.periodic]
F -->|NONE| J[SwerveDriveBrake or Idle request]
G --> K[drivetrain.setRequest]
H --> K
I --> K
J --> K
K --> L[CommandSwerveDrivetrain.periodic]
L --> M[setControl request]
M --> N[CTRE swerve calculates module targets]
N --> O[TalonFX / CANcoder / Pigeon hardware]
O --> P[logged pose, speeds, module states, currents]
The key idea is request ownership. A drive mode creates a SwerveRequest and writes it with:
drivetrain.setRequest(curRequest);Then CommandSwerveDrivetrain.periodic() applies the stored request:
setControl(request);That means you should ask two questions when debugging:
- Who wrote the current request?
- What request did
CommandSwerveDrivetrainapply?
Open Controllers.java.
The driver controller is:
public static final CommandXboxController TROY = new CommandXboxController(0);Manual drivetrain code reads:
Controllers.TROY.getLeftY()
Controllers.TROY.getLeftX()
Controllers.TROY.getRightX()Those are raw axis values from the Driver Station. They are not yet velocities. They are unitless stick inputs.
Drive mode can also depend on triggers:
public static final Trigger SHOOTING = SHOOT_REDUNDANCY.or(SHOOT);
public static final Trigger SHUTTLING = LEFT_SHUTTLE.or(RIGHT_SHUTTLE).and(SHOOTING.negate());Those triggers affect the manual drive scaling.
Open Robot.java.
The robot lifecycle sets high-level drivetrain mode:
public void disabledInit() {
robotContainer.drivetrainController.setDriveMode(Drive.Mode.NONE);
}public void autonomousInit() {
robotContainer.drivetrainController.setDriveMode(Drive.Mode.NONE);
}public void teleopInit() {
robotContainer.drivetrainController.setDriveMode(Drive.Mode.MANUAL);
}This matters because manual drive should not fight autonomous path following. During autonomous, the drive controller is set to NONE, and the autonomous command owns the drivetrain request.
Open RobotContainer.java.
The drivetrain objects are constructed here:
public final Drive drivetrainController;
public final CommandSwerveDrivetrain drivetrain = TunerConstants.createDrivetrain();Then:
drivetrainController = new Drive(drivetrain);The periodic order is:
public void periodic() {
superstructure.periodic();
drivetrainController.periodic();
drivetrain.periodic();
}Read that order as:
superstructure updates mechanism goals
-> drive controller chooses the active drivetrain request
-> CTRE drivetrain applies the request and records measured state
If a drive mode sets a request after drivetrain.periodic() has already run, that request would not be applied until the next cycle. Periodic order is part of behavior.
Open Drive.java.
The active mode is:
public enum Mode {
NONE,
MANUAL,
THETA_LOCK,
AUTO_ALIGN;
}Every cycle, Drive.periodic() clears child goals:
manual.setGoal(Manual.Goal.NONE);
thetaLock.setGoal(ThetaLock.Goal.NONE);
autoAlign.setGoal(AutoAlign.Goal.NONE);Then it activates the child that matches the current mode:
switch (mode) {
case MANUAL -> manual.setGoal(Manual.Goal.ACTIVE);
case AUTO_ALIGN -> autoAlign.setGoal(AutoAlign.Goal.ALIGN);
case THETA_LOCK -> thetaLock.setGoal(ThetaLock.Goal.ACTIVE);
default -> {}
}Then each child runs:
manual.periodic();
thetaLock.periodic();
autoAlign.periodic();This pattern makes mode ownership explicit. Only the active child should write the drivetrain request.
Open Manual.java.
Manual drive turns raw stick inputs into a SwerveRequest.FieldCentric.
The shaping chain is:
JOYSTICK_VALUES
.setValues(
Controllers.TROY.getLeftY(),
Controllers.TROY.getLeftX(),
Controllers.TROY.getRightX())
.exponentialCurve(TRANSLATION_EXP_CURVE, ROTATION_EXP_CURVE)
.scale(-MAX_SPEED, -MAX_ANGULAR_RATE)
.slewRateLimit(X_LIMITER, Y_LIMITER, ROT_LIMITER);That does four things:
- Reads raw axes.
- Applies an exponential curve for finer low-speed control.
- Scales unitless input into meters per second and radians per second.
- Applies slew-rate limiting.
Manual drive chooses between normal, shooting, and shuttling requests:
if (Controllers.SHUTTLING.getAsBoolean()) {
curRequest = shuttlingDrive.withVelocityX(...);
} else if (Controllers.SHOOTING.getAsBoolean()) {
curRequest = shootingDrive.withVelocityX(...);
} else {
curRequest = drive.withVelocityX(...);
}Finally, it writes the selected request:
drivetrain.setRequest(curRequest);This is driver-feel code. If the robot feels too fast, too slow, too sensitive near zero, or too jerky, inspect Manual before editing hardware constants.
Open ThetaLock.java.
Theta lock still uses manual translation:
.withVelocityX(manual.getValues().getX())
.withVelocityY(manual.getValues().getY())But it uses FieldCentricFacingAngle to control heading:
.withTargetDirection(desiredRotation.get())So theta lock is not a separate drivetrain. It is a different request mode:
driver controls X/Y translation
code controls robot heading
When debugging theta lock, inspect:
-
Drive.Mode, -
ThetaLock.Goal, - target angle,
- current angle,
- rotation error,
- heading PID gains,
- manual X/Y values.
Open AutoAlign.java.
Auto-align reads the drivetrain pose:
Pose2d currentPose = drivetrain.getState().Pose;It chooses a target pose:
Pose2d targetPose = ...It computes translation error:
Translation2d translationError = targetTranslation.minus(currentTranslation);
double currentDistanceToTarget = translationError.getNorm();Then it creates a field-centric facing-angle request:
return driveAtAngle
.withVelocityX(translationOutput.getX())
.withVelocityY(translationOutput.getY())
.withForwardPerspective(ForwardPerspectiveValue.BlueAlliance)
.withTargetDirection(targetPose.getRotation());Auto-align depends on pose correctness. If the robot pose is wrong, auto-align will drive to the wrong place even if the PID code is correct.
Open CommandSwerveDrivetrain.java.
The stored request starts idle:
private SwerveRequest request = new SwerveRequest.Idle();Other code writes a request with:
public void setRequest(SwerveRequest request) {
this.request = request;
}Then periodic() applies it:
setControl(request);This is the boundary between team-owned drive mode logic and CTRE's drivetrain implementation.
CommandSwerveDrivetrain also owns:
- operator perspective handling,
- sim pose injection,
- drivetrain telemetry,
- module target and measured state logging,
- IMU logging,
- current logging,
- vision measurement forwarding,
- bump detection.
After setControl(request), CTRE's swerve library handles the low-level swerve calculations.
The CTRE layer uses constants from TunerConstants.java, including:
- drive motor IDs,
- steer motor IDs,
- CANcoder IDs,
- encoder offsets,
- module positions,
- wheel radius,
- gear ratios,
- Pigeon ID,
- drive and steer gains,
- inversion,
- current limits.
If one module behaves differently from the others, inspect TunerConstants and the logged target/measured module states before changing driver-control code.
Autonomous uses the same stored-request mechanism, but the request comes from path following instead of manual drive.
flowchart TD
A[autonomousInit] --> B[Drive mode set to NONE]
A --> C[autos.getAutonomousCommand]
C --> D[B-Line FollowPath command]
D --> E[Path follower computes ChassisSpeeds]
E --> F[ApplyRobotSpeeds request]
F --> G[drivetrain.setRequest]
G --> H[CommandSwerveDrivetrain.periodic]
H --> I[setControl request]
I --> J[CTRE swerve output]
Open Autos.java.
The path builder writes robot speeds into the drivetrain:
speeds -> drivetrain.setRequest(pathApplyRobotSpeeds.withSpeeds(speeds))Open Robot.java.
Autonomous disables manual drive ownership:
robotContainer.drivetrainController.setDriveMode(Drive.Mode.NONE);That prevents Manual.periodic() from writing over the autonomous path request.
If an auto does not drive, inspect:
- selected auto routine,
- whether the auto command is scheduled,
- whether
Drive.ModeisNONE, - whether the path follower calls
drivetrain.setRequest(...), - current pose and start pose,
- whether
CommandSwerveDrivetrain.periodic()is applying the request.
Simulation still runs through CommandSwerveDrivetrain.
When the drivetrain is constructed in sim:
if (Utils.isSimulation()) {
startSimThread();
}startSimThread() creates MapleSimSwerveDrivetrain and runs it periodically:
simNotifier = new Notifier(mapleSimSwerveDrivetrain::update);
simNotifier.startPeriodic(SIM_LOOP_PERIOD);Then CommandSwerveDrivetrain.periodic() pulls the simulated pose back into CTRE's drivetrain state:
Pose2d simPose = mapleSimSwerveDrivetrain.mapleSimDrive.getSimulatedDriveTrainPose();
super.resetPose(simPose);Simulation is useful for validating request flow, autonomous scheduling, field pose behavior, and logs. It does not prove real CAN IDs, encoder offsets, module inversion, wheel slip, or driver feel.
CommandSwerveDrivetrain.periodic() records the drivetrain signals that matter most:
Logger.recordOutput("Drive/Pose", getState().Pose);
Logger.recordOutput("Drive/TargetStates", getState().ModuleTargets);
Logger.recordOutput("Drive/MeasuredStates", getState().ModuleStates);
Logger.recordOutput("Drive/MeasuredSpeeds", getState().Speeds);It also logs IMU and current values.
Manual drive logs raw joystick values:
Logger.recordOutput("Drive/Manual/Joystick/X", Controllers.TROY.getLeftY());
Logger.recordOutput("Drive/Manual/Joystick/Y", Controllers.TROY.getLeftX());
Logger.recordOutput("Drive/Manual/Joystick/Rot", Controllers.TROY.getRightX());Theta lock logs heading state.
Auto-align logs target pose, current pose, translation output, angle to target, and alignment errors.
Use these logs before editing:
- speed scales,
- PID gains,
- module offsets,
- inversion,
- wheel radius,
- gear ratios.
Use this order:
- Driver Station mode and enable state.
-
Drive.Mode:NONE,MANUAL,THETA_LOCK, orAUTO_ALIGN. - Active child goal:
Manual.Goal,ThetaLock.Goal, orAutoAlign.Goal. - Who last called
drivetrain.setRequest(...). - Raw joystick values.
- Shaped manual values.
- Request type: field-centric, facing-angle, brake, idle, or apply-robot-speeds.
- Drivetrain pose.
- Target module states.
- Measured module states.
- IMU yaw, pitch, and roll.
- Drive current and battery voltage.
-
TunerConstantsonly after the behavior points to hardware configuration.
Robot does not move in teleop:
- Check that the robot is enabled.
- Check that
Drive.ModeisMANUAL. - Check joystick logs.
- Check that
Manual.GoalisACTIVE. - Check whether
Manualcallsdrivetrain.setRequest(...).
Robot moves too slowly:
- Check whether shooting or shuttling mode is active.
- Check
SHOOTING_SPEEDandSHUTTLING_SPEED. - Check slew limiters.
- Check battery voltage and current limits.
Robot rotates when it should hold angle:
- Check whether
ThetaLockorAutoAlignis active. - Check target angle and current angle.
- Check heading PID gains.
- Check gyro yaw and operator perspective.
Auto-align drives to the wrong place:
- Check current pose.
- Check target pose.
- Check alliance flipping.
- Check coordinate frames.
- Check whether vision or odometry is stale.
Autonomous path does not drive:
- Check selected auto.
- Check whether the autonomous command is scheduled.
- Check that
Drive.ModeisNONE. - Check whether the path follower writes
drivetrain.setRequest(...). - Check pose reset and start pose.
One module behaves differently:
- Compare target module state to measured module state.
- Check that module's drive motor ID, steer motor ID, encoder ID, encoder offset, and inversion.
- Check CAN wiring and device health.
The current drivetrain architecture separates responsibilities:
-
Robotowns mode transitions. -
RobotContainerwires objects and periodic order. -
Driveowns active drive mode. -
Manualowns driver input shaping. -
ThetaLockowns heading lock. -
AutoAlignowns target-pose alignment. -
Autosowns path-following requests. -
CommandSwerveDrivetrainowns CTRE request application and telemetry. -
TunerConstantsowns generated hardware constants.
That separation makes drivetrain code easier to extend. Assisted drive behavior can be added in the drive package without turning RobotContainer into a large drive-state file.
Work through the code in this order:
- Open
RobotContainer.java. - Find where
CommandSwerveDrivetrainandDriveare constructed. - Find the periodic order.
- Open
Drive.javaand explain howModeselects child goals. - Open
Manual.javaand trace raw joystick input into aSwerveRequest. - Find where shooting and shuttling change speed limits.
- Open
ThetaLock.javaand explain how it combines manual translation with controlled heading. - Open
AutoAlign.javaand identify current pose, target pose, translation output, and target direction. - Open
CommandSwerveDrivetrain.javaand findsetRequest(...)andsetControl(request). - Open
Autos.javaand find where path speeds become a drivetrain request. - Open
Robot.javaand identify how disabled, autonomous, and teleop choose drive modes.
When you are done, explain manual drive:
In teleop manual drive, controller input starts in ___.
Manual drive shapes it using ___.
The selected request is written through ___.
The CTRE drivetrain applies it in ___.
Logs for target and measured module states come from ___.
Then explain autonomous:
In autonomous, Drive.Mode is set to ___ so manual control does not fight path following.
The path command writes requests through ___.
The stored request is applied by ___.
Be ready to answer:
- What does
Drive.Modecontrol? - Why does
RobotContainernot build the manual drive request anymore? - What does
Manualown? - What does
ThetaLockadd on top of manual drive? - What does
AutoAlignneed from drivetrain pose? - What does
CommandSwerveDrivetrain.setRequest(...)do? - Where is the stored request applied to CTRE?
- How do autos write drivetrain requests?
- Which logs would you inspect for a drive issue?
- Which constants should not be edited casually?
Review autonomous code with this request flow in mind. Autos are not separate from drivetrain behavior. They write drivetrain requests through the same CommandSwerveDrivetrain request path, but they must own the request while manual driving is inactive.