Skip to content

Lesson 3: Subsystems

James Li edited this page Jun 26, 2025 · 16 revisions

Why Use Subsystems?

Think of each subsystem as a self-contained box that manages a specific robot mechanism.

  • Keeps code organized by robot functions (eg. drivetrain, intake)
  • Improve readability
  • Makes testing easier

CTRE Talon FX Motor

alt text

  • Smart motor controller with integrated encoder (can translate mechanical motion into code representing an angle, position, speed, etc)
  • Supports advanced features
    • Built in PID controls
    • Motion profiling
    • Current limiting

Key Talon FX Features

  • Tells the motor to spin forward/backward at x percent power (Motor output power is in between -1.0 to 1.0): .Set(ControlMode::PercentOutput, value)
  • Returns motor's encoder position: .GetSelectedSensorPosition()
  • Tunes motor's built-in PID controllers: .Config_kP(...), .Config_kI(...), .Config_kD(...), .Config_kF(...)
  • .SetNeutralMode(Brake/Coast)

Motion Magic

A control mode in CTRE TalonFX motor controllers that:

  • Moves a mechanism to a target positionControls velocity and acceleration
  • Has built-in control so it is not needed to manually write PID loops

Overview

For each subsystem (eg. Wrist), you may see: image

Each plays a different role to make code clean, testable, and adaptable.

Why This Structure?

  • Makes it easier to switch between real robot and simulation
  • Supports hardware abstraction
  • Enables unit testing without needing the robot
  • Encourages clean separation of logic

File 1 - Wrist.java

aka the main logic of the subsystem

  • Receives sensor data from WristIO
  • Tells WristIO what motor outputs to apply
  • Holds PID loops, state machines, etc.

Think of it as the "brain of the wrist"

File 2 - WristIO.java

aka the Java interface file

  • Defines the contract between the logic and the hardware
  • Includes methods like updateInputs(), setVoltage(), etc. that must be provided by the real/sim classes.
public interface WristIO {
    public void updateInputs(WristIOInputs inputs);
    public void setVoltage(double volts);
}
  • It ensures that implementations like WristIOTalonFX or WristIOSim will work when using Wrist.java

No matter the type of implementation, follow this interface"

WristIO Inputs

  • A data-holding class in WristIO.java that represents the current state of the hardware
  • Used to pass data like senor readings and status info from the IO implementation (eg, WristIOTalonFX) to the main subsystem logic (Wrist.java)

Where It Is Set

  • Inside WristIOTalonFX or WristIOSim, for example:
@Override
public void updateInputs(WristIOInputs inputs) {
    inputs.position = Radians.of(sim.getAngleRads());
    inputs.velocity = RadiansPerSecond.of(sim.getVelocityRadPerSec());
    inputs.appliedVoltage = appliedVoltage;
    inputs.supplyCurrent = Amps.of(Math.abs(sim.getCurrentDrawAmps()));
    // Add more
}

Raw sensor data is being used to populate the fields in the inputs object

How It Is Passed Around

  • In Wrist.java, declared and updated like this:
public class Wrist extends SubsystemBase {
  private final WristIO io;
  private final WristIOInputs inputs = new WristIOInputs();
}

@Override
public void periodic() {
    io.updateInputs(inputs);
    // Use the input data for logic after
}

Syntax Example:

  • Aka a set of rules that defines how the code must be written so that the computer understands it
public static class WristIOInputs {
  public Angle position = Radians.zero();
  public AngularVelocity velocity = RadiansPerSecond.zero();
  public Voltage appliedVoltage = Volts.zero();
  public Current supplyCurrent = Amps.zero();
  public Current statorCurrent = Amps.zero();
  public Temperature temperature = Celsius.zero();
  public boolean connected = false;
}

File 3 - WristIOTalonFX.java

aka real hardware implementation

  • Implements WristIO using a TalonFX motor
  • Talks to real motor controllers
  • Updates sensor readings and sends voltage outputs

Plug in this file for real-world operation

File 4 - WristIOSim.java

aka simulation implementation

  • Mocks sensor values and simulates movement
  • Lets you run and test Wrist.java without a robot
  • Implements WristIO using WPILib's sim features

Swap this in when testing in the simulation

How They Work Together

Wrist.java

↕ (uses)

WristIO (interface)

↕ (implemented by)

WristIOTalonFX.java ← for real robot WristIOSim.java ← for sim/testing

Choose which implementation to use based on which real/sim build mode you want to run

How to "Plug In" The Implementation

  • Inside RobotContainer, create your subsystem like this:
// For real robot
Wrist wrist = new Wrist(new WristIOTalonFX());
// For sim
Wrist wrist = new Wrist(new WristIOSim());

When to Use This Pattern

  • If the subsystem uses motors/sensors
  • Systems that want to be simulated
  • Subsystems that's hardware might change later

Clone this wiki locally