pointersautomationreferenceplccodesys

Why use pointers and reference in codesys V3?


My question is: what are the benefits of using pointers and reference to?

I am new to codesys and in my previous job, I programmed in TIA portal (Siemens) and Sysmac Studio (Omron) and never came across pointers or something similar. I think I understand how they work but not sure when I should be using them myself.

For example, I just received a function block from a supplier:

Example of FB with pointers and reference

Why don't they just have an array for input and output?


Solution

  • There are mainly 6 that I can think of right now:

    1. Type Punning, which you can also achieve using a UNION, but you may not want to have to create a union for every single reinterpretation cast in your code.

    2. To save memory and copy execution time. Whenever you pass some data to a function/function block, it gets copied. This is not a big problem if your PLC has enough CPU power and memory, however if you are dealing with especially huge data on a low end PLC, then you may either exceed real time execution constraints, or run out of memory. When you pass a pointer/reference however, no matter how big the data is only the pointer/reference gets copied and passed, which is 4 bytes in 32 bit system, and 8 bytes in a 64 bit one.
      By the way, since CODESYS always treats variables declared with the type of an interface as references., if you have been passing function blocks as interfaces around, you've already been using references.

    3. If you want to pass some data and have it modified inside the function, you could return a copy of the modified data and overwrite the original, or just pass it by reference/as a pointer. Codesys simplifies this by using VAR_IN_OUT, which really is just a REFERENCE TO under the hood, so if you've ever used it, you have used references.

    4. In C style languages you'd use pointers/references when you want a function to return multiple values without the hassle of creating a custom structure every time. You can do the same here to, however in CODESYS function can have multiple outputs, for example:

    VAR_OUPUT
        out1 : INT; (*1st output variable *)
        out2 : INT; (*2nd output variable *)
        //...
    END_VAR
    
    1. One of the main uses in other languages is for dynamic memory allocations, but in CODESYS this is disabled by default and not all PLCs support it, and hardly anyone (that I know) uses it. However, if you do enable and use dynamic memory, you'll have to use pointers.

    2. And finally, perhaps the most important, pointers are very handy for generic programming, i.e. writing a function that can work with different data types or situations instead of writing a separate function for them all.

    For example:

    Suppose we want to have a Function Block that calculates the moving average on a given number series. A simple approach would be something like this:

    FUNCTION_BLOCK MyMovingAvg
    VAR_INPUT
        nextNum: INT;
    END_VAR
    VAR_OUTPUT
        avg: REAL;
    END_VAR
    VAR
        window: ARRAY [0..50] OF INT;
        currentIndex: UINT;
    END_VAR
    

    However, this has the problem that the moving window size is static and predefined. If we wanted to have averages for different window sizes we would either have to create several function blocks for different window sizes, or do something like this:

    FUNCTION_BLOCK MyMovingAvg
    VAR CONSTANT
        maxWindowSize: UINT := 100;
    END_VAR
    VAR_INPUT
        nextNum: INT;
        windowSize: UINT (0..maxWindowSize);
    END_VAR
    VAR_OUTPUT
        avg: REAL;
    END_VAR
    VAR
        window: ARRAY [0..maxWindowSize] OF INT;
        currentIndex: UINT;
    END_VAR
    

    where we would only use the elements of the array from 0 to windowSize and the rest would be ignored. This however also has the problems that we can't use window sizes greater than maxWindowSize and there's potentially a lot of wasted memory if maxWindowSize is set high.

    There are 2 ways to get a truly general solution:

    1. Use dynamic allocations. However, as I mentioned previously, this isn't supported by all PLCs, is disabled by default, has drawbacks (you'll have to split you memory into two chunks), is hardly used and is not very CODESYS-like.
    2. Let the user define the array of whatever size they want and pass the array to our function block:
    FUNCTION_BLOCK MyMovingAvg
    VAR_INPUT
        nextNum: INT;
    END_VAR
    VAR_OUTPUT
        avg: REAL;
    END_VAR
    VAR
        windowPtr: POINTER TO INT; // pointer to the first array element. You can access other elements with [] as usual (up to windowSize)
        windowSize: DINT;
        currentIndex: UINT;
    END_VAR
    
    METHOD FB_Init: BOOL
    VAR_INPUT
        bInitRetains: BOOL;
        bInCopyCode: BOOL;
    END_VAR
    VAR_IN_OUT // basically REFERENCE TO
        window_buffer: ARRAY [*] OF INT; // array of any size
    END_VAR
    
    THIS^.windowPtr := ADR(window_buffer);
    THIS^.windowSize := UPPER_BOUND(window_buffer, 1) - LOWER_BOUND(window_buffer, 1) + 1;
    
    // usage:
    PROGRAM Main
    VAR
        avgWindow: ARRAY [0..123] OF INT; // whatever size you want!
        movAvg: MyMovingAvg(window_buffer := avgWindow);
    END_VAR
    
    movAvg(nextNum := 5);
    movAvg.avg;
    

    The same principle can be applied to any function block that operates on arrays (for example, we also use it for sorting).

    UPDATE: Codesys introduced VAR_GENERIC CONSTANT with which you can essentially define a constant input in a function block, for example, arraySize and have the input array be defined with that, reducing the usefulness of the above approach.

    Moreover, similarly, you may want to have a function that works on any integer/floating number. For that you may use one of the ANY types which is basically a structure that holds a pointer to the first byte of the data, the size of the data (in bytes) and a type enum.