explainer
Integral windup: what happens when the actuator runs out of effort
Explore integral windup in a sampled PI controller. Compare requested effort, actuator limits, and integral memory as an impossible reference returns to a feasible value.
What you will learn
- Separate actuator saturation from accumulated integral state.
- Apply conditional integration with the correct direction test.
- Calculate back-calculation updates and explain their limits.
Before you start
A robot asks its motor for more effort than the motor can provide. The error stays positive, so the integral term keeps growing. Later, the requested speed drops. The error changes sign, yet the controller still asks for maximum effort because it has stored so much earlier error.
You can see that delay by plotting three separate signals: the requested effort, the applied effort, and the integral contribution.
Recognize integral windup in a saturated controller
The integral term in PID control remembers past error. That memory can remove a persistent tracking offset when the actuator can supply the necessary effort. During saturation, increasing the command may produce no additional action at the plant.
Integral windup occurs when accumulated integral state keeps the controller demanding unavailable effort and delays its response when conditions change. Clipping the command protects the actuator limit, but it does not by itself update the integral memory. MathWorks demonstrates this delayed return from saturation and two anti-windup methods.
This lesson uses PI control, with no derivative term, so the integral update is visible on its own.
Give the actuator a real limit
Use a first-order plant with a one-second time constant:
dy/dt = (u − y) / (1 s)
u = clamp(v, −1, 1)
Output y and efforts u and v use normalized, dimensionless units. Think of y as a scaled speed; connecting this model to a motor requires physical speed and effort scales. The sensor measures y exactly. The actuator applies the clamped input immediately and holds it between controller samples.
From y = 0, inputs confined to [−1, 1] keep the output in that interval. A sustained reference of 2 is therefore infeasible. At maximum positive effort, the output approaches 1. At 3 s, the default reference drops to 0.5, which requires only u = 0.5 at equilibrium.
State the order of each digital update
The controller samples every Tₛ = 0.05 s. Its proportional gain is Kₚ = 2. The stored state I is already an effort contribution: its update includes the integral gain.
At sample n, calculate the command using the existing state:
eₙ = rₙ − yₙ
vₙ = 2eₙ + Iₙ
uₙ = clamp(vₙ, −1, 1)
Then update I for the next sample using one of the three rules below. During the same interval, the constant applied input gives the exact plant update:
yₙ₊₁ = uₙ + (yₙ − uₙ) exp(−Tₛ / (1 s))
This order matches the forward-Euler integral convention in Imperix's discrete PI explanation: the current command uses the previously stored integral, and current error updates the next integral state. The controller is explicitly digital. The plant update is exact for its held input; the complete response is not an exact continuous-time PI solution.
At sample 60, time is exactly 3 s. The reference changes before that sample's command is calculated. The output and integral still carry the preceding interval's history.
Watch ordinary integration store an impossible request
Ordinary integration uses:
Iₙ₊₁ = Iₙ + TₛKᵢeₙ
If the reference stays at 2, the output approaches 1 and the error approaches 1. With Kᵢ = 1/s, I keeps growing by approximately 0.05 each sample. The actuator remains at u = 1 throughout that growth.
After the reference drops, negative error starts reducing I. A large positive I can still overpower the negative proportional term, keeping the requested effort above the actuator's upper limit. Reducing controller gains changes this behavior, but command clipping alone leaves the integral update unaware of saturation.
Stop integration only when it pushes farther into a limit
Conditional integration, often called integrator clamping, checks the direction of the proposed integral update. In this lesson, freeze I when either condition holds:
vₙ ≥ 1 and Kᵢeₙ > 0
or vₙ ≤ −1 and Kᵢeₙ < 0
Including equality prevents the integral update from pushing outward when the command is exactly on a limit. Otherwise, apply the ordinary integral update. This direction test leaves I without a fixed numerical bound.
Suppose v = 1.5 while e = −0.25. Integration decreases the command and helps it leave the upper limit, so it must remain enabled. Freezing whenever the actuator is saturated would block that useful correction. Imperix describes the same saturation-and-direction principle.
Feed the effort mismatch back into the integral
Back-calculation adds a correction based on the difference between applied and requested effort:
Iₙ₊₁ = Iₙ + Tₛ[Kᵢeₙ + Kᵦ(uₙ − vₙ)]
Above the upper limit, u − v is negative and pulls the integral downward. Below the lower limit, it is positive and pulls upward. Inside the actuator range, the correction is zero. Setting Kᵦ = 0 recovers ordinary integration exactly.
Kᵦ has units 1/s: it converts an effort mismatch into an integral-contribution rate. In the corresponding continuous correction loop, 1/Kᵦ is its tracking time constant. The digital implementation also depends on Tₛ. MathWorks explains this gain and tracking-loop interpretation.
Back-calculation does not require v = u while a persistent error remains. If the default reference stayed at 2 indefinitely, y = 1 and I = 0 would give v = 2, u = 1, and equal opposing update terms: Kᵢe = 1/s and Kᵦ(u − v) = −1/s. The integral stops growing, but the request remains infeasible.
Calculate the first update and the reference return
Start with r = 2, y = 0, I = 0, and Kᵢ = Kᵦ = 1/s. Every method initially gives e = 2, v = 4, and u = 1. Their next integral contributions differ:
| Method | First integral increment | I at 0.05 s |
|---|---|---|
| Ordinary integration | 0.05 × 2 | 0.100000 |
| Conditional integration | Frozen: positive update at the upper limit | 0.000000 |
| Back-calculation | 0.05 × [2 + (1 − 4)] | −0.050000 |
All three apply u = 1 over the first interval, so y(0.05) = 1 − exp(−0.05) = 0.048771. Different integral updates do not change the command already applied during that interval.
At 3 s, all three outputs are still y = 1 − exp(−3) = 0.950213. The new reference is 0.5, making e = −0.450213. Their stored histories now change the commands:
| Method | I at 3 s | Requested v | Applied u |
|---|---|---|---|
| Ordinary integration | 3.974166 | 3.073740 | 1.000000 |
| Conditional integration | 0.000000 | −0.900426 | −0.900426 |
| Back-calculation | −0.151179 | −1.051605 | −1.000000 |
For the naive case, a = exp(−0.05) gives an independent sum over the first 60 samples: I₆₀ = 0.05[60 + (1 − a⁶⁰)/(1 − a)]. The large stored contribution explains the positive applied effort after the error has become negative.
Compare integral memory and recovery
Start at the reference return, then compare the three presets without moving the replay time. The orange output trace always shows ordinary integration with the same initial reference and Kᵢ. Selecting ordinary integration makes the two output traces coincide.
At 12 s, the default naive output is 0.586532, the conditional output is 0.490634, and the back-calculation output is 0.488565. These values describe one instant; measuring settling would require a specified band and all later behavior. Both anti-windup examples initially pass below the new reference before approaching it.
Set the initial reference to 0.5 to remove the impossible request. Set Kᵦ to zero to check that back-calculation then reproduces naive PI. Increasing Kᵦ changes the correction, but the best response depends on the controller, sampling, and actuator model.
Keep anti-windup claims within the model
Anti-windup changes stored controller state. It cannot increase the actuator limit or make a sustained reference above 1 reachable in this plant. It also does not eliminate every transient tracking error.
For the feasible reference 0.5, y = I = 0.5 is an equilibrium for every method. Inside the limits, all three use the same local linear update. With a = exp(−0.05), its state-transition matrix for deviations from equilibrium is [[3a − 2, 1 − a], [−0.05Kᵢ, 1]]. Its eigenvalues lie inside the unit circle over the lab's Kᵢ range, so small deviations that stay unsaturated decay. That local result is not a general stability guarantee for a saturated robot controller.
The lab omits sensor noise, transport delay, changing loads, and actuator dynamics. Real motors have current and speed dynamics, and their applied effort may differ from a static clamp. A tracking correction must account for the actual actuator arrangement; MathWorks's cascaded-actuator and feedforward examples show why the feedback signal needs that context.
Reproduce the sampled response in Python
This standard-library program uses the same digital timing and an exact plant update for each held input. It records the state and current command before updating the next state.
from math import exp
TS = 0.05
decay = exp(-TS)
def run(method):
y, integral = 0.0, 0.0
rows = {}
for n in range(241):
reference = 2.0 if n < 60 else 0.5
error = reference - y
requested = 2.0 * error + integral
applied = max(-1.0, min(1.0, requested))
rate = error # Ki = 1/s
if method == "conditional":
outward = (requested >= 1 and rate > 0) or (
requested <= -1 and rate < 0
)
if outward:
rate = 0.0
elif method == "back-calculation":
rate += applied - requested # Kb = 1/s
if n in (60, 240):
rows[n] = (y, integral, requested, applied)
integral += TS * rate
y = applied + (y - applied) * decay
return rows
for method in ("none", "conditional", "back-calculation"):
for n, row in run(method).items():
y, integral, requested, applied = row
print(f"{method} t={n * TS:.2f}: y={y:.6f}, "
f"I={integral:.6f}, v={requested:.6f}, u={applied:.6f}")
Output:
none t=3.00: y=0.950213, I=3.974166, v=3.073740, u=1.000000
none t=12.00: y=0.586532, I=0.725564, v=0.552500, u=0.552500
conditional t=3.00: y=0.950213, I=0.000000, v=-0.900426, u=-0.900426
conditional t=12.00: y=0.490634, I=0.475586, v=0.494318, u=0.494318
back-calculation t=3.00: y=0.950213, I=-0.151179, v=-1.051605, u=-1.000000
back-calculation t=12.00: y=0.488565, I=0.470194, v=0.493063, u=0.493063
Try it yourself
Exercise 1: let the integral help leave saturation
At one sample, e = −0.25 and I = 2. Keep Kₚ = 2, Kᵢ = Kᵦ = 1/s, and Tₛ = 0.05 s. Find v, u, and the next integral contribution for conditional integration and back-calculation.
Check the two integral updates
v = 2(−0.25) + 2 = 1.5, so u = 1. Conditional integration remains enabled because negative error reduces an excessive positive command. Its next I is 2 + 0.05(−0.25) = 1.9875. Back-calculation adds 1 − 1.5 = −0.5 to the update rate, giving I next = 2 + 0.05(−0.25 − 0.5) = 1.9625.
Exercise 2: check the lower limit and the units
Now e = 0.25 and I = −2. Use the same gains and sample time. Find the two next integral contributions. Explain why Kᵦ has units 1/s in this notation.
Check the lower-limit correction
v = 2(0.25) − 2 = −1.5 and u = −1. The positive integral update helps leave the lower limit, so conditional integration gives I next = −2 + 0.05(0.25) = −1.9875. Back-calculation uses u − v = 0.5 and gives I next = −2 + 0.05(0.25 + 0.5) = −1.9625. I and u − v both have normalized effort units; multiplying their mismatch by Kᵦ must produce effort per second before the sample-time multiplication.
Sources and further study
- Imperix, Discrete PI controller implementation: parallel PI state conventions, forward-Euler sampling, and conditional integration.
- MathWorks, Anti-Windup Control Using PID Controller Block: saturation recovery, back-calculation, clamping, and actuator tracking arrangements.
Continue with PID control to connect this integral state to the other feedback terms, or use PID tuning to compare gain choices against a stated response objective.