PID control: build a command from error and measured motion

Build a PID controller from proportional, integral, and derivative terms. Compare tracking and load rejection, calculate each command contribution, and check the stability limit of integral gain.

By 13 min read

What you will learn

  • Calculate the proportional, integral, and derivative contributions to a command.
  • Keep controller signs and gain units consistent with the plant.
  • Explain how integral action can supply a holding command at zero error.
  • Compare P, PI, PD, and PID using the same plant and disturbance.
  • Check the analytic stability condition before interpreting a finite response plot.

Before you start

A controller needs a nonzero command to hold this plant at its target. Proportional control supplies that command through a remaining error. Integral action can supply it even after the error reaches zero, provided the loop stays stable.

You will build PID control one term at a time, compare four controller types, and inspect the command behind each response.

Define the plant for PID control

Use a normalized second-order plant with output y, command u, and opposing load d. Let T = 1 s set its time scale:

T²ÿ + Tẏ + y = u − d, with T = 1 s
ÿ + ẏ + y = u − d, using numerical time in seconds

Output, command, reference, and load are dimensionless normalized signals. The restoring term y means that holding a positive output requires a positive command, even with d = 0. This is a teaching model with fixed coefficients, not a fitted robot joint.

The reference r steps from zero to one at t = 0 and stays there. The load starts at zero, then changes to the selected nonnegative value at t = 4 s. Positive d opposes positive output acceleration because it enters as −d.

Every run starts with y = 0, ẏ = 0, and zero accumulated controller contribution. Measurement is exact, and the actuator applies the command immediately without saturation. The feedback-control lesson explains how the measurement path closes a loop; Modern Robotics' torque-input overview introduces PID terms for a physical joint model.

Turn current error into a proportional command

Define tracking error as e = r − y. Positive error means the output is below its target. A nonnegative proportional gain Kp creates:

P = Kp e = Kp(r − y)

With r = 1, y = 0.8, and Kp = 2, the proportional contribution is 0.4. If y rises above one, the error becomes negative and P requests motion back toward the target. Kp is dimensionless because input and output share normalized units.

P responds to the present error. It has no memory of how long that error has persisted. In this plant, zero error would make a P-only command zero, leaving no command to balance the restoring term or an opposing load.

Accumulate error in an integral state

Store the integral contribution directly in a controller state I:

İ = Ki e
I(t) = Ki ∫₀ᵗ e(τ) dτ, with I(0) = 0

Ki has units of 1/s. Integrating a dimensionless error over time and multiplying by Ki gives a dimensionless command contribution. With Ki = 1/s and a constant error of 0.2 over half a second, I increases by 0.1.

When the error reaches zero, I stops changing and keeps its stored value. That memory can supply the command required to hold the output against a constant load. A negative error decreases I; it does not instantly erase the accumulated contribution.

The experiment disables integral action when Ki = 0 and fixes I at zero. Each gain change restarts the calculation from the initial state. It does not retune a running controller while retaining an old integral value.

React to measured motion with derivative action

Use the measured output rate for the derivative contribution:

D = −Kd ẏ

Kd has units of seconds, so Kd times a rate in 1/s gives a dimensionless command. With Kd = 1 s and ẏ = 0.1/s, D = −0.1. Motion in the negative direction makes D positive.

For a constant reference, ė = −ẏ, so this matches derivative action on error. At a reference step, derivative on measurement avoids differentiating that jump. Our initial rate is zero, so D(0+) = 0, while the proportional contribution jumps to Kp.

MathWorks' PID controller forms call the full arrangement with P and I on error and D on measurement PI-D. It belongs to the PID family and gives the derivative term zero reference weight. An ideal derivative of error would produce a different reference response at a step.

The lab assumes exact measured rate. Real differentiation can amplify high-frequency sensor noise, and practical derivative filters add dynamics. Here D acts like extra damping in the plant equation; it does not predict an unknown future motion.

Derivative kick and filtering compares a reference step with a noisy measurement. Calculate the filtered command and see how the filter changes its amplitude and phase.

Add the three command contributions

The actuator receives their sum:

u = P + I + D
u = Kp(r − y) + I − Kd ẏ

For a checkable snapshot, choose Kp = 2, Ki = 1/s, Kd = 1 s, y = 0.8, ẏ = 0.1/s, and I = 0.7. These are supplied state values, not a claim about the default trajectory at a particular time.

ContributionCalculationValue
P2 × (1 − 0.8)0.4
IStored contribution0.7
D−1 × 0.1−0.1
Total u0.4 + 0.7 − 0.11.0

If d = 0.5, the plant acceleration is 1.0 − 0.5 − 0.1 − 0.8 = −0.4/s². The output still moves upward at this instant, but its rate decreases. Meanwhile İ = 1 × 0.2 = 0.2/s, so the integral contribution continues to increase.

Compare P, PI, PD, and PID

Set selected gains to zero to remove their terms. The four main presets keep Kp = 2 and use the same opposing load of 0.5 beginning at four seconds.

TypeKi (1/s)Kd (s)Output at 6 sError at 6 s
P000.4701410.529859
PI100.8865470.113453
PD010.4834870.516513
PID110.8249770.175023

