02 — FUNCTION
The stateless calculator — same inputs, same answer, every scan, called from anywhere.
Estimated 24 minutes.
Forty loops asking the same question
Every analog input in the plant asks one thing: the card handed me 16589 counts — how many metres is that? Tank level, extruder pressure, seal-water flow, jacket temperature. Forty loops, forty copies of the same three lines of arithmetic, and each copy is one transposed constant away from a startup nobody enjoys. A FUNCTION is where that arithmetic goes so it exists once.
The whole POU, top to bottom
Two things make this a FUNCTION rather than anything else: the return type after the colon on line 1, and the line that assigns the function's own name. Scale := … is the return statement — there is no other way a value gets out.
Drive one Scale() across the whole 4–20 mA range
The real engine, scan by scan. Click any point on the loop-current sweep at the bottom, or scrub the transport. The mA reading, the tank level and the percent of span are three calls to the same function inside a single scan.
The same call on a rung and on a line
EN and ENO are the ladder editor's way of putting a function inside a rung. IEC 61131-3 makes ENO optional in Structured Text, and in practice almost nobody writes it.
The rung that stops being scanned
The AI_OK contact opens because the analog card flags a broken wire, so the Scale box on that rung is no longer enabled. What is sitting in Lvl_m on the next scan?
- Zero — a disabled box writes 0 to its destination
- Whatever it held on the last scan the box was enabled
- The raw count, unscaled
- An invalid-value marker the HMI can detect
No memory. That is the entire contract.
A FUNCTION gets a slice of stack when you call it and loses it the instant it returns. VAR_INPUT holds the arguments; VAR_TEMP is scratch, handed over zeroed on every call and taken back on the way out. There is no instance, no drawer, nothing that survives — so identical arguments always produce an identical answer. That single guarantee is what makes it safe to call the same function from a rung, from inside a FOR loop, from an alarm-text builder and from a diagnostic screen in the same scan without any of them disturbing the others.
Build a function that stops being a function
Fit ingredients into the body one at a time. The analyser calls the result twice in the same scan, from two different rungs, with identical arguments — and tells you whether the two answers can still be trusted to match. Nothing resets; take an ingredient back out and the verdict comes back with it.
Four calls, how many answers?
Scale(16589, 5530, 27648, 0.0, 12.5) is called on rung 12 and again on rung 340 of the same scan, then twice more on the next scan. How many different answers can those four calls produce?
- One — the arguments are identical
- Two — one answer per scan
- Four — each call gets its own fresh state
- It depends how long the scan took
The timer that never elapses
Sooner or later somebody writes a Debounce function with a TON inside it. IEC 61131-3 forbids it for exactly one reason: a function has no static storage, so there is nowhere for the timer's elapsed time to live between calls. CODESYS and TwinCAT reject the declaration outright. TryPLC's browser engine is more permissive, which is worse — it compiles clean, and then the delay simply never lands.
One keyword apart
Same TON, same PT, same two lines of body. The keyword on line 1 is what decides whether the runtime hands this code a place to keep ET between scans — and nothing else in either listing changes.
Watch the same TON in both homes
One position switch feeding two channels. On the left, TON Delay lives inside FUNCTION Debounce. On the right, the identical TON lives inside Guard, an instance of FB_Debounce. Both POUs compiled without a single error. Only one of them ever elapses.
Clamping, in both languages
This is the rung the transmitter bench was quietly running: at 21.34 mA the raw scaling returned 13.55 m and LIMIT held the HMI tag at 12.50. Clamping is the second most common job a function does.
What actually belongs in a FUNCTION
Anything that is a formula. 4–20 mA scaling. Degrees Fahrenheit to Celsius. Litres to kilograms given a density argument. Clamping a setpoint into its legal window. A CRC-16 over a Modbus RTU frame. One segment of a linear interpolation down a type-K thermocouple curve. Packing three booleans into the status word your SCADA tag expects, and unpacking them again at the other end. If you can phrase it as "given these numbers, produce that number", it is a function. If it needs to know what happened last scan, it is not.
Calibrate the line with two points
Drag the two calibration points until all three commissioning checks light green — 4.00 mA must read 0.00 m, 12.00 mA must read 6.25 m, 20.00 mA must read 12.50 m. Then try dragging both points onto the same raw count and see what the arithmetic does.
Three functions worth stealing
None of them is longer than a rung, and none of them can hurt you. On this engine F_to_C(212.0) returns 100.0, Clamp(13.6, 0.0, 12.5) returns 12.5, and PackStatus(TRUE, FALSE, TRUE) returns 5 — bit 0 and bit 2 set, which is exactly the word the SCADA tag is expecting.
The retentive totaliser
A colleague needs a running flow total: add this scan's litres to a total that survives every scan and holds through a warm restart. They ask whether to write it as a FUNCTION that takes the flow rate and returns the new total. What do you tell them?
- Yes — pass the old total in as an argument and return the new one
- Yes — declare the total as a VAR inside the function
- No — a totaliser is a machine with state, so it wants a FUNCTION_BLOCK
- No — totalising can only be done in ladder
What you learned
- A FUNCTION declares its return type on the header line — FUNCTION Scale : REAL — and returns by assigning its own name.
- VAR_INPUT holds the arguments; VAR_TEMP is scratch that is handed over zeroed on every call and taken back on return.
- No memory between calls: identical arguments always give an identical answer, which is what makes it safe to call from anywhere, any number of times per scan.
- Reading a global, keeping a static, writing an output or starting a timer all break that guarantee — and a TON inside a function has nowhere to keep ET, so it never elapses.
- In ladder a function is an EN/ENO box: a disabled box does not execute and its destination keeps last scan's value. ENO is optional in ST.
- Real work for functions: 4–20 mA scaling, unit conversion, clamping, CRC, curve interpolation, bit packing and unpacking.
- If it needs to know what happened last scan, it is not a function — it is a FUNCTION_BLOCK.
FUNCTION — done
- You can write a FUNCTION with a return type, arguments and temporaries, and call it from a rung or from a line of ST.
- You can look at any block of logic and say whether it is pure — and name exactly what makes it impure.
- You watched the same TON never elapse inside a function and elapse correctly inside an instance, on the real engine.