Paul
Paul

Reputation: 23

Beckhoff-PLC: Declaring an array as remanent inside a Function Block

this is my first question on StackOverlfow, so feel free to give me feedback on the problem :)

I'm new to working with controllers from Beckhoff and I'm trying to program a program block for communicating machine data to the PC. To store the data of different types I use an array of T_ARG in the function block "Communication", which is instantiated in "MAIN". At each restart its data are reset, which leads to the fact that I would have to load the information at the start always again into the array, whereby I would have the data then twice on the system.

Code inside the FB "Communication":

VAR
    Values : ARRAY[DataArrayLow..DataArrayHigh] OF T_ARG;
    ValueChanged : ARRAY[DataArrayLow..DataArrayHigh] OF BOOL;
END_VAR

I am using to the array "ValueChanged" to track change of the data by using a setvalue-method, which marks the index in "Values" to be transmitted.

For now i am calling the dunction block in "MAIN" as usual:

VAR
    Communication : FB_Comm;
END_VAR
__________________________________________________________________________
Communication();

I already tried to mark the variables as persistent, which did not work.

I dont really wont to create an external global variable and give it to the function block as input, since it defeats the purpose of capsulation and it would be cluttered.

Upvotes: 1

Views: 1221

Answers (4)

Luka
Luka

Reputation: 1

I assume your're on TwinCAT 3 (not 2): https://infosys.beckhoff.com/english.php?content=../content/1033/tc3_plc_intro/2528803467.html , read the "i" sections :)

  • Use VAR_PERSISTENT only for the data that needs saving and do it directly where you need them - inside a PRG, GVL, FB or vars inside VAR_INST section of a method. Those variables are "variables of the POU" - but private to the method - and can be marked persistent..
  • Don't complicate with extra POUs just for persistent storage. Jakob's code is doing both - using persistent inside FB and overcomplicates by introducing extra FB.
  • Local variables (of a function or a method) cannot be declared as persistent. Storing any stuff inside FUN is bad by design.
  • Do not declare whole FB as persistent, it may lead to unwanted behavior and you also don't want its intestines to clutter persistence file. There is a recommended max limit of persistent data due to disk write speed and the sUPS shutdown time.
  • You may call an instance of FB_WritePersistentData in order to trigger saving persistent variables, but it will only write the vars, already marked as persistent. Occasional such trigger may be good to prevent parameter loss due to an unclean shutdown, but calling that cyclically may bring troubles (SD card wearout etc).

Sample - declaring persistent structure inside array of FBs: Structure of our persistent stuff, say parameters of some sort

STRUCT
    param1  : REAL;
    stored  : BOOL;
END_STRUCT
END_TYPE

Only need to store params variable as persistent

VAR
 ...
END_VAR
VAR PERSISTENT
    params  : myDT;
END_VAR

And usage inside a PRG. Only thing that is stored in this case is one set of params variable per member of array.

VAR
    myArr : ARRAY[0..9] OF myPOU;
END_VAR

Do not put extra PERSISTENT inside that MAIN prg.

Upvotes: 0

ziga
ziga

Reputation: 390

Here is a simple project that can show you that PERSISTENT data inside a function block can indeed be saved.

Declare a test structure:

TYPE ST_DataToBePersistent :
STRUCT
    bVar    : BOOL;
    nVar    : INT;
    fVar    : REAL;
END_STRUCT
END_TYPE

Now declare a function block, FB_Test using that struct as internal parameter set. Adding the FB_WritePersistentData as VAR_IN_OUT. We need to call it to save the persistent data inside this function block.

FUNCTION_BLOCK FB_Test

VAR PERSISTENT
    arrPersistentParameterStruct    : ARRAY [0..10] OF ST_DataToBePersistent;
END_VAR

VAR_IN_OUT
    fbWritePersistentData   : FB_WritePersistentData;
END_VAR

VAR
    bSavePersistentData : BOOL;
END_VAR

Body of the FB:

