Skip to main content

Overview

WPILib provides a unified interface for controlling various motor controllers through the MotorController interface. This guide covers PWM motor controllers and best practices.

MotorController Interface

Source: wpilibj/src/main/java/edu/wpi/first/wpilibj/motorcontrol/MotorController.java:12 All motor controllers implement the MotorController interface:
public interface MotorController {
  // Set motor speed (-1.0 to 1.0)
  void set(double speed);
  
  // Set voltage with compensation
  void setVoltage(double outputVolts);
  
  // Get current speed
  double get();
  
  // Invert motor direction
  void setInverted(boolean isInverted);
  boolean getInverted();
  
  // Stop motor
  void disable();
  void stopMotor();
}

PWM Motor Controllers

Source: wpilibj/src/main/java/edu/wpi/first/wpilibj/motorcontrol/PWMMotorController.java:15 PWM motor controllers connect to the roboRIO’s PWM ports (0-9 onboard, 10-19 on MXP).

Basic Usage

import edu.wpi.first.wpilibj.motorcontrol.PWMSparkMax;
import edu.wpi.first.wpilibj.motorcontrol.PWMVictorSPX;
import edu.wpi.first.wpilibj.motorcontrol.PWMTalonSRX;

public class DriveSubsystem {
  private PWMSparkMax m_leftMotor;
  private PWMVictorSPX m_rightMotor;

  public DriveSubsystem() {
    // PWM channel 0
    m_leftMotor = new PWMSparkMax(0);
    // PWM channel 1
    m_rightMotor = new PWMVictorSPX(1);
  }

  public void setMotors(double leftSpeed, double rightSpeed) {
    m_leftMotor.set(leftSpeed);
    m_rightMotor.set(rightSpeed);
  }
}

Setting Motor Speed

Source: wpilibj/src/main/java/edu/wpi/first/wpilibj/motorcontrol/PWMMotorController.java:53

Speed Control

Motor speed ranges from -1.0 (full reverse) to 1.0 (full forward):
// Full forward
motor.set(1.0);

// Half speed forward
motor.set(0.5);

// Stopped
motor.set(0.0);

// Full reverse
motor.set(-1.0);

// Stop motor (preferred over set(0))
motor.stopMotor();

Voltage Control

Source: wpilibj/src/main/java/edu/wpi/first/wpilibj/motorcontrol/MotorController.java:31 Voltage control compensates for battery voltage drops:
// Set to 6 volts (compensates for battery sag)
motor.setVoltage(6.0);

// Must be called regularly (every loop)
@Override
public void teleopPeriodic() {
  motor.setVoltage(desiredVoltage);
}

Motor Inversion

Source: wpilibj/src/main/java/edu/wpi/first/wpilibj/motorcontrol/PWMMotorController.java:88 Invert motor direction in software:
PWMSparkMax leftMotor = new PWMSparkMax(0);
PWMSparkMax rightMotor = new PWMSparkMax(1);

// Right motor is mounted backwards
rightMotor.setInverted(true);

// Now both motors drive forward with positive values
leftMotor.set(0.5);
rightMotor.set(0.5);

Follower Motors

Source: wpilibj/src/main/java/edu/wpi/first/wpilibj/motorcontrol/PWMMotorController.java:156 Make one motor follow another:
PWMSparkMax leader = new PWMSparkMax(0);
PWMSparkMax follower1 = new PWMSparkMax(1);
PWMSparkMax follower2 = new PWMSparkMax(2);

// Followers mirror leader
leader.addFollower(follower1);
leader.addFollower(follower2);

// Only control the leader
leader.set(0.8);
// follower1 and follower2 automatically set to 0.8

Motor Controller Groups

Group multiple motor controllers together:
import edu.wpi.first.wpilibj.motorcontrol.MotorControllerGroup;
import edu.wpi.first.wpilibj.motorcontrol.PWMSparkMax;

PWMSparkMax leftFront = new PWMSparkMax(0);
PWMSparkMax leftRear = new PWMSparkMax(1);
MotorControllerGroup leftMotors = new MotorControllerGroup(leftFront, leftRear);

PWMSparkMax rightFront = new PWMSparkMax(2);
PWMSparkMax rightRear = new PWMSparkMax(3);
MotorControllerGroup rightMotors = new MotorControllerGroup(rightFront, rightRear);

// Control both motors as one
leftMotors.set(0.5);
rightMotors.set(-0.5);

Deadband Elimination

Source: wpilibj/src/main/java/edu/wpi/first/wpilibj/motorcontrol/PWMMotorController.java:147 Eliminate motor controller deadband for more precise low-speed control:
PWMSparkMax motor = new PWMSparkMax(0);

// Enable deadband elimination
motor.enableDeadbandElimination(true);

Complete Drive Example

import edu.wpi.first.wpilibj.TimedRobot;
import edu.wpi.first.wpilibj.Joystick;
import edu.wpi.first.wpilibj.motorcontrol.PWMSparkMax;
import edu.wpi.first.wpilibj.motorcontrol.MotorControllerGroup;
import edu.wpi.first.wpilibj.drive.DifferentialDrive;

public class Robot extends TimedRobot {
  private PWMSparkMax m_leftFront;
  private PWMSparkMax m_leftRear;
  private PWMSparkMax m_rightFront;
  private PWMSparkMax m_rightRear;
  private DifferentialDrive m_drive;
  private Joystick m_controller;

  @Override
  public void robotInit() {
    // Initialize motors
    m_leftFront = new PWMSparkMax(0);
    m_leftRear = new PWMSparkMax(1);
    m_rightFront = new PWMSparkMax(2);
    m_rightRear = new PWMSparkMax(3);

    // Right side is inverted
    m_rightFront.setInverted(true);
    m_rightRear.setInverted(true);

    // Group motors
    MotorControllerGroup leftMotors = new MotorControllerGroup(m_leftFront, m_leftRear);
    MotorControllerGroup rightMotors = new MotorControllerGroup(m_rightFront, m_rightRear);

    // Create drive
    m_drive = new DifferentialDrive(leftMotors, rightMotors);
    m_controller = new Joystick(0);
  }

  @Override
  public void teleopPeriodic() {
    // Arcade drive
    m_drive.arcadeDrive(
      -m_controller.getY(),  // Forward/backward
      m_controller.getX()    // Rotation
    );
  }

  @Override
  public void disabledInit() {
    m_drive.stopMotor();
  }
}

Motor Safety

Motor controllers have built-in safety timeouts to prevent runaway robots:
PWMSparkMax motor = new PWMSparkMax(0);

// Motor will stop if not updated within timeout
motor.setExpiration(0.5);  // 500ms timeout
motor.setSafetyEnabled(true);

@Override
public void teleopPeriodic() {
  // Must call set() regularly to keep motor running
  motor.set(speed);
}

Best Practices

  • Always stop motors when disabled - Override disabledInit() to call stopMotor()
  • Use voltage control for feedforward - Provides consistent behavior regardless of battery voltage
  • Invert in software - Use setInverted() rather than swapping wires
  • Group motors logically - Use MotorControllerGroup for motors that always move together
  • Enable motor safety - Prevents runaway robots if code crashes
  • Check current draw - Monitor with PowerDistribution class to avoid breaker trips

Next Steps

Build docs developers (and LLMs) love