These are numerical results for this plant and these gains. PI has less error than PID at this particular instant. Adding another term does not guarantee a better score on every response measure.

D changes damping, so compare the complete transient as well as one reading. I changes the eventual holding behavior and adds a dynamic state. The PID tuning lesson turns those effects into choices against stated goals.

Find what holds the output at its target

After the load becomes constant, an equilibrium has ẏ = ÿ = 0. The plant then requires u = y + d.

With P or PD and I disabled, D is zero at equilibrium. Solving Kp(1 − y) = y + d gives:

y∞ = (Kp − d)/(1 + Kp)
e∞ = (1 + d)/(1 + Kp)

For Kp = 2 and d = 0.5, the output approaches 0.5, the error approaches 0.5, and u approaches 1.0. The derivative gain changes the transient but vanishes from this equilibrium calculation. Even at zero load, this finite-gain P controller needs some error to balance the restoring term.

With Ki positive, a stable equilibrium also requires İ = Ki e = 0. Thus e = 0, y = 1, P = D = 0, and I = u = 1 + d = 1.5. Lynch and Park's PID treatment shows how a stored integral contribution supports a constant load at zero error.

This conclusion needs a stable loop and enough actuator authority. The integral-windup lesson adds actuator limits, which can prevent the command from producing the motion that the integrator expects.

Check how integral gain changes stability

Substitute the controller into the plant, then analyze its homogeneous state dynamics. When Ki = 0, the active model has two states and characteristic polynomial:

T²s² + (T + Kd)s + (1 + Kp), with T = 1 s

All its coefficients are positive for the lab's nonnegative gains. Both active modes decay. An unused, frozen integral variable is not an additional active pole at zero in this reduced experiment.

For Ki positive, I adds a third state. The characteristic polynomial and strict stability condition become:

p(s) = T²s³ + (T + Kd)s² + (1 + Kp)s + Ki
(T + Kd)(1 + Kp) > T²Ki

The cubic Routh criterion gives this inequality. With numerical time in seconds and T = 1, the lab reports the Routh gap as (1 + Kd)(1 + Kp) − Ki. Positive means asymptotic stability; zero marks the marginal boundary; negative gives two right-half-plane modes.

If a nonzero computed gap is too small to determine its sign reliably, the helper labels it near the boundary and withholds a stable-limit prediction. That numerical uncertainty does not establish an exactly marginal model.

For Kp = 2 and Kd = 0, the boundary is Ki = 3/s. Its numerical polynomial factors as (s + 1)(s² + 3). A decaying mode accompanies two undamped oscillatory modes; the loop is not BIBO stable because a bounded resonant input can produce unbounded output.

Ki = 4/s crosses that boundary and creates growing oscillations. This instability occurs even with an unlimited actuator. The default PID gains give a positive gap of (1 + 1)(1 + 2) − 1 = 5.

Because D uses measurement only, the zero-state reference transfer for Ki positive is Y(s)/R(s) = (Kp s + Ki)/p(s). The opposing-load transfer is Y(s)/D(s) = −s/p(s). Derivative feedback changes their denominator while leaving the derivative of the reference out of the numerator.

Inspect the command and response together

Current error, accumulated error, measured motion

Build a command from P, I, and D

The target steps to one at time zero. An opposing load begins at four seconds. Compare controller terms on the same plant and check stability before judging the response.

2.00
1.00
1.00
0.50

The plant is y″ + y′ + y = u − d, using seconds and normalized signals. The controller uses u = Kp(1 − y) + I − Kd y′ and I′ = Ki(1 − y). Every experiment starts with y, y′, and I equal to zero. Setting Ki to zero disables I.

6.00
Output during a target step and opposing loadP-only output, Selected output over twelve seconds. The load begins at four seconds. The replay time is 6.000000 seconds. P-only output: 0.470141. Selected output: 0.824977. Values come from numerical integration with steps at most 0.001 seconds.04812-102Time (s)Output
Solid blue: selected controller. Orange dashes: P alone, with the same Kp and load. Gray short dashes: target of one. P-only curves coincide when Ki and Kd are zero. The blue dot marks the selected output at the replay time.
Command during a target step and opposing loadP contribution, I contribution, D contribution, Total command over twelve seconds. The load begins at four seconds. The replay time is 6.000000 seconds. P contribution: 0.350045. I contribution: 1.165782. D contribution: -0.046649. Total command: 1.469178. Values come from numerical integration with steps at most 0.001 seconds.04812-103Time (s)Command
Orange dashes: P. Purple dots: I. Green dash-dot line: D. Solid blue and its dot: their total command. Zero gains make their terms flat at zero; curves overlap when values agree. The command includes no actuator limit.
Output y
0.824977
Output rate (1/s)
0.046649
Tracking error e
0.175023
P contribution
0.350045
I contribution
1.165782
D contribution
-0.046649
Total command u
1.469178
Opposing load d
0.500000
Output acceleration (1/s²)
0.097551
Integral rate (1/s)
0.175023
Active dynamic order
3
Stability
Asymptotically stable
Routh gap
5.000000
Long-time error
0.000000
Long-time command
1.500000

