2

I'm trying to build a function that sums a single dimension array of any type in TwinCAT.

I'm relatively inexperienced with pointers so the answer may be obvious, but I could not find any solutions. I read through all these and they helped a bit.

T_ARG Convert Byte arrays The Wonders of ANY

My conceptual code works for an INT array, but I realized I would have to write a FOR loop for each datatype to make it work and that seems wrong. I also wanted to avoid having a CASE in a FOR loop as that seemed inefficient.

Is it possible to have a pointer (of unset type) to a pointer (of a specific type)? It looks possible in C, but not sure about IEC-61131.

FUNCTION fn_SumArray : DINT
VAR_INPUT 
    inArr : ANY; 
    inElem : ANY;
    
END_VAR

VAR
    i : DINT := 0;
    stepsize : DINT :=1;

    pLREAL : POINTER TO LREAL; //64 bit
    pREAL : POINTER TO REAL; //32 bit
    pBYTE : POINTER TO BYTE; //8 bit
    pWORD : POINTER TO WORD; //16 bit
    pDWORD : POINTER TO DWORD; //32 bit
    pLWORD : POINTER TO LWORD; //64 bit
    pSINT : POINTER TO SINT; //8 bit
    pUSINT : POINTER TO USINT; //8 bit
    pINT : POINTER TO INT; //16 bit
    pUINT : POINTER TO UINT; //16 bit
    pDINT : POINTER TO DINT; //32 bit
    pUDINT : POINTER TO UDINT; //32 bit
    pLINT : POINTER TO LINT; //64 bit
    pULINT : POINTER TO ULINT; //64 bit
END_VAR

--------------------------------------------------
CASE inElem.TypeClass OF
    __SYSTEM.TYPE_CLASS.TYPE_LREAL:    
        stepsize := 8;
        //set generic pointer here
    __SYSTEM.TYPE_CLASS.TYPE_INT:
        stepsize := 2;    
        //set generic pointer here
ELSE
    fn_SumArray :=0;
END_CASE;


    FOR i := 0 TO inArr.diSize-1 BY stepsize DO
            genericPointer:= ADR(inarr.pValue[i]);
            fn_SumArray := fn_SumArray +genericPointer^;
        END_FOR;

 
Gene Parmesan
  • 211
  • 2
  • 8

2 Answers2

3

As far as I know, you'll have to repeat the code for all types you want to cover. CODESYS doesn't have true generic programming, so it is what it is:

(* DECLARATION *)
METHOD fn_SumArray : BOOL // if TRUE, then error
VAR_INPUT 
    arr_first : ANY_NUM;
    arr_size : DINT;
    sum_out : ANY_NUM;
END_VAR
VAR
    i : DINT;
    ptr_arr: NUMBER_POINTER; // just a UNION with all number type pointers
    ptr_sum: NUMBER_POINTER;
END_VAR

(* IMPLEMENTATION *)
IF (arr_first.TypeClass <> sum_out.TypeClass) THEN
    fn_SumArray := TRUE;
    // set all bytes of sum_out to 0
    FOR i := 0 TO sum_out.diSize - 1 DO
        sum_out.pValue[i] := 0;
    END_FOR
    RETURN;
END_IF

CASE arr_first.TypeClass OF
    __SYSTEM.TYPE_CLASS.TYPE_LREAL:
        ptr_arr._LREAL := arr_first.pValue;
        ptr_sum._LREAL := sum_out.pValue;
        ptr_sum._LREAL^ := 0;
        FOR i := 0 TO arr_size - 1 DO
            ptr_sum._LREAL^ := ptr_sum._LREAL^ + ptr_arr._LREAL[i];
        END_FOR
    // Repeat the above for all ...
ELSE
    fn_SumArray := TRUE;
    // set all bytes of sum_out to 0
    FOR i := 0 TO sum_out.diSize - 1 DO
        sum_out.pValue[i] := 0;
    END_FOR
END_CASE;
// A POINTER for every data type of ANY_NUM
// PS. A pointer has the exact same size for any type
TYPE NUMBER_POINTER :
UNION
    // floating
    _REAL: POINTER TO REAL;
    _LREAL: POINTER TO LREAL;
    
    // unsigned
    _USINT: POINTER TO USINT;
    _UINT: POINTER TO UINT;
    _UDINT: POINTER TO UDINT;
    _ULINT: POINTER TO ULINT;
    
    // signed
    _SINT: POINTER TO SINT;
    _INT: POINTER TO INT;
    _DINT: POINTER TO DINT;
    _LINT: POINTER TO LINT;
