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

Loop timing and bulk reads

Measure the control loop, refresh cached inputs, and reduce hardware traffic before adding threads

Measure where a slow loop spends its time before rewriting the controller. A short calculation can still wait on hardware communication, and a fast telemetry counter can coexist with stale data.

Measure the whole cycle

Measure the interval between successive control updates as well as the time spent doing work inside one . The interval includes waits and scheduling delays outside that work. Record the typical interval, slowest intervals, and the age of the measurements the controller uses.

For a travelling at 1 m/s1\,\mathrm{m/s}1m/s, a 40 ms interval means about 40 mm of travel between updates. Reducing that interval can improve reaction time, but it does not repair an incorrect scale or add traction.

Keep the robot and workload the same when comparing changes. Include vision, , and telemetry that normally run together. Compare stopping error and consistency alongside timing.

Bulk caching

A REV hub can return several eligible inputs together. LynxModule exposes three cache modes:

ModeRefresh behaviorUse
OFFEligible reads do not reuse the bulk cacheComparison or diagnosis
AUTORepeating an eligible command refreshes the cache automaticallyA useful starting point without manual cache ownership
MANUALReuse cached data until your code clears itA coordinated read phase with one owner

With MANUAL, clear each hub's cache once at the beginning of the control . The next eligible read fetches data. Forgetting to clear it can leave encoders apparently frozen while the robot keeps moving. Clearing it inside every subsystem defeats much of the benefit.

Bulk caching covers eligible hub inputs, including encoder data. It does not turn separate I2C sensors such as a Pinpoint or colour sensor into part of the hub's encoder packet. They retain their own update and communication requirements.

Read-only timing OpMode

Configure a motor with an encoder as testMotor. Save this complete SDK 12 file as LoopTiming.java. It sends no motor power commands. With the robot safely supported and the mechanism unpowered, move the encoder by hand to confirm the readings continue to change.

package org.firstinspires.ftc.teamcode;

import com.qualcomm.hardware.lynx.LynxModule;
import com.qualcomm.robotcore.eventloop.opmode.LinearOpMode;
import com.qualcomm.robotcore.eventloop.opmode.TeleOp;
import com.qualcomm.robotcore.hardware.DcMotorEx;
import java.util.List;

@TeleOp(name = "Loop Timing", group = "Tests")
public class LoopTiming extends LinearOpMode {
    @Override
    public void runOpMode() {
        DcMotorEx motor = hardwareMap.get(DcMotorEx.class, "testMotor");
        List<LynxModule> hubs = hardwareMap.getAll(LynxModule.class);
        for (LynxModule hub : hubs) {
            hub.setBulkCachingMode(LynxModule.BulkCachingMode.MANUAL);
        }
        waitForStart();
        if (!opModeIsActive()) return;
        long previousStart = 0;
        long nextReport = 0;
        double sumMs = 0;
        double maxMs = 0;
        int samples = 0;
        while (opModeIsActive()) {
            long start = System.nanoTime();
            if (previousStart != 0) {
                double intervalMs = (start - previousStart) / 1e6;
                sumMs += intervalMs;
                maxMs = Math.max(maxMs, intervalMs);
                samples++;
            }
            previousStart = start;
            for (LynxModule hub : hubs) hub.clearBulkCache();
            int ticks = motor.getCurrentPosition();
            double ticksPerSecond = motor.getVelocity();
            // In a real OpMode: update controllers here using these saved inputs.
            double workMs = (System.nanoTime() - start) / 1e6;
            if (start >= nextReport) {
                telemetry.addData("Encoder (ticks)", ticks);
                telemetry.addData("Velocity (ticks/s)", "%.1f", ticksPerSecond);
                telemetry.addData("Mean interval (ms)", "%.2f",
                        samples == 0 ? 0 : sumMs / samples);
                telemetry.addData("Max interval (ms)", "%.2f", maxMs);
                telemetry.addData("Read/control work (ms)", "%.2f", workMs);
                telemetry.update();
                nextReport = start + 250_000_000L;
            }
            idle();
        }
    }
}

The work measurement excludes the telemetry block; the next cycle's interval includes it. These numbers describe this small , not your full routine. Add the same measurements around your real read, localization, control, and reporting phases to locate its bottleneck.

Reduce traffic

Read each required input once, then share the saved value with control logic and telemetry. Avoid putting a second getCurrentPosition() call inside a telemetry statement. Rate-limit expensive reporting and camera previews independently from the controller.

Check whether a library already manages bulk caching or updates the localizer. Give one layer ownership of those operations. Multiple resets, duplicate I2C updates, or competing motor writes can make worse even when each component works alone.

For output caching, establish what the SDK or wrapper already caches before adding another layer. An overly large change threshold can discard the small corrections needed near a target. Test STOP and transitions to zero after any write-caching change.

Threads and freshness

The SDK coordinates Lynx hardware communication with locks. Moving hardware calls onto more threads does not make a shared transport perform all of them simultaneously. It can also leave the controller reading values from different moments.

Start with one responsive nonblocking loop. Vision frameworks already perform camera processing outside the ordinary OpMode loop. If a separate task is justified, define how it publishes a consistent result, its timestamp, shutdown, and who owns each hardware output. Measure end-to-end response; counting only the faster thread is misleading.

Native communication projects such as Blaze FTC are a separate integration choice. Compare their supported hardware and robot results after establishing a baseline with the SDK.

Sources

  • FIRST: motor bulk-read sample
  • FTC SDK 12 Hardware source — LynxModule cache modes and LynxUsbDeviceImpl transmission lock.
  • FTC SDK 12 RobotCore source — motor velocity units.

Code organization

Keep an FTC project easy to navigate as the robot grows

Formatting

Practices to maintain readability in a team's codebase

On this page

Measure the whole cycleBulk cachingRead-only timing OpModeReduce trafficThreads and freshnessSources