Skip to content
CrocoDocs
Starter GuideProgrammingEngineeringDesignPortfolio
Documentation

Introduction

IntroductionBIOBUZZ game overview

Getting started

Getting startedBlocksOnBot JavaAndroid StudioYour first OpModeJava basicsMotors and servosNonblocking codeDebugging robot code

Control systems

Control systemsJoystick mappingField-centric mecanum drivePID controlFeedforwardFlywheel velocity controlSystem identificationMotion profilingKalman filterLow-pass filter

Autonomous

AutonomousTimed and encoder movementOdometryLocalization troubleshootingMotion planningReliable autonomous sequencesPure PursuitSensor fusionAuto to TeleOp handoff

Season topics

Season topicsProjectile mathematicsTurret aimingShooting on the move

Code practices

Code practicesNaming conventionsCode organizationLoop timing and bulk readsFormattingComments and documentationTeam collaboration

Libraries

LibrariesNextFTCFTCLibSolversLibPedro PathingRoad RunnerFTC DashboardMercurial v2Mercurial v1PanelsSloth

Community software

Community softwareBlaze FTCOpenLoopHive Vision

Sensors and vision

Vision overviewVision basicsLimelight 3A
Starter Guide
Engineering
Design
Portfolio
Appearance

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.

A timed move stops after a clock interval even if the wheel has not reached a distance; an encoder move checks wheel rotation and stops when its tick target is reached.

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 :

  1. Mark a start line and reset the drive encoders.
  2. Drive a measured distance slowly, such as 24 inches.
  3. Record the average absolute encoder change from the two drive sides.
  4. Divide that count by the measured distance.
counts per inch = encoder count change ÷ measured inches
target counts   = desired inches × counts per inch

The 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.

Sources

  • FTC SDK programming resources
  • Game Manual 0: Encoders

Autonomous

Build and test routines that run without driver input

Odometry

Estimate where your robot is from measured movement

On this page

Timed movementEncoder countsEncoder movementEncoder limitationsSources