🚫 Software · Reliability

Motor Stall Detection

When a motor hits a hard stop — an intake jam, an arm at its limit, a claw crushing a game piece — it draws full current and overheats. Stall detection cuts power automatically, protecting the motor and preventing match failures.

1
What Is a Stall
2
Current
3
Velocity
// Section 01
What a Motor Stall Actually Is
A stall happens when a motor is commanded to move but physically cannot — something is blocking it. Understanding the physics helps you write better detection code.
🚫
A stalled motor draws maximum current continuously. The V5 Smart Motor is current-limited to 2.5A, but at stall that current flows entirely as heat with no mechanical output. Within 30–60 seconds of continuous stall, the motor triggers thermal protection and cuts out — leaving you with a disabled mechanism mid-match.

Common Stall Scenarios in VRC

Two Ways to Detect a Stall in Code

The V5 Smart Motor reports both its current draw (get_current_draw() in milliamps) and its actual velocity (get_actual_velocity() in RPM). A stall shows up as one or both of:

The next two sections show both approaches with complete code.

// Section 02
Current-Based Stall Detection
The simplest and most reliable method. If current is above threshold and velocity is near zero for longer than a brief window — it is a stall.

The Current + Velocity Check

Both conditions together prevent false positives: high current alone happens during normal acceleration. Low velocity alone happens when the motor is not being driven. The combination — high current AND low velocity — is a stall.

src/intake.cpp — stall detection for an intake motor
// Stall detection constants — tune these to your robot const int STALL_CURRENT_MA = 2200; // milliamps (2.5A limit, 2.2A = likely stall) const double STALL_VELOCITY_RPM = 5.0; // RPM below this = not moving const int STALL_TIME_MS = 300; // ms above threshold before acting static uint32_t stallStartTime = 0; static bool isStalled = false; bool intakeCheckStall() { int current = intake.get_current_draw(); // milliamps double velocity = abs(intake.get_actual_velocity()); bool highCurrent = (current > STALL_CURRENT_MA); bool lowVelocity = (velocity < STALL_VELOCITY_RPM); if (highCurrent && lowVelocity) { if (stallStartTime == 0) { stallStartTime = pros::millis(); // start the timer } if (pros::millis() - stallStartTime > STALL_TIME_MS) { isStalled = true; } } else { stallStartTime = 0; // reset timer if not stalling isStalled = false; } return isStalled; } // Updated intake control — stops on stall void intakeForward() { if (intakeCheckStall()) { intake.move(0); // stall detected — cut power return; } intake.move(127); }
💡
The 300ms timer prevents false positives. A motor briefly draws high current during startup and acceleration. By requiring the high-current + low-velocity condition to persist for 300ms, you avoid cutting power during normal operation. Tune this window lower (150ms) for mechanisms that jam quickly, higher (500ms) if getting false stall cuts.

Adding Auto-Reverse on Stall

For intakes, cutting power is not always enough — a jammed game element stays jammed. Auto-reversing for a brief moment often clears it:

static uint32_t reverseEndTime = 0; void intakeForwardSmart() { // If we are in the auto-reverse window, keep reversing if (pros::millis() < reverseEndTime) { intake.move(-80); return; } if (intakeCheckStall()) { reverseEndTime = pros::millis() + 400; // reverse for 400ms intake.move(-80); return; } intake.move(127); }
// Section 03
Velocity-Based Detection & Hard Stop Protection
For arms and lifts that hit physical end-stops, detecting velocity alone is cleaner — and protecting hard stops with a position limit prevents the motor from fighting the frame.

Velocity-Only Stall Detection

For arms with position presets using move_absolute(), current-based detection can interfere with the motor's internal PID. Velocity-only detection is simpler and more compatible:

src/arm.cpp — velocity stall detection
const double ARM_STALL_VELOCITY = 3.0; // RPM — arm is effectively stopped const int ARM_STALL_MS = 400; // ms before declaring stall static uint32_t armStallStart = 0; void armManualSafe(int speed) { double vel = abs(arm.get_actual_velocity()); // Only check stall when actively commanding movement if (abs(speed) > 20 && vel < ARM_STALL_VELOCITY) { if (armStallStart == 0) armStallStart = pros::millis(); if (pros::millis() - armStallStart > ARM_STALL_MS) { arm.move(0); // cut power — arm is against a hard stop return; } } else { armStallStart = 0; } arm.move(speed); }

Protecting Hard Stops With Position Limits

Even better than detecting a stall after it happens: prevent the arm from ever reaching the hard stop in the first place. Use the motor's encoder to define soft limits:

// Define safe position range during initialize() after zeroing the arm const int ARM_MIN = 0; // floor position (encoder ticks) const int ARM_MAX = 1800; // max height — tune to your robot void armManualLimited(int speed) { int pos = (int)arm.get_position(); // Block downward movement at floor limit if (pos <= ARM_MIN && speed < 0) { arm.move(0); return; } // Block upward movement at height limit if (pos >= ARM_MAX && speed > 0) { arm.move(0); return; } arm.move(speed); }
⚠️
Calibrate arm zero position in initialize() by running the arm to its lowest position (physical stop at low speed), then calling arm.tare_position(). This sets the encoder to 0 at the floor. All soft limits are then relative to this calibrated zero — consistent across every power cycle.

Which Method to Use

⚙ STEM Highlight Electrical Engineering: Motor Stall Physics & Thermal Management
A stalled V5 motor draws maximum current (2.5A) but produces zero rotational work — all electrical power converts to heat via I²R losses in the windings. Power = I²R; at 2.5A stall, a motor dissipates all 11W as heat. The motor’s thermal mass (specific heat × mass) determines how quickly temperature rises — a lightweight motor reaches thermal limits faster than a heavy one under the same power. The motor’s internal temperature sensor implements thermal runaway protection — exactly the same principle used in lithium battery management systems.
🎤 Interview line: “Motor stall converts all electrical power to heat via I²R losses — at 2.5A stall current, the motor dissipates 11W as heat with zero useful work output. Our stall detection cuts power before the thermal limit, implementing software-based thermal protection analogous to the hardware thermal cutoffs in battery management systems.”
🔬 Check for Understanding
A V5 motor stalls for 3 seconds. Its current is 2.5A and resistance is approximately 2Ω. How much energy was dissipated as heat?
5 joules
7.5 joules
37.5 joules
15 joules
Related Guides
🎰 Intake Design →⚡ Concurrent Actions →⚙️ Finite State Machine →🤖 Full Robot Code →
← ALL GUIDES