All articles
Theory June 4, 2026 19 min read

Digital PID: sample time, discretisation and what a PLC actually computes

Positional vs velocity PID, Euler vs Tustin, Ts rules of thumb, aliasing, bumpless transfer and discrete anti-windup for real PLCs.

Almost every PID you will ever commission is discrete. The textbook continuous law is useful for thinking, but the box that writes the valve is a sampled algorithm: it reads PV once per period Ts, updates an integrator and a filtered derivative, and holds the output until the next tick. Get the discretisation and the sample time wrong and a loop that looks healthy in continuous analysis will hunt, kick on setpoint changes, or refuse to go into automatic without a bump. This article is the engineering of that gap: positional versus velocity form, forward Euler versus backward difference versus Tustin, how coarse Ts adds delay, why derivative action collapses when you sample too slowly, and how bumpless transfer and anti-windup have to be written as discrete updates rather than as afterthoughts.

PID Solver 360 treats the controller as a sampled law against a continuous plant, which is the same split a PLC uses. Use the closed-loop simulator with a realistic Ts when you want the discrete behaviour, and keep the documentation open for the continuous identities. The step-by-step tuning guide still applies; you just cannot ignore the clock.

What “digital PID” actually means

A continuous PID in parallel form is

u(t) = Kp e(t) + Ki ∫ e(τ) dτ + Kd de/dt
e(t) = SP(t) − PV(t)

A digital implementation replaces the integral with a running sum and the derivative with a difference, then applies a zero-order hold (ZOH) so the actuator sees a staircase. Between samples the plant is continuous; the controller is not. That mismatch is not a numerical curiosity. It is extra delay, extra high-frequency gain, and a state that must be initialised whenever someone moves the loop from manual to automatic.

Three clocks sit on top of each other and they are easy to confuse. Ts is the controller sample period: the interval at which P, I and D are recomputed. The plant integration step in a simulator may be smaller than Ts; in the field the plant is analog. The PLC scan time is the interval at which the whole program, including this loop and everything else, gets a chance to run. If the PID function block is called every scan, Ts equals scan time only if the scan is deterministic. If the PID is scheduled on a slower cyclic interrupt, Ts is that interrupt, and the scan can be faster without helping the loop.

A ZOH of period Ts contributes phase lag that grows with frequency. A common first-order approximation is that sampling plus hold looks like half a sample of extra dead time:

L_eq ≈ L + Ts / 2

That extra L is why a loop tuned in continuous time, then dropped onto a coarse PLC, loses phase margin. It is also why Ts must be chosen from the process, not from how fast the CPU happens to be.

Positional form: compute the full output every sample

The positional (absolute) algorithm stores an integrator state I_k and a derivative state, then writes the complete manipulated variable each tick:

P_k  = Kp e_k
I_k  = I_{k−1} + Ki Ts e_k          (forward Euler on I)
D_k  = Kd (e_k − e_{k−1}) / Ts      (backward difference on D)
u_k  = P_k + I_k + D_k

The names “positional” and “velocity” are historical. Position means the controller emits u, the absolute output, typically 0–100 % to a valve. You need the full u every sample if the actuator is analog or if a downstream limiter will clip it. Most DCS PID blocks are positional under the hood, with the incremental update hidden inside the function block.

Positional form makes saturation obvious: after you compute u_k you clamp it, and you then have a well-defined unsaturated versus saturated pair for anti-windup. It also makes the integral state a physical object you can inspect: I_k is the bias that holds the valve at the right place when error is zero. That bias is exactly what you must preload for bumpless transfer.

The cost of positional form is that any jump in SP, any noise spike in PV, and any reset of the integrator appear immediately in u. Derivative kick is a positional-form symptom: if D acts on e and SP steps, (e_k − e_{k−1}) / Ts is a pulse of height ΔSP / Ts. On a 1 s sample that pulse is already large; on a 50 ms motion loop it can saturate the drive for a sample even when the plant has not moved.

Velocity form: compute the increment, then accumulate

The velocity (incremental, delta) algorithm computes Δu and then adds it to the last output:

Δu_k = Kp (e_k − e_{k−1})
     + Ki Ts e_k
     + (Kd / Ts) (e_k − 2 e_{k−1} + e_{k−2})
