en
cz

Loops

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.

Other types of loops, such as WHILE and REPEAT, have a similar syntax:

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.

The following example would be very difficult to implement without using loops:

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;

But instead use the library type MultiIoBool, which is a type declared in the Lib.Core.V1_0 library as follows:

TYPE
       MultiIoBool : ARRAY[0..15] OF BOOL;
END_TYPE

The complete code then looks like this:

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 principle is as follows:

  • Upon a rising edge at input Bx (or a falling edge at input Bxf), we increase the shift (Shift) by one, up to the maximum number of used outputs, after which the value overflows to 0. Pulses are supplied to the input at regular intervals or based on a manual request for alternation. This ensures the regular rotation of the units and their even wear.
  • The vector of inputs ready for operation is rotated counterclockwise by Shift, so the first input to be activated is in the first position.
  • Outputs are progressively activated until the required number is active, with non-functional units being skipped (backup function).
  • The output vector is then rotated clockwise, so the outputs that need to be activated are shifted by Shift.

In the program, the use of the block looks like this:

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)];

The source code of the auxiliary block is very simple:

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.

Finally, a note about setting default values for the array during declaration:

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.

 All parts of the article