Advanced
PLC PID Control Loop Tuning: Implementation & Examples in Structured Text
Implement and tune PID controllers in Structured Text — from basic proportional control to full PID with anti-windup, used in temperature, pressure, and flow regulation.
What Is PID Control?
PID (Proportional-Integral-Derivative) control is the most widely used feedback control algorithm in industrial automation. It continuously calculates an error between a desired setpoint and a measured process variable, then applies a correction based on three terms:
| Term | Symbol | Action | Corrects |
|---|---|---|---|
| Proportional | Kp | Reacts to current error | Present error |
| Integral | Ki | Accumulates past error | Steady-state offset |
| Derivative | Kd | Predicts future error | Overshoot/oscillation |
The PID Formula
Output = Kp × Error + Ki × ∫Error dt + Kd × dError/dt
In discrete PLC form (sampled every cycle):
Output = Kp × Error + Ki × ErrorSum × dt + Kd × (Error - PrevError) / dt
Basic PID in Structured Text
PROGRAM BasicPID
VAR
// Process
Setpoint : REAL := 75.0; // Desired temperature °C
ProcessValue : REAL := 20.0; // Measured temperature °C
Output : REAL := 0.0; // Control output 0-100%
// Tuning parameters
Kp : REAL := 2.0;
Ki : REAL := 0.5;
Kd : REAL := 0.1;
// Internal
Error : REAL := 0.0;
PrevError : REAL := 0.0;
ErrorSum : REAL := 0.0;
ErrorDiff : REAL := 0.0;
dt : REAL := 0.1; // Sample time in seconds
// Output limits
OutMin : REAL := 0.0;
OutMax : REAL := 100.0;
END_VAR// Calculate error
Error := Setpoint - ProcessValue;
// Proportional term
// Integral term (accumulated error)
ErrorSum := ErrorSum + (Error * dt);
// Derivative term (rate of change).
// dt is the sample period in seconds. It MUST be > 0 — set it to your
// actual cyclic task period (e.g. 0.1 for a 100 ms task) and never expose
// it as a tunable that can land on zero. Real PLCs fault on dt = 0.
ErrorDiff := (Error - PrevError) / dt;
// PID output
Output := (Kp Error) + (Ki ErrorSum) + (Kd * ErrorDiff);
// Clamp output
IF Output > OutMax THEN
Output := OutMax;
ELSIF Output < OutMin THEN
Output := OutMin;
END_IF;
// Store previous error
PrevError := Error;
Reusable PID Function Block
Professional PLC programs use a reusable function block:
FUNCTION_BLOCK FB_PID
VAR_INPUT
Enable : BOOL := FALSE;
Setpoint : REAL := 0.0;
ProcessValue : REAL := 0.0;
Kp : REAL := 1.0;
Ki : REAL := 0.0;
Kd : REAL := 0.0;
dt : REAL := 0.1;
OutMin : REAL := 0.0;
OutMax : REAL := 100.0;
ManualMode : BOOL := FALSE;
ManualOutput : REAL := 0.0;
Reset : BOOL := FALSE;
END_VAR
VAR_OUTPUT
Output : REAL := 0.0;
Error : REAL := 0.0;
IsActive : BOOL := FALSE;
END_VAR
VAR
PrevError : REAL := 0.0;
Integral : REAL := 0.0;
Derivative : REAL := 0.0;
RawOutput : REAL := 0.0;
END_VARIF Reset THEN
Integral := 0.0;
PrevError := 0.0;
Output := 0.0;
RETURN;
END_IF;
IF ManualMode THEN
Output := ManualOutput;
// Bumpless transfer: pre-seed Integral so the first Auto-mode output
// equals ManualOutput. Rearranging Output = Kp·Error + Ki·Integral
// gives Integral = (ManualOutput - Kp·Error) / Ki. The divisor is Ki,
// so guard Ki — not Kp. A P-only controller (Ki = 0) has no integral
// term to track, so leave Integral cleared on every Manual scan.
IF Ki <> 0.0 THEN
Integral := (ManualOutput - Kp * (Setpoint - ProcessValue)) / Ki;
ELSE
Integral := 0.0;
END_IF;
RETURN;
END_IF;
IF NOT Enable THEN
Output := 0.0;
IsActive := FALSE;
RETURN;
END_IF;
IsActive := TRUE;
// Error
Error := Setpoint - ProcessValue;
// Integral with anti-windup
Integral := Integral + (Error * dt);
// Derivative — dt is a VAR_INPUT with default 0.1, but the caller can
// override it. Bind dt to the actual task period; if the FB is wired
// up with dt = 0 it will fault here on every scan.
Derivative := (Error - PrevError) / dt;
// Raw PID output
RawOutput := (Kp Error) + (Ki Integral) + (Kd * Derivative);
// Clamp and anti-windup
IF RawOutput > OutMax THEN
Output := OutMax;
// Anti-windup: stop integrating when saturated
Integral := Integral - (Error * dt);
ELSIF RawOutput < OutMin THEN
Output := OutMin;
Integral := Integral - (Error * dt);
ELSE
Output := RawOutput;
END_IF;
PrevError := Error;
Anti-Windup Explained
Integral windup occurs when the output is saturated (at min/max) but the integral term keeps accumulating. When the error finally reverses, the accumulated integral causes massive overshoot.
The anti-windup technique above stops accumulating the integral when the output is clamped. This is called conditional integration or clamping anti-windup.
Real-World Example: Temperature Control
PROGRAM OvenControl
VAR
// Inputs
TempSensor : REAL := 25.0; // °C from thermocouple
TempSetpoint : REAL := 180.0; // Target °C
Enable : BOOL := TRUE;
// PID controller
TempPID : FB_PID;
HeaterOutput : REAL := 0.0;
// Safety
OverTempAlarm : BOOL := FALSE;
MaxTemp : REAL := 220.0;
END_VAR// Safety check first
OverTempAlarm := TempSensor > MaxTemp;
// Run PID
TempPID(
Enable := Enable AND NOT OverTempAlarm,
Setpoint := TempSetpoint,
ProcessValue := TempSensor,
Kp := 3.0,
Ki := 0.2,
Kd := 0.5,
dt := 0.1,
OutMin := 0.0,
OutMax := 100.0
);
HeaterOutput := TempPID.Output;
// Override on alarm
IF OverTempAlarm THEN
HeaterOutput := 0.0;
END_IF;
Manual Tuning Method (Ziegler-Nichols)
The Ziegler-Nichols method is the most common manual tuning approach:
| Controller | Kp | Ki | Kd |
|---|---|---|---|
| P only | 0.5 × Ku | — | — |
| PI | 0.45 × Ku | 1.2 × Kp / Tu | — |
| PID | 0.6 × Ku | 2 × Kp / Tu | Kp × Tu / 8 |
Example Calculation
If Ku = 4.0 and Tu = 2.0 seconds:
Practical Tuning Tips
Common PID Pitfalls
Cascaded PID Loops
Advanced processes use an inner/outer loop cascade:
PROGRAM CascadeControl
VAR
// Outer loop: Temperature (slow)
OuterPID : FB_PID;
TempSetpoint : REAL := 180.0;
Temperature : REAL := 25.0;
// Inner loop: Flow (fast)
InnerPID : FB_PID;
FlowSetpoint : REAL := 0.0;
FlowRate : REAL := 0.0;
ValveOutput : REAL := 0.0;
END_VAR// Outer loop sets flow setpoint
OuterPID(
Enable := TRUE,
Setpoint := TempSetpoint,
ProcessValue := Temperature,
Kp := 2.0, Ki := 0.1, Kd := 0.3,
dt := 1.0, // Slow loop: 1 second
OutMin := 0.0, OutMax := 100.0
);
FlowSetpoint := OuterPID.Output;
// Inner loop controls valve
InnerPID(
Enable := TRUE,
Setpoint := FlowSetpoint,
ProcessValue := FlowRate,
Kp := 1.5, Ki := 0.8, Kd := 0.0,
dt := 0.1, // Fast loop: 100ms
OutMin := 0.0, OutMax := 100.0
);
ValveOutput := InnerPID.Output;
Try PID Control in Our Simulator
Experiment with PID tuning in our online ST editor. Adjust Kp, Ki, and Kd in real-time and watch how the process responds. Check our motion control lessons for guided PID exercises.