Loops are exactly the reason to use the ST language. We will demonstrate the use of a simple loop by calculating the average value over the last 15 minutes. This is a continuous unweighted average.
The variable MINUTY is of the type Analog Shift Register (B110_SHIFT_REGISTER). The declaration on line 6 effectively inserts and names a functional block of this type.
When a full minute passes—that is, the seconds pointer SINT1 = 0—the rising edge of the variable SHIFTNOW shifts the values in the shift register one step forward and inserts the current power value into the IN input.
As a result, the shift register contains sampled values taken each minute over the last (at least) 15 minutes (the shift register has 24 positions, but we’ll use only the first 15).
In the FOR…END_FOR loop (see lines 20 to 22), the last 15 values are summed up, and on the following line, the average is calculated by dividing the total.
WHILE J<10 DO
J:=J+2;
END_WHILE;
REPEAT
J:=J+2;
UNTIL J>10;
They differ mainly in that with REPEAT, the condition is evaluated at the end of the loop, meaning the loop executes at least once. The condition should be written safely to avoid the risk of being bypassed, which could lead to an infinite loop.
Step-Based Switching with Backup and Rotation
To control a group of units—such as a boiler cascade, a set of heat pumps, or just a multi-stage electric heater—a block that alternates outputs is useful. This allows for more even wear and provides backup in case some units are non-functional.
The example below shows a modified version of the standard block T14. It also demonstrates working with arrays, although the arrays are not declared in the usual way.
test : ARRAY [0..15] OF BOOL;
TYPE
MultiIoBool : ARRAY[0..15] OF BOOL;
END_TYPE
FUNCTION_BLOCK t14_light
VAR
BxPrev : BOOL;
BxfPrev : BOOL;
i, j : INT;
Set : INT;
InOKRot, OutDerot : Lib.Core.V1_0.MultiIoBool := [16(false)];
END_VAR
VAR_INPUT
Bx : BOOL := FALSE;
Bxf : BOOL := FALSE;
// List of operational units (derived, for example, from alarm blocks)
InOK : Lib.Core.V1_0.MultiIoBool := [true, 15(false)];
NumOfActive, NumOfOutputs : INT := 1;
// NumOfActive: Number of outputs to be active (e.g., a demand from a PI controller)
// NumOfOutputs: Total installed number of units (constant)
END_VAR
VAR_OUTPUT
Out : Lib.Core.V1_0.MultiIoBool;
END_VAR
VAR RETAIN
Shift : INT; // By how much the outputs are rotated (for alternation purposes)
END_VAR
METHOD IncrementShift
Shift := Shift + 1;
IF Shift > NumOfOutputs - 1 THEN
Shift := 0;
END_IF;
END_METHOD
IF NOT BxPrev AND Bx THEN //Rising edge detection for rotation
IncrementShift();
END_IF;
IF BxfPrev AND NOT Bxf THEN
IncrementShift();
END_IF;
FOR i := 1 TO 16 DO // Resetting the output array
Out[i] := False;
END_FOR;
// De-rotation of the prepared input list by Shift, so the outputs can be activated starting from 1.
FOR j := 1 TO NumOfOutputs DO
IF (j - Shift < 1) THEN
InOKRot[(j - Shift + NumOfOutputs)] := InOK[j]
ELSE
InOKRot[(j - Shift)] := InOK[j]
END_IF;
END_FOR;
// Resetting the output array and other auxiliary variables
FOR i := 1 TO 16 DO
OutDerot[i] := False;
END_FOR;
Set := 0;
i := 0;
// Setting the number of active outputs when skipping non-functional ones – thanks to the de-rotation, we start from 1.
WHILE NOT (Set >= NumOfActive OR i > NumOfOutputs) DO
IF InOKRot[i] THEN
OutDerot[i] := True;
Set := Set + 1;
END_IF;
i := i + 1;
END_WHILE;
// Rotating the outputs back to the physically correct order
FOR j := 1 TO NumOfOutputs DO
IF (j - Shift < 1) THEN
Out[j] := OutDerot[(j - Shift + NumOfOutputs)]
ELSE
Out[j] := OutDerot[(j - Shift)]
END_IF;
END_FOR;
BxPrev := Bx; //For detecting the rising or falling edge for Shift
BxfPrev := Bxf;
END_FUNCTION_BLOCK
The variable ActiveOutputs contains the request for the number of active outputs. The total number of units is a constant, so this input is not exposed. The auxiliary block Bool_to_MultiIoBool converts the corresponding number of bool type variables (in this case, 8) into an array of binary variables, which is the data type used in the standard T14 block, as seen in the declaration.
InOKRot, OutDerot : Lib.Core.V1_0.MultiIoBool := [16(false)];
FUNCTION_BLOCK Bool_to_MultiIoBool
VAR
i: int;
END_VAR
VAR_INPUT
in1, in2, in3, in4, in5, in6, in7, in8: bool; // there may be more
END_VAR
VAR_OUTPUT
out : Lib.Core.V1_0.MultiIoBool;
END_VAR
out[1] := in1;
out[2] := in2;
out[3] := in3;
out[4] := in4;
out[5] := in5;
out[6] := in6;
out[7] := in7;
out[8] := in8; // atd.
END_FUNCTION_BLOCK
It would certainly be possible to avoid using an array in the block T14_light and declare the inputs "hardcoded," but we would still need to place them into some kind of array for rotational operations and loops. Furthermore, the data type MultiIoBool allows for dynamic outputs within the block:
and thus easily modify the number of visible outputs of the block in the FUPLA graphic.
test : ARRAY [0..15] OF BOOL := [16(false)];
This means that all 16 positions of the array have the default value False. Default values can be written either individually or in bulk. Both methods can be combined:
test : ARRAY [0..15] OF BOOL := [true, 15(false)];
This sets the first position to True and the remaining 15 positions to False.