Chapter 13 / 36

Functions and function blocks

Separate calculations from stateful devices and define clear interfaces.

Sequence, device adapter and physical drive separated by a command-and-feedback contract, with response-timeout supervision
A useful block boundary. The machine sequence asks for work. The device block owns the ordinary request, supervises response and reports a reason when it cannot comply.

A function block is worthwhile when it owns a promise you can test in isolation. Give a motor block a request, readiness and observed running feedback. Ask it to report whether it accepted the request and whether expected feedback arrived in time. Keep carton ownership in the sequence: a motor block cannot establish product arrival from drive feedback alone. That separation makes both pieces easier to explain.

A block should own a small promise

You have written logic for one extraction fan. Now the drawing shows eight. Copying the routine eight times feels quick, until somebody changes the start timeout in seven copies and forgets the eighth. Reuse solves that problem only if the reused block has a clear job. A large block with twenty mode switches can be harder to trust than eight simple routines.

Start with the promise: when requested and permitted, command this fan; if running feedback does not arrive within the allowed time, report a failed start. This is an ordinary equipment-control promise. It does not establish a safety function or prove that airflow exists. A contactor auxiliary contact, a drive running bit and an airflow switch describe different evidence. Choose the feedback that matches the requirement.

The machine sequence decides when extraction is needed. The fan block manages this particular fan. The physical output mapping writes the terminal. Giving each decision one owner makes the program easier to explain before it makes the program shorter.

Calculation or memory?

A function is a good home for a calculation such as converting a measured volume into an estimated fill percentage. Design it so the result depends on its explicit inputs, with no hidden changes elsewhere. You can test that calculation with a table of numbers.

A function block is the natural home for behaviour that remembers: a timer, a running request accepted earlier, a previous input used for edge detection, or a latched fault. Each instance owns its own working memory. ExtractFan and CoolingFan can use the same block definition while keeping different timers and fault histories. Beckhoff's documentation distinguishes function calls from function-block instances and their stored state; the exact declaration and visibility rules belong to the target environment. Verify the programming model.

Do not infer persistence through power loss from the word “memory.” State across cyclic calls and retained state across restart are separate design choices. We will make that distinction explicit in Types, arrays and data.

Write the interface in ordinary language

Before writing ST, describe these signals:

SignalMeaning
RequestRunThe equipment owner currently wants this fan running
PermitRunOrdinary process conditions allow that request
RunningFeedbackThe selected feedback currently reports running
ResetPulseOne deliberate request to clear an inactive fault
StartLimitMaximum time allowed to establish running feedback
RunCommandThis block's requested output state
StartFailedA remembered unsuccessful start

Notice what is absent: no references to “Tank 3,” no direct HMI button addresses, and no writes to a global mode. Those belong outside this reusable promise. Configuration such as StartLimit must be validated before use; a negative or unsupported time value must not silently turn into an acceptable setting.

A small block, including its limits

This is a complete illustrative block definition using an IEC-style TON. It assumes ResetPulse has already been made into a one-cycle event. Library names and declaration editors vary by vendor. Integrating it requires I/O mapping, configuration validation, task setup and the machine's separate protective systems.

FUNCTION_BLOCK FB_FanStart
VAR_INPUT
    RequestRun : BOOL;
    PermitRun : BOOL;
    RunningFeedback : BOOL;
    ResetPulse : BOOL;
    StartLimit : TIME := T#3s;
END_VAR
VAR_OUTPUT
    RunCommand : BOOL;
    StartFailed : BOOL;
END_VAR
VAR
    AwaitRunning : TON;
END_VAR

IF ResetPulse AND NOT RequestRun THEN
    StartFailed := FALSE;
END_IF;

RunCommand := RequestRun AND PermitRun AND NOT StartFailed;
AwaitRunning(
    IN := RunCommand AND NOT RunningFeedback,
    PT := StartLimit
);

