Skip to content
2 min read
In progress

Loop Overruns and Watchdog Warnings

Why 'Loop time of 0.02s overrun' appears, how the scheduler watchdog points at the culprit, and how to keep the 20 ms budget.

Lesson
44 / 51
Reward
+10XP

Sign in to track progress, earn XP, and save lessons.

TimedRobot runs your periodic code 50 times a second -- a hard 20 ms budget per loop shared by every subsystem periodic() and every command. Blow that budget and the DS prints a watchdog message (Watchdog not fed within 0.020000s and/or Loop time of 0.02s overrun), control gets choppy, and odometry drifts because updates arrive late.

The watchdog tells you who

WPILib's watchdog and epoch reporting in the command scheduler print which command or subsystem consumed the time when a loop overruns. Read that output first -- it usually names the offender directly, so you don't have to guess.

Cause 1: Print statements

System.out.println is surprisingly expensive and should be removed for competition -- it is a classic overrun source. Replace debugging prints with structured Epilogue logging.

Cause 2: Over-eager logging and repeated CAN queries

The CPU cost of logging is rarely the logging itself -- it's calling expensive methods to fetch the data, most often querying devices on the CAN bus. WPILib's guidance: if logging causes overruns, reduce the number of logged fields, or restructure to read and cache device data once per loop and log the cached value instead of re-querying inside every log call. On Phoenix 6 devices you can batch this with BaseStatusSignal.refreshAll(...):

// Bad: three separate CAN reads per loop just for logging
log(motor.getPosition().getValueAsDouble());
log(motor.getVelocity().getValueAsDouble());
log(motor.getMotorVoltage().getValueAsDouble());
// Better: refresh a status-signal batch once, then read cached values
BaseStatusSignal.refreshAll(posSig, velSig, voltSig);

Cause 3: Blocking calls in the loop

Never Thread.sleep(), busy-wait, or run an unbounded while loop in periodic or command code -- everything shares the one 50 Hz thread. Persisting SPARK parameters (PersistMode.kPersistParameters) blocks CAN, so do it only at setup, never per loop.

Cause 4: Heavy vision or math on the RIO

Run AprilTag detection on a coprocessor (Limelight, an Orange Pi with PhotonVision), not the RIO. Offload motion profiling to the motor controller (Motion Magic) when you can.

Confirm the fix

Check roboRIO CPU% in the DS or the DS log viewer before and after your change. A healthy robot sits comfortably under budget with headroom; if CPU is pinned near 100%, you'll keep overrunning under load. Reproduce the overrun reliably (it often only appears when many mechanisms move at once), apply one change, and confirm the watchdog warnings stop in the same scenario.

Spot an error or something out of date?Log in to suggest an edit

Key takeaways

  • TimedRobot gives all periodic/command code a shared 20 ms (50 Hz) budget; exceeding it triggers watchdog/overrun warnings.
  • The scheduler watchdog reports which command or subsystem caused the overrun -- read it before guessing.
  • Remove print statements and avoid re-querying CAN devices for logging; cache device reads once per loop (e.g. BaseStatusSignal.refreshAll on Phoenix 6).
  • Never block (sleep, busy-wait, persist flash) in the loop; offload vision to a coprocessor and profiling to the motor controller.

Lesson quiz

Required

Answer all 3 questions correctly to complete this lesson.

01.TimedRobot runs periodic code 50 times a second, giving every subsystem and command a shared budget. How long is that per-loop budget?

02.What is the CPU cost of logging usually caused by when it produces loop overruns?

03.Where should AprilTag detection run to avoid loop overruns?

Answer every question to submit.

All 51 lessons in Programming, Controls & Sensors