Skip to content

PRJEDU_pid — a PID lab: a heater, its controller, and a tuning to find

Every file of this example — the XML, the scripts, the page — is in PRJEDU_pid on GitHub. Run it with run.bat (Windows) or ./run.sh (Linux) from that folder.

The laboratory of a control course, in one engine: a heater block that behaves like the real ones on a lab bench, a PID controller written in script, and a page to run the experiments from.

run.bat

Then open http://127.0.0.1:8092/.

On Linux the same example starts with ./run.sh (the engine in ../bin/linux-x64; see "On Linux" in the top README).

Document Role What it is
PLT/S00 the plant a heater block: first order plus dead time, sensor noise, and a fan as the disturbance. It knows its heater power and gives back a temperature
CTL/S00 the controller a PID in script, the step-response measurement, a step test that fits a model and suggests a tuning, and the lab page

The two meet at two values only: the controller reads doc('PLT/S00')/id('TM')/@val, the plant reads doc('CTL/S00')/id('MV')/@val. That is where a real heater would be wired in instead.

The rig starts warm and in balance: 40 °C, held by 28.3 % of heater power. The first set-point step can be made at once.

The plant

tau * dT/dt = K * u(t - theta) - (T - T_AMB) * loss
Value Element
gain K 0.6 °C per % K_GAIN
time constant tau 30 s TAU
dead time theta 3 s DEAD
room 23 °C T_AMB
heat loss with the fan on × 1.6 FAN_LOSS
sensor noise 0.05 °C, read in 0.01 °C steps NOISE

At full power the block settles at 23 + 60 = 83 °C. A set point above that cannot be reached, which is what the anti-windup experiment below is about. The numbers are read every step. Change them in db\db_PLT.xml to give the controller a harder plant: a longer DEAD is the classic way to make a loop oscillate.

The controller

The textbook ISA form, discrete, every T_PID period (h = 0.5 s):

MV = Kp * ( e  +  (1/Ti) * integral(e)  +  Td * de/dt ),   then 0..100 %
  • the derivative acts on the measurement, not on e, so a set-point step gives no derivative kick. It is filtered (N = 10), or the sensor noise would come through Td amplified
  • MANUAL: the heater power is the operator's (MV_MAN), and the integral tracks it (I = MV − P − D). Switching back to AUTO does not bump the output
  • anti-windup (AW): while MV is at 0 or 100 %, the integral stops growing in the direction that cannot help (conditional integration)
  • a trip at T_TRIP (90 °C): heater off, MANUAL, whatever the tuning says

Every set-point change made in AUTO is measured while it settles: rise time (10 → 90 %), overshoot, settling time (the last moment outside a band of 2 % of the step) and IAE. The result is logged after 180 s and added to the page's table.

The starting tuning, Kp 2 and Ti 60 s, is deliberately sluggish: a loop that works and leaves the student something to do.

What it measures

The step 40 → 50 °C, each from the rig in balance, measured by the controller itself:

Tuning Rise 10–90 % Overshoot Settling (2 %) IAE
Kp 2, Ti 60 s (the start) 123.5 s 0 % not within 180 s 444 °C·s
Kp 8.3, Ti 24 s (SIMC from the true plant) 7.5 s 1.5 % 53.5 s 96 °C·s
Kp 6.92, Ti 28 s (SIMC from the step test) 8.5 s 1.1 % 43.5 s 91 °C·s

The step test, run from the page, fitted K 0.59 °C/%, tau 28.5 s, theta 3.5 s to a plant that is really 0.6, 30 and 3, and suggested Kp 6.92, Ti 28 s. The noise and the two-point method put it close, not exact, which is what a real step test does too.

Anti-windup, with Kp 8.3 and Ti 24 s. The set point goes to 85 °C (above the 83 °C the heater can reach), then after two minutes back to 50 °C:

Integral when the set point drops Heater after the drop The step 85 → 50 settles in IAE
AW off 587 % still at 100 % for 20 s. The block climbs on to 82.5 °C 113.5 s 1578 °C·s
AW on 75 % off at once 51.5 s 480 °C·s

The fan (a heat loss 1.6 times higher) with the same tuning: the block drops 1.6 °C, the controller raises the heater from 28 to 45 % and brings it back within about a minute. Fan off: 1.7 °C too high, the same way back.

What it teaches

A loop is two documents. The controller never reads the plant's T, the true temperature. It reads TM, what the sensor says, with noise and 0.01 °C steps. The plant never knows the set point. Everything the controller knows is in its own document, and every term of the PID is a <var>: P_TERM, I_TERM, D_TERM, and the page draws them.

The sample time is configuration. main.tick reads evt_per from its own timer element. Change T_PID's evt_per to 2 in db\db_CTL.xml and the controller's h is 2 s. A tuning that was fine at 0.5 s may oscillate.

The page asks, the controller decides. xdb_acl="0xFF111111" on the controller document: a page reads everything and writes only the lab's controls (SP, MODE, MV_MAN, KP, TI, TD, AW, TUNE), and each of them is clamped by ATR.lmt. A set point of 500 becomes 85. A write to PV or T_TRIP is refused, and the log says so: CTL/S00:PV.val: xdb_acl yazma reddetti (rol 0). The fan belongs to the plant, whose document has no xdb_acl: the lab bench's knobs are for turning.

A step test is a state machine. main.tune: MANUAL at the current power, wait until the temperature moves less than 0.3 °C in 15 s, step the heater by 20 %, record until it moves less than 1 % of the change in 20 s, fit, put the loop back the way it was. Every phase is in TUNE_STATE and the log.

Try this

Do See
+10, with the starting tuning the table's first row: slow, and not settled within the 180 s the measurement waits
Run step test, then Use the suggestion, then +10 the third row. Compare
Kp 20, Ti 24, then the same with Td 3 how much faster, and at what price in overshoot? What does the derivative change? (not measured here: that is the exercise)
raise Kp until the loop oscillates, then set DEAD to 6 in db_PLT.xml and restart the gain at which it starts to oscillate, before and after. Dead time is what limits a loop
Kp 8.3, Ti 24, untick anti-windup, set point 85, wait two minutes, set point 50 the table above: the integral climbs past 500 %, and the heater stays full on after the set point has dropped
fan on a disturbance. The P term reacts at once, the integral takes over
MANUAL, move the heater, then AUTO no bump: the integral was tracking
download CSV every sample since the page was opened: t, SP, PV, MV, P, I, D, the tuning, the mode, the fan. For the report

A real heater

The page, the controller and its measurements do not change. Replace PLT/S00 with a connection to the heater: something that reads the temperature into a <var> and writes MV to the heater. Then point main.tick at those two values instead of doc('PLT/S00')/id('TM') and doc('CTL/S00')/id('MV'). Many control courses use a small Arduino heater board with a serial text protocol, which PRO.raw over CON.ser can speak (PRJPER_tello shows PRO.raw with a text protocol). That part is not in this example and not tested.

Licence

Two connections, three documents: well inside FREE.