The active three-state loop is stable for this model. With the constant load held indefinitely, integral action can supply the needed command at zero tracking error.

The load-time guide marks 4 s even when its magnitude is zero. States and command stay continuous there; acceleration changes instantly. Long-time readouts describe continued operation after the load step, not the current cursor. The finite plot uses RK4 with steps at most 0.001 s and does not prove stability.

This experiment assumes exact, noise-free measured rate, immediate command delivery, and no saturation or delay. Practical derivative filtering and actuator limits change the model.

Compare P, PI, PD, and PID while keeping the load unchanged. The output chart retains a P-only comparison with the same Kp. Inspect I after the load begins and watch D change sign as the output changes direction.

Move the cursor to zero: the initial command comes entirely from P. At four seconds, the load changes acceleration immediately while output, rate, and controller states remain continuous. Select the boundary and excessive-integral-gain presets to compare the plot with the analytic stability label.

The chart covers twelve seconds. It uses fourth-order Runge–Kutta integration with steps at most 0.001 s and splits the calculation exactly at the load change. Independent state-transition calculations and step refinement check its numerical accuracy; a finite simulation cannot establish stability or an infinite-time settling claim.

The gain sliders recalculate a complete experiment from rest. This is a comparison tool for continuous-time models. It omits sensor noise, derivative filtering, sample-and-hold control, delays, and saturation, each of which can change practical behavior.

Reproduce the controller comparison in Python

This standard-library example integrates the three states and prints the four preset responses. Each RK4 interval uses one fixed load value, including its final stage, so the step at four seconds never leaks into earlier motion.

from math import ceil


def response(kp, ki, kd, load=0.5, t=6.0):
    x = (0.0, 0.0, 0.0)  # output, rate, integral contribution
    for duration, d in ((min(t, 4.0), 0.0), (max(t-4.0, 0.0), load)):
        n = ceil(duration / 0.001)
        if not n:
            continue
        h = duration / n

        def f(state):
            y, v, integral = state
            return (v, kp*(1-y)+integral-(1+kd)*v-y-d, ki*(1-y))

        def add(state, slope, factor):
            return tuple(a+factor*b for a, b in zip(state, slope))

        for _ in range(n):
            a = f(x)
            b = f(add(x, a, h/2))
            c = f(add(x, b, h/2))
            e = f(add(x, c, h))
            x = tuple(xi+h*(ai+2*bi+2*ci+ei)/6
                      for xi, ai, bi, ci, ei in zip(x, a, b, c, e))
    y, v, integral = x
    p, derivative = kp*(1-y), -kd*v
    return y, 1-y, p+integral+derivative, p, integral, derivative


for name, gains in (("P", (2, 0, 0)), ("PI", (2, 1, 0)),
                    ("PD", (2, 0, 1)), ("PID", (2, 1, 1))):
    y, error, u, *_ = response(*gains)
    print(f"{name}: y={y:.6f}, error={error:.6f}, u={u:.6f}")
_, _, _, p, integral, derivative = response(2, 1, 1)
print(f"PID terms: P={p:.6f}, I={integral:.6f}, D={derivative:.6f}")
print(f"PID initial command: {response(2, 1, 1, t=0)[2]:.6f}")
print(f"PI boundary gap: {(1+0)*(1+2)-3:.6f}")

Expected output:

P: y=0.470141, error=0.529859, u=1.059718
PI: y=0.886547, error=0.113453, u=1.387259
PD: y=0.483487, error=0.516513, u=1.044937
PID: y=0.824977, error=0.175023, u=1.469178
PID terms: P=0.350045, I=1.165782, D=-0.046649
PID initial command: 2.000000
PI boundary gap: 0.000000

Try it yourself

Exercise 1. Take r = 1, y = 0.75, ẏ = −0.2/s, and stored I = 0.6. Use Kp = 2, Ki = 1/s, Kd = 1 s, and load d = 0.5. Find P, I, D, total command, acceleration, and the rate of change of I.

Check the signed command budget

Error is 0.25, so P = 0.5 and İ = 0.25/s. The measured rate is negative, making D = +0.2. Together with I = 0.6, the command is 1.3.

Acceleration is 1.3 − 0.5 − (−0.2) − 0.75 = 0.25/s². The output currently moves downward, but the positive acceleration reduces that downward rate.

Exercise 2. Use Kp = 1, Kd = 1 s, and Ki = 4/s. Classify the active dynamics. Then disable integral action and find the new dynamic order and the steady error under load d = 0.5.

Check the boundary and reduced model

The numerical Routh gap is (1 + 1)(1 + 1) − 4 = 0. The cubic factors as (s + 2)(s² + 2), so the modes are −2/s and ±j√2/s. The loop is marginal and not BIBO stable.

With Ki = 0 and I disabled, the active second-order polynomial is s² + 2s + 2. Its roots −1 ± j per second decay. Steady error is (1 + 0.5)/(1 + 1) = 0.75, output is 0.25, and command is 0.75.

Sources and further study

Continue with PID tuning to choose gains against response and command requirements, then use integral windup to account for a command that the actuator cannot deliver.