Search code examples
twincat

passing FB1 to programs with FB2s that want to use the interface that FB1 is implementing


This is my first post on stack overflow, so here goes ^^.

I am trying to pass over from the "conventional" type of PLC programming to OOP (still very new). New project I have will have 3 different programs which will run on a single runtime, all are run in a single main loop, the code is not too big, since there is a seperate system that has most of the configurtaion and only passes data to PLC via ADS protocol.

My question is, in these 3 programs, I have many function block instances which will trigger persistent data saving (FB_WritePersistentData). I only want a single FB_WritePersistent instance - no need for more instances and more instances may just cause writing at at the same time, which I would want to avoid.

Each of the programs has multiple function blocks which use the interface for writing the persistent data.

What is the proper way to pass a single FB definition that implements that interface to all my function blocks that want to use the interface? My first try was as follows:

  • define the fbWritePersistentData(implements I_PersistentDataStorage) in MAIN
  • have the same FB as VAR_IN_OUT in the 3 programs that will want to use this function block
  • the FBs that use the interface have method .FB_Init which provides the reference to the interface - dependency injection
  • the persistent FB is passed to other FBs in programs via the VAR_IN_OUT, but this issues a warning in TwinCAT as well as, well, some page faults, probably addressing error by using the VAR_IN_OUT

How I avoided this is by simply passing the persistent FB to the ones that implement the interface like this : fb1 : FB_1(main.fbSavePersistent) But this is not what I want to have as it feels, well, wrong :)

Should I have used the REFERENCE_TO instead? Thanks for your help in advance.


Solution

  • Explanation

    I think the thing that you're getting confused about here is the relative application of interfaces. In Structured Text interfaces are handled as REFERENCES, that means that they do not need to be handled specifically through VAR_IN_OUT but can be just treated as standard VAR_INPUT as long as you perform the appropriate checks before attempting to call them.

    (As Uwe says though, when designing interfaces you do also need to take into account that they can be used from multiple sources, so I would recommend using a method to enqueue data to be processed in the logger)

    Implementing interfaces in this way makes life a lot simpler if you want to do things such as replace the persistent data logger with a secondary logger, or change up the logger to be a publisher, as long as you implement the interface correctly in your new FB you can just directly replace the old logger with the new one and everything still just works


    Example

    A very basic example implementation (no logic) of how I would handle a situation like this:

    PROGRAM MAIN
    VAR
        Logger1         : fb_PersistentDataLog; // Single data logger
        Source1         : fb_DataSource
                            :=( Logger  := Logger1 ); // Data source
        Source2         : fb_DataSource
                            :=( Logger  := Logger1 ); // Data source
    END_VAR
    
    Source1();
    Source2();
    
    Interface i_Logger
    METHOD EnqueueData : UDINT // Interface method prototype
    
    FUNCTION_BLOCK fb_PersistentDataLog IMPLEMENTS i_Logger
    METHOD EnqueueData : UDINT
        // Run code here to enqueue data to logger
    
    FUNCTION_BLOCK fb_DataSource
    VAR_INPUT
        Logger          : i_Logger; // Definition of the interface as an input
    END_VAR
    
    IF Logger <> 0 THEN // Check interface reference is valid
        Logger.EnqueueData(); // Call interface method
    END_IF