END_UNION
END_TYPE
(* DECLARATION *)
PROGRAM PLC_PRG
VAR CONSTANT
    SIZE: DINT := 50;
END_VAR
VAR
    i: DINT;
    arr_int: ARRAY [0..(SIZE - 1)] OF INT;
    sum_int: INT;
    arr_dint: ARRAY [0..(SIZE - 1)] OF DINT;
    sum_dint: DINT;
    arr_real: ARRAY [0..(SIZE - 1)] OF REAL;
    sum_real: REAL;
    first_run: BOOL := TRUE;
    sum_test: REAL;
    test_failed: BOOL;
END_VAR

(* IMPLEMENTATION *)
IF (first_run) THEN
    first_run := FALSE;
    FOR i := 0 TO SIZE - 1 DO
        arr_int[i] := DINT_TO_INT(i);
        arr_dint[i] := i * 10;
        arr_real[i] := DINT_TO_REAL(i) / 10;
    END_FOR
END_IF

fn_SumArray(arr_first := arr_int[0], arr_size := SIZE, sum_out := sum_int);
sum_test := 0;
FOR i := 0 TO SIZE - 1 DO
    sum_test := sum_test + arr_int[i];
END_FOR
IF (sum_int <> REAL_TO_INT(sum_test)) THEN
    test_failed := TRUE;
END_IF

fn_SumArray(arr_first := arr_dint[0], arr_size := SIZE, sum_out := sum_dint);
sum_test := 0;
FOR i := 0 TO SIZE - 1 DO
    sum_test := sum_test + DINT_TO_REAL(arr_dint[i]);
END_FOR
IF (sum_dint <> REAL_TO_DINT(sum_test)) THEN
    test_failed := TRUE;
END_IF

fn_SumArray(arr_first := arr_real[0], arr_size := SIZE, sum_out := sum_real);
sum_test := 0;
FOR i := 0 TO SIZE - 1 DO
    sum_test := sum_test + arr_real[i];
END_FOR
IF (sum_real <> sum_test) THEN
    test_failed := TRUE;
END_IF

The result of the program above:

screanshot of codesys

If writing the same code for every type is too tedious, you can always use a script to automate this:

#!/usr/bin/env python3

code_template = \
"""CASE arr_first.TypeClass OF
{0}
ELSE
\tfn_SumArray := TRUE;
\tFOR i := 0 TO sum_out.diSize - 1 DO
\t\tsum_out.pValue[i] := 0;
\tEND_FOR
END_CASE;
"""

case_template = \
"""\t__SYSTEM.TYPE_CLASS.TYPE{0}:
\t\tptr_arr.{0} := arr_first.pValue;
\t\tptr_sum.{0} := sum_out.pValue;
\t\tptr_sum.{0}^ := 0;
\t\tFOR i := 0 TO arr_size - 1 DO
\t\t\tptr_sum.{0}^ := ptr_sum.{0}^ + ptr_arr.{0}[i];
\t\tEND_FOR
"""

types = ['_REAL', '_LREAL', '_USINT', '_UINT', '_UDINT', '_ULINT', '_SINT', '_INT', '_DINT', '_LINT']

cases = [case_template.format(t) for t in types]
code = code_template.format(''.join(cases))

print(code)

Just run the above script with python 3.x and you'll get the case statements code for the types listed in the script.

Guiorgy
  • 1,405
  • 9
  • 26
0

What I ended up going with was a case statement, with for loops inside.

Not as elegant as I had hoped, but still functional.

FUNCTION fn_SumArray : REAL
VAR_INPUT 
    inArr : ANY; //Array to size
    inElem : ANY; //Any element in the array
END_VAR

VAR
    i : DINT := 0;
    pBYTE : POINTER TO BYTE; //8 bit
    pSINT : POINTER TO SINT; //8 bit
    pUSINT : POINTER TO USINT; //8 bit
    pWORD : POINTER TO WORD; //16 bit
    pINT : POINTER TO INT; //16 bit
    pUINT : POINTER TO UINT; //16 bit
    pDINT : POINTER TO DINT; //32 bit
    pUDINT : POINTER TO UDINT; //32 bit
    pDWORD : POINTER TO DWORD; //32 bit
    pREAL : POINTER TO REAL; //32 bit
    pLREAL : POINTER TO LREAL; //64 bit
    pLWORD : POINTER TO LWORD; //64 bit
    
