Skip to content

22Structured programs

Arrays in Structured Text

Declaring ARRAY [1..10] OF REAL, indexing, looping with FOR, a moving-average filter, and the out-of-range index that really does stop controllers.

Lesson 22 of 3412 minIEC 61131-3

When separate variables become the problem

You want the average of the last ten level readings. With separate variables:

S10 := S9; S9 := S8; S8 := S7;   (* ... nine more lines *)
Avg := (S1 + S2 + S3 + S4 + S5 + S6 + S7 + S8 + S9 + S10) / 10.0;

Twenty lines, and changing ten to twenty means rewriting all of it. An array fixes that.

Declaring one

VAR
  Samples : ARRAY [1..10] OF REAL;
  Motors  : ARRAY [1..32] OF BOOL;
  Grid    : ARRAY [1..4, 1..8] OF INT;   (* two dimensions *)
END_VAR

The range [1..10] sets the first and last index. The standard lets you choose, but plant convention is to start at 1, because the first motor is M1 and not M0, and that removes a class of off-by-one mistakes.

Indexing

Samples[1]    := Level;
Last          := Samples[10];
Samples[i]    := Samples[i - 1];
Motors[Index] := TRUE;

The index is an expression, so it can be a variable or a calculation, and that is where the trouble starts.

FOR over an array

Total := 0.0;
FOR i := 1 TO 10 DO
  Total := Total + Samples[i];
END_FOR;
Avg := Total / 10.0;

Four lines that work for 10 elements and for 200. To shift:

FOR i := 10 TO 2 BY -1 DO
  Samples[i] := Samples[i - 1];
END_FOR;
Samples[1] := Level;

Note the direction: top down, or you copy element one over the whole array.

The filter: a moving average

A level reading wobbles because the liquid moves and because electrical noise rides on the cable. A moving average calms it:

Tick(IN := NOT Tick.Q, PT := T#100ms);

IF Tick.Q THEN
  FOR i := 10 TO 2 BY -1 DO
    Samples[i] := Samples[i - 1];
  END_FOR;
  Samples[1] := Level;
END_IF;

Total := 0.0;
FOR i := 1 TO 10 DO
  Total := Total + Samples[i];
END_FOR;
AvgLevel := Total / 10.0;

Do not sample every scan. Ten samples at a 10 ms scan is a 100 ms window, which filters nothing. Sampling every 100 ms gives you a full second. Filter window = sample count x sample interval, and that is the number to work out before you type anything.

The out-of-range index

This genuinely stops production lines:

Samples[i] := Level;   (* what if i is 0? or 11? or 300? *)

What happens depends on the platform: some raise an exception and stop the controller, others quietly write over another variable's memory. The second is worse, because the program keeps running and keeps being wrong. The dangerous index is the one that came from outside: an HMI field, a comms message, a calculation.

Always guard it:

IF i >= 1 AND i <= 10 THEN
  Samples[i] := Level;
ELSE
  IdxFault := TRUE;
END_IF;

Or clamp it:

i := LIMIT(1, RawIndex, 10);

Clamping beats ignoring, and raising an alarm beats clamping silently.

Not runnable in the lab

The lab does not support ARRAY. The lab program below gets the same moving average out of four separate variables, which is exactly the style an array exists to replace.

Try it in the lab

The lab runs in your browser: an ST editor, a virtual process that suits the lesson, the same program as a live ladder diagram, a watch table, and export to Siemens SCL.

What to try

  1. Run the program and press START.
  2. Watch Smp1 to Smp4 in the watch table: values move one slot every half second, as in an array.
  3. Compare Level with Avg: the average follows later and smoother.
  4. Change T#500ms to T#2s: the average gets slower and the level overshoots 70% before the pump stops.

People find this lesson by searching

ARRAY structured textplc array exampleFOR loop over array plcmoving average filter plcARRAY OF REAL IEC 61131-3array index out of range plcstructured text array declaration

Course outline

  1. 01What is Structured Text, and when to use it instead of ladder
  2. 02How a PLC runs your code: the scan cycle and the process image
  3. 03Program structure: POUs, PROGRAM, FUNCTION and FUNCTION_BLOCK
  4. 04Your first program: starting a pump with seal-in logic
  5. 05Data types: BOOL, INT, REAL, TIME and the rest
  6. 06Declaring variables: VAR, VAR_INPUT and RETAIN
  7. 07Literals and constants: 16#FF, 2#1010 and T#5s
  8. 08Type conversion and scaling a 4-20 mA signal
  9. 09Assignment and arithmetic in ST: := and operator precedence
  10. 10Boolean logic, comparisons and bit operations
  11. 11Standard functions: ABS, SQRT, MIN, MAX, LIMIT, SEL, MUX
  12. 12IF, ELSIF and ELSE: the rules and when to avoid them
  13. 13The CASE statement and your first state machine
  14. 14The FOR loop and the watchdog
  15. 15WHILE, REPEAT, EXIT, CONTINUE and RETURN
  16. 16Edge detection: R_TRIG, F_TRIG and the "it fired every scan" bug
  17. 17Timers: TON, TOF and TP
  18. 18Counters: CTU, CTD and CTUD
  19. 19SR and RS flip-flops: which one wins
  20. 20Writing your own function
  21. 21Writing a function block
  22. 22Arrays in Structured Text
  23. 23Structs and enumerations
  24. 24References and pointers
  25. 25The state machine pattern: a batch sequence that cannot get stuck
  26. 26Alarm management to ISA-18.2: delay, deadband, latch, acknowledge, first-out
  27. 27Analogue signal processing: counts to engineering units, NE43 and filtering
  28. 28Closed-loop control in ST: hysteresis first, then a practical PID
  29. 29Motors and valves in practice: permissives, feedback and travel time
  30. 30The same ST on every platform: SCL, Studio 5000, CODESYS, TwinCAT
  31. 31Testing and debugging ST: the watch table, forcing and simulation
  32. 32An ST style guide: naming, comments, constants and version control
  33. 33Final project: a complete tank station with a state machine and alarms
  34. 3420 interview questions on ST and PLC programming, with answers