09 — INTERFACE
the plug, not the device — write code against a shape and swap the hardware underneath.
Estimated 24 minutes.
Three pump vendors, one control program
The bid package goes out with a Grundfos centrifugal set on the P&ID. Procurement comes back with a Netzsch progressive-cavity pump because the product turned out to be shear-sensitive. Commissioning starts three weeks before the pump lands on site, so the first two weeks run against a stub. Written the usual way, that is three versions of the control program and three sets of testing. Written against an interface, it is three blocks and one program that never changed.
Fit a device to the socket
The receptacle on this skid is wired to a contract called I_Pump: five signatures, five bores. Offer every block in the library up to it — including the two that have no business being pumps — and watch which ones seat.
The declaration, in both languages
An INTERFACE has no VAR section, no bodies and no instances. It is a list of things a block must be able to answer, and the compiler checks it before you can download.
What is actually inside an interface?
You open I_Pump in the project tree. It declares Start, Stop, SetSpeed, Speed and Healthy. What else is in there?
- Default bodies for each method, which a block may override
- Nothing — signatures only, no code and no variables
- A VAR section shared by every block that implements it
- One instance, created by the runtime at start-up
The socket pattern on the wall
A panel socket does not know what will be plugged into it. It guarantees a pin layout, a voltage and an earth, and it refuses anything whose pins do not line up. That is the entire idea. The control program is the socket: it guarantees that whatever sits behind PU-101 can be started, stopped, given a speed and asked how it is doing. What that thing does with a speed demand — ramp, jump, refuse — is the device's business, not the socket's.
A block that honours the contract
This is what you would download to a CODESYS, TwinCAT or Machine Expert controller. TryPLC's browser engine implements the IEC 61131-3 2nd-edition core, so INTERFACE, IMPLEMENTS, METHOD and PROPERTY are shown here as source and never executed — the live stage on the next screen runs the composition form that does the same job.
Swap the vendor, leave the program alone
Three concrete pump blocks, one set of commands, running scan by scan on the real engine. Move the binding between them and watch two things: the machine on the skid changes completely, and the four lines of control logic at the top of the stage do not change at all.
Calling through the reference
Note the order: bind once at cold start, guard, then call. Those three steps in that order are the whole discipline of using an interface on a controller.
The same job without the keyword
On a controller with no interface type you get the same swap by holding all the candidates and selecting one. It costs memory for the blocks you are not using and a CASE at every read-back — which is exactly the work an interface reference does for you in one indirection.
Where does shared code go?
FB_CentrifugalPump and FB_ProgCavityPump both need the same forty lines of run-hours counting and alarm latching. You already have I_Pump. What gives them that code?
- I_Pump — put the forty lines in the interface
- A base function block both of them extend, or one both of them contain
- A second interface, I_Counted, with the code in it
- Nothing — shared code cannot exist between two blocks
Interface or base block?
A base block carries implementation and state, and a block gets exactly one parent. An interface carries neither, and a block can satisfy as many as it can honour. So the rule of thumb on a plant is: use a base block when the children genuinely share working code, and an interface when unrelated things need to appear in the same list. A pump and a damper share nothing internally, and both belong in the device manager's list — that is an interface. Two pump variants that share their alarm handling — that is a base block, or a shared sub-block, with the interface still on top.
One block, three contracts
The same instance can sit in the device list as an I_Pump, in the maintenance list as an I_Maintainable and behind one HMI faceplate as an I_HmiFaceplate. Each list sees only the shape it asked for, and none of them can reach the block's variables.
A reference is an address, not a machine
Declaring Pump : I_Pump does not create anything. It reserves a reference — four bytes that hold the address of an instance — and until something is assigned to it those four bytes are zero. Calling a method through zero is not a warning and not a skipped call: the runtime reads a method address out of address zero and jumps there. On CODESYS and TwinCAT that is an access violation and the task stops.
Take a controller down, then fix it
Two lines are in play: the cold-start binding and the guard in front of the call. Try all four combinations and read what the CPU does in each one.
The runtime checks, and what they are worth
Two checks, one habit: never call through a reference you did not bind in the same POU, and never assume an instance answers a contract just because it is in the array.
The commissioning swap
Two weeks of commissioning ran against FB_PumpSim, bound to Pump at cold start. The Netzsch unit lands on site and you bind Pump := PU101_PC instead. It runs, but the operator reports the pump will not go below 20 % however low the setpoint goes. What is wrong?
- The interface is broken — SetSpeed is not being called
- Nothing is wrong: the real block enforces a limit the stub never had
- The reference is still pointing at the simulator
- IMPLEMENTS failed silently and the call went nowhere
What you learned
- An INTERFACE is a list of signatures — no bodies, no VAR section, no instance and no memory.
- FUNCTION_BLOCK … IMPLEMENTS I_Pump is a promise the compiler checks before you can download.
- A variable of interface type is a reference; you assign an instance to it, normally once at cold start.
- A block can implement several interfaces and still inherit from at most one parent, because interfaces carry no implementation.
- Use a base block for shared code, an interface for unrelated things that must appear in the same list.
- An unassigned interface reference holds zero, and calling through it is an access violation that stops the task.
- Guard with Pump <> 0; __QUERYINTERFACE and __ISVALIDREF are useful vendor extensions, not IEC text.
INTERFACE — done
- You can write an interface and make a block answer for it, and you know exactly what the compiler checks.
- You can swap a vendor, or a simulator, behind a control program that never learns which one is fitted.
- You can spot a null interface reference before it stops a controller, and guard the call that would have.