Counting Array Value Occurrences in S7 SCL: TIA Portal Tutorial

David Krause10 min read
SiemensTIA PortalTutorial / How-to
Licensed PE Working through this on a live machine? A Maine-licensed engineer can take it from here — included with IMD hardware, by the hour for everything else. Book an engineer

Counting Array Value Occurrences in S7 SCL: TIA Portal Tutorial

This tutorial shows how to count how many times a specific value appears inside a Siemens SIMATIC S7 array (PLC tag of type Array[0..50] of Int) using Structured Control Language (SCL) in TIA Portal. A working SCL function, a pure ladder (LAD) alternative, edge-case handling, and a commissioning verification procedure are provided.

1. Problem Definition

Given a PLC tag of the form:

Array1 : Array[0..50] of Int;  // 51 elements, indexes 0..50

with arbitrary values such as:

Index Value
Array1[0] 0
Array1[1] 3
Array1[2] 2
Array1[3] 1
Array1[4] 3
... ...

you must determine, for a given search value (for example, the integer 3), how many elements of Array1 are equal to that value. The expected result for the example above is 2.

2. Prerequisites

  • Siemens TIA Portal V15.1 or later (tested with V16, V17, V18, V19). The SCL syntax used is valid back to V13 SP1.
  • CPU family: S7-1200 (firmware V4.2+) or S7-1500 (firmware V1.8+). The SCL FOR loop with TO / BY / DO is supported on both. Reference: Siemens S7-1200/1500 SCL programming guideline.
  • A defined PLC data block (DB) or local tag of array type, for example Array[0..50] of Int or, for variable-length cases, an array with explicit upper bound such as Array[0..n] of Int where n is a constant in the same block.
  • An empty function (FC) or function block (FB) into which the search logic will be placed. For SCL, the block must be created with the language set to SCL (right-click the FC/FB in the project tree → Properties → General → Language: SCL).
Important: In S7-1200/1500, the array index range is fixed at declaration time. SCL cannot iterate a "dynamic-length" array unless you use the LOWER_BOUND and UPPER_BOUND intrinsics (S7-1500) or you keep a parallel length tag. For S7-1200, declare a constant for the upper bound and reference it in both the array declaration and the loop.

3. Solution: SCL FOR Loop

Create a new FC, for example FC_CountValueInArray, with the following interface:

Section Name Type Comment
Input i_array Array[*] of Int (or Array[0..50] of Int) Array to scan
Input i_searchValue Int Value to count
Input i_startIdx DInt Lower bound (optional, default 0)
Input i_endIdx DInt Upper bound (optional)
Output o_count DInt Number of matches
Output o_error Bool TRUE on range violation

3.1 SCL Source Code

// FC_CountValueInArray - count occurrences of i_searchValue in i_array
// Compatible with S7-1200/1500, TIA Portal V15.1+

#o_count := 0;
#o_error := FALSE;

// Range validation
IF #i_startIdx < LOWER_BOUND(#i_array) OR #i_endIdx > UPPER_BOUND(#i_array) THEN
    #o_error := TRUE;
    RETURN;
END_IF;

FOR #i_idx := #i_startIdx TO #i_endIdx BY 1 DO
    IF #i_array[#i_idx] = #i_searchValue THEN
        #o_count := #o_count + 1;
    END_IF;
END_FOR;

Where #i_idx is a local DInt tag (Temp section) declared as i_idx : DInt;. The hash (#) prefix is automatic block-local addressing in TIA Portal; it is optional but recommended for readability.

3.2 SCL Construct Breakdown

