Declare values of array of structures in POU using PLC ST - plc

I have structure declared like this:
TYPE board:
STRUCT
number: INT;
color: DWORD;
END_STRUCT
END_TYPE
And i want to declare array of these structures with starting values in POU. I do it like this:
Program PLC_PRG
VAR
arr1: ARRAY[1..61] OF board;
board: board;
arr1[1].color := 16#FF0000;
END_VAR
But i get an error which say: "Error 4024: PLC_PRG(10): Expecting ':' before '['".
Do anybody know how to solve this problem?

You didn't specify a platform but I'll assume TwinCAT. To initialize an array of structures, you have to do it all in the variable declaration line.
arr1: ARRAY[1..61] OF board := [(number:=7, color:=16#FF0000), (number:=5, color:=16#FF0001)];
This example will initialize elements 1 and 2 of the array. To my knowledge you can't selectively initialize individual array elements as you are maybe wanting to do. You could use statements like arr1[17].color := 16#FF0000 of course but it would have to be outside of your variable declaration block.

Related

Multi-variable assignment to an array in TwinCAT

How can assign a new variable to an array in TwinCAT?
in TwinCAT you can initialize all your array's argument directly for example for array a we can use:
a : ARRAY [1..3] OF INT := [3(0)];
or
a : ARRAY [1..3] OF INT := [0,0,0];
but if you want to assign the array in the main program(not initializing part) for example
a:=[2,8,5];
you will face this error tip: Unexpected array initialisation.
any help would be appreciated.
You cannot directly initialize arrays inside the program part.
That beeing said, the best option is porbably to define a constant containing the desired initialization values and then assigning the value of that constant to your array:
VAR
aiMyArray : ARRAY [1..2] OF INT;
END_VAR
VAR CONSTANT
aiInitializerMyArrayOptionA : ARRAY [1..2] OF INT := [1,2];
aiInitializerMyArrayOptionB : ARRAY [1..2] OF INT := [3,4];
END_VAR
IF bCondition THEN
aiMyArray := aiInitializerMyArrayOptionA;
ELSE
aiMyArray := aiInitializerMyArrayOptionB;
END_IF
The other option would be to manually initialize each index one by one which easily gets impracticale with decent array sizes:
IF bCondition THEN
aiMyArray[1] := 1;
aiMyArray[2] := 2;
ELSE
aiMyArray[1] := 3;
aiMyArray[2] := 4;
END_IF
Looping thorugh all elements and assigning values might be an option too. But that would only be usefull when the values are no arbitrary constatns but you are able to calculate the values from some formula.

User defined functions with params

In codesys some functions support what in other languages is usually called 'params', i.e. a function that can take a varied amount of similarly typed variables. For example the ADD Operator (function in ladder).
My question is, if there's any way to do the same in user defined functions?
The only idea that I have so far is to take an ARRAY [*] OF SOMETHING and use LOWER_BOUND and UPPER_BOUND to do the computations. This does work, but requires the user to create an additional array variable every time they want to call my function. For example, we have the CONCAT function that concatenates 2 strings. Suppose I want a CONCAT_ALL function that takes n strings and concatenates them all:
STRS: ARRAY [0..9] OF STRING := [STR1, STR2, STR3, STR4, STR5, STR6, STR7, STR8, STR9, STR10];
// This works, but I want to avoid creating an array variable!
CONALL1: STRING := CONCAT_ALL(STRINGS := STRS);
// This doesn't work!
CONALL2: STRING := CONCAT_ALL(STRINGS := [STR1, STR2, STR3, STR4, STR5, STR6, STR7, STR8, STR9, STR10]);
(EDIT: As I was asked, I am using Schneider Electric Machine Expert 1.2, or CODESYS compiler 3.5.12.80)
There is hope in the future!
In Codesys V3.5 SP16 it seems to be finally possible to use FUNCTIONs and METHODs with optional arguments. Of course this will be in non-codesys products, like TwinCAT and Schneider, in later versions.
This means you can finally create a CONCAT with 100 arguments and call it with just for example 3! Awesome.
https://www.codesys.com/fileadmin/data/Images/Download/features-and-improvements-V35SP16-en.pdf
Here is an object oriented example of a string concatenator Function Block:
First we define an Interface with 2 methods:
INTERFACE I_MultipleConcat
METHOD concatString : I_MultipleConcat
VAR_INPUT
sTarget : STRING;
END_VAR
METHOD getResult
VAR_IN_OUT
sRetrieveResult : STRING(1000);
END_VAR
Then the Function Block which implements the Interface:
FUNCTION_BLOCK FB_MultipleConcat IMPLEMENTS I_MultipleConcat
VAR_OUTPUT
uiLen : UINT;
END_VAR
VAR
sResult : STRING(1000);
END_VAR
//-------------------------------------------------------------------
METHOD concatString : I_MultipleConcat
VAR_INPUT
sTarget : STRING;
END_VAR
//make sure that the length of sResult is not exceeded
IF uiLen + INT_TO_UINT(LEN(sTarget)) <= (SIZEOF(sResult)-1)
THEN
//add uiLen as offset to sResult memory access
memcpy(ADR(sResult) + uiLen,ADR(sTarget),LEN(sTarget));
uiLen := uiLen + INT_TO_UINT(LEN(sTarget));
END_IF
//return the instance of this FuncBlock in order to concat new strings
//with concatString() or pass the result to another STRING with getResult()
concatString := THIS^;
//-------------------------------------------------------------------
METHOD getResult
VAR_IN_OUT
sRetrieveResult : STRING(1000);
END_VAR
sRetrieveResult := sResult;
sResult := '';
uiLen := 0;
You can call it like this:
IF NOT bInit
THEN
bInit := TRUE;
//s1 must be a STRING(1000) otherwise compile error
fbMultipleConcat
.concatString('Test1 ')
.concatString('Test2 ')
.concatString('Test3 ')
.getResult(s1);
END_IF
Short answer: There is no way to pass n arguments to a function.
Structured text is a strongly and statically typed language designed for hard real time requirements and it is not a scripting language like Python.
If you have a lot of string manipulations in your code that you don't want to do in python but in your real time loop (and you should assess if it's really necessary depending on your requirements) and still want to make it in a comfortable way, then you have to put some effort in it and build a string manipulation library yourself.
After that you could have a very comfortable function call like this:
sResult := F_Concat6(str1,str2,str3,str4,str5,str6);
I understand that it is tempting to adopt thought and programming patterns learned from other programming languages, but structured text and real time industrial control programming is really another kind of beast compared to common user land programming.
With that I mean, that there are specific reasons why the language is designed as it is and when those principles are correctly understood and applied, rock solid architectures derive from them.
To sum it up, my two cents of advice on this:
Think and write software as expected by your domain and do not port incompatible working methods from other domains.
No you cannot pass n arguments to function.
But you can pass an array, with none fixed number of elements. Syntaxyx for Codesys 2.3.
FUNCTION CONCAT_ALL : STRING(250)
VAR_INPUT
asParts: POINTER TO ARRAY[0..10000] OF STRING(20); (* Array of strings *)
iNum: INT; (* Number of elements *)
END_VAR
VAR
iCount: INT; (* For cycle *)
END_VAR
FOR iCount := 0 TO 10000 DO
IF iCount > iNum THEN
EXIT;
END_IF;
CONCAT_ALL := CONCAT(CONCAT_ALL, asParts^[iCount]);
END_FOR;
END_FUNCTION
PROGRAM PLC_PRG
VAR
(* Array 1 to test *)
asTest1: ARRAY[1..2] OF STRING(20) := 'String 1', 'String 2';
(* Array 2 to test *)
asTest2: ARRAY[1..3] OF STRING(20) := 'String 1', 'String 2', 'String 3';
s1: STRING(250);
s2: STRING(250);
END_VAR
s1 := CONCAT_ALL(ADR(asTest1), 2);
s1 := CONCAT_ALL(ADR(asTest2), 3);
END_PROGRAM

Initialize Array of Custom Types in Structured Text Syntax

In my project I have a type like:
TYPE myDataStruct :
STRUCT
A : UINT;
B : WORD;
C : REAL;
D : Custom_Obj;
END_STRUCT
END_TYPE
And I need to keep an array of this type for persistent memory. I can't just use VAR RETAIN because this particular piece of memory needs to persist through a download. The controller I am using has a way to do this but in order for it to work I need to be able to set the array equal to an initial value. So if I have declared
myarray := ARRAY[0..20] OF myDataStruct;
How do I then initialize this array to a blank array? What is the equivalent of new in other languages?
I have guessed
myarray := [21(A := 0,
B := '',
C := 0.0,
D := ??? )];
But that doesn't appear to be right. It could be simplified if there were only one level deep of custom structs and for this application I could do that. However, I still don't think I have the syntax right.
What is the equivalent of new in other languages?
The analog of this is
VAR
EmptyArray : ARRAY[0..20] OF myDataStruct;
END_VAR
If you want to pre-populate it with default values
VAR
EmptyArray : ARRAY[0..20] OF myDataStruct := [
(A := 100, B := 200, С := 0.0, D := ???),
(A := 34, B := 45, С := 0.1, D := ???),
..... etc
];
END_VAR
For CoDeSys 2.3 delete [ and ].
What you have to understand that EmptyArray is not a prototype of data you need but already initialized variable.
There is no way to initialize it in "x = new struct()" way. You also can't assign the whole array in the code with something like myarray = [1, 2, 3] etc, as far as I know.
If you just want to set it empty with values like 0, '', etc, then there are two ways that I would use:
1. Use MEMSET function to set all bytes to 0
Link to the online help
//Something like
MemSet(
pbyBuffer := ADR(myarray), //Address of the variable
byValue := 0, //Byte that address is filled with
dwSize := SIZEOF(myarray) //How many bytes? (variable size)
)
2. Create a dummy variable and assign it to the myarray
The variable is always initialized to zeros, so EmptyArray values are all 0/empty etc.
VAR
EmptyArray : ARRAY[0..20] OF myDataStruct;
END_VAR
//In the code
myarray := EmptyArray;
I hope I understood your question correctly.

Twincat 3: How to convert 4 HEX array to Float?

we are receiving (via UDP datagram) a float value codified by 4 bytes hex array.
We need to convert from 4 hex bytes to a float.
udp_data[0] = 'BE';
udp_data[1] = '7A';
udp_data[2] = 'E0';
udp_data[3] = 'F4';
In the given example, the correct equivalence, after transformation, udp_data is equivalent to -0.24499:
What is the optimal conversion in Twincat 3 PLC? maybe some library? We need to perform 52 transformation at once of this type.
I attached an example with an example taken from an online calculator:
Thanks!!
You can use a UNION type, which will at the same address hold a byte array (like the one you get from your UDP communication) and the real var which you want to convert to.
When you change the byte array, the real automatically reflects it. The conversion works the other way around also, in fact.
TYPE U_Convert :
UNION
arrUDP_Data: ARRAY [0 .. 3] OF BYTE; // Array must start with LSB
rReal : REAL;
END_UNION
END_TYPE
In MAIN you can declare the following var.
VAR
uConvert: U_Convert;
fValue : REAL;
END_VAR
And in the body of MAIN, update the byte array to requested values.
// Here we update the byte array
uConvert.arrUDP_Data[0] := 16#F4; // LSB
uConvert.arrUDP_Data[1] := 16#E0;
uConvert.arrUDP_Data[2] := 16#7A;
uConvert.arrUDP_Data[3] := 16#BE; // MSB
// Here we 'use' the converted value
fValue := uConvert.rReal;
I assume you have an array of bytes.
Header (put this in own function block if you want):
PROGRAM MAIN
VAR
aByteArray : ARRAY[1..4] OF BYTE := [16#F4, 16#E0, 16#7A, 16#BE];
pt : POINTER TO REAL;
fRealValue : REAL;
END_VAR
Body:
pt := ADR(aByteArray);
fRealValue := pt^;
Will give you the desired result:

How to convert a string to a variable name?

I would like to have a construct like below to declare variable names based on a string queue. The below doesn't compile. So I would like to know if a similar approach is possible in Systemverilog.
Below is a simplified version of what I want to actually implement.
`define declare_int(NAME) int ``NAME`` = 1;
string str_q[$] = {"a", "b", "c"};
foreach (str_q[i]) begin
`declare_int(str_q[i])
end
NOTE: I am aware that `declare_int(a) will translate to int a = 1;. But as shown in the example above, I need a foreach loop to call this macro multiple times and so the input of the macro has to be some datatype, like a string in this case. The purpose is to auto-declare stuff as the queue changes with time.
In other words, how can I define the `declare_int macro so that `declare_int("a") translates to int a = 1;?
As Verilog is not interpreted but compiled in simulation, I doubt theres any way to dynamically declare variables at runtime. However, there are work arounds that achieve a similar effect.
I think the closest thing you could get is an associative array with the keys as your names (a, b, c) and your values for the values. For example, instead of your code, you'd have:
int vars[string];
string str_q[$] = {"a", "b", "c"};
foreach (str_q[i]) begin
vars[str_q[i]] = 1;
end
...
// To use the variable, just do:
vars["a"];
For more on associative arrays: http://www.asic-world.com/systemverilog/data_types13.html