u_k  = u_{k−1} + Δu_k

This is the discrete derivative of the positional law, so in the linear unsaturated case the two forms are equivalent. They cease to be equivalent the moment you clamp, switch modes, or lose a sample.

Velocity form is popular on PLCs that write to a motorised valve or a stepper, because those actuators naturally accept increments. It is also popular because clamping is cheap: if u is already at 100 %, you simply refuse to add a positive Δu. That is a primitive anti-windup. It is not the same as back-calculation, but it stops the increment from requesting more than the actuator can do, and it does not require you to reconstruct I from P and D.

The trap is initialisation. Velocity form stores u_{k−1} as its memory. If you enter automatic with u_{k−1} equal to zero while the valve is sitting at 47 %, the first samples will walk the output from 0 toward the correct bias at a rate set by Ki Ts e. That walk is a bump. You must seed u_{k−1} with the current OP. Positional form has the same requirement on I_k; velocity form just hides it in a different variable.

A second trap is sample loss. If one scan is skipped, positional form still computes a consistent u from the current error and the stored I. Velocity form that was derived assuming a fixed Ts will apply Ki Ts e with the wrong Ts, and the second-difference D term will see a gap. Dedicated loop tasks with a watchdog are the fix, not a cleverer difference formula.

Discretising the integral: forward Euler, backward Euler, Tustin

The continuous integrator 1/s has to become a discrete filter. Three replacements dominate industrial code.

Forward (explicit) Euler, also called a rectangular sum using the old error, maps

1/s  →  Ts / (z − 1)
I_k  = I_{k−1} + Ki Ts e_{k−1}

It is cheap and causal. It is also the least accurate at high frequency, and for a pure integrator it can be shown to add a small extra delay relative to the trapezoidal rule. On slow process loops with Ts ≪ τ the error is negligible. On fast loops, or when you are discretising a derivative filter rather than the PID integrator, forward Euler can push poles outside the unit circle.

Backward (implicit) Euler uses the new error:

1/s  →  Ts z / (z − 1)
I_k  = I_{k−1} + Ki Ts e_k

This is the default in a great many PLC PID blocks because it is still one multiply-add and it is slightly more stable. The integrator now “sees” the current sample, so a large error on this tick immediately charges I. Combined with a large Ki and a slow plant, that can look like extra proportional action for one sample. For Ts in the usual process range it is the right default.

Tustin (bilinear, trapezoidal) averages the old and new error:

s  →  (2 / Ts) (z − 1) / (z + 1)
I_k  = I_{k−1} + Ki Ts (e_k + e_{k−1}) / 2

This is the mapping that preserves the analog frequency response most faithfully up to a decent fraction of the Nyquist frequency, at the price of frequency warping:

ω_discrete = (2 / Ts) tan(ω_continuous Ts / 2)

For the PID integrator, Tustin is almost always an improvement on Euler and almost never worth arguing about on a temperature loop with τ = 8 min and Ts = 1 s. It becomes important when you discretise a derivative filter whose cutoff sits near Nyquist, or when you are matching a continuous design of a fast motion loop. Pre-warping the cutoff,

ω_pre = (2 / Ts) tan(ω_desired Ts / 2)

then applying Tustin, puts the discrete filter’s gain crossover where you intended.

PID Solver 360’s plant stepper is RK4 with the PID held constant over Ts, which is a ZOH, not a claim that the PID internals use Tustin. When you compare a continuous Bode plot with a sampled implementation, remember that the mapping of C(s) is a separate choice from the mapping of G(s).

Discretising the derivative: the term that feels the sample time first

Ideal D is Kd s. At frequency ω its magnitude is Kd ω, which is unbounded. Any discrete approximation is a high-pass filter whose gain at Nyquist is finite and usually large.

Forward Euler on D is rarely used: it looks ahead. Backward difference is the workhorse:

D_k = Kd (e_k − e_{k−1}) / Ts

The pulse transfer is Kd (1 − z^{−1}) / Ts. At z = −1 (Nyquist) the gain is 2 Kd / Ts. Halve Ts and you double that gain. That is the opposite of the intuition “faster sampling is always safer”. Faster sampling without a filter makes unfiltered D noisier in engineering units of OP per sample. You must either filter, or take D on a slower reconstructed PV, or both.

