Skip to content

Closed-Loop Mechanisms That Oscillate, Sag, or Stall

learnfrc.com
learnfrc.comAuthor
Veer Bajaj
Veer BajajMaintainer

Symptom A: It oscillates / buzzes at the setpoint

Section titled “Symptom A: It oscillates / buzzes at the setpoint”

Usually too much kP or kD , or a feedback delay. Fix: back kP off (or, per WPILib’s guidance for position loops, increase kD instead) until the oscillation stops — WPILib’s tuning docs describe this qualitatively, not as a fixed percentage cut. Confirm your loop reads a fresh sensor each iteration and that you’re not double-applying feedforward and integral windup. Lower the velocity/acceleration constraints to see if the mechanism is being asked to move impossibly fast.

Symptom B: It sags or drifts under gravity

Section titled “Symptom B: It sags or drifts under gravity”

Missing or wrong gravity feedforward. For an arm , kG must be multiplied by cos(angle) (use ArmFeedforward, which does this); a constant kG on an arm will hold at one angle and sag at others. For an elevator , kG is constant (use ElevatorFeedforward). Tune kG first, raise it until the mechanism just holds against gravity without drifting.

Classic encoder phase / sign error or wrong conversion factor. The controller sees the mechanism move the ‘wrong’ way and commands harder, accelerating the runaway. Diagnose: disable the motor, move the mechanism by hand, and confirm the reported position increases in the commanded-positive direction. Fix the encoder inversion or conversion factor (counts-to-radians/meters) before touching gains.

No motion profile, or profile constraints set too aggressively. Use a ProfiledPIDController / TrapezoidProfile so the controller chases a physically realistic position+velocity+acceleration trajectory instead of an instantaneous step. WPILib recommends starting with modest constraints and increasing.

The correct tuning order (don’t improvise)

Section titled “The correct tuning order (don’t improvise)”

For gravity-loaded mechanisms, WPILib’s sequence is: kG -> kV -> kA -> kP, then add kD only if needed, and find kS via SysId. Tuning kP before kG is the most common reason a mechanism ‘won’t tune’, the feedback is fighting uncompensated gravity.

Run a SysId quasistatic + dynamic routine to get kS/kV/kA/(kG) empirically. These get you most of the way; only then hand-tune kP/kD for the last bit of tracking.

  1. Verify sensor direction/scale by hand.
  2. Add/verify the correct gravity feedforward (arm = cosine, elevator = constant).
  3. Set conservative profile constraints.
  4. Characterize with SysId; apply kS/kV/kA/kG.
  5. Raise kP until it just starts to oscillate, then back it off (or add kD) until the oscillation stops — WPILib doesn’t specify a fixed percentage.
  • Tune in the order kG -> kV -> kA -> kP; tuning kP first while gravity is uncompensated is why mechanisms ‘won’t tune’.
  • Sag means missing gravity FF: arms need cosine-scaled kG (ArmFeedforward), elevators need a constant kG (ElevatorFeedforward).
  • Runaway is almost always an encoder sign/conversion error, verify by hand before changing gains, and use a motion profile to stop slamming.

This lesson was adapted from learnfrc.com.