Unit 8 · Lesson 1

Why Open-Loop Control Isn't Enough

In Unit 5 you wrote m_motor.set(0.5). The motor spun. It felt like control. It wasn't — not really. Open-loop control is a command without awareness. It cannot know what the motor is actually doing, and it cannot adapt when the physical world doesn't cooperate. This lesson explains what open-loop control is, where it breaks down, and why the rest of Unit 8 exists.

By the end of this lesson, you will:

  • Define open-loop control and explain why it cannot guarantee a specific output
  • Identify the three main sources of open-loop error in FRC mechanisms: battery voltage sag, load variation, and friction
  • Explain why percent-output commands produce different physical speeds at different battery voltages
  • Describe the four levels of the control hierarchy — open-loop, feedforward, feedback, combined — and what each adds
  • Identify at least three FRC mechanisms where open-loop control produces measurably wrong behavior

What "Open-Loop" Actually Means

A control loop is open when there is no feedback path from the output back to the input. You send a command. The mechanism executes it. The mechanism never reports back whether it achieved what you asked for. The loop has no closing path — hence "open."

In FRC code, open-loop control is m_motor.set(0.5): command 50% output and assume the motor does what 50% output means. The assumption is the problem. That 50% command does not control speed, does not control position, does not control force. It controls one thing: the fraction of battery voltage applied to the motor. And that fraction produces wildly different physical outcomes depending on conditions.

💡 Open-loop is not always wrong

Open-loop control is appropriate for mechanisms where the output just needs to be "running" or "not running" — an intake roller that picks up game pieces, a conveyor that moves pieces toward a shooter. These mechanisms don't require a specific speed or position; any reasonable speed works. The problems begin when your mechanism needs to reach a specific speed (shooter flywheel), a specific position (arm angle, elevator height), or maintain constant behavior across matches (autonomous routines).

The Three Open-Loop Failure Modes

👇 Click each card to see what open-loop can't account for
🔋
Battery Voltage Sag
tap to learn
Voltage × output = actual voltage

A fully charged battery supplies ~12.5V. After 2 minutes of driving, it may be at 11.0V. At 9.5V late in the match, the same set(0.5) produces 4.75V instead of 6.25V — 24% less. Your shooter spins slower. Your autonomous path is shorter. Your arm falls short of its target. Open-loop code is unaware of any of this.

⚖️
Load Variation
tap to learn
Same voltage, different load = different speed

A shooter flywheel at 0.6 output with an empty chamber spins at 4000 RPM. With a game piece loaded and pressing against the wheels, the same command might produce 3400 RPM — 15% slower. The ball arrives at the goal on a different trajectory. Open-loop doesn't know a piece is present; closed-loop would immediately compensate.

🔧
Friction & Wear
tap to learn
Same command, different robot

A freshly assembled gearbox with new bearings has less friction than one after 50 matches of competition. Grease migrates. Bearings wear. Gear mesh tightens as bolts settle. The open-loop command that worked at bag night might produce 8% less speed at week six because the mechanical system has changed. Open-loop is calibrated once and drifts forever.

The Voltage Sag Problem — Made Concrete

Adjust the sliders below to see how the same percent-output command produces dramatically different actual voltages — and therefore different actual speeds — depending on battery state and motor demand.

Open-loop output: what 50% actually delivers
Battery voltage 12.5 V
Percent output command 50%
Motor load (friction) 0.5 V
Applied voltage:
Net driving voltage:
Est. speed (% of max):
vs. fresh battery, same command:
Adjust battery voltage toward the end-of-match range (9–10V) with a 50% command and watch what happens to the actual applied voltage. This is why a shooter tuned on a fresh battery misses shots late in the match.

Where It Breaks in Competition

Three mechanisms illustrate exactly where open-loop fails — and why closed-loop control exists:

🏀 Shooter flywheel — open-loop
Command 70% output. At match start (12.5V), the wheel spins at 4100 RPM. First match point shot: game piece loads, RPM drops to 3800 during shot. Battery is now at 11.2V — second shot is at 3650 RPM. The ball has a progressively flatter arc with each shot in the match. By Q4, you're missing the goal entirely.
🏀 Shooter flywheel — velocity PID
Command 4000 RPM. PID detects RPM dropped during shot, instantly increases voltage to compensate. Battery sag is corrected cycle-by-cycle. Every shot — first and last of the match — flies on the same arc to the same location. Consistency is built into the physics of the loop.
💪 Arm — open-loop
Command 30% output to raise the arm. At bag night with new bearings, it stops at 85°. After 20 hours of use, friction has increased enough that the same command stalls at 78°. You lower the current limit after week 2 to prevent breaker trips — now it only reaches 70°. Every calibration session is fighting a moving target.
💪 Arm — position PID
Command 85°. The PID loop compares measured angle to target, increases voltage when friction resists, decreases when it approaches to avoid overshoot. Wear increases friction? The loop applies more force. Battery sags? Same. The arm arrives at 85° (within tolerance) on the hundredth match as reliably as the first.

The Control Hierarchy

Open-loop → feedforward → feedback → combined isn't a list of options. It's a progression. Each level adds a layer of intelligence that addresses a specific failure mode of the level below it. Unit 8 walks this hierarchy from bottom to top.