END_VAR
//---------------------------------
CASE inElem.TypeClass OF
    __SYSTEM.TYPE_CLASS.TYPE_SINT:
        FOR i := 0 TO inArr.diSize-1 BY 1 DO
            pSINT := ADR(inarr.pValue[i]);
            fn_SumArray := fn_SumArray + pSINT^;
        END_FOR;
    __SYSTEM.TYPE_CLASS.TYPE_USINT:
        FOR i := 0 TO inArr.diSize-1 BY 1 DO
            pUSINT := ADR(inarr.pValue[i]);
            fn_SumArray := fn_SumArray + pUSINT^;
        END_FOR;
    __SYSTEM.TYPE_CLASS.TYPE_WORD:
        FOR i := 0 TO inArr.diSize-1 BY 2 DO
            pWORD := ADR(inarr.pValue[i]);
            fn_SumArray := fn_SumArray + pWORD^;
        END_FOR;    
    __SYSTEM.TYPE_CLASS.TYPE_INT:
        FOR i := 0 TO inArr.diSize-1 BY 2 DO
            pINT := ADR(inarr.pValue[i]);
            fn_SumArray := fn_SumArray + pINT^;
        END_FOR;    
    __SYSTEM.TYPE_CLASS.TYPE_UINT:
        FOR i := 0 TO inArr.diSize-1 BY 2 DO
            pUINT := ADR(inarr.pValue[i]);
            fn_SumArray := fn_SumArray + pUINT^;
        END_FOR;    
    __SYSTEM.TYPE_CLASS.TYPE_DINT:
        FOR i := 0 TO inArr.diSize-1 BY 4 DO
            pDINT := ADR(inarr.pValue[i]);
            fn_SumArray := fn_SumArray + pDINT^;
        END_FOR;    
    __SYSTEM.TYPE_CLASS.TYPE_UDINT:
        FOR i := 0 TO inArr.diSize-1 BY 4 DO
            pUDINT := ADR(inarr.pValue[i]);
            fn_SumArray := fn_SumArray + pUDINT^;
        END_FOR;    
    __SYSTEM.TYPE_CLASS.TYPE_DWORD:
        FOR i := 0 TO inArr.diSize-1 BY 4 DO
            pDWORD := ADR(inarr.pValue[i]);
            fn_SumArray := fn_SumArray + pDWORD^;
        END_FOR;
    __SYSTEM.TYPE_CLASS.TYPE_REAL:
        FOR i := 0 TO inArr.diSize-1 BY 8 DO
            pREAL := ADR(inarr.pValue[i]);
            fn_SumArray := fn_SumArray + pREAL^;
        END_FOR;    
    __SYSTEM.TYPE_CLASS.TYPE_LREAL: 
        FOR i := 0 TO inArr.diSize-1 BY 8 DO
            pLREAL := ADR(inarr.pValue[i]);
            fn_SumArray := fn_SumArray + pLREAL^;
        END_FOR;    
ELSE
    fn_SumArray :=0;
END_CASE;
Gene Parmesan
  • 211
  • 2
  • 8
  • There is no need to get the address of every element. You can just get the address of the first element and then access all the rest of the elements with the array operator (`[index]`), as long as the pointer you store the first address is of the correct size. Also, you could use a UNION with all the pointers instead of declaring pointers of every type. – Guiorgy Apr 16 '22 at 14:26
  • Im not quite clear on what you meant? I tried of a few iterations of what you suggested but couldnt get it to work? I ended up getting page faults with various combinations of the below. `__SYSTEM.TYPE_CLASS.TYPE_REAL:` `pREAL := ADR(inarr.pValue[0]);` `//pReal:=inArr.pValue;` `//fn_SumArray := pREAL^;` `FOR i := 0 TO Size-1 DO` `//pReal:=inArr.pValue[i];` `fn_SumArray := fn_SumArray + pReal^;` `END_FOR;` – Gene Parmesan Apr 16 '22 at 15:26
  • 1
    Figured out my issue ` __SYSTEM.TYPE_CLASS.TYPE_REAL: pReal:=inArr.pValue; FOR i := 0 TO Size-1 DO fn_SumArray := fn_SumArray + pReal[i]; END_FOR; ` – Gene Parmesan Apr 16 '22 at 15:36