Timed and encoder movement
Make a timed move, then improve it with measured encoder feedback
A time-based move runs a for a chosen duration. An -based move sets a target wheel rotation and checks when the motor reaches it. The first is quick to try; the second gives the program feedback about wheel rotation. Neither one tells the its exact field position if its wheels slip.
The clock measures how long power was applied. An encoder measures how far the motor turned. Try one method at a time so you can compare its result on the same robot.
Timed movement
This short LinearOpMode example drives two motors forward for one second. Replace the hardware names and direction settings to match your robot:
import com.qualcomm.robotcore.eventloop.opmode.Autonomous;
import com.qualcomm.robotcore.eventloop.opmode.LinearOpMode;
import com.qualcomm.robotcore.hardware.DcMotor;
import com.qualcomm.robotcore.hardware.DcMotorSimple;
@Autonomous(name = "Timed Drive Test")
public class TimedDriveTest extends LinearOpMode {
@Override
public void runOpMode() {
DcMotor left = hardwareMap.get(DcMotor.class, "left_drive");
DcMotor right = hardwareMap.get(DcMotor.class, "right_drive");
left.setDirection(DcMotorSimple.Direction.REVERSE); // Example; verify on blocks.
right.setDirection(DcMotorSimple.Direction.FORWARD);
telemetry.addLine("Raise the robot and verify both wheels turn forward.");
telemetry.update();
waitForStart();
if (!opModeIsActive()) return;
try {
left.setPower(0.25);
right.setPower(0.25);
sleep(1000); // milliseconds
} finally {
left.setPower(0.0);
right.setPower(0.0);
}
}
}import com.qualcomm.robotcore.eventloop.opmode.Autonomous
import com.qualcomm.robotcore.eventloop.opmode.LinearOpMode
import com.qualcomm.robotcore.hardware.DcMotor
import com.qualcomm.robotcore.hardware.DcMotorSimple
import com.qualcomm.robotcore.util.ElapsedTime
import kotlin.math.abs
import kotlin.math.roundToInt
@Autonomous(name = "Timed Drive Test")
class TimedDriveTest : LinearOpMode() {
override fun runOpMode() {
val left = hardwareMap.get(DcMotor::class.java, "left_drive")
val right = hardwareMap.get(DcMotor::class.java, "right_drive")
left.direction = DcMotorSimple.Direction.REVERSE // Example; verify on blocks.
right.direction = DcMotorSimple.Direction.FORWARD
telemetry.addLine("Raise the robot and verify both wheels turn forward.")
telemetry.update()
waitForStart()
if (!opModeIsActive()) return
try {
left.power = 0.25
right.power = 0.25
sleep(1000) // milliseconds
} finally {
left.power = 0.0
right.power = 0.0
}
}
}0.25 is motor power and 1000 is milliseconds. Measure the distance the robot actually moved, then repeat with a different battery or surface. The result can change because of battery , friction, wheel slip, and load. Use timed movement to test direction or an idea; do not assume one duration always means one distance.
Encoder counts
An encoder reports motor or wheel rotation as counts, often called ticks. To convert those counts into a wheel-distance estimate, calibrate your assembled :
- Mark a start line and reset the drive encoders.
- Drive a measured distance slowly, such as 24 inches.
- Record the average absolute encoder change from the two drive sides.
- Divide that count by the measured distance.
counts per inch = encoder count change ÷ measured inches
target counts = desired inches × counts per inchThe value depends on the motor encoder, gear reduction, wheel diameter, and how the motor is connected. A motor's published counts-per-motor-revolution is only one part of this calculation; do not copy a generic motor value as COUNTS_PER_INCH.
Encoder movement
RUN_TO_POSITION asks the motor controller to move its encoder to a target count. Start with a short distance and low power. The values below are examples; measure your own COUNTS_PER_INCH and verify encoder signs before enabling a long move.
import com.qualcomm.robotcore.eventloop.opmode.Autonomous;
import com.qualcomm.robotcore.eventloop.opmode.LinearOpMode;
import com.qualcomm.robotcore.hardware.DcMotor;
import com.qualcomm.robotcore.hardware.DcMotorSimple;
import com.qualcomm.robotcore.util.ElapsedTime;
@Autonomous(name = "Encoder Drive Test")
public class EncoderDriveTest extends LinearOpMode {
private static final double COUNTS_PER_INCH = 50.0; // Replace with your measurement
@Override
public void runOpMode() {
DcMotor left = hardwareMap.get(DcMotor.class, "left_drive");
DcMotor right = hardwareMap.get(DcMotor.class, "right_drive");
left.setDirection(DcMotorSimple.Direction.REVERSE); // Example; verify on blocks.
right.setDirection(DcMotorSimple.Direction.FORWARD);
left.setZeroPowerBehavior(DcMotor.ZeroPowerBehavior.BRAKE);
right.setZeroPowerBehavior(DcMotor.ZeroPowerBehavior.BRAKE);
left.setMode(DcMotor.RunMode.STOP_AND_RESET_ENCODER);
right.setMode(DcMotor.RunMode.STOP_AND_RESET_ENCODER);
left.setMode(DcMotor.RunMode.RUN_USING_ENCODER);
right.setMode(DcMotor.RunMode.RUN_USING_ENCODER);
telemetry.addLine("Confirm the robot has clear space ahead.");
telemetry.update();
waitForStart();
if (!opModeIsActive()) return;
try {
driveInches(12.0, 0.25, left, right);
} finally {
left.setPower(0.0);
right.setPower(0.0);
}
}
private boolean driveInches(double inches, double power, DcMotor left, DcMotor right) {
if (!opModeIsActive()) return false;
int leftTarget = left.getCurrentPosition()
+ (int) Math.round(inches * COUNTS_PER_INCH);
int rightTarget = right.getCurrentPosition()
+ (int) Math.round(inches * COUNTS_PER_INCH);
left.setTargetPosition(leftTarget);
right.setTargetPosition(rightTarget);
left.setMode(DcMotor.RunMode.RUN_TO_POSITION);
right.setMode(DcMotor.RunMode.RUN_TO_POSITION);
left.setPower(Math.abs(power));
right.setPower(Math.abs(power));
ElapsedTime timeout = new ElapsedTime();
while (opModeIsActive()
&& (left.isBusy() || right.isBusy())
&& timeout.seconds() < 3.0) {
telemetry.addData("Left ticks", left.getCurrentPosition());
telemetry.addData("Right ticks", right.getCurrentPosition());
telemetry.update();
idle();
}
boolean reachedTarget = !left.isBusy() && !right.isBusy();
left.setPower(0.0);
right.setPower(0.0);
left.setMode(DcMotor.RunMode.RUN_USING_ENCODER);
right.setMode(DcMotor.RunMode.RUN_USING_ENCODER);
return reachedTarget && opModeIsActive();
}
}import com.qualcomm.robotcore.eventloop.opmode.Autonomous
import com.qualcomm.robotcore.eventloop.opmode.LinearOpMode
import com.qualcomm.robotcore.hardware.DcMotor
import com.qualcomm.robotcore.hardware.DcMotorSimple
import com.qualcomm.robotcore.util.ElapsedTime
import kotlin.math.abs
import kotlin.math.roundToInt
@Autonomous(name = "Encoder Drive Test")
class EncoderDriveTest : LinearOpMode() {
companion object { private const val COUNTS_PER_INCH = 50.0 }
override fun runOpMode() {
val left = hardwareMap.get(DcMotor::class.java, "left_drive")
val right = hardwareMap.get(DcMotor::class.java, "right_drive")
left.direction = DcMotorSimple.Direction.REVERSE // Example; verify on blocks.
right.direction = DcMotorSimple.Direction.FORWARD
left.zeroPowerBehavior = DcMotor.ZeroPowerBehavior.BRAKE
right.zeroPowerBehavior = DcMotor.ZeroPowerBehavior.BRAKE
left.mode = DcMotor.RunMode.STOP_AND_RESET_ENCODER
right.mode = DcMotor.RunMode.STOP_AND_RESET_ENCODER
left.mode = DcMotor.RunMode.RUN_USING_ENCODER
right.mode = DcMotor.RunMode.RUN_USING_ENCODER
telemetry.addLine("Confirm the robot has clear space ahead.")
telemetry.update()
waitForStart()
if (!opModeIsActive()) return
try {
driveInches(12.0, 0.25, left, right)
} finally {
left.power = 0.0
right.power = 0.0
}
}
private fun driveInches(inches: Double, power: Double, left: DcMotor, right: DcMotor): Boolean {
if (!opModeIsActive()) return false
val leftTarget = left.currentPosition + (inches * COUNTS_PER_INCH).roundToInt()
val rightTarget = right.currentPosition + (inches * COUNTS_PER_INCH).roundToInt()
left.targetPosition = leftTarget
right.targetPosition = rightTarget
left.mode = DcMotor.RunMode.RUN_TO_POSITION
right.mode = DcMotor.RunMode.RUN_TO_POSITION
left.power = abs(power)
right.power = abs(power)
val timeout = ElapsedTime()
while (opModeIsActive() && (left.isBusy || right.isBusy) && timeout.seconds() < 3.0) {
telemetry.addData("Left ticks", left.currentPosition)
telemetry.addData("Right ticks", right.currentPosition)
telemetry.update()
idle()
}
val reachedTarget = !left.isBusy && !right.isBusy
left.power = 0.0
right.power = 0.0
left.mode = DcMotor.RunMode.RUN_USING_ENCODER
right.mode = DcMotor.RunMode.RUN_USING_ENCODER
return reachedTarget && opModeIsActive()
}
}Follow the example in stages: convert 12 inches to target counts, give both motors those targets, and wait while either side is still moving. The prevents the from waiting forever if a motor cannot reach its target. If driveInches returns false, stop the route instead of starting the next movement.
The example sets motor power to a positive magnitude because the FTC RUN_TO_POSITION mode chooses direction from the target error. For a reverse move, use a negative inches value to make a lower target. Verify both logical encoder readings change in the expected direction on before trying it on the floor.
Encoder limitations
Encoders measure rotation, not ground travel. They improve a straight distance move, but wheel slip or a collision can make the robot stop at a different field position than expected. Long routes also accumulate small scale errors. For field pose, heading correction, or curves, continue to Odometry, Sensor Fusion, and Motion Planning.