Home · ← Blog posts

Robotics

PID Tuning Guide for Robots: How to Tune Kp, Ki and Kd (FTC Examples)

Probot Studio

What we'll do

  1. See what P, I and D each do, in one diagram.
  2. Tune the gains in the right order: P first, then D, and I only if you need it.
  3. Try it yourself with sliders.
  4. Look at short FTC Java examples for an arm and for driving straight.

Why it helps: a well-tuned PID gets your robot to the target without swinging past it or stalling a couple of centimeters short, and it lands in the same spot every autonomous run.

Three sleds drive to a target 60 cm away: slowly with low P, swinging around the target with high P, fast and smooth with P plus D

Every graph here is a simulation of the same simple model: a sled driving 60 cm forward. Your numbers will be different, but the behavior is the same.

PID at a glance

PID flow diagram: the error feeds the P, I and D terms, their sum is the motor power, the sensor measures the new position and the loop repeats

Every loop, PID looks at the gap between the target and the measured value (the error) and turns it into motor power. Finding good gains for it is what PID tuning means:

power = Kp × error + Ki × (sum of error) + Kd × (rate of change of error)

You need a sensor: drive encoders for distance, an IMU for heading, a motor encoder or angle sensor on an arm.

Before you start

Tuning step by step

1. P only

Position over time: with low Kp the sled never reaches the target, with mid Kp it overshoots a little and settles, with high Kp it swings around the target

Set Ki and Kd to zero. Start Kp small (about 0.1 to 0.2 power at the largest error) and raise it step by step. You want a robot that gets there fast, overshoots a little once or twice and settles.

2. Add D to brake

Position over time: with Kd at zero the sled oscillates, with mid Kd it settles fast with almost no overshoot, with high Kd it creeps toward the target

Keep Kp fixed and raise Kd in small steps; overshoot drops. You can then push Kp a little higher. Going back and forth between the two a few times is normal.

3. Last, and only if needed: a little I

Position over time with a constant load: P and PD stop 5 cm short of the target, PID overshoots a little and settles on the target

If the robot always stops the same small distance short (steady-state error), add a very small Ki and raise it slowly. I can add some overshoot.

Most mechanisms don't need I at all; the FTC community guide Game Manual 0 calls it the least important term for FTC. If the cause is gravity or friction, try feedforward first (see the arm example). If you do add I, limit the integral.

Try it yourself

This is a sandbox with Kp, Ki and Kd sliders; it needs JavaScript. If it didn't load, the three graphs above show the same experiments.

Suggested order: start with P only and tame the oscillation with Kd. Then tick Add a constant load, watch the sled stop short, and add a small Ki.

What you see and what to do

What you see Likely cause What to do
Swings back and forth around the target Kp high, Kd low Lower Kp or raise Kd
Very slow Kp low Raise Kp
Raised Kp, speed didn't change Motor already at full power Check whether power is pinned at ±1
Stops a little short of the target Gravity, friction Feedforward (kS, kG on an arm), then a small Ki
Jitters at the target Kd high, noisy sensor Lower Kd, add a tolerance band
Big overshoot on large target changes Integral windup Limit the integral, reset it when the target changes
Behaves differently late in the match Battery voltage drops Tune with a half-charged battery

FTC: holding an arm at an angle

An arm rises from horizontal to vertical; the bar and cosine chart show the holding power is largest when level and zero when upright

Gravity always pulls the arm down. With PID alone, the arm has to sag a little before there is any error to push back. That's why you tune gravity feedforward first and build PID on top of it.

  1. Kp, Ki, Kd = 0. Bring the arm level and let go; raise kG until the arm stays where it is.
  2. Then P, D, and I if needed. With good feedforward you usually won't need I.
// Every loop. DcMotorEx arm (RUN_WITHOUT_ENCODER), ElapsedTime timer.
// Gains start at 0; you tune them. At start: lastAngle = first reading, integral = 0.
double dt = timer.seconds(); timer.reset();
double angle = (arm.getCurrentPosition() - LEVEL_TICKS) / TICKS_PER_DEGREE;  // degrees from horizontal
double error = targetAngle - angle;

integral += error * dt;
if (kI > 0) integral = Range.clip(integral, -0.2 / kI, 0.2 / kI);  // integral limit
double derivative = dt > 0 ? -(angle - lastAngle) / dt : 0;  // from the measurement: no kick when the target changes
lastAngle = angle;

double feedforward = kG * Math.cos(Math.toRadians(angle));  // gravity feedforward
double power = feedforward + kP * error + kI * integral + kD * derivative;
arm.setPower(Range.clip(power, -1, 1));

Set integral = 0 when the target angle changes. The motor runs in RUN_WITHOUT_ENCODER so it doesn't fight the SDK's own velocity control; the encoder still reads.

FTC: driving straight and holding heading

Robot at 179 degrees, target -179 degrees. Without wrapping the error is -358 degrees and the robot turns almost a full circle; wrapped, it turns 2 degrees

"Drive 60 cm forward" is two PIDs: a distance PID turns the average of the left and right encoders into forward power, and a heading PID (usually just P) turns the IMU angle into a turn correction.

ticks_per_cm = (ticks per revolution × gear ratio) / (π × wheel diameter in cm)
double headingError(double target, double measured) {
    double e = target - measured;
    while (e > 180) e -= 360;    // wrap to -180..180
    while (e <= -180) e += 360;
    return e;
}
// In the loop:
double heading = imu.getRobotYawPitchRollAngles().getYaw(AngleUnit.DEGREES);
double turn = kPheading * headingError(targetHeading, heading);
left.setPower(drivePower + turn);
right.setPower(drivePower - turn);

When is the SDK's built-in PID enough?

The 6 most common mistakes

  1. Copying another team's numbers: gains depend on units, gearing and weight.
  2. Changing all three gains at once: go P, then D, then I if needed.
  3. Fixing everything with I: use feedforward for gravity and friction; limit I.
  4. Ignoring dt: loop time varies, so integral and derivative need dt.
  5. Not wrapping heading error: the robot spins the long way around.
  6. Tuning Kp while the motor is maxed out: if output is at ±1, Kp changes nothing.

Sources

Accessed October 17, 2026