Creating a Basic Driving Robot#
Lets get moving!

Overview#
This section is designed to help you program a basic driving robot, start to finish.
See table of contents for a breakdown of this section.
Creating the Drivetrain Subsystem#
Before we begin we must create the class file for the drivetrain subsystem. See Creating a New Subsystem for info on how to do this.
What will be added to the Drivetrain#
In the Drivetrain class we will tell the subsystem what type of components it will be using.
- A Drivetrain needs motor controllers. In our case we will use Neo SparkMaxes (a brand of controller for motors made by Rev Robotics).
- You could use other motor controllers such as Victor SPs or Talon SRs but we will be using NEO SparkMaxes
- If you are using other motor controllers, replace SparkMax with Talon, TalonSRX, Victor, or VictorSP in the code you write depending on the type you use.
- You can use 2 motors (left and right), but for this tutorial we will use 4.
More Info
Be sure to read Visual Studio Code Tips before getting started! It will make your life a lot easier.
Creating the SparkMax Variables#
1) Create 4 global variables of data type SparkMax and name them: leftLeader, rightLeader, leftFollower, rightFollower
- To get started type the word SparkMax followed by the name i.e.
private Final SparkMax leftLeader; - These will eventually hold the object values for SparkMaxes, their port numbers, and their motor type (brushed or brushless).
2) These are declared without values right now.
- We do this to make sure it is empty at this point.
- When we assign these variables a value, we will be getting the motor controller's port numbers out of Constants
- This means we cannot assign them at the global level
Example code
SparkMax Motor Member Variables:
Using a different motor controller?
The steps in this section use SparkMax, but the pattern is the same for other controllers. Replace SparkMax with your controller type (e.g. TalonFX, VictorSP) and use the corresponding import. Constructor parameters and configuration APIs differ by vendor — consult your vendor's documentation or WPILib's hardware API guide for the correct syntax.
If an error occurs (red squiggles)
-
Mouse Over the word SparkMax: The following menu should appear.
-
💡 Click "quick fix"

-
Select "Import 'SparkMax' (com.revrobotics.spark)"

-
Your error should be gone!
Creating and filling the constructor#
Now that we have created the SparkMaxes and the Drive Constants we must initialize them and tell them what port on the roboRIO they are on.
1) Initialize (set value of) leftLeader to new SparkMax(LEFT_LEADER_ID, MotorType.kBrushless).
- This initializes a new SparkMax,
leftLeader, in a new piece of memory and states it is on the port defined byLEFT_LEADER_ID. - This should be done within the constructor
Drivetrain() - This calls the constructor
SparkMax(int, MotorType)in the SparkMax class.- The constructor
SparkMax(int, MotorType)takes a variable of typeintfor the CAN ID andMotorTypefor brushless or brushed. In this case theint(integer) refers to the CAN ID on the roboRIO.
- The constructor
roboRIO port diagram

