1

Suppose I have a function block POU1 which has local variables val1: INT and val2: INT, e.g.

FUNCTION_BLOCK POU1
VAR
    val1: INT := 1;
    val2: INT := 1;
END_VAR

Now suppose the user of the FB declares it as RETAIN, e.g.

VAR RETAIN
    p1: POU1;
END_VAR
p1.val1 := 2;
p1.val2 := 2;

This will result in both val1 and val2 retaining the value of 2 in case of a warm reset, but what if I don't want that to happen to say val2 i.e. I want val1 to retain it's current value, but val2 to be reset in case of a warm reset (if the user declares my FB as RETAIN, otherwise I want both to reset)

How can I achieve this? (Also. same question goes for PERSISTENT)

PS. I tried {attribute 'init_on_onlchange'} and {attribute 'no_copy'} but they did nothing (maybe I used them wrong?). I also tried creating an additional FB with {attribute 'no_instance_in_retain'} and adding it as a local variable of POU1 but that resulted in a build error.

Guiorgy
  • 1,405
  • 9
  • 26
  • did you try `VAR_TEMP`? – Sergey Romanov Jul 27 '20 at 13:32
  • @SergeyRomanov, sorry, but, although VAR_TEMP will not be retained between warm resets, it will also not be retained between normal calls. VAR_TEMP by definition is reset at every call of the function block. I need a normal local variable (one that keeps it's value between function block calls) that resets after a warm reset, even if the user of the FB declared it as RETAIN or PERSISTENT – Guiorgy Jul 28 '20 at 12:20

2 Answers2

1

One way I just found is to implement FB_Exit explicitly and reset those variable in it:

METHOD FB_Exit: BOOL
VAR_INPUT
    bInCopyCode: BOOL;  // TRUE: the exit method is called in order to leave the instance which will be copied afterwards (online change).  
END_VAR
val2 := 1; // reset all variables you don't want retained to their defaults

This seems to work, but not sure if this might have other consequences. e.g. Is FB_Exit called in case of a power failure?

Guiorgy
  • 1,405
  • 9
  • 26
1

The problem with FB_Exit or FB_Init and VAR PERSISTENT/RETAIN is that I couldn't find a consistent behaviour across platforms (Twincat/Codesys) And yes there are cases where fb_exit is not called, for example in Twincat when you do a cold reset.

My approach on this would be different. I would neither use attributes nor fb_exit or fb_init which under certain circumstances could be difficult to debug. Instead I would use a simple global FB like this one:

FUNCTION_BLOCK FB_System

VAR
    bInit : BOOL;
    nCycleCount : UINT;
END_VAR

VAR CONSTANT
    cINIT_AFTER_CYCLE : UINT := 2;
END_VAR


IF NOT bInit 
THEN
    nCycleCount := nCycleCount + 1;
END_IF

IF nCycleCount >= cINIT_AFTER_CYCLE
THEN
    bInit := TRUE;
END_IF

METHOD isInit : BOOL

isInit := bInit;

Now, add an input to your retain/persistent FB:

VAR_INPUT
    bSystemInit : BOOL;
END_VAR 

And call it like this:

fbRetain(bSystemInit := fbSystem.isInit());

Initialize your values if the system is not initialized. Add this check in your FB implementation:

IF NOT bSystemInit THEN
    anIntVar := 0;
    //or call a reset() method where you put all your variables that need to be initialized
END_IF 

If you have many FBs that need this kind of initialization, you can extend them with an FB that has this example code in it. By doing so you can reuse your code efficiently.

Having said that, I must warn you that I had many problems with persistent data in the past. It happened to me repeatedly that persistent data became corrupt causing production problems or even preventing the runtime to start.

If I had to design a system from scratch I would use the XML-server from Beckhoff or the XML-utility from codesys to store relevant production data in an xml-file and retrieve this data at runtime start.

Filippo Boido
  • 1,136
  • 7
  • 11
  • I am guessing you declare the FB_System instance in a Program at the top of the main task? And you call it at the end of the main task? Also is there a way to achieve this without explicitly instantiating it in a program, for example, using global variables and a function with attribute 'call_after_global_init_slot' or a similar (for example, in case you need it in a library, without tasks)? – Guiorgy Jul 24 '20 at 14:23
  • 1
    FB_System is declared in a global variable list and called wherever you want (not necessarily at the end of the main task).Because the bInit value of fbSystem is read only, it's ok to put this FB in a global variable list. – Filippo Boido Jul 24 '20 at 19:26
  • Regarding the second part of your question, FB_System needs to run cyclically, because the FB checks cyclically if the Init-phase is over.You decide how many cycles this phase lasts thanks to the cINIT_AFTER_CYCLE constant – Filippo Boido Jul 24 '20 at 19:47
  • Code that is in libraries also runs in a task or it does not run at all. – Filippo Boido Jul 24 '20 at 19:56
  • I wouldn't use the property because it is difficult to debug for someone that doesn't know your code – Filippo Boido Jul 24 '20 at 19:57
  • *"Code that is in libraries also runs in a task or it does not run at all."*, that's the point. From what I see, the user would have to explicitly run FB_System exactly onece on every cycle. If they don't, then the logic will not work. *"I wouldn't use the property because it is difficult to debug for someone that doesn't know your code"*. well, in your case too, someone that doesn't know to call FB_System will be confused as to why it doesn't work, no? – Guiorgy Jul 25 '20 at 13:15
  • In plc programming you expect code to run cyclically.You can figure out things easily when code is structured and calls are there to see for everyone.There are many other possibilities to taccle the problem you have in code.It also depends from your architecture.The example showed above though, is a very easy and straightforward implementation. – Filippo Boido Jul 25 '20 at 13:47