IF AwaitRunning.Q THEN
    StartFailed := TRUE;
    RunCommand := FALSE;
END_IF;
END_FUNCTION_BLOCK

The timer is called every scan. If the run command disappears, its input becomes false and its nonretentive timing resets. Calling the timer only inside an IF RunCommand branch would hide the reset calls and make its state harder to reason about.

The final assignment removes the command in the same scan that detects timeout. Without it, the earlier expression would leave RunCommand true until the next call. Neither order is magic; they produce different traces, so select deliberately.

This small block also detects feedback loss after a successful start using the same delay. That may be an unsuitable requirement. If startup gets three seconds but running feedback loss gets half a second, use two clearly named monitors. A block is correct against its contract, not because its name sounds professional.

Follow one instance through time

At time zero, request and permission are true. Feedback is false, so the command turns on and the timer starts. At 0.7 seconds, feedback arrives. The timer input becomes false and the timer resets. At 5 seconds, the request disappears. The command turns off immediately; there is no coast-down verification in this block.

Now remove feedback permanently. At the first call when elapsed time reaches the timer preset, the fault latches and the command turns off. Leaving RequestRun true while pressing Reset does nothing. The operator must first remove the request. This avoids reset behaving as a fresh start in this particular contract. Other machines need other restart procedures, which must be designed explicitly.

Give the second fan a separate instance. Calling one instance for fan A and then again for fan B in the same scan does not create two fans. It reuses one timer and one fault memory twice. The resulting behaviour depends on call order and can be surprisingly convincing during a short test.

When abstraction starts to hurt

A block should expose useful status, not every internal variable. Exposing StartFailed helps the HMI explain a stopped fan. Allowing the HMI to write the timer's elapsed time lets another component quietly alter the block's memory.

Keep process ownership outside: which fan is duty, whether a filter needs cleaning, and whether the chamber may start production. Add a new parameter only when two real applications need different behaviour. Do not add an option merely because a future machine might exist.

Ladder uses the same idea. A vendor's reusable instruction or function-block call can encapsulate the timer and latch. Drawing contacts does not remove the need for independent instances, documented reset rules or a single output owner.

Try it

Two pumps share the block idea. Pump A must prove flow within four seconds; pump B within six. A colleague declares one instance named PumpMonitor, calls it twice, and connects its fault output to two alarms. Explain the defect. Then specify a test that demonstrates independence without waiting for every possible process condition.

Work through the answer

The block definition can be shared; the instance cannot. Declare PumpAMonitor and PumpBMonitor, each with its own timer and latch. Give each instance its own request, permission, feedback and configured delay. Map each command once.

Request both pumps. Provide A's flow feedback after one second but keep B's absent. A must remain healthy while B faults after its six-second allowance. Remove B's request and reset B. A's command, elapsed timing and status must remain unchanged. Finally reverse the roles to expose a wiring copy error. This test checks ownership, timing and reset isolation together, rather than merely proving that both outputs can turn on.

Record the block's version and that behaviour table with its tests. Reuse becomes valuable when a later programmer can understand exactly what is being reused.

Now make the decision yourself

Use the chapter’s model on a fresh question, then compare your reasoning with the worked decision.

How this becomes a program

Why did motor B inherit motor A’s fault?

Two motors use reusable control logic. Each needs its own command, running feedback, timer and fault memory.

MotorA instanceMotorB instanceIndependent state

Your first artifact

Put persistent device state in a function-block instance. Give each real motor its own instance. Keep a stateless unit conversion in a function.

Open the worked decision
MotorA(Command := RequestA, Running := FeedbackA);
MotorB(Command := RequestB, Running := FeedbackB);

Why this line belongs here

MotorA and MotorB here denote separately declared instances of your project device block. Calling one instance twice with different inputs shares its memory; it does not create two devices.

Change the task

List which state must belong to the motor instance and which coordination state must belong to the machine above it.