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.

By 11 min read

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:

MethodFirst integral incrementI at 0.05 s
Ordinary integration0.05 × 20.100000
Conditional integrationFrozen: positive update at the upper limit0.000000
Back-calculation0.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:

MethodI at 3 sRequested vApplied u
Ordinary integration3.9741663.0737401.000000
Conditional integration0.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

See the controller's stored effort

What remains after the reference drops?

Compare three integral update rules with the same actuator limit. Replay the response to connect integral memory to applied effort.

2.00
1.00
1.00
3.00

A digital PI controller samples every 0.05 s with Kp = 2. The plant has a one-second time constant and effort limits of -1 and 1. Output and integral start at zero. Kb applies only to back-calculation; zero Kb reproduces ordinary integration.

Selected outputNaive PI outputReference r
Selected and naive PI output after the reference returnsThe selected controller and ordinary PI use the same reference and integral gain. The reference becomes 0.5 at three seconds; selecting an initial reference of 0.5 leaves it unchanged. Lines join sampled output values. At 3.000000 seconds, selected output is 0.950213 and naive output is 0.950213.-1.01.03.00612OutputTime (s)
Output and reference use normalized units. The dotted vertical line marks 3 s, when the reference becomes 0.5. The circle marks replay time. The finite plot shows response history, not a settling-time guarantee.
Requested vApplied uIntegral I
Requested effort, applied effort, and integral memoryThe requested command, clamped applied effort, and integral contribution are stair-step signals at 0.05-second samples. Horizontal dotted lines show limits minus one and one. At 3.000000 seconds, requested effort is 3.073740, applied effort is 1.000000, and integral contribution is 3.974166.-2.02.57.00612EffortTime (s)
All three signals use normalized effort units. The circle marks applied effort at the replay sample. The integral increment readout is the update for the next sample; I is the current stored contribution.
Reference r
0.500000
Output y
0.950213
Tracking error e
-0.450213
Requested effort v
3.073740
Applied effort u
1.000000
Integral contribution I
3.974166
Integral increment per sample
-0.022511
Naive PI output
0.950213
Naive PI integral
3.974166
Actuator state
Upper limit
Integration rule
Ordinary integration

The default response is shown. Enable JavaScript to change the experiment.

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

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.