CODESYS Β· Track 04 Β· OOP for PLCs

Function Blocks, Evolved

CODESYS pushed real object orientation into IEC 61131-3. A function block can now have methods and properties, EXTENDS another FB to inherit it, and implement an INTERFACE so different FBs are interchangeable. It can also break its logic into actions and methods β€” and the difference between those two is the part everyone trips on. We'll X-ray it.

πŸ”– actionsβš™οΈ methods🧬 EXTENDSπŸ”Œ INTERFACE
01 Β· Inheritance & polymorphism

One call, the right behaviour

A base FB FB_Drive implements I_Drive with a Start() method. FB_VFD and FB_Servo both EXTEND it and override Start() their own way. Pick which object the I_Drive reference points at, then call ref.Start() β€” watch polymorphism run the right override automatically.

πŸ”Œ INTERFACE I_Drive : Start() Β· Stop() Β· Speed
FB_Drive
implements I_Drive
Start() β†’ enable
Stop() β†’ disable
FB_VFD
EXTENDS FB_Drive
Start() β†’ ramp to freq
+ AccelTime property
FB_Servo
EXTENDS FB_Drive
Start() β†’ home then enable
+ Position property

your code β€” written once, against the interface

drive REFERENCE TO I_Drive β†’

That's the magic: your line code says drive.Start() once. Point drive at a VFD and it ramps; point it at a servo and it homes first. Add FB_Stepper next year β€” your line code never changes, because it only knows I_Drive. No CASE-on-type, no edits to working logic.

02 Β· The OOP toolkit

Four ideas worth the upgrade

βš™οΈ Methods & properties β€” an FB gains named actions (.Start()) and get/set properties (.Speed), instead of a wall of input/output pins. Cleaner call sites.

🧬 EXTENDS β€” inherit a base FB and add or override behaviour. Build a generic FB_Drive once; specialise it per device without copy-paste.

πŸ”Œ INTERFACE β€” a contract of methods. Many unrelated FBs can implement it, so code that holds an I_Drive works with all of them. Polymorphism.

βš–οΈ Use it judiciously β€” OOP shines for device abstraction & libraries. For a simple interlock, plain ladder is still clearer. Reach for OOP when it removes real duplication.

03 Β· Action vs Method

Two ways to bolt behaviour onto one FB

Both attach a named block of logic to a function block, and in the project tree they look like siblings. Under the hood they're opposites in one decisive way β€” what memory they get. This is the single most-confused pair in IEC OOP, so here's a live X-ray of FB_Tank's memory while each one runs.

πŸ”– An Action is a chapter heading inside the FB's own story β€” it reads and writes the very same pages (the member variables). It brings no bag of its own.

πŸ“¦ A Method is a parcel you hand things to and get something back β€” it carries its own private drawer, an in-tray for parameters and an out-tray for the result.

two ways to add Fill behaviour β†’
Shared Β· the tank instance's own variables
Level : REAL120
Valve : BOOLFALSE

Both an action and a method can reach these.

Private Β· exists only while a method runs
VAR_INPUT  n : INT50
VAR  result : REALβ€”
↩ return : INTβ€”
βœ• none β€” an action has no private scope at all

the call site

(* ACTION Fill β€” no scope of its own *)
ACTION Fill
    Valve := TRUE;        // the FB's own var
    Level := Level + 1;   // shared state only
END_ACTION

// call it β€” looks like a field, no ( )
tank.Fill;
(* METHOD AddLitres β€” own scope + a return *)
METHOD AddLitres : INT
VAR_INPUT
    n : INT;          // a parameter you pass in
END_VAR
    Level := Level + n;  // still sees member vars
    AddLitres := Level;  // hand a value back
END_METHOD

// call it β€” pass in, receive out
newLvl := tank.AddLitres(50);
πŸ”– ACTIONπŸ“¦ METHOD
Its own local variablesβœ• no β€” shares the FB'sβœ“ VAR / VAR_INPUT / VAR_OUTPUT
Takes parametersβœ• noneβœ“ pass values in
Returns a valueβœ• neverβœ“ optional typed return
Touches the FB's member varsβœ“ directlyβœ“ via the implicit THIS^
Recursion / reentrancyβœ•βœ“
Lives in an INTERFACE & can be overriddenβœ•βœ“ the basis of polymorphism
Call syntaxinst.Fill;x := inst.Do(p);
Reach for it when…splitting a long FB body into named, readable chunks that share stateyou need an encapsulated operation with inputs/outputs, or polymorphism
04 Β· THIS^ & SUPER^

Every method gets two pointers for free

Inside any method you silently receive two references: THIS^ β€” this exact instance β€” and SUPER^ β€” the parent FB's version. They're how an override can extend the base instead of throwing it away. Here FB_Servo.Start() overrides FB_Drive.Start() but reuses it first:

METHOD Start   // FB_Servo's override of FB_Drive.Start
    SUPER^.Start();   // run the PARENT's Start first  β†’ Enable := TRUE
    THIS^.Home();    // then THIS servo's own extra step
END_METHOD
watch the call walk the chain β†’

the call trace

Go deeper

Want the deep cut?

Method anatomy token by token, properties that hide getters & setters, real polymorphism across an array of devices, the ABSTRACT / FINAL controls β€” and the honest take on when not to use OOP at all.

πŸ”¬ Open the OOP deep dive β†’
05 Β· Check yourself

Two quick gut-checks

1 Β· Your code calls drive.Start() on a REFERENCE TO I_Drive. Next year you add a brand-new drive type. What must change in that calling code?

2 Β· You need a reusable operation that takes a setpoint, returns a computed result, and can be declared in an INTERFACE so any FB can supply its own version. Action or method?