The flat Robot.java works, but it does not scale. The moment you add a second mechanism you will be fighting tangled state. WPILib strongly recommends command-based for new teams. Let us refactor.
A command-based project has four root files: Robot (mostly empty), RobotContainer (declares subsystems + bindings), Constants, and Main (do not touch). Subsystems live in subsystems/, commands in commands/.
DriveSubsystem.java:
package frc.robot.subsystems;
import com.revrobotics.spark.SparkMax;
import com.revrobotics.spark.SparkLowLevel.MotorType;
import edu.wpi.first.wpilibj.drive.DifferentialDrive;
import edu.wpi.first.wpilibj2.command.SubsystemBase;
public class DriveSubsystem extends SubsystemBase {
private final SparkMax m_left = new SparkMax(1, MotorType.kBrushless);
private final SparkMax m_right = new SparkMax(2, MotorType.kBrushless);
private final DifferentialDrive m_drive =
new DifferentialDrive(m_left::set, m_right::set);
public void arcade(double fwd, double rot) {
m_drive.arcadeDrive(fwd, rot);
}
}
Note the CAN IDs 1 and 2 match the IDs you persisted in Project 1. We now use the CAN SparkMax class (from the REVLib 2025+ com.revrobotics.spark package) instead of PWM, and pass its ::set method to DifferentialDrive.
RobotContainer.java wires a default command so the drivetrain is always listening to the sticks:
public class RobotContainer {
private final DriveSubsystem m_drive = new DriveSubsystem();
private final CommandXboxController m_ctrl =
new CommandXboxController(0);
public RobotContainer() { configureBindings(); }
private void configureBindings() {
m_drive.setDefaultCommand(
m_drive.run(() ->
m_drive.arcade(-m_ctrl.getLeftY(), -m_ctrl.getRightX())));
// hold B for a quick 'spin in place' demo
m_ctrl.b().whileTrue(m_drive.run(() -> m_drive.arcade(0, 0.5)));
}
}
Robot.java just runs the scheduler:
@Override public void robotPeriodic() {
CommandScheduler.getInstance().run();
}
Why this matters: the CommandScheduler runs each loop (the default TimedRobot period is 20ms / 50Hz) and, each loop, polls triggers, schedules new commands, runs scheduled commands, removes finished ones, then runs subsystem periodic() methods. The single most common command-based bug is forgetting CommandScheduler.getInstance().run() in robotPeriodic() — without it, nothing happens and the robot looks dead even though code is deployed. The setDefaultCommand pattern guarantees the drivetrain always has something to do when no other command requires it.
Key takeaways
- Command-based splits code into Subsystems, Commands, and bindings in RobotContainer — it scales where flat TimedRobot does not
- Forgetting CommandScheduler.getInstance().run() in robotPeriodic() makes the whole robot look dead
- setDefaultCommand keeps a subsystem (like drive) responsive whenever no other command requires it
Keep going
Take the quiz · +10 XP with an accountMore in Worked Examples & Mini-Projects
Sources & corrections
This lesson is AI-assisted: drafted from primary sources, then reviewed and edited by hand. Errors still get through. When one is reported we fix it and write down what changed — publicly, in the corrections log.
Sources and further reading
- Structuring a Command-Based Robot Projectdocs.wpilib.org
- What Is Command-Based Programming?docs.wpilib.org
- Migrating to REVLib 2025 (REV)docs.revrobotics.com
Read next
Articles on this, for competition day
The lesson covers how it works. These cover what to do when it breaks.
- 9 min read
FRC Command-Based Programming: Subsystems, Commands, and the Scheduler
A beginner-friendly guide to WPILib command-based programming in Java: subsystems, commands, the CommandScheduler, triggers, and composing commands.
Read - 13 min read
FRC Code Structure Best Practices: Command-Based Project Architecture
How to structure an FRC command-based robot project the right way — subsystems, commands, RobotContainer, and Constants — verified against official WPILib docs.
Read
Lesson quiz
RequiredAll 3 right completes the lesson. Miss one and only that question comes back — anything you already answered correctly stays banked.
0 of 3 answered
01.When refactoring the drivetrain into a command-based subsystem, which base class does DriveSubsystem extend?
02.Why is a default command set on the drive subsystem with setDefaultCommand?
03.What happens if you forget to call CommandScheduler.getInstance().run() in robotPeriodic()?
Answer every question to submit.
All 28 lessons in Getting Started with FRC
- Not started:Project 1 — Make a NEO Spin with the REV Hardware Client
- Not started:Project 2 — Deploy a Real Arcade-Drive Program
- Not started:Project 3 — Refactor into a Command-Based Drive Subsystem
- Not started:Project 4 — Build a Fuel Launcher for REBUILT
- Not started:Project 5 — A One-Button Autonomous Routine
- Not started:The Connection Chain: When the Driver Station Won't Connect
- Not started:Brownouts: Why the Robot Goes Limp Mid-Match
- Not started:CAN Bus Gremlins: Missing and Conflicting Devices
- Not started:Software Gotchas: Inverted Drives, Scheduler Stalls, and Reading the RioLog
- Not started:Inspection-Day Failures: Bumpers, Size, and Weight
- Not started:Closed-Loop Control: PID + Feedforward for a Consistent Shot
- Not started:Swerve Drive: Omnidirectional Movement with YAGSL
- Not started:AprilTag Vision: Knowing Where You Are with PhotonVision
- Not started:Data-Driven Strategy: Scouting, EPA/OPR, and Alliance Selection
- Not started:Choosing Your Hardware Ecosystem: REV vs CTRE