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.
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.
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.
βοΈ 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.
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.
Both an action and a method can reach these.
(* 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 syntax | inst.Fill; | x := inst.Do(p); |
| Reach for it when⦠| splitting a long FB body into named, readable chunks that share state | you need an encapsulated operation with inputs/outputs, or polymorphism |
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
Go deeper
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 β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?