Level 1: Open-Loop
Command a fixed fraction of battery voltage. Fast to write. Unpredictable behavior. Output varies with every change in battery state, load, or wear. Appropriate only for mechanisms where any speed is acceptable.
Level 2: Feedforward
Command a physics-based predicted voltage for a desired speed. Uses a model of the motor's behavior (kS, kV, kA) to predict the voltage needed. 90–95% accurate. Doesn't correct for errors. Covered in Lesson 2.
Level 3: Feedback (PID)
Command based on measured error between desired and actual output. Reactive: sees the error and corrects it. Slow to respond if used alone because it must first observe the error before acting. Covered in Lessons 5–7.
Level 4: Combined
Feedforward predicts; PID corrects the remaining error. Feedforward does 90% of the work immediately. PID cleans up the 10% that physics prediction couldn't capture. Responsive, accurate, and consistent. The 2910 standard for any controlled mechanism. Covered in Lesson 10.
🔍 LRI Perspective: "The control tier is visible in the robot's behavior"

I can identify a team's control approach in the first 30 seconds of their driver practice. Open-loop robots have a drift: each shot is slightly different from the last, autonomous paths are shorter late in the match, mechanisms hesitate or overshoot. Feedforward robots snap into position quickly but might oscillate slightly at steady state. Combined feedforward + PID robots are the ones where the arm just stops at the right position, every time, without a visible approach phase. The difference isn't magic — it's understanding the physics of what the motor is actually doing and writing code that accounts for it.

🔌 System Check — Observe Open-Loop Drift in Action

Before building any feedforward or PID controller, observe the open-loop problem directly:

  • Command your shooter flywheel at 60% output (m_motor.set(0.6)). Publish getVelocity().getValueAsDouble() * kWheelCircumference to SmartDashboard. Record the velocity at three points: fresh battery, after 5 minutes of driving, after 10 minutes. The drop should be visible — 5–15% reduction is typical.
  • Command your arm to move to a setpoint with open-loop. Record the final angle with a fresh battery. Then lower the battery to 11V (drive aggressively to discharge it). Command the same setpoint. Record the new final angle. The difference is your open-loop position error due to battery sag.
  • This is not a debugging exercise — this is calibration. You need to see the problem to understand why feedforward exists. Teams that never observe open-loop failure don't understand what they're building when they implement feedforward.

Knowledge Check

1. A robot's autonomous routine drives 2 meters forward and scores a game piece in testing. At competition, the same code drives only 1.82 meters. No code was changed. What is the most likely cause?

  • A The competition field is slightly longer than the practice field.
  • B The competition battery is more discharged than the practice battery, producing less voltage for the same percent-output command — resulting in less actual motor force and a shorter actual drive distance.
  • C The wheel circumference constant in code is wrong.
  • D The autonomous routine was interrupted by the FMS.

2. The team programs an intake roller at set(0.4). It works reliably at the start of a match. By Q3, the intake occasionally misses game pieces. No code changed. What systemic cause should be investigated first?

  • A A software bug in the command scheduler that occasionally skips the intake command.
  • B Battery voltage sag. At Q3, the battery may be at 10–11V instead of 12.5V. The same set(0.4) command produces ~15% less actual motor force — enough to reduce intake reliability for borderline game piece interactions.
  • C The game pieces are heavier in Q3 matches than Q1 matches.
  • D The joystick deadband changed between Q1 and Q3.

3. Feedforward control is described as "predictive" and PID as "reactive." Why is combining both better than using either one alone?

  • A Combining them averages out their individual errors, producing a more stable output.
  • B Feedforward immediately applies 90% of the required voltage based on physics, so the motor starts moving correctly before any error accumulates. PID then corrects the remaining 10% of error caused by imperfect modeling, wear, and disturbances. Together: fast response from feedforward, precision from PID — without the oscillation of high-gain PID alone or the drift of feedforward alone.
  • C Combined control requires less tuning than either approach individually.
  • D PID alone is not allowed by FRC rules for drivetrain motors.
💪 Practice Prompt

Observe Open-Loop Variability

  1. Choose a mechanism on your robot — a drive motor, a shooter flywheel, or an arm motor. Write a simple open-loop command: m_motor.set(0.5). Publish the actual velocity to SmartDashboard using the encoder feedback. Measure the velocity three times: immediately after enabling, after 2 minutes of heavy driving, and after 5 minutes. Record the three values. Calculate the percentage change from start to end.
  2. Now calculate what feedforward would need to compensate: if the battery dropped from 12.5V to 10.5V and you wanted to maintain the same physical speed, what percent-output command would be needed at 10.5V to match the original output at 12.5V? (Hint: output = desired_voltage / battery_voltage. Desired voltage = 0.5 × 12.5 = 6.25V. At 10.5V: 6.25 / 10.5 = 59.5%.)
  3. Look at the four levels of the control hierarchy in this lesson. For each mechanism on your robot (intake, shooter, arm, elevator), decide which level is appropriate and write a one-sentence justification. Not all mechanisms need Level 4 — the intake might be fine at Level 1. The decision should match the precision requirements of the mechanism.
  4. Stretch goal: Research WPILib's MotorSafety watchdog — what happens if a motor is not commanded within 100 ms? How does this interact with open-loop control? This is a real safety feature that prevents runaway motors from stuck code. Understand it before it surprises you at competition.