Post

Init array in ST


In this post, I would like to go over the different ways to initialize an array of structures and function blocks. The main reason is that I found a syntax that wasn’t well documented or, at least, not to my knowledge.

Standard datatypes arrays

- Basic example

Let’s start simple by initializing an array of a standard data type.:

VAR
	aIntArray:		ARRAY[0..4] OF INT := [0,1,2,3,4];
END_VAR

- Partial initialization

If you only want to initialize the first few elements, you can do so without any issues by using the following syntax:

VAR
	aIntArray:		ARRAY[0..4] OF INT := [0,1];
END_VAR

- Repeated values

But what if you have many elements? Well, there is a shortcut that can be taken to define multiple elements with the same value. The syntax X(.) will repeat everything inside the brackets X times. Both of the following initializations create an array with similar values:

VAR
	aIntArray_1:	ARRAY[0..9] OF INT := [5(0), 1, 2, 3(-1)];
	aIntArray_2:	ARRAY[0..9] OF INT := [0,0,0,0,0,1,2,-1,-1,-1];
END_VAR

Multi dimensions

- Multi-dimensional array

For arrays with multiple dimensions, you need to flatten the array before assigning:

VAR
	aIntArray_1:	ARRAY[0..2, 0..1] OF INT := [ 0,1,2,3,4,5 ];
	(*	[
			[ 0 , 1 ],
			[ 2 , 3 ],
			[ 4 , 5 ]
		] 	*)
	IntArray_2:		ARRAY[0..2, 0..1] OF INT := [ 6(123) ];
	(*	[
			[ 123 , 123 ],
			[ 123 , 123 ],
			[ 123 , 123 ]
		]	*)
	aIntArray_3:	ARRAY[0..2, 0..1] OF INT := [ 2(0), 2(1), 2(2) ];
	(*	[
			[ 0 , 0 ],
			[ 1 , 1 ],
			[ 2 , 2 ]
		]	*)
	//Sadly the following syntax to have rows with the same value, doesn't work.
	aIntArray_4:	ARRAY[0..2, 0..1] OF INT := [ 3(0,1) ]; //Unexpected array initialization
END_VAR

- Array of array

Another option is to use ARRAY[.] OF ARRAY[.]. When accessing an element, you need to use [X][Y] instead of [X,Y]. During initial assignment, you can use X([..]) to repeat rows, and X([ Y(.) ]) to fill the whole array.

VAR
	aIntArray_1:	ARRAY[0..2] OF ARRAY[0..1] OF INT := [ [0,1], [2,3], [4,5] ];
	(*	[
			[ 0 , 1 ],
			[ 2 , 3 ],
			[ 4 , 5 ],
		]	*)
	aIntArray_2:	ARRAY[0..2] OF ARRAY[0..1] OF INT := [ 3([ 2(123) ]) ];
	(*	[
			[ 123 , 123 ],
			[ 123 , 123 ],
			[ 123 , 123 ],
		]	*)
	aIntArray_3:	ARRAY[0..2] OF ARRAY[0..1] OF INT := [ 3( [0,1] ) ];
	(*	[
			[ 0 , 1 ],
			[ 0 , 1 ],
			[ 0 , 1 ]
		]	*)
	aIntArray_4:	ARRAY[0..2] OF ARRAY[0..1] OF INT := [ [2(0)], [2(1)], [2(2)] ];
	(*	[
			[ 0 , 0 ],
			[ 1 , 1 ],
			[ 2 , 2 ]
		]	*)
END_VAR

- Constants

PLC programmers often use constants to define the size of their arrays. This way, they can easily update the size and reuse it in FOR loops. These constants can also be used to fill the array during initialization. The following examples show how to fill multi-dimensional arrays with 1.
NOTE: When testing, line 10 resulted in a syntax error for aIntArray_2! The X needs to be surrounded by brackets, while the brackets are optional for Y. The logic behind this syntax can be confusing and may appear to be a bug.

VAR CONSTANT
	X:		INT := 3;
	Y: 		INT := 2;
END_VAR

VAR
	//Works
	aIntArray_1:	ARRAY[1..X, 1..Y] OF INT			:= [ (X*Y)(1) ];
	//Doesn't works!!!!!
	aIntArray_2:	ARRAY[1..X] OF ARRAY[1..Y] OF INT 	:= [  X(  [  Y (1) ] )];
	//Works
	aIntArray_3:	ARRAY[1..X] OF ARRAY[1..Y] OF INT 	:= [ (X)( [  Y (1) ] )];
	//Works
	aIntArray_4:	ARRAY[1..X] OF ARRAY[1..Y] OF INT 	:= [ (X)( [ (Y)(1) ] )];	

	//Also works
	aIntArray_5:	ARRAY[1..(2*X)] OF INT 				:= [ (X-Y)(1), X(1), Y(1) ];
