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
FORloop withTO/BY/DOis 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 Intor, for variable-length cases, an array with explicit upper bound such asArray[0..n] of Intwherenis 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).
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. |
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
- In the project tree, expand Program blocks and click Add new block.
- Choose Function (FC), name it
FC_CountValueInArray, set the language to SCL, and confirm. - Open the FC, switch to the SCL editor, and declare the interface per the table in section 3.
- Paste the SCL source from section 3.1.
- Compile the block (Project tree → right-click the FC → Compile → Software (only)). Look for warnings about implicit type conversions in the Info tab.
- 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 ); - 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.
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
- Create a watch table in TIA Portal (Project tree → Watch and force tables → Add new watch table).
- 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 to0. - Trigger the FC call by setting the OB1 call conditions (or, in the watch table, manually toggling a one-shot).
- Observe
o_count: it must become2for search value3. - 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) → expecto_error = TRUE,o_count = 0. -
i_endIdx = 1000when array upper bound is 50 → expecto_error = TRUE,o_count = 0.
- search value that occurs zero times → expect
- Force a one-shot in the watch table, force a transition from
o_busy = FALSEtoo_busy = TRUE, and confirmo_donerises exactly one OB1 cycle after the last index is processed. - 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
REALwith=. 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, useDInt(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.
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.