The following is a quick overview that should help you get up-and-running with DC Motors on your FTC robot. We assume you’re using Android Studio or the OnBot Java interface to write and build your programs. Want to contribute information related to another method, or make a correction? Let us know.
Hardware
In order to work with DC Motors, we’ll need a few things:
- Modern Robotics Power Distribution Module (PDM)
- Modern Robotics Motor Controller
OR
- REV Robotics Expansion Hub
AND
- DC Motor with compatible cable (see Game Manual Part 1 for information about competition-legal electronics)
- Compatible battery
Optionally, we may wish to attach an encoder to measure the movement of the motor. For some motors, like the AndyMark NeveRest and REV Core Hex motors, the encoder is built-in. We just need a separate encoder cable to plug it in.
If we use the Modern Robotics modules, the motor attaches to one of the two motor ports on the motor controller using the powerpole connectors. Then, a pair of wires (with powerpole connectors) run from the power port of the motor controller to one of the ports on the PDM. Finally, a USB A-to-B-Mini cable runs from the motor controller to one of the USB-A ports on the PDM.
If we use the REV Expansion Hub, the motor attaches to one of the four motor ports on the hub using the JST VH connectors (for more on JST connectors, check this out).
Software Setup
To declare a variable representing the motor, we’ll need to include an import statement:
1 |
import com.qualcomm.robotcore.hardware.DcMotor; |
We probably want to declare a class-level variable for each motor. Make sure to name variables something relevant to what the motor does:
1 2 3 4 5 6 |
public class YourClassName extends OpMode { DcMotor arm_motor; // ... } |
During the init() process (or before waitForStart() in a Linear OpMode) we should initialize your motor variable. Inside the get("...") method, we need to use the name we gave the motor in the configuration file on the Robot Controller phone (in this case, we imagine it’s called “arm”). It’s a good idea to do this in init() because if the robot can’t find your motor, you’ll want to know before starting the match.
1 2 3 4 5 6 |
public void init() { arm_motor = hardwareMap.dcMotor.get("arm"); // ... } |
We might want to reverse the direction of a motor (for example, if we have left and right drive motors facing opposite directions). We can do that right after initializing the motor:
1 |
arm_motor.setDirection(DcMotor.Direction.REVERSE); |
Running the Motor
Chances are, we want to do everything you see below in the loop() method (or in runOpMode() after the waitForStart() if we’re writing a Linear OpMode). One of the most common ways to run a motor is to set power directly:
1 |
arm_motor.setPower(-0.75); |
We can use any decimal value between -1.0 and 1.0. Setting the power back to zero will make the motor stop. Now, this isn’t the only way to run a motor; we’ll see another way a little later. For now, let’s check the encoder value, assuming one is connected:
1 2 |
int position = arm_motor.getCurrentPosition(); telemetry.addData("Encoder Position", position); |
This will store the encoder’s current reading in a new variable, and then display its value on the Driver’s Station phone. If we want to reset the encoder back to zero, we can do that:
1 2 |
arm_motor.setMode(DcMotor.RunMode.STOP_AND_RESET_ENCODER); arm_motor.setMode(DcMotor.RunMode.RUN_WITHOUT_ENCODER); |
Note that we’re setting the RunMode back to RUN_WITHOUT_ENCODER after the reset. Unless you’re doing something special (see Run to Position below), this is probably the mode you want to be in during normal operation. It’s also the default mode.
Driving with Joysticks
Often, one of the first things we want to do is drive using the joysticks on a gamepad. This is a multi-step process, if we do it correctly. For this, let’s assume with have two drive train motors called left_drive and right_drive. We’ll do a simple tank drive:
1 2 3 4 5 |
float left_power = -gamepad1.left_stick_y; float right_power = -gamepad1.right_stick_y; left_drive.setPower(left_power); right_drive.setPower(right_power); |
That’s a lot, so let’s break it down. The code is running in multiple parts:
- Get the current value of the joysticks. Did you know that the Y-axis of the joysticks is negative when pushed up, and positive when pushed down?
- Then we take the values and feed them to the motors.
Notes: (1) We use to manually trim or clip the joystick values to the range [-1, 1]. This is no longer necessary, as the gamepad class does this for us. (2) We probably want to reverse the left drive motor(s) in a traditional setup. Alternatively, we can reverse the right drive motor(s) and remove the negative signs when reading the gamepad input.
PID Modes
As mentioned above, there are other ways to use the motor. During normal operation, we tell the motor our desired power. The output of the motor at a given power will depend on the motor, its internal gearbox, and its work load. We can also change the motor to run in terms of speed or target position.
Both of these modes use PID control to determine the motor’s power (and direction, in the case of target positioning). For example, if the motor is really close to its target position, it will use a small amount of power to advance or retreat. If it’s far away, it will use much more power.
Warning: Before we discuss the PID modes in further detail, let’s talk about the motor’s max speed. During normal operation of a motor, there’s nothing for the motor controller to decide. Whatever we give to setPower() is supplied directly to the motor. In PID modes, the motor’s output is limited by a maximum speed variable. This variable is set by the motor profile we choose while configuring the Robot Controller. It is therefore quite important to choose the correct motor type.
Run to Position
If we know the encoder value we want the motor to reach, we can ask the controller to take it there (using PID). In the init() phase, include the following after we initialize the motor variable:
1 |
arm_motor.setMode(DcMotor.RunMode.RUN_TO_POSITION); |
Now, we need to give the motor two pieces of information: where to go, and how fast. We can change one without changing the other:
1 2 |
arm_motor.setTargetPosition(some_encoder_value); arm_motor.setPower(some_power); |
The motor controller will automatically choose the correct direction to move. When the encoder value gets close to its target, the motor will automatically slow down, and correct itself if it moved too far. If another force (like a robot, or gravity) pulls the motor away from its target position, the controller will attempt to correct for it.
If we plan to use this method, it’s very important that we pay attention to resetting encoder values. If we forget to reset the value at the beginning of the program, or if you reset it while a target is set, there may be unintended consequences. Be safe!
Run Using Encoder
Another interesting mode for motors is RUN_USING_ENCODER. Intuitively, this mode causes the motor to run based on speed instead of power. Using PID control, the input you give to setPower() will be interpreted as a a percentage of some maximum speed. The power of the motor will be set to achieve that speed.
In the init() phase, include the following after we initialize the motor variable:
1 |
arm_motor.setMode(DcMotor.RunMode.RUN_USING_ENCODER); |
Now, we can use arm_motor.setPower() with a number between -1.0 and 1.0 to set the desired speed. Remember to setMaxSpeed()!
Zero Behavior
Normally, when a motor is set to zero power, the motor “brakes” — that is, it actively tries to stay in the same position. We can change this behavior using the setZeroPowerBehavior() method. It has two options: brake, the default, or float, where the motor is allowed to spin freely. This can be useful when we know that something (gravity, another robot, etc.) is going to be pushing against a motor and we do or don’t want to fight it. Here’s what setting the behavior looks like:
1 2 |
arm_motor.setZeroPowerBehavior(DcMotor.ZeroPowerBehavior.BRAKE); arm_motor.setZeroPowerBehavior(DcMotor.ZeroPowerBehavior.FLOAT); |
Of course, just because we set the motor to “float” doesn’t mean it will be able to move freely mechanically. The type of motor, gearbox, etc. may still prevent movement.
- Edited October 8, 2017 to include REV Expansion Hub and motor profiles.
- Edited November 15, 2016 to include Zero Power Behavior.
- Originally published August 7, 2016.