Statement Purpose
#o_count := 0; Initialize the result; SCL does not auto-initialize local outputs.
LOWER_BOUND(#i_array) / UPPER_BOUND(#i_array) Built-in SCL functions returning the declared array bounds as DInt. Available on S7-1500. For S7-1200 use a constant ARRAY_LOW / ARRAY_HIGH.
FOR #i_idx := #i_startIdx TO #i_endIdx BY 1 DO Standard SCL FOR loop. Step BY 1 may be omitted (default is 1).
IF #i_array[#i_idx] = #i_searchValue THEN Element-by-element comparison. Works for Int, DInt, Real, Bool, Byte, Word, DWord, and structured (UDT) tags.
#o_count := #o_count + 1; Increment; DInt count supports up to 2,147,483,647 matches.
Edge case — negative numbers: Int in S7-1200/1500 is 16-bit signed (range -32768..32767). If the search value is negative, the equality comparison still works because both sides are Int. Mixing Int and DInt in the same comparison is permitted but the smaller type is implicitly widened.

4. Step-by-Step Procedure in TIA Portal

  1. In the project tree, expand Program blocks and click Add new block.
  2. Choose Function (FC), name it FC_CountValueInArray, set the language to SCL, and confirm.
  3. Open the FC, switch to the SCL editor, and declare the interface per the table in section 3.
  4. Paste the SCL source from section 3.1.
  5. Compile the block (Project tree → right-click the FC → Compile → Software (only)). Look for warnings about implicit type conversions in the Info tab.
  6. From the calling block (OB1, FB, or another FC), invoke the FC and wire the inputs/outputs. Example call from OB1 in SCL:
    "FC_CountValueInArray"(
        i_array       := "DataBlock_1".Array1,
        i_searchValue := 3,
        i_startIdx    := 0,
        i_endIdx      := 50,
        o_count       => "DataBlock_1".resultCount,
        o_error       => "DataBlock_1".resultError
    );
  7. Download the project to the CPU and go online.

5. Alternative: Pure Ladder (LAD) Implementation

If your project standard mandates LAD (no SCL), implement the loop with a counter and an indexed access using a pointer-incremented DB. On S7-1200/1500, the cleanest pure-LAD approach uses the PEEK/POKE box and a dedicated loop counter, but in practice LAD is impractical for arbitrary-length integer scans. The recommended approach is therefore an FB written in LAD that increments a loop index on each scan:

Network Logic
NW1 Initialize on first call: M0.0 (first scan) → reset DB1.DBD0 (count) to 0; load i_endIdx into DB1.DBD4 (loop var).
NW2 Compare "DataBlock_1".Array1[DB1.DBD4] with i_searchValue; if equal, increment DB1.DBD0.
NW3 Decrement DB1.DBD4 by 1; if > 0, jump back to NW2; else o_done := TRUE.

This emulates the FOR loop in LAD but loses the deterministic single-scan execution that SCL provides. For any non-trivial array, prefer the SCL version.

Recommendation: SCL is the right tool for array scans. LAD forces you to manage loop control via flags/DB variables and lose the readability advantage. Adopt SCL for any block that iterates an array.

6. Edge Cases and Safety Checks

Scenario Risk Mitigation
i_endIdx > declared upper bound Access violation, CPU stop on S7-1200 (SF) Range check at FC entry; set o_error; never read past UPPER_BOUND.
i_startIdx > i_endIdx FOR loop does not execute; o_count stays 0 (safe but possibly unintended) Validate and set o_error if inverse range is forbidden by your project standard.
Empty array (low = high + 1) Loop body skipped; o_count = 0 Acceptable; document in block header comment.
Array of REAL (floating point) Direct = compare is unreliable due to rounding Use ABS(a - b) < 1.0e-6 for floats, or compare integers only.
Array of STRING or WSTRING Direct = works since SCL V14; equality is content-based Ensure the runtime firmware supports it (S7-1500 V2.0+).
Array of UDT / STRUCT = performs member-wise compare Supported in SCL; ensure UDT has no VARIANT or ANY fields.
Multi-dimensional array Single index insufficient Nested FOR loops, one per dimension, or flatten into 1D before scanning.
Array in optimized (symbolic) DB No direct bit/byte/word access Use symbolic access (Array1[i]); avoid DBW/DBD.
Watchdog time exceeded (S7-1200) OB1 cycle too long; CPU goes STOP For arrays larger than ~10 k elements, split the scan across multiple OB1 cycles or call from a cyclic OB (e.g., OB35 at 100 ms).

7. Performance and Cycle Time

Approximate execution time per element on representative CPUs (SCL, optimized DB access):

CPU Time per iteration (Int compare + increment)
51-element scan 1000-element scan
S7-1214C (FW V4.4) ~3.0 µs ~0.15 ms ~3.0 ms
S7-1516-3 PN (FW V2.9) ~0.05 µs < 0.01 ms ~0.05 ms
S7-1500 ET200sp CPU 1510SP ~0.08 µs < 0.01 ms ~0.08 ms

For arrays above ~20,000 elements on S7-1200, split the scan across OB35 cycles by passing the current index as an in/out static on the FB; otherwise the main OB1 watchdog (default 150 ms) will trip.

8. Generic Reusable FB Variant

For project-wide reuse, promote the FC to an FB and store the count, the running index, and a busy/done state in the static section. Example header (SCL):

FUNCTION_BLOCK "FB_CountValueInArray"
TITLE = 'Generic array value counter (multi-call safe)'
{ S7_Optimized_Access := 'TRUE' }
VERSION : '1.0'

VAR_INPUT
    i_trigger   : Bool;     // edge-triggered start
    i_searchValue : Int;
    i_startIdx  : DInt;
    i_endIdx    : DInt;
END_VAR

VAR_OUTPUT
    o_busy      : Bool;
    o_done      : Bool;
    o_count     : DInt;
    o_error     : Bool;
END_VAR

VAR
    s_state     : Int;      // 0=idle, 1=running
    s_idx       : DInt;
END_VAR

VAR_TEMP
    t_arrLow    : DInt;
    t_arrHigh   : DInt;
END_VAR

This pattern allows safe multi-cycle execution. See Siemens SCL programming style guide for further conventions on state machines and FB design.

9. Verification Procedure

  1. Create a watch table in TIA Portal (Project tree → Watch and force tables → Add new watch table).
  2. Add the array tag and pre-load it with a known pattern using the Modify column. For the original problem statement, set indices 1 and 4 to 3, all others to 0.
  3. Trigger the FC call by setting the OB1 call conditions (or, in the watch table, manually toggling a one-shot).
  4. Observe o_count: it must become 2 for search value 3.
  5. Repeat for the following cases to validate boundary behavior:
    • search value that occurs zero times → expect o_count = 0, o_error = FALSE.
    • search value equal to every element (pre-load entire array with the same value) → expect o_count = array length.
    • i_startIdx = 5, i_endIdx = 4 (inverted) → expect o_error = TRUE, o_count = 0.
    • i_endIdx = 1000 when array upper bound is 50 → expect o_error = TRUE, o_count = 0.
  6. Force a one-shot in the watch table, force a transition from o_busy = FALSE to o_busy = TRUE, and confirm o_done rises exactly one OB1 cycle after the last index is processed.
  7. Check the diagnostic buffer for any access-violation entries during the test (Online → Diagnostics → Diagnostic buffer). The buffer must be clean.

10. Comparison of Approaches

Approach Readability Performance Reusability Watchdog risk Recommendation
SCL FOR loop (FC) High High High Low for < 20 k elements Preferred
SCL FOR loop (FB with state) High High High (multi-cycle safe) None Use for large arrays
LAD with manual index increment Low Medium Low Low (one element per cycle) Avoid unless mandated
SCL WHILE loop Medium High Medium Same as FOR Use when bound is computed
SCL REPEAT...UNTIL Medium High Medium Same as FOR Rarely needed for counting

11. Field-Proven Notes

  • Always validate bounds. A single out-of-range index in optimized-block SCL produces a CPU STOP (SF LED) on S7-1200 with diagnostic buffer entry "Area length error" (event ID 16#8001 in some cases). The range check in section 3.1 prevents this.
  • Avoid comparing REAL with =. Floating-point values from analog inputs almost never match a literal exactly. Use a tolerance window.
  • Use the right type for the count. Int (16-bit) overflows at 32,767. If the array can hold more than 32 k matches, use DInt (32-bit) for the counter.
  • Symbolic access only in optimized blocks. Absolute (DBW, DBD) access to optimized DBs is blocked by the compiler.
  • KNOW_HOW_PROTECT: if you protect the FC, you cannot view the SCL online. Document the algorithm in the block header comment so that future maintainers can re-derive it.
Safety note: If the array drives a safety function (e.g., counting safety-related diagnostic bits), the scan must run in a safety program (F-CPU) using the F-library conventions. The SCL FOR loop is allowed in F-blocks on S7-1500F with firmware V2.0+. See the SIMATIC S7 F-CPU programming manual.

How do I count occurrences of a value in a Siemens S7 array?

Use an SCL FOR loop inside an FC or FB. Initialize a DInt counter to 0, loop from the lower to the upper bound, compare each element with the search value using IF arr[i] = value THEN count := count + 1; END_IF;, and return count. Validate bounds with LOWER_BOUND and UPPER_BOUND before accessing the array to avoid CPU STOP.

Can I do this in LAD without SCL on S7-1200/1500?

Technically yes, but it is impractical. You must maintain a loop index in a static or DB variable, compare Array1[idx] with the search value, and increment a counter each cycle. For any array larger than a few elements, SCL is faster to write, easier to maintain, and runs in a single OB1 cycle.

What happens if I read past the upper bound of the array?

On S7-1200/1500 with an optimized DB, accessing an index outside the declared range triggers a runtime error and the CPU goes to STOP (SF LED). The diagnostic buffer records an "Area length error" or "Range error" event. Always check LOWER_BOUND / UPPER_BOUND first, or wrap the call with a range check on i_startIdx and i_endIdx.

Will this work for arrays of REAL or STRING?

For STRING, = in SCL performs a content comparison and is safe. For REAL, the literal = comparison is unreliable because of floating-point rounding. Use a tolerance test such as IF ABS(arr[i] - value) < 1.0E-6 THEN .... For BOOL, BYTE, WORD, DWORD, INT, and DINT, direct = is exact.

How do I count values across a multi-dimensional array?

Use nested FOR loops, one per dimension. For a 2D array declared as Array[0..9, 0..9] of Int, the outer loop iterates the first index and the inner loop iterates the second, applying the same equality test and counter increment. To keep the watchdog safe, prefer the multi-cycle FB variant for arrays with more than a few thousand total elements.

Back to blog