Subsystems
An FRC robot is composed of several subsystems. Each subsystem controls one part of the robot, and they work together to perform complete actions. The 2026 game, REBUILT, is a shooting game, so many robots use subsystems such as:
-
A drive base that moves the robot.
-
An intake that collects game pieces.
-
An indexer that moves game pieces toward the shooter.
-
A shooter or turret that aims and launches game pieces.
-
A vision system that helps estimate the robot's position.
How the subsystems work together
RobotContainer
creates and connects everything
│
┌───────────────────┴───────────────────┐
│ │
Driver controls Autonomous routine
│ │
└──────────────► Commands ◄─────────────┘
│
tell subsystems what to do
│
┌──────────┬───────────┼──────────┬──────────┐
▼ ▼ ▼ ▼ ▼
Drive base Intake Indexer Shooter Vision
│ │ │ ▲ │
│ └──► ball ──┴─────────►│ │
│ │ │
└──────── moves the robot ◄──── aiming data ─┘
Each subsystem controls one part of the robot, but commands coordinate them to perform a complete action. For example, a shooting command can use vision to find the target, aim the shooter, run the indexer, and keep the drive base pointed in the correct direction.
Subsystems that do not share hardware can run at the same time. The command scheduler prevents two commands from controlling the same subsystem at once.
Writing a subsystem
A subsystem should own the hardware for one mechanism and provide small methods that commands can call. This example creates an intake subsystem that controls a Talon FX motor. If CAN IDs and buses are new to you, review How the Devices Communicate first.
package frc.robot.subsystems;
import edu.wpi.first.wpilibj2.command.SubsystemBase;
import com.ctre.phoenix6.hardware.TalonFX;
public class IntakeSubsystem extends SubsystemBase {
private static final int MOTOR_ID = 10;
private static final String CAN_BUS = "rio";
private static final double INTAKE_VOLTAGE = 6.0;
private static final double EJECT_VOLTAGE = -6.0;
private final TalonFX intakeMotor;
public IntakeSubsystem() {
intakeMotor = new TalonFX(MOTOR_ID, CAN_BUS);
}
public void intake() {
intakeMotor.setVoltage(INTAKE_VOLTAGE);
}
public void eject() {
intakeMotor.setVoltage(EJECT_VOLTAGE);
}
public void stop() {
intakeMotor.setVoltage(0);
}
}
IntakeSubsystem extends SubsystemBase, which allows WPILib's command scheduler to treat it as a subsystem. Its constructor creates the TalonFX using the specified CAN ID and CAN bus.
This class has two main responsibilities:
- It owns the hardware. Other classes should control the intake through this subsystem instead of accessing
intakeMotordirectly. - It provides actions while hiding hardware details. A command can call
intake(),eject(), orstop()without needing to know the motor's CAN settings or output values.
The constants INTAKE_VOLTAGE and EJECT_VOLTAGE determine how strongly and in which direction the motor runs. The opposite signs make the motor turn in opposite directions. Keeping those values outside the action methods makes them easy to tune without changing the subsystem's behavior.
Constructing the subsystem
Defining a subsystem class does not create the subsystem. You must construct it in RobotContainer.java:
private final IntakeSubsystem intake = new IntakeSubsystem();
This creates one IntakeSubsystem object when RobotContainer is created. That object initializes the intake hardware and is the shared instance used when you configure commands and controller buttons.
Construct each subsystem only once. Commands should receive the existing subsystem through their constructors instead of creating a new one. Otherwise, multiple objects could try to represent and control the same physical hardware.