Tuning Methodology
You know what every gain does. Now you need a repeatable process for finding the right values quickly — in a two-hour practice session or a ten-minute competition pit window. This lesson is a systematic field guide: one ordered sequence, specific starting values, a symptom-to-fix diagnostic tool, and the live-tuning pattern that lets you adjust gains without redeploying code.
By the end of this lesson, you will:
- Follow the 2910 six-step tuning sequence from feedforward through kI without guessing the order
- Start with concrete numerical values for each gain and adjust by prescribed ratios
- Use the SmartDashboard live-tuning pattern to change gains in under 30 seconds without redeploying
- Map any observed behavior — oscillation, sluggishness, steady-state error, noise — to the specific gain change that fixes it
- Distinguish D-term noise amplification from kP-caused oscillation using the published-DOutput channel
- Know when to stop tuning — the "good enough" threshold for competition
The Cardinal Rule: Feedforward Before PID
The most common tuning mistake is reaching for kP before feedforward is working. When feedforward is absent or wrong, the PID controller must do all the work — driving to the setpoint, holding against gravity, overcoming friction, fighting battery sag. The gains required to do all that simultaneously are aggressive and unstable. The result is a twitchy, noisy, or oscillating mechanism that seemingly cannot be tuned.
The correct sequence is: feedforward first, PID second. When feedforward provides 90% of the required output — smooth approach, minimal steady-state error, good hold against gravity — the PID controller only needs modest gains to clean up the remaining 10%. The controller is inherently more stable, the gains are smaller, and tuning converges in minutes instead of hours.
The robots that spend three hours fighting kP — oscillating arm, aggressive kD that causes buzzing, kI that overshoots every time — almost always have one thing in common: their feedforward is wrong or missing. When I check the SmartDashboard and see FFVolts publishing 0 while the PID output is 8V, it's all PID, no feedforward. The fix is to characterize the mechanism, add the feedforward model, and then add a small kP of 0.01. Often the mechanism works better than it ever did with high-gain PID alone. Feedforward is not optional for competition-quality control.
The Six-Step Tuning Sequence
Click through each step. Every step builds on the one before it. Do not skip ahead.
Before touching PID gains, verify feedforward is correct in isolation. Set kP=0, kI=0, kD=0. Command the mechanism to a target velocity or position. The feedforward-only response tells you the baseline quality of your physics model.
For velocity control (flywheel, drive motor): publish actual velocity and target velocity to SmartDashboard. The steady-state actual velocity should be within 5–10% of the target. If it undershoots by 20%+, kV is too small. If it overshoots, kV is too large. Adjust and retest before adding kP.
For position control (arm, elevator): run the gravity-only hold test from Lesson 4 — command velocity=0 at multiple positions. If the mechanism holds still everywhere (arm) or at any height (elevator), feedforward is working. If it drifts, kG is wrong.
Pass criteria:
- Velocity mechanisms: steady-state error ≤ 10% of target at normal operating speed
- Position mechanisms: mechanism holds against gravity without drifting when velocity=0 is commanded
With feedforward working and kD=0, kI=0, start increasing kP from a very small value. Command the mechanism to a setpoint, watch it move, and observe the response. Keep the SmartDashboard error channel and actual position/velocity visible.
What you're looking for: The kP value at which the mechanism first begins to oscillate (wobble) around the setpoint instead of settling cleanly. This is called the ultimate gain (Ku). Record it — it becomes the reference for all subsequent gains.
Increase by these ratios: 0.001 → 0.002 → 0.005 → 0.01 → 0.02 → 0.05 → 0.1 (double or 2.5× at each step). Between each step: command a step input, observe the response, confirm no oscillation before increasing again.
How oscillation looks: The mechanism reaches the setpoint and then bounces back and forth. On SmartDashboard: the error channel oscillates positive and negative at a consistent frequency instead of decaying to zero.
Stop condition: First consistent oscillation. This kP value is Ku. Do not go higher.
You found Ku (the oscillation threshold). Set kP to 50–60% of Ku as your working gain. This gives headroom for the kD and kI additions to come without re-triggering oscillation.
Command the mechanism again. With kP at 50% of Ku, you should see one of these responses:
- Slightly underdamped (minor overshoot, settles quickly): Good. This is the right ballpark. Proceed to kD.
- Heavily overdamped (very slow, no overshoot): kP is too conservative. Try 60% or 65% of Ku. Aim for slight overshoot — overdamped is slower than needed.
- Still oscillating: Rare at 50% of Ku. Back off to 40%. Reconfirm your Ku measurement.
The acceptable range at this step: The mechanism reaches the setpoint within 2× the desired settling time, with ≤ one overshoot. You'll tighten this with kD next.
kD brakes the mechanism as it approaches the setpoint. Start small and increase until overshoot disappears or reaches an acceptable level.
Publish DOutput separately to SmartDashboard. This is the critical diagnostic for kD tuning. During a normal approach to the setpoint:
- DOutput should be negative (opposing motion) during the approach phase
- DOutput should be near zero at steady state
- DOutput oscillates rapidly when mechanism is still → kD is amplifying encoder noise → reduce kD by 50%, consider adding a low-pass filter to the encoder reading
Increase kD in steps of kP/10 until overshoot is acceptably small (≤ 5% of setpoint for most FRC mechanisms). If increasing kD causes high-frequency buzzing or the mechanism vibrates audibly at rest, you've hit the noise limit — back off to the last quiet value.
Good kD: Mechanism approaches setpoint smoothly, minimal overshoot, no buzzing. DOutput is smooth and decaying, not oscillating.
After Steps 3 and 4, check if steady-state error exists. Let the mechanism settle for at least 3 seconds. Read m_pid.getError() or publish it to SmartDashboard.
If error is within your tolerance: Skip kI entirely. kI=0 is not a failure — many FRC mechanisms with good feedforward never need kI. Every mechanism without kI is a mechanism without windup risk.
If error is outside tolerance (arm stops 3° short, elevator 5 cm short):
- Set
setIntegratorRange(−maxI, maxI)where maxI = 0.5–1.0V before adding kI - Set
setIZone(tolerance × 3)so I only accumulates near the setpoint - Command the mechanism, let it settle. The I term should creep it the last 3° slowly. If it overshoots, reduce kI by 50%.
- Re-test windup: hold mechanism away from setpoint for 10 seconds, release. Verify no large overshoot spike. If there is one, reduce integrator range.
The gains that work at bench conditions must also work in competition conditions. Validate before committing to constants.
Validation tests:
- Battery range: Test at fresh battery (12.5V) and at discharged battery (11V). If the mechanism behaves measurably differently, feedforward is not fully compensating for battery sag. Check that
setVoltage()is used, notset(). - Step from different starting positions: Command from minimum angle to maximum, then back. Both directions should settle within tolerance with similar settling time.
- Repeat consistency: Command the same setpoint 10 times in a row. All 10 final positions should be within your tolerance band. Any inconsistency suggests the mechanism has a mechanical binding point.
- Loaded vs. unloaded: For mechanisms that carry game pieces, test with a game piece present. If kG changes significantly with load, consider two gain sets (Lesson 4 stretch goal).
- Temperature: Test after 20 minutes of operation. Gearbox oil thins as it warms — friction may decrease, shifting the tuning optimum slightly.
The "good enough" bar: The mechanism reaches within tolerance 95%+ of the time under full competition simulation. It is not required to be perfect — it is required to be reliable.
Concrete Starting Values by Mechanism
These are not final values — they are starting points that put you in the right order of magnitude. Use the sequence above to refine from here.
| Mechanism | kP start | kD start | kI start | Notes |
|---|---|---|---|---|
| Arm (degrees, roboRIO) | 0.01–0.05 | kP/10 | kP/100 if needed | High kG via ArmFeedforward reduces kP needed. Encoder noise is common — keep kD conservative. |
| Elevator (meters, roboRIO) | 0.5–2.0 | kP/10 | kP/100 if needed | Position in meters produces small errors (0.01 m), so kP needs to be larger to generate meaningful output. |
| Flywheel velocity (RPS) | 0.001–0.005 | 0 (rarely needed) | 0 (rarely needed) | With good kV feedforward, kP alone is often sufficient. kD is noisy for velocity control. Skip kI unless RPM drift is visible match-to-match. |
| Swerve steering (Phoenix 6 Slot0, rotations) | 30–80 | 0.5–2.0 | 0 | Units are V/rotation-of-error. High gains needed at 1 kHz because errors are small fractions of a rotation. Test on blocks before driving. |
| Turn-to-angle (degrees, roboRIO) | 0.02–0.08 | kP/8 | 0 (use continuous input) | enableContinuousInput required. kI usually not needed — heading error is eliminated by PathPlanner's built-in tolerance. |
Live Tuning: Changing Gains Without Redeploying
Redeploying takes 30–60 seconds per iteration. A tuning session with ten gain changes takes 10 minutes just in deploy time. The SmartDashboard live-tuning pattern eliminates this — gains are read from NetworkTables each loop, updated in the PIDController, and take effect immediately.
SmartDashboard.putNumber("Arm/Tune/kP", Constants.Arm.kP);
SmartDashboard.putNumber("Arm/Tune/kI", Constants.Arm.kI);
SmartDashboard.putNumber("Arm/Tune/kD", Constants.Arm.kD);
// ── In periodic(): read and apply every loop ──────────
@Override
public void periodic() {
// Read live gains from SmartDashboard
double kP = SmartDashboard.getNumber("Arm/Tune/kP", Constants.Arm.kP);
double kI = SmartDashboard.getNumber("Arm/Tune/kI", Constants.Arm.kI);
double kD = SmartDashboard.getNumber("Arm/Tune/kD", Constants.Arm.kD);
// Safety: clamp to sane maximum to prevent dangerous values
kP = MathUtil.clamp(kP, 0, 1.0);
// Apply if changed (avoid unnecessary PID state resets)
if (kP != m_pid.getP() || kI != m_pid.getI() || kD != m_pid.getD()) {
m_pid.setPID(kP, kI, kD);
}
// Publish diagnostics for tuning visibility
SmartDashboard.putNumber("Arm/AngleDeg", getAngleDegrees());
SmartDashboard.putNumber("Arm/Error", m_pid.getError());
SmartDashboard.putBoolean("Arm/AtTarget", m_pid.atSetpoint());
}
In Shuffleboard, the Arm/Tune/kP, kI, kD channels appear as editable number fields. Double-click a value, type the new gain, press Enter. The change takes effect on the next 20 ms loop cycle — under half a second. A full tuning session goes from 10-minute deploy cycles to 10-second test cycles. When you find values you like, copy them from SmartDashboard into Constants.java and remove the live-tuning code before competition (or guard it with a Constants.kTuningMode boolean).
Symptom-to-Fix Diagnostic Tool
Select what you're observing and get the specific fix.
Distinguishing kP Oscillation from kD Noise
Two different problems look similar on SmartDashboard — oscillating error — but have opposite fixes. Applying the wrong fix makes things worse.
- kP oscillation: The mechanism physically bounces back and forth. The frequency is low (0.5–5 Hz). The error channel oscillates with large amplitude. DOutput is near zero at rest. Fix: decrease kP or increase kD.
- kD noise amplification: The mechanism appears still or nearly still, but the error channel shows rapid small oscillations and the motor makes a buzzing sound. The frequency is high (20–200 Hz). DOutput oscillates rapidly even when error is nearly constant. Fix: decrease kD; the D term is responding to electrical noise in the encoder signal, not to actual motion.
The published DOutput channel is the diagnostic. If DOutput is large and rapidly alternating while the mechanism is at rest, kD is amplifying noise. If DOutput is near zero while the mechanism oscillates visibly, kP is the problem.
When a mechanism oscillates, the intuition is "add more damping." This is correct — up to a point. If kD is increased past the noise threshold, the encoder noise is amplified into audible motor buzzing, the mechanism vibrates, and the motor draws constant high current even at rest. Teams then reduce kP to stop the buzzing, which slows response, and the oscillation returns at lower amplitude. The correct approach: reduce kP first to confirm the oscillation frequency drops, then increase kD carefully until oscillation is gone without introducing buzzing. These are two separate adjustments, not one slider.
The night before a competition, confirm all of these:
- Command each mechanism 10 times to its competition setpoints (scoring position, intake position, etc.). All 10 final positions must be within tolerance. Inconsistency at this stage usually indicates mechanical binding or a loose encoder — a hardware fix, not a gain fix.
- Simulate a game: run the full TeleOp sequence as the driver would. If a mechanism is commanded repeatedly at high frequency, verify the integral doesn't wind up between commands. The
resetPID()call ininitialize()is the preventive measure. - Check that live-tuning code is either removed or guarded by a
Constants.kTuningMode = falseflag. Running SmartDashboard.getNumber() for gains in every periodic() loop adds unnecessary CPU time and NetworkTables traffic during competition. - Verify that published gain values in SmartDashboard match what's in Constants.java. If a tuning session changed values live, those changes live only in SmartDashboard — they're gone on the next deploy unless you also updated the code.
Knowledge Check
1. You run the sequence: kP=0 → observe feedforward-only response → steady-state velocity is 30% below target. What should you do before touching any PID gain?
2. During tuning, you find Ku = 0.08 (kP at which oscillation first appears). You set kP = 0.04 (50% of Ku). The mechanism now settles without oscillation but takes 2.5 seconds to reach the setpoint. What is the most likely fix?
3. A motor makes an audible high-frequency buzzing sound at rest. The mechanism is not visibly moving. SmartDashboard shows the DOutput channel oscillating between +1.2V and −1.2V at high speed while the error reads near 0. What is the cause and fix?
Full Tuning Session — One Mechanism, End to End
- Choose a mechanism on your robot with an encoder and a setpoint. Add the live-tuning code from this lesson to its subsystem's periodic(). Add all diagnostic channels: error, pOutput (kP×error), iOutput, dOutput, total output, and actual vs. target value. Open Shuffleboard and confirm all channels appear.
- Set kP=0, kI=0, kD=0 and verify feedforward is working per Step 1's pass criteria. Adjust kS, kV, or kG if needed. Do not proceed to kP until the feedforward-only result is within 10% of target or the mechanism holds against gravity correctly.
- Follow Steps 2–5 of the tuning sequence. At each step, record your gain values in a table: Step, kP, kI, kD, settling time (seconds), final error (units), observed behavior. This table becomes your team's tuning history for this mechanism — useful when gains drift after rebuild.
- Run the validation from Step 6. Test at two battery voltage levels (fresh vs. after 5 minutes of driving). Command the mechanism 10 times to the same setpoint. Measure final position each time and compute the mean and standard deviation. A standard deviation within your tolerance band is a well-tuned mechanism.
- Stretch goal: After finding good gains, intentionally introduce the three failure modes from Lesson 5: overdamped (reduce kP by 80%), underdamped (increase kP by 200%), and steady-state error (set kI=0 and reduce kP until error appears). Then restore correct gains and time how long each recovery takes using the six-step sequence. This drill builds the muscle memory for competition-day tuning.