Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/Holden9607/FRC-Academy/llms.txt

Use this file to discover all available pages before exploring further.

Most FRC drivetrains use four or more motors — two per side — to generate the torque needed to push heavy robots across the field. This lesson extends the 2-motor arcade drive from Lesson 3 by adding a follower motor to each side. Follower motors mirror their leader’s output automatically, so your drive loop stays exactly the same while doubling the power on each side of the drivetrain.

What You’ll Learn

  • The follower motor concept: why it exists and how it reduces code complexity
  • How to configure a SparkMax as a follower using SparkMaxConfig.follow()
  • How to configure a TalonFX as a follower using the Follower control request
  • How CIM motors use addFollower() from WPILib’s MotorController interface
  • How to assign unique CAN IDs to four motors (IDs 1–4)
  • How to extend the 2-motor solution without rewriting the drive loop

Follower Motors Explained

A follower motor is a motor controller that is configured to automatically mirror the output of a designated leader motor. Instead of calling .set() on every motor individually in your drive loop, you configure the follower once during initialization and then only command the leader. The follower watches the leader’s output and applies the same duty cycle in real time.
Only the leader motors need explicit set() or setControl() calls in your drive loop. Follower motors update their output automatically whenever the leader’s setpoint changes.
Benefits of using follower motors:
  • Simpler drive loop — half as many set() calls
  • Followers stay in sync with leaders even if your code has timing irregularities
  • Less risk of accidentally commanding one motor without the other

Solutions by Motor Type

Four SPARK MAX controllers share two CAN IDs per side (IDs 1–4). The left follower is configured to follow m_leftLead, and the right follower follows m_rightLead. Right-side inversion is applied to the right leader only.
import edu.wpi.first.wpilibj.TimedRobot;
import edu.wpi.first.wpilibj.XboxController;
import edu.wpi.first.wpilibj.drive.DifferentialDrive;
import com.revrobotics.spark.SparkMax;
import com.revrobotics.spark.SparkLowLevel.MotorType;
import com.revrobotics.spark.config.SparkMaxConfig;
import com.revrobotics.spark.SparkBase.ResetMode;
import com.revrobotics.spark.SparkBase.PersistMode;

public class Robot extends TimedRobot {
    private static final int kLeftLeadID = 1;
    private static final int kLeftFollowID = 2;
    private static final int kRightLeadID = 3;
    private static final int kRightFollowID = 4;

    private SparkMax m_leftLead;
    private SparkMax m_leftFollow;
    private SparkMax m_rightLead;
    private SparkMax m_rightFollow;

    private final XboxController m_controller = new XboxController(0);
    private DifferentialDrive m_robotDrive;

    @Override
    public void robotInit() {
        m_leftLead = new SparkMax(kLeftLeadID, MotorType.kBrushless);
        m_leftFollow = new SparkMax(kLeftFollowID, MotorType.kBrushless);

        m_rightLead = new SparkMax(kRightLeadID, MotorType.kBrushless);
        m_rightFollow = new SparkMax(kRightFollowID, MotorType.kBrushless);

        SparkMaxConfig leftFollowConfig = new SparkMaxConfig();
        leftFollowConfig.follow(m_leftLead);
        m_leftFollow.configure(leftFollowConfig, ResetMode.kResetSafeParameters, PersistMode.kPersistParameters);

        SparkMaxConfig rightLeadConfig = new SparkMaxConfig();
        rightLeadConfig.inverted(true);
        m_rightLead.configure(rightLeadConfig, ResetMode.kResetSafeParameters, PersistMode.kPersistParameters);

        SparkMaxConfig rightFollowConfig = new SparkMaxConfig();
        rightFollowConfig.follow(m_rightLead);
        m_rightFollow.configure(rightFollowConfig, ResetMode.kResetSafeParameters, PersistMode.kPersistParameters);

        m_robotDrive = new DifferentialDrive(m_leftLead, m_rightLead);
    }

    @Override
    public void teleopPeriodic() {
        m_robotDrive.arcadeDrive(-m_controller.getLeftY(), m_controller.getLeftX());
    }
}

CAN ID and PWM Port Assignment

Every motor controller must have a unique ID or port. In a 4-motor drivetrain, you need four distinct values. FRC Academy uses the following default assignments: CAN-based motors (NEO / Kraken):
MotorCAN ID
Left Leader1
Left Follower2
Right Leader3
Right Follower4
PWM-based motors (CIM):
MotorPWM Port
Left Leader0
Left Follower1
Right Leader2
Right Follower3
Duplicate CAN IDs will cause one or both controllers to behave erratically or not respond at all. Always verify your CAN IDs are unique using REV Hardware Client (for SPARK MAX) or CTRE Phoenix Tuner X (for TalonFX) before running the robot.

Follower API Differences

Each motor type has its own follower API: NEO (SparkMax) — uses SparkMaxConfig.follow(leaderMotor) applied via .configure():
SparkMaxConfig leftFollowConfig = new SparkMaxConfig();
leftFollowConfig.follow(m_leftLead);
m_leftFollow.configure(leftFollowConfig, ResetMode.kResetSafeParameters, PersistMode.kPersistParameters);
Kraken (TalonFX) — uses new Follower(leaderID, opposeLeader) set via setControl():
m_leftFollow.setControl(new Follower(m_leftLead.getDeviceID(), false));
opposeLeader = false means the follower runs in the same direction as the leader. Set true only if the follower is physically mounted in reverse relative to the leader. CIM (PWMSparkMax) — uses WPILib’s addFollower() directly on the leader:
m_leftLead.addFollower(m_leftFollow);

In FRC Academy

To reach this lesson in the app:
1

Open the Main Menu

Launch FRC Academy and click Motors from the main menu.
2

Select a motor type

Choose NEO, Kraken, or CIM.
3

Open Lesson 4

Select 4. 4 Motor Tank Drive (Arcade) from the lesson list.
4

Choose a mode

Pick Guided Mode or Unguided Mode and build on your Lesson 3 solution.
Only the leader motors need explicit set() or arcadeDrive() calls in the drive loop. Followers update automatically — adding redundant set() calls on follower motors may cause unexpected behavior.

← 2-Motor Tank Drive

Lesson 3 — The 2-motor arcade drive foundation this lesson builds upon.

Motor Types Overview

Compare NEO, Kraken, and CIM motor controllers — hardware specs, vendor libraries, and when to use each.

Build docs developers (and LLMs) love