Constructor Initialization Example
**Full Constructor: **
public CANDriveSubsystem() {
// create brushed motors for drive
leftLeader = new SparkMax(LEFT_LEADER_ID, MotorType.kBrushed);
leftFollower = new SparkMax(LEFT_FOLLOWER_ID, MotorType.kBrushed);
rightLeader = new SparkMax(RIGHT_LEADER_ID, MotorType.kBrushed);
rightFollower = new SparkMax(RIGHT_FOLLOWER_ID, MotorType.kBrushed);
// set up differential drive class
drive = new DifferentialDrive(leftLeader, rightLeader);
// Set can timeout. Because this project only sets parameters once on
// construction, the timeout can be long without blocking robot operation. Code
// which sets or gets parameters during operation may need a shorter timeout.
leftLeader.setCANTimeout(250);
rightLeader.setCANTimeout(250);
leftFollower.setCANTimeout(250);
rightFollower.setCANTimeout(250);
// Create the configuration to apply to motors. Voltage compensation
// helps the robot perform more similarly on different
// battery voltages (at the cost of a little bit of top speed on a fully charged
// battery). The current limit helps prevent tripping
// breakers.
SparkMaxConfig config = new SparkMaxConfig();
config.voltageCompensation(12);
config.smartCurrentLimit(DRIVE_MOTOR_CURRENT_LIMIT);
// Set configuration to follow each leader and then apply it to corresponding
// follower. Resetting in case a new controller is swapped
// in and persisting in case of a controller reset due to breaker trip
config.follow(leftLeader);
leftFollower.configure(config, ResetMode.kResetSafeParameters, PersistMode.kPersistParameters);
config.follow(rightLeader);
rightFollower.configure(config, ResetMode.kResetSafeParameters, PersistMode.kPersistParameters);
// Remove following, then apply config to right leader
config.disableFollowerMode();
rightLeader.configure(config, ResetMode.kResetSafeParameters, PersistMode.kPersistParameters);
// Set config to inverted and then apply to left leader. Set Left side inverted
// so that postive values drive both sides forward
config.inverted(true);
leftLeader.configure(config, ResetMode.kResetSafeParameters, PersistMode.kPersistParameters);
}
See CANDriveSubsystem.java for the complete constructor implementation.
Using Constants#
Note
Constants.java fills the same role that older WPILib projects called RobotMap.java — a single file mapping every physical port to a named constant. If you see references to RobotMap in older FRC code examples, treat them the same as Constants.
Since each subsystem has its own components with their own ports, it is easy to lose track of which ports are being used and for what. To counter this you can use a class called Constants to hold all these values in a single location.
- Names should follow the pattern SUBSYSTEM_NAME_OF_COMPONENT
- The name is all caps since it is a constant (more info on constants).
Before we initalize the SparkMax objects we are going to create constants to hold the CAN ID's of the motors. This will happen in constants.java
- Inside the constants class, create a new class called
public static DriveConstants. - Inside
DriveConstantsclass, create for constants calledLEFT_LEADER_ID,LEFT_FOLLOWER_ID,RIGHT_LEADER_ID, andRIGHT_FOLLOWER_ID. - Back in your
DriveTrainclass indrivetrain.java, import theDriveConstantsclass as follows:Import frc.robot.Constants.DriveConstants;.
Tip
Make sure to declare constants with public static final so they cannot be changed at runtime.
Danger
If you set this to the wrong value, you could damage your robot when it tries to move!
Note
To use Constants, instead of putting 0 for the port in the SparkMax type:
Note
Replace the remaining numbers with constants.
Tip
Remember to save both Drivetrain.java and Constants.java
DriveConstants Example
Drive Constants Definition:
public static final class DriveConstants {
// Motor controller IDs for drivetrain motors
public static final int LEFT_LEADER_ID = 1;
public static final int LEFT_FOLLOWER_ID = 2;
public static final int RIGHT_LEADER_ID = 3;
public static final int RIGHT_FOLLOWER_ID = 4;
// Current limit for drivetrain motors. 60A is a reasonable maximum to reduce
// likelihood of tripping breakers or damaging CIM motors
public static final int DRIVE_MOTOR_CURRENT_LIMIT = 60;
}
Full Constants.java with all Robot Constants:
See Constants.java for the complete constants file including OperatorConstants and other subsystem constants.
Warning
Remember to use the values for YOUR specific robot or you could risk damaging it!
Configuring the SparkMaxes#
Setting CAN Timeout:
Each SparkMax motor must be configured with a CANTimeout. (How long to wait for a response from the motor controller)
leftLeader.setCANTimeout(250);
rightLeader.setCANTimeout(250);
leftFollower.setCANTimeout(250);
rightFollower.setCANTimeout(250);
Voltage Compensation and Current Limiting:
Create the configuration to apply to motors. Voltage compensation helps the robot perform more similarly on different battery voltages (at the cost of a little bit of top speed on a fully charged battery). The current limit helps prevent tripping breakers.
SparkMaxConfig config = new SparkMaxConfig();
config.voltageCompensation(12);
config.smartCurrentLimit(DRIVE_MOTOR_CURRENT_LIMIT);
See CANDriveSubsystem.java for the full configuration implementation in the constructor.
Creating the arcade drive#
What is the Drive Class#
- The FIRST Drive class has many pre-configured methods available to us including DifferentialDrive, and many alterations of MecanumDrive.
- DifferentialDrive contains subsections such as TankDrive and ArcadeDrive. For more information and details on drive bases see the WPILib documentation.
- For our tutorial we will be creating an ArcadeDrive
- Arcade drives run by taking a moveSpeed and rotateSpeed. moveSpeed defines the forward and reverse speed and rotateSpeed defines the turning left and right speed.
- To create an arcade drive we will be using our already existing Drivetrain class and adding to it.
Programing a RobotDrive#
1) Create the DifferentialDrive object.
Member Variable Declaration:
This defines the drive object that we will use to drive the robot.Constructor Initialization:
This initializes the differential drive object with the left and right leader motors.- Since DifferentialDrive takes 2 parameters we pass the left and right leader motors.
- The follower motors are configured to follow these leaders through the SparkMax configuration.
Warning
You should only group motors that are spinning the same direction physically when positive power is being applied otherwise you could damage your robot.
2) In order to configure the motors to drive correctly, we need to configure one on each side as the leader and one as the follower. In the constructor we are going to set the follower motors and link them to the leader motors. To do this we will need to include a couple more classes from the REV Library:
import com.revrobotics.spark.SparkBase.PersistMode;
import com.revrobotics.spark.SparkBase.ResetMode;
Set follower configuration:
config.follow(leftLeader);
leftFollower.configure(config, ResetMode.kResetSafeParameters, PersistMode.kPersistParameters);
config.follow(rightLeader);
rightFollower.configure(config, ResetMode.kResetSafeParameters, PersistMode.kPersistParameters);
Configure right leader:
config.disableFollowerMode();
rightLeader.configure(config, ResetMode.kResetSafeParameters, PersistMode.kPersistParameters);
Invert left leader for correct motor direction:
config.inverted(true);
leftLeader.configure(config, ResetMode.kResetSafeParameters, PersistMode.kPersistParameters);
Full Drive Subsystem Example
See CANDriveSubsystem.java for the complete implementation with all motor configuration and initialization.
Creating the arcadeDrive method#
Now it’s time to make an arcadeDrive from our differentialDrive!
Abstract
1) Let’s create a public void method called “arcadeDrive” with type “double” parameters moveSpeed and rotateSpeed.
Below the periodic method create a new method called arcadeDrive. This method will be called from our Drive command to actually move the robot.
Tip
By putting something in the parentheses it makes the method require a parameter when it is used. When the method gets used and parameters are passed, they will be store in moveSpeed and rotateSpeed (in that order). See parameters for more info.
Abstract
2) Now lets make our method call the differentialDrive’s arcadeDrive method.
Inside our method type the call to the differential drive:
DifferentialDrive’s arcadeDrive method takes parameters moveValue and rotateValue.
Note
At this point you could instead create a tank drive, however implementation differs slightly.
To do so type differentialDrive.tankDrive(moveSpeed, rotateSpeed); instead of differentialDrive.arcadeDrive(moveSpeed, rotateSpeed); and change the method name reflect this.
Tip
If you want to limit the max speed you can multiple the speeds by a decimal (i.e. 0.5*moveSpeed will make the motors only move half of their maximum speed)
You may want to do this for initial testing to make sure everything is going the right direction.
Making our robot controllable#
Creating the Drivearcade Command#
- Remember that methods tell the robot what it can do but in order to make it do these things we must give it a command. See Command Based Robot
- Now that we have created the method, we need to create a command to call and use that method.
- Let’s create a new command called DriveArcade that calls arcadeDrive method we just created!
Before we begin we must create the class file for the DriveArcade command. See Creating a New Command for info on how to do this and info on what each pre-created method does.
Define variables#
Note
1) Create xspeed and zrotation variables. (to be passed to drive subsystem). These will be declared as DoubleSuppliers, which is a function that return a type. This is important for later.
2) Create an emtpy driveSubsystem instance of Drivetrain
Warning
DoubleSupplier and Drivetrain will have to be imported as follows:
java title=”DriveArcadeCommand.java”
--8<-- “docs/code_examples/2026KitBotInline/commands/DriveArcadeCommand.java:class-variables”
In the constructor#
Note
1) Inside the parenthesis of the constructor driveArcade() add 3 variables:
java title=”DriveArcadeCommand.java”
--8<-- “docs/code_examples/2026KitBotInline/commands/DriveArcadeCommand.java:constructor-signature”
These are values that will be passed into the command in RobotContainer.java
Note
2) Inside constructor implementation type:
java title=”DriveArcadeCommand.java”
--8<-- “docs/code_examples/2026KitBotInline/commands/DriveArcadeCommand.java:constructor-body”
- The lines starting with
thisset the global variables we defined at the top of our class file to the values being passed into the consturctor.
Tip
this is how the class instance object refers to itself in code.
!!! note “”
- addRequirements means this command will end all other commands currently using drivetrain and will run instead when executed.
- It also means, other commands that require drivetrain will stop this command and run instead when executed.
!!! warning
If you use the light bulb to import ‘Robot’, be sure to import the one with “frc.robot”
In the execute method#
Note
1) In the execute method we will we want to call the arcadeDrive method we created in Drivetrain and give it the variables moveSpeed xspeed and rotateSpeed zrotation we created as parameters.
java title=”DriveArcadeCommand.java”
--8<-- “docs/code_examples/2026KitBotInline/commands/DriveArcadeCommand.java:execute-method”
In the isFinished method#
Since we will be using this command to control the robot we want it to run indefinitely.
Note
1) To do this we are going to continue having isFinished return false, meaning the command will never finish.
java title=”DriveArcadeCommand.java”
--8<-- “docs/code_examples/2026KitBotInline/commands/DriveArcadeCommand.java:is-finished-method”
Tip
- If we did want a command to finish, we make this return true.
- This can be done by replacing false with true to make it finish instantly
- Alternatively we can make a condition which can return true
- For example
(timePassed > 10)will return true after 10 seconds but return false anytime before 10 seconds have passed.
- For example
In the end method#
Note
1) We will call the arcadeDrive method and give it 0 and 0 as the parameters. this will stop the robot when the command completes.
java title=”DriveArcadeCommand.java”
--8<-- “docs/code_examples/2026KitBotInline/commands/DriveArcadeCommand.java:end-method”
- This make the motors stop running when the command ends by setting the movement speed to zero and rotation speed to zero.
Completed Example#
See DriveArcadeCommand.java for the complete command class implementation.
Creating the Joystick#
In order to drive our robot, it needs to know what will be controlling it. To do so, we will use the joystick in RobotContainer.java, as m_drivecontroller.
Note
1) Open Constants.java
Check and make sure the kDriverControllerPort constant is present.
2) Open RobotContainer.java
- in the imports section, change ExampleCommand to DriveArcade.
- inside the class, find the line private final ExampleSubsystem m_exampleSubsystem = new ExampleSubsystem(); and change ExampleSubsystem to Drivetrain and m_exampleSubsystem to drivetrain.
Finding your joystick port in the Driver Station
If you are not sure which port your controller is on, open the Driver Station application, click the USB tab (the icon that looks like a USB plug on the left side), and look at the numbered list of connected devices. The number next to your controller is the port you should use for kDriverControllerPort in Constants. Devices can be dragged up or down in the list to change their assigned port number.
Using setDefaultCommand#
Note
1) Back in RobotContainer.java We will need to remove everything inside the configureBindings method.
2) in the configureBindingswe will call the setDefaultCommand of drivetrain and create a new DriveArcade command with parameters.
Tip
- Commands in this method will run when the robot is enabled.
- They also run if no other commands using the subsystem are running.
- This is why we write addRequirements(Robot.subsystemName) in the commands we create, it ends currently running commands using that subsystem to allow a new command is run.
- We will the default command for the drive subsystem to an instance of the
DriveArcadewith the values provided by the joystick axes on the driver controller. - The Y axis of the controller is inverted so that pushing the stick away from you (a negative value) drives the robot forwards (a positive value).
- Similarly for the X axis where we need to flip the value so the joystick matches the WPILib convention of counter-clockwise positive
driveSubsystem.setDefaultCommand(
driveSubsystem.driveArcade(
() -> -driverController.getLeftY() * DRIVE_SCALING,
() -> -driverController.getRightX() * ROTATION_SCALING));
Tip
- Notice the
()->notation above. This notation creates lamdas or anonymous methods. More about Lambdas - The lambas are required because we set the parameter types of
xpeedand 'zrotation' in ourDriveArcadeto beDoubleSuppliers, which are methods that return doubles. (Which is what the lambdas above return.) - These are declared as such so that they get and send the updated values from
m_driverController.getLeftY()andm_driverController.getRightX()to the drive motors continuously.
Tip
Remember to use the light bulb for importing if needed!
Tip
The New keyword creates a new instance of a class (object)
Full RobotContainer Example
See RobotContainer.java for the complete RobotContainer implementation.
The key part for drive configuration is in configureBindings():
driveSubsystem.setDefaultCommand(
driveSubsystem.driveArcade(
() -> -driverController.getLeftY() * DRIVE_SCALING,
() -> -driverController.getRightX() * ROTATION_SCALING));
This sets arcade drive as the default command, using:
- The negative Y-axis of the left joystick (inverted so pushing away drives forward)
- The negative X-axis of the right joystick (inverted for WPILib counter-clockwise positive convention)
- Both axes scaled for controllability