A filtered continuous derivative

D(s) = Kd s / (Tf s + 1),   Tf = Td / N

with N typically 8 to 20, discretises cleanly with backward Euler:

α   = Ts / (Tf + Ts)
D_k = (1 − α) D_{k−1} + α Kd (e_k − e_{k−1}) / Ts

or with Tustin if Tf is only a few samples. If Tf < Ts you do not have a filter; you have a comment in the code. Choose Ts so that Tf is at least two or three samples, which is another way of saying that derivative action and sample time are not independent knobs.

At coarse Ts the backward difference is not “the derivative of the plant”. It is a chord over a long interval. If the PV is an exponential with time constant τ and you sample at Ts = τ / 2, the chord underestimates the initial slope after a step and overestimates later, when the curve has flattened. The D term then injects a delayed, distorted damping torque. On temperature loops that is why adding Kd at Ts = 5 s on a 40 s lag often does nothing useful except amplify thermocouple noise. Either speed up the loop task or leave D at zero and retune PI. The structure decision in P, PI, PD or PID has to include Ts.

Sample-time rules of thumb that survive contact with plants

The rule that appears in the documentation and in every competent commissioning note is

Ts ≤ τ / 10

for a dominant lag τ. It is a process-time rule, not a Shannon-Nyquist rule, and the distinction matters.

Nyquist says you must sample faster than twice the highest frequency you wish to reconstruct. A thermocouple plus a 1 s analog filter has almost no energy above a few hertz, so Nyquist is satisfied at Ts = 100 ms with room to spare. The loop can still be badly sampled because the controller, not the observer, needs several samples across the closed-loop rise. If the PV takes 30 s to go 10–90 % after a setpoint step, you want Ts on the order of 1–3 s so that P, I and D see the transient rather than a single jump. Sampling at 50 ms does not make that loop faster; it just burns scan time and feeds D more noise.

A second process rule is to keep Ts well below the dead time L, so the delay buffer in the plant (and the apparent delay in the loop) is resolved to several samples:

Ts ≤ L / 10   when L is the design dead time

If L = 4 s and Ts = 2 s, the delay is two samples and a one-sample jitter in execution time is a 50 % error in L. Tuning rules that spend their conservatism on L, including SIMC and AMIGO in the auto tuner, then become lottery tickets.

A third rule comes from the extra half-sample delay. The phase lag of a ZOH at the gain-crossover frequency ωc is approximately

φ_ZOH ≈ ωc Ts / 2   (radians)

If you have budgeted 50° of phase margin in continuous design, and ωc Ts / 2 is 15°, you have spent a third of the budget on the clock. Either lower ωc (slower loop) or lower Ts. This is the same story as gain and phase margin: sampling is a robustness tax.

Very fast sampling has costs too. Quantisation of a 12-bit analog input, expressed per sample, looks like high-frequency noise. Unfiltered D will chase the least significant bits. Communication load, scan overrun, and the temptation to put the PID in a 10 ms task “because we can” all argue for sampling only as fast as the process and the derivative filter require. A practical window for process loops is

τ / 100  ≤  Ts  ≤  τ / 10

with the lower bound relaxed if D is off and the measurement is quiet.

The extra delay of 0.5 Ts, computational delay, and jitter

The half-sample figure assumes the PID is evaluated at the sampling instant and the new OP is written immediately. Real firmware is less kind. A typical sequence is: analog scan, linearisation, PID, analog output refresh. If those steps are spread across the scan, you add a computational delay Tc between the PV used in the error and the OP that the valve sees. The equivalent extra dead time becomes

L_eq ≈ L + Ts / 2 + Tc

If the PID runs at the start of the scan and the output card updates at the end, Tc is most of the scan. If both happen in a dedicated timed interrupt with the analog IO in the same cycle, Tc is milliseconds. On a flow loop with L = 0.4 s, a 200 ms scan with the PID buried at the bottom of a long program is not a small perturbation.

