Skip to content

Lesson 3: Subsystems

James Li edited this page Jun 2, 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)

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

  • Owns an instance of WristIO
  • 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 default void updateInputs(WristIOInputs inputs) {}
  public default void setVoltage(Voltage voltage) {}
  public default void setBrakeMode(boolean enable) {}
  /** Reset the motor encoder to the specified position. */
  public default void resetPosition(Angle position) {}
}
  • It ensures that implementations like WristIOTalonFX or WristIOSim will work when using Wrist.java

Implementation example:

public class WristTalonFX implements WristIO {
  @Override
  public void setVoltage(Voltage voltage) {
    motor.setControl(voltageRequest.withOutput(voltage.in(Volts)));
  }
  // more implementation...
}

No matter the type of implementation, follow this interface

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.java (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

Task for This Lesson

Follow the instructions here. A template project is provided for you in the same folder.

Clone this wiki locally