IF bSavePersistentData THEN
    bSavePersistentData := FALSE;
    fbWritePersistentData.START := TRUE;
END_IF

A simple method to set the value of the internal structure:

METHOD PUBLIC WriteToStruct
VAR_INPUT
    arrPersistentParameterStruct : ARRAY[0..10] OF ST_DataToBePersistent;
END_VAR

And it's body. When called internal structure array will be changed and we also set a flag to call the passed FB_WritePersistentData which will saved ALL persistent data on the dedicated port (local):

THIS^.arrPersistentParameterStruct := arrPersistentParameterStruct;
bSavePersistentData := TRUE;

Instantiate 2 instances, just to set the value of the array and adding FB_WritePersistentData that will be called when the method FB_Test.WriteToStruct is called. Also adding some variables for diagnostic purpose and triggering the methods:

PROGRAM MAIN
VAR
    fbTestInstance1 : FB_Test;
    fbTestInstance2 : FB_Test;
    fbWritePersistentData   : FB_WritePersistentData;
    bSetData1   : BOOL;
    bSetData2   : BOOL;
    arrDataToSet    : ARRAY[0..10] OF ST_DataToBePersistent;
    TofPersistentDataSaved  : TOF;
    FtrigDataSaveComplete   : F_TRIG;
    bDataSaveComplete       : BOOL;
END_VAR

In the main body, cyclically call both fbs as well as the persistent data FB. Set the values of the arrDataToSet in online mode to anything you want and trigger which function block you want to change. Observe the bDataSaveComlete is set for 2 seconds after saving is complete.

fbTestInstance1(fbWritePersistentData := fbWritePersistentData);
fbTestInstance2(fbWritePersistentData := fbWritePersistentData);

IF bSetData1 THEN
    bSetData1 := FALSE;
    fbTestInstance1.WriteToStruct(arrPersistentParameterStruct := arrDataToSet);
END_IF

IF bSetData2 THEN
    bSetData2 := FALSE;
    fbTestInstance2.WriteToStruct(arrPersistentParameterStruct := arrDataToSet);
END_IF

fbWritePersistentData(NETID := '', PORT := 851);

FtrigDataSaveComplete(CLK := fbWritePersistentData.BUSY);
TofPersistentDataSaved(IN := FtrigDataSaveComplete.Q, PT := T#2S);
bDataSaveComplete := TofPersistentDataSaved.Q;

IF fbWritePersistentData.BUSY THEN
    fbWritePersistentData.START := FALSE;
END_IF

Example:

Initial state of the fbTestInstance1: enter image description here

Now I set the structure: enter image description here

I will write to fbTestInstance by setting the bSetData1: enter image description here

I will set different values to second fb: enter image description here

Now I will initiate a restart of the target machine's TwinCAT: enter image description here

Logging back in, the data is still the same: enter image description here

Rebooting the target machine: enter image description here

Persistent data is always loaded properly upon TwinCAT, be it restarting the runtime or operating system.

Hope this answers your question. As Jakob already mentioned, what dwpessoa is saying ONLY applies to functions since all of the memory they occupy is discarded when it has been released.

Upvotes: 1

dwpessoa
dwpessoa

Reputation: 632

I don't know Beckoff in depth, but it's very similar to Codesys, and from the documentation here, it follows the same principle:

Declaring a local variable as a PERSISTENT in a function has no effect. Data persistence cannot be used in this way.

From what it seems the correct way for you is to use Globals, maybe you can study a way to modularize your variable structure in order to pass it to your block as VAR_IN_OUT, possibly a ARRAY of STRUCTS can help you achieve this.

Upvotes: 0

Jakob
Jakob

Reputation: 1540

Normally persistent variables should be saved automatically during proper shutdown or reboot of TwinCAT. So if you just remove power (without UPS), the persistent data will normally not be saved.

What you can do to be sure that the persistent data is stored is to use the WritePersistentData function block. In your case, you shouldn't call this function block all the time, bur rather only do it as soon as any of your variables change.

Upvotes: 0

Related Questions