Jitter is the variation of the actual period. A Windows soft-PLC, a Python script, or a PLC task that sometimes misses its deadline produces a Ts that wanders. Velocity-form D, which divides by Ts, will spike when a long interval is followed by a short one if you use the nominal Ts in the formula but the timestamps in the difference. Either timestamp the samples and use the true Δt, or run the PID in a hardware-timed task so that Ts is a constant you can put in the difference equation.

For frequency-domain work, the extra delay is a straight line on the Bode phase plot: −ω L_eq in radians. That is why a loop that met 45° phase margin in a continuous tool can sit at 25° on the wire. Recompute margins with L replaced by L_eq, or include a discrete C(z) and a ZOH in the open loop, before you declare the design robust.

Aliasing: the process you did not mean to control

Sampling folds every frequency above the Nyquist frequency fN = 1/(2 Ts) back into the baseband. A 50 Hz electrical pickup on a poorly grounded 4–20 mA loop, sampled at Ts = 100 ms (fN = 5 Hz), does not appear as 50 Hz. It appears as a slow wander at the beat frequency between 50 Hz and the nearest multiple of 10 Hz, and the PID will try to fight it. Integral action is especially willing to chase an aliased drift that looks like a load change.

The fix is analog, not digital. An anti-aliasing filter before the sampler, with cutoff well below fN, keeps the illegal frequencies out of the difference equation. Many analog input cards already include a filter; many cheap remote IO modules do not. If you then add a digital filter inside the PID, you have added more lag to the loop and you have not removed aliasing that already occurred.

Stiction and a sticky valve create another aliased pattern: the PV jumps in irregular steps. Those steps have broadband content. D will fire on each jump. That is not a sample-time bug, but a coarse Ts makes each jump look like a full-scale derivative event because ΔPV / Ts is large. Fix the valve, filter D, or slow D by raising Tf — do not “tune through” a mechanical fault with a faster scan.

Derivative at coarse Ts: when PID becomes clumsy PI

Suppose Td = 8 s, N = 10, so Tf = 0.8 s, and you set Ts = 2 s. Then:

  • The discrete D gain at Nyquist is already using a chord of 2 s, longer than Tf, so the filter cannot do what the continuous formula promised.
  • Noise of 0.2 engineering units becomes a D contribution of Kd × 0.2 / 2. If Kd is large because you copied a continuous design, that contribution is a visible OP twitch every sample.
  • After a genuine process move that takes 20 s, you get about ten D samples. That is enough to help. After a 6 s rise on a faster loop, you get three samples and D is a lottery.

The practical test is to look at OP with D on and D off at the intended Ts, with realistic noise. If OP variance is dominated by D, you have not bought damping; you have bought actuator travel. Either increase Tf until OP is acceptable, or set Td = 0 and retune PI. The optimiser will happily raise Kd to shave ITAE on a noise-free model. That Kd is fiction at coarse Ts. Put noise in the simulation or impose a Kd cap that matches the filtered discrete gain.

Bumpless transfer: the discrete state is the bump

Bumpless transfer means the OP does not jump when the operator moves auto/manual, or when a cascade master is connected, or when you download new gains. In discrete time that is a state-alignment problem.

When switching from manual to automatic, the plant is already at some PV and the valve is at some u_man. The PID must start with u_0 = u_man. In positional form,

I_0 = u_man − Kp e_0 − D_0

with D_0 usually set to 0 or to the filtered derivative of the current PV slope if you can estimate one. If you instead reset I to 0, the output jumps by roughly Kp e_0, which on a loop sitting 4 % off setpoint with Kp = 3 is a 12 % valve kick. Operators remember that kick for years.

When switching from automatic to manual, freeze u at the last automatic value and stop integrating. The velocity form does this naturally if you stop adding Δu. Positional form must stop writing I.

Gain scheduling and online retuning have the same requirement. If you change Kp while I still holds the old bias, the identity u = P + I + D is instantly violated in the direction of the P change. Some blocks recalculate I after a gain change so that u is invariant at that sample. If your PLC does not, change gains only when error is small, or use velocity form so that a Kp change affects only the next increment.

Setpoint ramps are a cousin of bumpless transfer. A step in SP with D on error is a bump by construction. Taking D on −PV, and optionally using setpoint weighting on P, removes that bump without touching the load-disturbance path. Those features are discrete-state choices: you store PV_{k−1} rather than e_{k−1} for the D difference.