END_VAR

Array of structures

- Basic example

Let’s define a simple structure:

TYPE ST_Struct :
STRUCT
	xBool:			BOOL;
	lrReal:			LREAL;
END_STRUCT
END_TYPE

For initialization we can do the following. Not all elements needs to be defined. The missing elements will stay at their default value.

VAR
	aStruct: 		ARRAY [0..3] OF ST_Struct := 	[
							(xBool := TRUE),
							(lrReal := 1.0),
							(xBool := FALSE, lrReal := -3.0)
						];
END_VAR

- Repeated elements

Now we are coming to the point where I (re-)discoverd the syntax for initializing an array of structures with repeated elements. This was the main reason why I created this blog post, as I haven’t seen anyone else documenting it.

VAR
	aStruct: 		ARRAY [0..5] OF ST_Struct := 	[ 
							4((xBool := TRUE)),
							(xBool := TRUE, lrReal := 5.0)
						];
END_VAR

Did you notice the double brackets? X(.) will repeat everything inside the brackets X times. As the structure also needs brackets, you end up with two brackets. It may seem obvious once you know it, but without any examples, most people (like me) will initially try it with single brackets, encounter a syntax error, and give up.

Array of function blocks

Function blocks act in the same way as structures. You can initialize inputs, outputs, local variables, and even properties! Inputs and properties are a logical use, but outputs and local function block variables cannot typically be assigned during code execution. However, it is possible to do so during initialization. Personally, I consider it bad practice.

FUNCTION_BLOCK FB_Test
VAR_INPUT
	iIn:	INT;
END_VAR
VAR_OUTPUT
	iOut:	INT;
END_VAR
VAR
	iVar:	INT;
	iProp:	INT;
END_VAR
PROPERTY p_Prop : INT
//---Getter---
p_Prop 		:= iProp;
//---Setter---
iProp 		:= p_Prop;

To initialize an array of these function blocks:

VAR										
	aFb_Test: 		ARRAY [0..1] OF FB_Test := [ 
							2((p_Prop := 5, iVar := 2, iIn := 6, iOut := 9))
					];
END_VAR

- FB_init

Another way to initialize function blocks is to use the FB_init method. This method is called before the initial assignments and can be extended with custom parameters. The parameters can be mapped to internal variables for initialization. The key difference is that with FB_init, all parameters are required and can’t be left as default. Depending on the use case, this can be beneficial or unwanted.

Let’s extend previous function block with the following FB_init method:

METHOD FB_init : BOOL
VAR_INPUT
	bInitRetains: 	BOOL; // if TRUE, the retain variables are initialized (warm start / cold start)
	bInCopyCode: 	BOOL;  // if TRUE, the instance afterwards gets moved into the copy code (online change)
	iPara:			INT;
END_VAR
//------------------------------------------------
p_Prop := iPara;

These examples will call FB_init with the parameter iPara. This will set the p_Prop property, which in turn sets the internal variable iProp:

VAR										
	//All these examples will both have p_Prop and iProp equal to 2
	aFb_Test1: 		ARRAY [0..1] OF FB_Test(2);
	aFb_Test2: 		ARRAY [0..1] OF FB_Test(iPara := 2);
	//Somehow this is also seen as 'init all fb's with the same value'
	aFb_Test3: 		ARRAY [0..1] OF FB_Test[(iPara := 2)]; 
	
	//Initialize the function blocks with different values
	aFb_Test4: 		ARRAY [0..1] OF FB_Test[ (iPara := 1),(iPara := 2) ];
	
	//Syntax error: FB-Init-Initializers(2) does not match the number of array-elements(3)
	aFb_Test5:		ARRAY [0..2] OF FB_Test[ (iPara := 1),(iPara := 2) ];
	//Syntax error: Repeating of values can't be used with FB_init :-(
	aFb_Test6:		ARRAY [0..2] OF FB_Test[ 3((iPara := 1)) ];
	
	//Both FB_init and property initialization
	aFb_Test7:		ARRAY [0..1] OF FB_Test(-1) := [ 2((p_Prop := 10)) ];
	//=> The fb's will have p_Prop and iProp equal to 10. 
	//Because FB_init is called first, and after that the initial assignments
END_VAR

Conclusion

There are many ways to initialize arrays of standard and complex datatypes. Although it’s possible to repeat the same value multiple times without the need to repeat it in code, the syntax for doing so is not well documented or consistent. It would be great if the language were updated to allow the failing examples to work.

If you know of or have found other examples that aren’t listed here, please leave them in the comments below, and I will update this post accordingly.

Thank you for reading! If you found this post useful, please consider giving it a like.

This post is licensed under CC BY 4.0 by the author.