Changing a setpoint gently
Tell a 30 kW pump drive to go from 0 to 50 Hz in one scan and the drive will refuse, trip on overcurrent or slam the water column in the pipe hard enough to bang the check valves. Tell a furnace controller that the setpoint has jumped from 200 °C to 800 °C and the heating elements go to full power while the refractory lining cracks from thermal shock. Real equipment wants its targets to move, not jump.
A ramp generator sits between the target the operator asks for and the setpoint the equipment receives. Every scan it moves its output towards the target by at most one step, where the step is the ramp rate times the scan time. A 5 Hz/s ramp in a 100 ms task moves 0.5 Hz per scan, so 0 to 50 Hz takes 10 s.
PROGRAM Main
VAR
Target : REAL := 50.0; (* input: speed the operator asks for, Hz *)
RampRate : REAL := 5.0; (* input: Hz per second *)
Step : REAL;
SpeedRef : REAL; (* output: setpoint to the drive, Hz *)
END_VAR
Step := RampRate * 0.1; (* this page scans every 0.1 s *)
IF SpeedRef < Target THEN
SpeedRef := MIN(SpeedRef + Step, Target); (* never step past the target *)
ELSIF SpeedRef > Target THEN
SpeedRef := MAX(SpeedRef - Step, Target);
END_IF;
END_PROGRAMWatch SpeedRef climb 0.5 Hz per scan and stop exactly at 50. Type a new target of 20 while it is still climbing: it turns round and ramps down. The MIN and MAX are the important part. Without them the ramp oversteps the target, and the next scan steps back:
The ramp rate is really a scan-time calculation. If the task runs every 10 ms instead of 100 ms, the step must be ten times smaller for the same Hz per second. Hard-code 0.1 and move the program to a faster task, and every ramp on the machine gets ten times steeper. In production code pass the cycle time in, or read it from the task configuration, and run ramps and controllers in a fixed-interval task: a cyclic interrupt OB (OB30–OB38) on an S7-1500, a cyclic task in CODESYS or TwinCAT, a periodic task in Studio 5000.
Closing the loop: proportional control
A ramp is open loop: it decides the output without looking at the result. Closed-loop control measures the process and corrects. Every controller in this chapter starts from the error:
Error = Setpoint − ProcessValue
The simplest controller makes its output proportional to the error: Out = Kp · Error. A big error gives a big correction, a small error a small one. Kp is the gain, in percent of output per unit of error.
To watch a loop you need a process, so the programs from here on include one. It is a small electrically heated water bath, modelled as a first-order process: the temperature moves towards the temperature the heater could sustain, at a rate set by a time constant. With the heater at 100 % it would settle 80 °C above a 20 °C ambient. The time constant is 5 s — compressed so that you can watch a loop settle in seconds rather than the half hour a real tank takes.
PROGRAM Main
VAR
SP : REAL := 60.0; (* input: setpoint, deg C *)
Kp : REAL := 5.0; (* input: gain, % per deg C *)
PV : REAL := 20.0; (* process value: bath temperature *)
Err : REAL;
Out : REAL; (* heater power, % *)
Dx : REAL;
END_VAR
(* The controller *)
Err := SP - PV;
Out := LIMIT(0.0, Kp * Err, 100.0);
(* The process: first-order, gain 0.8 deg C per %, time constant 5 s *)
Dx := 20.0 + 0.8 * Out - PV; (* how far PV is from where Out would take it *)
PV := PV + Dx * 0.1 / 5.0; (* Ts / Tau of the way, every scan *)
END_PROGRAMLet it run for ten seconds. The temperature rises fast, then slows down — and stops short of the setpoint.
P-only control is still the right answer in some places: a level in a surge tank, where "somewhere near the middle" is all you want, is often run P-only on purpose. For temperature, pressure and flow, the offset is not acceptable, and something has to remember the error.
Integral action: remembering the error
The integral term adds up the error over time. As long as any error remains, the integral keeps growing, and the output keeps moving, until the error is zero. Then the integral stops changing and holds exactly the output the process needs. That is how a PI controller removes the offset.
In a PLC the integral is a running sum, updated once per scan:
I := I + Kp · (Ts / Ti) · Error, then Out := Kp · Error + I
Ts is the scan time and Ti is the integral (reset) time in seconds — the time the integral would take to repeat the proportional action for a constant error. Smaller Ti means stronger, faster integral action. This "Kp times (Ts/Ti)" form is the one PID_Compact, PIDE and most vendor blocks use, so the numbers you learn here are the numbers you will type into them. Because I must survive between scans, a real controller is a function block, never a function (chapter 18).
PROGRAM Main
VAR
SP : REAL := 60.0; (* input: setpoint, deg C *)
Kp : REAL := 2.0; (* input: gain *)
Ti : REAL := 4.0; (* input: integral time, s *)
PV : REAL := 20.0;
Err : REAL;
P : REAL;
I : REAL;
Out : REAL;
Dx : REAL;
END_VAR
Err := SP - PV;
P := Kp * Err;
IF Ti > 0.0 THEN
I := I + Kp * 0.1 / Ti * Err; (* Ts = 0.1 s *)
END_IF;
Out := LIMIT(0.0, P + I, 100.0);
Dx := 20.0 + 0.8 * Out - PV;
PV := PV + Dx * 0.1 / 5.0;
END_PROGRAMThis time the temperature arrives at 60 °C and stays there. Watch the hand-over: at the start almost all of the output is P; at the end P is near zero and I holds the 50 % the bath needs at 60 °C. Change SP to 40 and watch the integral unwind to 25 %. Try Ti = 1 for a faster, overshooting response, or Ti = 20 for a slow crawl. Tuning is the art of choosing Kp and Ti for the process you have; the mathematics is these two lines.
Windup, and how to stop it
The clamp creates a new problem. When the output is pinned at 100 % because the process cannot keep up, the error stays large and the integral keeps adding it up. By the time the process arrives, the integral holds an enormous value, and the output stays at 100 % long after it should have backed off. The temperature sails past the setpoint. This is integral windup, and it is the most common fault in home-made PI code.
The fix used here is conditional integration: stop integrating while the output is saturated and the error is pushing it further into saturation.
PROGRAM Main
VAR
SP : REAL := 90.0; (* input: a big step, so the heater saturates *)
AntiWindup : BOOL; (* input: restart with it on and off *)
Kp : REAL := 2.0;
Ti : REAL := 4.0;
PV : REAL := 20.0;
Err, P, I, Out, Dx : REAL;
PeakPV : REAL; (* highest temperature reached *)
Saturated : BOOL;
END_VAR
Err := SP - PV;
P := Kp * Err;
Saturated := (P + I >= 100.0 AND Err > 0.0) OR (P + I <= 0.0 AND Err < 0.0);
IF NOT (AntiWindup AND Saturated) THEN
I := I + Kp * 0.1 / Ti * Err;
END_IF;
Out := LIMIT(0.0, P + I, 100.0);
Dx := 20.0 + 0.8 * Out - PV;
PV := PV + Dx * 0.1 / 5.0;
PeakPV := MAX(PeakPV, PV);
END_PROGRAMRun it for twenty seconds with AntiWindup off: I climbs past 120 while the heater is already at full power, and the bath overshoots to about 97 °C before it comes back. Switch it on, press Restart, and run again: the peak is about 90.1 °C. Same gains, same process — the only difference is that the integral stopped growing when growing could not help.
Other anti-windup methods exist. Clamping the integral to the output range is the crudest. Back-calculation bleeds the integral back towards the clamped output with its own time constant and is what many vendor blocks use internally. All of them do the same job: keep the integral from promising an output the actuator cannot deliver.
Manual, auto and the bumpless transfer
Every real loop has a manual mode. The operator takes the valve, sets 40 %, fixes something, then hands the loop back to the controller. If the controller's integral is still holding last week's value, the output jumps the moment auto is selected, from 40 % to whatever the integral says. Valves slam, levels surge, operators lose trust in the loop.
A bumpless transfer prevents the jump. The simple version has two parts:
- In manual, the output follows the operator's value, and the integral tracks it: set
I := ManualOut - Pevery scan, so that P + I already equals the manual output. - When auto takes over, the first scan computes
P + Iand gets the value the operator left, plus one scan's worth of integral. No bump. From there the controller moves the output smoothly towards what the process needs.
Going the other way, most HMIs copy the controller output into the manual value at the moment of switching. Vendor blocks do the tracking for you; the second challenge below makes you build it once so you know what they are doing.
Vendor PID blocks, and when to use them
You will rarely write a PID controller from scratch for a production machine. Every platform ships a proven one:
| Platform | Block | Worth knowing |
|---|---|---|
| Siemens S7-1200 / S7-1500 | PID_Compact | Must be called in a cyclic interrupt OB. Pre-tuning and fine tuning from the TIA commissioning screen; outputs as REAL, analog or PWM. PID_3Step for motorised valves. |
| Rockwell ControlLogix / CompactLogix | PIDE | Velocity form, so manual/auto and output limits are bumpless by design. Usually used in function block diagrams; the older PID instruction exists in ladder. |
| CODESYS | PID / PID_FIXCYCLE (Util library) | PID_FIXCYCLE takes the cycle time as an input, which makes it deterministic in a fixed task. |
| Beckhoff TwinCAT 3 | FB_CTRL_PID, FB_CTRL_PI (Tc2_ControllerToolbox) | Configured through a parameter structure that includes the cycle time and anti-windup limits. |
Use them. They handle the corner cases — derivative filtering, bumpless mode changes, output rate limits, the cycle-time maths — that a home-made controller takes months of commissioning to discover. But a vendor block is only as good as the gains you type in and the scan time you call it at. The most common PID_Compact fault in the field is not the algorithm; it is a block called from OB1, whose scan time varies, with gains copied from another machine.
The derivative term (the D in PID) reacts to how fast the error is changing. It helps on processes with long lags, but it amplifies measurement noise, and most level, flow and pressure loops run as PI. If you add it, apply it to the process value, not to the error, so a setpoint change does not produce a kick.
From loops to well-built programs
You can now ramp a setpoint without overstepping, explain why a P controller settles short, add integral action with the scan time in the right place, stop it winding up, and hand a loop between manual and auto without a bump. And you have watched a first-order process respond — the most useful picture to carry into a commissioning job.
The programs in this chapter are getting long enough that their structure starts to matter as much as their logic. The next chapter is about exactly that: naming, commenting and organising ST so that it reads like the machine, and so that it survives the move between CODESYS, TwinCAT, TIA Portal and Studio 5000.