Anti-windup in discrete time

Windup is an integrator that keeps charging while the actuator cannot follow. In discrete time the integrator is a number that grows by Ki Ts e_k every tick. After N saturated samples the excess is N Ki Ts e. When PV finally crosses SP, that excess has to be unwound by error of the opposite sign, which is overshoot. The continuous story in anti-windup explained is the same physics; the implementation is a sampled update.

Back-calculation (tracking) recomputes the integral so that the unsaturated sum sits on the limit:

u_unsat = P_k + I_k + D_k
u_sat   = clip(u_unsat, u_min, u_max)
I_k     ← I_k + (Ts / Tt) (u_sat − u_unsat)

Tt is the tracking time constant. A common choice is Tt ≈ Ti, or Tt ≈ sqrt(Ti Td) for PID. In the limit of very small Tt this approaches the algebraic reset

I_k ← u_sat − P_k − D_k

which is what PID Solver 360 uses when anti-windup is enabled: the integral is rebuilt so P + I + D equals the clamp. Conditional integration (clamping, freezing) is simpler:

if u_unsat is past the limit in the direction of e, do not update I

That if-statement is easy to get wrong at the instant the error changes sign. Back-calculation is smoother and plays better with Tustin’s average of two errors.

Velocity-form anti-windup is “do not add Δu that would drive past the stop”. It prevents further windup of u, but if you also maintain an I state for bumpless reasons you must still freeze or back-calculate that I. Otherwise a later switch to positional reconstruction will surprise you.

Discrete anti-windup must run at the same Ts as the PID. A slower supervisory freeze, or an output clamp on the analog card that the PID does not know about, is invisible windup: the algorithm thinks u is 85 % while the card is already at 100 %. Read the OP after the clamp, or write the clamp inside the PID block.

PLC scan versus dedicated loop time

A PLC scan is a batch: input image, logic, output image, communications, diagnostics. Scan time varies with program size and with whether a heavy sequence is active. A PID that is called every scan therefore has a moving Ts. For a tank level with τ = 20 min, a scan that wanders between 40 ms and 80 ms is irrelevant. For a pressure loop with τ = 1.5 s, that wander is a 5 % jitter in the difference gain.

A dedicated cyclic task (timed interrupt, constant-scan PID, motion task) fires at a fixed Ts regardless of the main program. That is the right home for any loop whose bandwidth is within a decade of the scan, and for any loop that uses D. Put slow supervisory logic in the main scan; put the PID in a timed task; timestamp the analog input if the card supports it.

Priority and cache effects still matter. If the timed task is starved by a higher-priority communications interrupt, you have jitter again. Watchdog the actual period. If the measured period exceeds 1.2 Ts for more than a few ticks, the loop is not the loop you tuned.

Multirate systems — a fast inner flow loop at 50 ms and a slow temperature master at 2 s — are discrete by construction. The master should write a setpoint that the slave can follow, not an OP. Sampling the slave PV into the master without regard to aliasing is a classic source of a temperature loop that “never quite settles”. Filter or decimate on purpose.

A commissioning sequence that respects the clock

Identify K, τ and L from a bump test as in the tuning guide. Choose Ts from τ and L, not from CPU idle time: start at τ/10, faster if you need D, slower only if the measurement is noisy and D is off. Put the PID in a timed task at that Ts. Add analog anti-aliasing if the IO is raw.

Discretise I with backward Euler or Tustin; discretise D as a filtered backward difference with Tf ≥ 2 Ts. Enable OP limits that match the real actuator, and enable discrete anti-windup inside the same block. Seed I or u_{k−1} from the current OP on every manual-to-auto transition.

Simulate that discrete law against the FOPDT plant in the simulator. If the continuous Bode margins in gain and phase margin were 50° and 10 dB, add Ts/2 to L and check again. If D looks like noise, drop Td rather than chasing a smaller Ts you will not be allowed to use in production.

Then, and only then, put the loop in automatic. The digital PID is not a poor copy of the analog one. It is the controller. Treat Ts, the difference equations, and the discrete states with the same respect you already give Kp, Ti and Td.

Try it in the solver

Put this into practice — model your process, auto-tune it and check the stability margins.

Launch PID Solver 360