VEX V5 PYTHON | 05.3 PID STABILITY
Guide objective
Recognize windup, noisy derivative output and saturation, then test bounded integral and filtering policies before applying them to movement.
01 | THREE PROBLEMS THAT LOOK LIKE BAD TUNING
A PID loop can behave poorly even when its basic equation is correct. Three common causes are stored integral error, noisy derivative estimates and commands that exceed the available output.
| Problem | Typical clue | First question |
|---|---|---|
| Integral windup | Correction persists after the original error has disappeared | Has stored error grown while output was limited? |
| Derivative noise | Output chatters as small readings change | Is differentiating noise creating a large D term? |
| Output saturation | Requested output is larger than the allowed command | Is the controller staying at its cap? |
Log error, P, I, D, the unclamped request and the final command. A final command of 25% alone does not tell you why the controller asked for it.
02 | WHAT INTEGRAL WINDUP MEANS
The integral remembers error over time. That can help correct a small persistent offset, but it can also store a correction the robot could not carry out.
Imagine a simulated robot with an error of +80 mm for two seconds. The integral grows to 160 mm·s. With Ki = 0.5 percent/(mm·s), its I contribution becomes +80%.
Even if position then reaches the target and P falls to zero, that stored I contribution can still request forward movement. The robot may overshoot while the stored correction unwinds.
Windup is accumulated controller state, not a motor fault. A physically blocked or saturated system is a common trigger. Explore blocked behaviour in simulation, rather than holding a real robot against an obstacle.
03 | CLAMPING OUTPUT DOES NOT CLEAR MEMORY
| After two seconds of +80 mm error | Value |
|---|---|
| Integral accumulation | 160 mm·s |
| I contribution at Ki = 0.5 | +80% |
| P contribution at Kp = 0.8 | +64% |
| Unclamped total request | +144% |
| Command with a ±25% cap | +25% |
The cap prevents a command above 25%. It does not stop the unprotected accumulator from growing.
When the error becomes zero, the same accumulator still gives +80%, so the command stays at +25%. Output limiting and anti-windup solve related but different problems.
04 | CHOOSE AN INTEGRAL POLICY
| Policy | Action | Tradeoff |
|---|---|---|
| Start with Ki = 0 | Use P or PD first | Persistent offset may remain |
| Integral activation zone | Accumulate only near the target; clear outside the zone | A small zone may prevent useful correction |
| Integral contribution cap | Limit how much I can add to output | Does not by itself account for all saturation |
| Conditional integration | Freeze accumulation that drives further into saturation | Depends on the actual output-limiting policy |
| Reset on new move or sign change | Discard old stored error | May remove a useful bias correction |
The 05.2 starter combines these policies. Its I contribution is capped at 2%, and Ki begins at zero. These values are examples rather than universal settings.
Resetting I every update would make it ineffective. Reset it for a documented reason, such as a new move, disable/restart policy or loss of valid feedback.
A robot that needs sustained output to oppose gravity may need feedforward or a different holding design. The drivetrain move-and-stop policy is not automatically suitable for a lift.
05 | CONDITIONAL INTEGRATION IN PLAIN LANGUAGE
First calculate a candidate accumulator. Then ask whether accepting it would push the total request farther beyond the cap.
If output is already positively saturated and positive error would add more positive integral, freeze that accumulation. If error would pull a saturated command back toward the allowed range, permit useful unwinding.
The lab checks saturation before the command clamp. Its policy assumes that the final output limit is the cap used by the controller.
If your project later adds a speed ramp, combines heading correction or applies another limit, the actual command may differ. Anti-windup should account for that complete output path; merely inspecting an earlier calculation can miss downstream saturation.
06 | WHY DERIVATIVE MAGNIFIES NOISE
Derivative divides the change in error by elapsed time. A small change over a short interval can produce a large rate.
| Error sample | Change over 20 ms | Calculated rate |
|---|---|---|
| 20.2 mm to 19.8 mm | -0.4 mm | -20 mm/s |
| 19.8 mm to 20.2 mm | +0.4 mm | +20 mm/s |
With Kd = 0.2 percent·s/mm, those rates produce alternating D contributions of -4% and +4%. The position changed by only 0.4 mm, yet the output swings by 8 percentage points.
Encoder quantization, vibration and sensor noise can all affect the estimate. Check mounting, wiring and sensor behaviour before treating filtering as a cure for every issue.
07 | FILTER D WITHOUT HIDING THE TRADEOFF
A first-order low-pass filter makes the derivative estimate change more gradually:
# dt and filter_time are in seconds. alpha = dt / (filter_time + dt) filtered_rate += alpha * (raw_rate - filtered_rate) d_term = kd * filtered_rate
With dt = 0.02 s and a filter time of 0.05 s, alpha is approximately 0.286. Each update moves the filtered estimate partway toward the new rate.
A longer filter time usually smooths more but adds lag. Too much lag can delay useful damping. Compare output traces and physical movement when changing it.
The first derivative sample needs an initialization policy. The lab starts at zero because there is no previous error. A new movement creates fresh derivative state.
08 | AVOID FALSE DERIVATIVE SPIKES
Heading wrap: an error changing from +179 to -179 degrees has crossed the wrap boundary. A raw subtraction gives -358 degrees. The wrapped difference is +2 degrees for the assumed small inter-sample change.
Setpoint changes: changing a target suddenly changes error even if the robot has not moved. Derivative of error can then produce a kick. For changing targets, consider derivative on measurement and appropriate target profiling.
Timing: compute rates with elapsed seconds. Do not silently assume every loop ran on time. The 05.2 movement loop rejects long gaps rather than treating them as normal updates.
A wrapped difference assumes rotation between samples is small enough to interpret. It cannot reconstruct arbitrary missed revolutions.
09 | UNDERSTAND OUTPUT LIMITS
Saturation means the request has reached an allowed boundary. With a ±25% limit, requests of +30%, +60% and +100% all become the same +25% command.
Increasing Kp while the loop is saturated may not increase the early command at all. It can instead keep the controller saturated closer to the target, increasing overshoot risk.
| Limit | Purpose | Does not guarantee |
|---|---|---|
| Magnitude cap | Bound the requested motor output | A particular torque or stopping distance |
| Slew-rate limit | Bound command change over time | A cap on absolute output unless also applied |
| Movement timeout | Bound a failed movement attempt | Protection from every jam before the deadline |
| Position/rate finish windows | Define feedback success | Verified physical field arrival |
VEX PERCENT commands request motor velocity percentage; they are not a fixed percentage of torque. Brake behaviour, traction and load still affect the physical result.
Cancellation and protective stops should not be delayed by a normal acceleration ramp. Keep ordinary smoothing and stop policy deliberate.
10 | MOTOR-FREE COMPARISON LAB
This complete script runs in ordinary Python or below the generated region in VEXcode V5. It needs no hardware declarations because it commands no motors.
The first experiment holds a simulated error while output saturates, then sets error to zero. Compare unprotected integral with the protected policy from 05.2. The second experiment isolates derivative noise using small alternating error readings.
# =============================================================================
# TEAM [MY TEAM NAME / NUMBER] | MOTOR-FREE PID STABILITY LAB
# =============================================================================
# Runs in ordinary Python or below '# Begin project code' in VEXcode V5.
# No ports, motor declarations or movement commands are needed.
# The blocked-error sequence is simulated; do not jam a real robot.
MAX_OUTPUT = 25.0
I_OUTPUT_CAP = 2.0
DERIVATIVE_FILTER_S = 0.05
def clamp(value, low, high):
return max(low, min(high, value))
# =============================================================================
# PID STATE: ONE INSTANCE PER CONTROLLED SIDE, RESET FOR EACH MOVE
# =============================================================================
class PID:
def __init__(self, kp, ki, kd, i_zone, wrapped=False):
self.kp, self.ki, self.kd = kp, ki, kd
self.i_zone = i_zone
self.wrapped = wrapped
self.integral = 0.0
self.previous = None
self.filtered_rate = 0.0
def update(self, error, dt):
# Caller rejects non-finite error and unsuitable dt first.
first = self.previous is None
delta = 0.0 if first else error - self.previous
if self.wrapped and not first:
delta = (delta + 180) % 360 - 180
rate = None if first else delta / dt
raw_rate = 0.0 if first else rate
alpha = dt / (DERIVATIVE_FILTER_S + dt)
self.filtered_rate += alpha * (raw_rate - self.filtered_rate)
p = self.kp * error
d = self.kd * self.filtered_rate
# Clear stored error outside the I zone or on an error sign change.
if (self.ki <= 0 or abs(error) > self.i_zone
or (not first and error * self.previous < 0)):
self.integral = 0.0
else:
candidate = self.integral + error * dt
candidate = clamp(candidate, -I_OUTPUT_CAP / self.ki,
I_OUTPUT_CAP / self.ki)
trial = p + self.ki * candidate + d
# Freeze accumulation that would push further into saturation.
if abs(trial) <= MAX_OUTPUT or trial * error < 0:
self.integral = candidate
i = self.ki * self.integral
self.previous = error
return clamp(p + i + d, -MAX_OUTPUT, MAX_OUTPUT), p, i, d, rate
# =============================================================================
# EXPERIMENT 1: CLAMPING ALONE VERSUS CONDITIONAL INTEGRATION
# =============================================================================
DT = 0.02
KP, KI = 0.8, 0.5
naive_integral = 0.0
protected = PID(KP, KI, 0.0, i_zone=100.0)
print("step,error,naive_I,naive_request,naive_command,protected_I,protected_command")
# Keep an 80-unit error for two seconds, then reach zero error.
for step, error in enumerate([80.0] * 100 + [0.0] * 5):
naive_integral += error * DT
naive_i = KI * naive_integral
naive_request = KP * error + naive_i
naive_command = clamp(naive_request, -MAX_OUTPUT, MAX_OUTPUT)
command, p, i, d, rate = protected.update(error, DT)
if step % 10 == 0 or step >= 100:
print("%d,%.1f,%.2f,%.2f,%.2f,%.2f,%.2f" %
(step, error, naive_i, naive_request, naive_command, i, command))
# =============================================================================
# EXPERIMENT 2: SMALL POSITION NOISE, LARGE DERIVATIVE CHANGES
# =============================================================================
filtered = PID(0.0, 0.0, 0.2, i_zone=100.0)
previous = None
print("step,error,raw_rate,raw_D,filtered_D,filtered_command")
for step in range(20):
# A fixed target and an alternating noisy error measurement.
error = 20.0 + (0.2 if step % 2 == 0 else -0.2)
raw_rate = 0.0 if previous is None else (error - previous) / DT
raw_d = 0.2 * raw_rate
command, p, i, d, rate = filtered.update(error, DT)
print("%d,%.2f,%.2f,%.2f,%.2f,%.2f" %
(step, error, raw_rate, raw_d, d, command))
previous = error
print("Simulation complete. No motors were commanded.")
Experiment 1 is a prescribed error sequence, not a physics simulation of a moving robot. It demonstrates controller memory and limiting behaviour; it does not predict your robot’s final position.
Experiment 2 uses a D-only calculation to make noise visible. It is not a recommendation to control a drivetrain with D only.
11 | APPLY THE IDEAS TO THE MOVEMENT PROJECT
Use the complete 05.2 PID movement project for hardware integration. The PID class in this lab matches that project’s update policy; do not paste a second competing controller or Competition object into it.
- Begin with Ki and Kd at zero and verified feedback signs.
- Inspect P-only saturation and the physical finish.
- Add small D only when evidence supports it; compare filtered and raw rates.
- Enable small I only for a repeatable persistent offset.
- Check the actual output path, settling checks and timeout return statuses.
- Repeat on the floor with normal load and save the configuration.
The 05.2 finish-rate check intentionally uses the raw estimated rate. Filtering D therefore does not automatically fix a noisy settling decision. Set a justified rate tolerance and investigate the measurements.
12 | DIAGNOSE USING THE TERMS
| Observed behaviour | Inspect | Possible next action |
|---|---|---|
| Command stays positive at zero error | Stored I contribution | Review reset and accumulation policy |
| Command alternates with tiny error changes | Raw and filtered D | Reduce Kd or adjust filtering; inspect sensor |
| Command sits at the cap for most of the move | Unclamped request and saturation time | Review gain, target and cap together |
| Overshoot grows after adding I | Integral history | Disable I and compare; apply bounded policy |
| Movement feels delayed after smoothing | Filtered-rate lag | Reduce filter time and retest |
| Changing cap changes tuning behaviour | Actual output and anti-windup | Retune with the new limit |
| Feedback settles but robot misses target | Physical position and wheel slip | Fix measurement/geometry before gain changes |
CHALLENGES | EXPLAIN, SIMULATE AND VERIFY
- Calculate: find I after +30 mm error persists for one second with Ki = 0.2.
- Compare: run the motor-free lab and explain output after error becomes zero.
- Modify: change the integral contribution cap and activation zone separately.
- Calculate: compare derivative rates for a 0.4 mm change over 20 ms and 40 ms.
- Filter: compare filter times of 0.02, 0.05 and 0.10 seconds using the same noisy sequence.
- Wrap: explain the difference between raw and wrapped error changes at ±180 degrees.
- Trace: draw your actual command path from PID terms through all output limits.
- Evaluate: justify whether your movement routine needs I, D, both or neither.
Success evidence
A calculation sheet, recorded lab outputs and a documented controller policy. Identify which results are simulated and which come from repeatable hardware trials. Explain one benefit and one tradeoff of each protection you use.
OFFICIAL REFERENCES
- WPILib PID state, integral limits and output clamping
- WPILib common control-loop issues
- VEX motor output and feedback API
WPILib is a conceptual reference, not a dependency of this VEX Python example. The lab and learning activities are independently written. VEX and VEXcode are trademarks of their respective owners.