1

In IEC 61131-3 implementations (e.g. CoDeSys, TwinCAT, Unity), what actually happens when you use the assignment operator on variables of Function Block type? I've looked at the TwinCAT and CoDeSys docs without finding any kind of clear description; I assume it's defined by the 61131-3 spec, but I don't have a copy of that.

E.g:

VAR
   fbA, fbB : TON;
END_VAR
   
// Function Block assignment
fbB := fbA;

In this case, memory is statically allocated for both the fbA and fbB instances before the assignment is executed. Does the assignment operator just copy the VAR_INPUT, VAR, and VAR_OUTPUT internal state variables from fbA's memory area to fbB's? Does anything else happen?

What if fbA or fbB are instances of a Function Block type that implements FB_init(), FB_reinit() or FB_exit()? Does assigning fbB := fbA result in calls to any of those methods?

Hydrargyrum
  • 3,378
  • 4
  • 27
  • 41
  • That compiles? NO clue what happens. Maybe you can test it :) – Roald Jul 05 '23 at 12:54
  • I tried it and ALL the values get copied from fbA to fbB. As far as init, erinit and exit, I don't think they affect the instances in any way at all, they are explicit calls, fbInit is called when the program is first started, reinit when an online change is made and exit is called when the program is about to terminate, none of which have anything to do with assigning values of the instance, at least that is my thought on the matter. – ziga Jul 05 '23 at 15:55
  • You can read more on the explicit calls at [beckhoff infosys](https://infosys.beckhoff.com/english.php?content=../content/1033/tc3_plc_intro/6415331211.html&id=) – ziga Jul 05 '23 at 16:04
  • @Roald yeah I think I will do some tests to empirically determine whether init/reinit/exit have any interaction with FB assignment and post the results here for the sake of posterity. Thanks to compiler warnings in recent versions, I know that FB_exit is called for FBs which are stack-allocated (e.g. as VAR_TEMP in an FB, or as VAR or return type in a FUN or method or property getter). That didn't used to be the case in older versions of CoDeSys/TwinCAT. I presume FB_init *is* called when those stack allocations occur. – Hydrargyrum Jul 06 '23 at 04:21

1 Answers1

3

I wrote a bit of test code (TwinCAT 3.1.4024.44)

MAIN.TcPOU:

PROGRAM MAIN
VAR_INPUT
    nInits: LINT;
    nReinits: LINT;
    nExits: LINT;
END_VAR
VAR
    fbA, fbB, fbC : FB_Test;
    bRun : BOOL;
    bCallStackFB : BOOL;
    nStackResult : INT;
END_VAR


-------------

IF bRun THEN
    fbA();
    
    fbB := fbA;
    
    fbB();
    
    fbC := fbB;
    
    fbA.nInput := fbB.nOutput;
    bRun := FALSE;
END_IF

IF bCallStackFB THEN
    nStackResult := UseStackFB(10);
    bCallStackFB := FALSE;
END_IF

==============

METHOD UseStackFB : INT
VAR_INPUT
    nInput : INT;
END_VAR
VAR
    fbStackFB : FB_Test;
END_VAR

---------------

fbStackFB(nInput := nInput);
UseStackFB := fbStackFB.nOutput;

FB_Test.TcPOU:

FUNCTION_BLOCK FB_Test
VAR_INPUT
    nInput : INT;
END_VAR
VAR_OUTPUT
    nOutput : INT;
    nRunCount : LINT := 0;
END_VAR
VAR
    nVar : INT;
END_VAR

---------------

nVar := nInput + 1;
nOutput := nVar + 1;
nRunCount := nRunCount + 1;

================

METHOD FB_exit : BOOL
VAR_INPUT
    bInCopyCode : BOOL; // if TRUE, the exit method is called for exiting an instance that is copied afterwards (online change).
END_VAR

----------------

MAIN.nExits := MAIN.nExits + 1;

================

METHOD FB_init : BOOL
VAR_INPUT
    bInitRetains : BOOL; // if TRUE, the retain variables are initialized (warm start / cold start)
    bInCopyCode : BOOL;  // if TRUE, the instance afterwards gets moved into the copy code (online change)
END_VAR

----------------

MAIN.nInits := MAIN.nInits + 1;

================

METHOD FB_reinit : BOOL
VAR_INPUT
END_VAR

----------------

MAIN.nReinits := MAIN.nReinits + 1;

Running the main program in the TC debugger shows that MAIN.nInits starts at 3 (one each for the statically allocated fbA, fbB and fbC instances). Toggling bRun does not increment the init, reinit, or exit counters; so clearly the FB_init(), FB_reinit() and FB_exit() methods are not invoked when one FB instance's data is overwritten by that of another.

Inspecting the behaviour of fbA, fbB and fbC in the debugger shows that the assignment operation definitely copies all of the FB's VAR_INPUT, VAR, and VAR_OUTPUT state when the FB is assigned.

Toggling bCallStackFB does increment both MAIN.nInits and MAIN.nExits, so creating a temporary instance of an FB as a stack variable, such as a VAR in a FUNCTION, METHOD or PROPERTY implementation, definitely calls both FB_init() and FB_exit(). (Note: TwinCAT versions before 3.1.4022 and CoDeSys versions before V3.5 SP9 Patch 8 do not call FB_exit() for stack-allocated FB instances.)

Hydrargyrum
  • 3,378
  • 4
  • 27
  • 41
  • I have never actually tested and I basically never use function block copy (in everything I do passing references (or pointers) to instances is the better choice), but I would expect that function block instances (of any type) nested in the function block instance being copied will also be copied the same way, as copy is basically a straight "memory copy" (it might actually be exactly that under the hood). If the instance contains pointers or references, however, the copy would refer/point to the same memory location as the original (the copy operation does not assign memory). – Fred Jul 07 '23 at 11:36
  • 2
    As an aside, the "{attribute 'no_assign'}" in a function block's header so that the compiler will error out if there is in an attempt in code to assign an instance of the type. I find this invaluable, because it catches cases where one uses ":=" rather than "REF=" on types that should never be copied, a mistake that can be hard to debug. I consider it standard in my code, and would only remove it in special cases. – Fred Jul 07 '23 at 11:41