SCL Array Comparison: Fixing Invalid Type Errors in TIA Portal

David Krause13 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

Overview

Engineers migrating S7-300/S7-400 STL code that compared two 8-character arrays often paste their accumulator-based pattern into TIA Portal SCL, only to see a compiler error reading Invalid type of address or Expression invalid. The original logic is straightforward in STL: load two DWORDs from each array, add them in the accumulator, store the result, then compare the two DINTs. SCL has no accumulator model, so every one of those statements must be rewritten as a typed expression over the array symbols themselves.

This reference documents three working SCL patterns for comparing an Array[0..7] of Char against another array, together with the constraints imposed by block access optimization, the differences between S7-1200 and S7-1500 runtime behavior, and the verification steps needed before commissioning. The patterns also generalize to longer arrays, BYTE arrays, and STRING-typed fields once the underlying data is converted with Chars_TO_Strg.

Why STL Patterns Fail When Pasted Into SCL

STL operates on a two-register accumulator model (ACCU1 and ACCU2). Each L DBx.dbdx pushes a 32-bit value into ACCU1, shifting the previous ACCU1 into ACCU2. The +D instruction adds ACCU1 and ACCU2 and returns the sum to ACCU1. The result is implicitly cast to DINT when stored to T A (a temporary DINT marker). The same sequence applied to the second array produces T B, and a final compare yields the "not equal" output. The pattern works because the absolute address syntax DBx.dbdy is resolved by the STL compiler at compile time.

SCL is a high-level Pascal-descended language without implicit accumulators. The compiler maps each statement to typed operations on tags. Three specific constructs in the STL snippet are illegal in SCL:

  1. Accumulator load with absolute addresses — L DBx.dbdx is not a valid SCL expression. SCL only accepts symbolic names ("Data".ArrayA[0] or a slice) on the right-hand side of an assignment.
  2. Implicit DINT cast through +D — SCL has no +D operator. The equivalent is the + operator with a DINT target, but the inputs must already be DINT-typed values, not raw bytes loaded from a DB.
  3. Equality on accumulators — the final compare in SCL requires a Boolean expression such as A <> B on two valid DINT operands. The temporary DINT marker T A from STL is not an SCL symbol.

The TIA Portal compiler surfaces the first violation as the error Invalid type of address in the SCL editor's Compile output. The fix is to abandon the accumulator abstraction and operate directly on the array elements.

Important: SCL on S7-300/S7-400 (STEP 7 V5.x) and SCL on S7-1200/S7-1500 (TIA Portal) share most syntax but differ in array bounds, structured tag access, and string handling. Code in this reference targets TIA Portal V16 or later, which is the active mainstream version supporting the extended string and character instructions.

Prerequisites

Before applying any of the patterns below, confirm the following in the project configuration:

  • Two data blocks (or one DB with two arrays) containing Array[0..7] of Char fields of identical length. If the lengths differ, the comparison must be bounded by the shorter array, or the longer one must be sliced.
  • The arrays are declared with ARRAY[0..7] OF CHAR (zero-based) or ARRAY[1..8] OF CHAR (one-based). The Chars_TO_Strg instruction only accepts zero-based arrays.
  • The DBs have a known access setting. Optimized block access (the default for new TIA Portal DBs) prohibits absolute addressing and changes the layout in PLC memory. Non-optimized access retains the byte layout the original STL assumed.
  • A Boolean output tag is available for the "arrays differ" signal. Edge-triggered detection of the change requires either an R_TRIG / F_TRIG instance or a manual edge flag.
  • The CPU firmware supports the chosen instruction. Chars_TO_Strg on the S7-1200 requires firmware V4.0 or later.

Solution 1 — FOR Loop With Byte-by-Byte Comparison

The portable, optimizer-safe pattern is a FOR loop that walks both arrays in lockstep. As soon as a single byte differs, the loop sets the output and exits. This is the recommended starting point because it makes the comparison logic explicit and works on every S7-1200 and S7-1500 firmware version.

Declaration Block

FUNCTION_BLOCK FB_CompareCharArrays
VAR
    i     : INT;
    Differ : BOOL;
    Edge   : BOOL;
END_VAR
VAR CONSTANT
    LEN : INT := 8;
END_VAR

Implementation

Differ := FALSE;

FOR i := 0 TO LEN - 1 DO
    IF "Data".ArrayA[i] <> "Data".ArrayB[i] THEN
        Differ := TRUE;
        EXIT;
    END_IF;
END_FOR;

Edge := Differ AND NOT Edge;

The loop body performs a direct CHAR compare. CHAR in SCL is an 8-bit unsigned value; the <> operator on two CHAR tags is legal and produces a BOOL. If a project requires numeric comparison (for example to ignore case on ASCII letters), wrap each element with CHAR_TO_INT first. The EXIT statement short-circuits the loop on the first mismatch, which is important when the arrays are 8 elements long (worst case 8 iterations) but becomes critical for arrays of 64 or 256 bytes.

Edge detection at the bottom converts the level signal Differ into a one-shot pulse. If only a level is required (for example, to drive a HMI indicator), drop the last line.

Performance: On an S7-1516, the unrolled version (8 explicit IF statements) is roughly 4-5x faster than a FOR loop because the loop overhead is removed by the SCL compiler's optimizer. If the comparison runs in a 1 ms OB and the arrays are guaranteed to be exactly 8 elements, unroll manually. For variable lengths, keep the loop.

Comparison Flow

Differ := FALSE i := 0 i < LEN ? A[i] <> B[i] ? Differ := TRUE i := i + 1 END NO NO YES YES

Solution 2 — AT View Over DWORD

The second pattern preserves the spirit of the original STL — load a 32-bit slice and compare — but does it in SCL through an AT overlay. An AT view reinterprets a slice of the source array as another type without copying data. The comparison is then a single <> on two DWORDs.

FUNCTION_BLOCK FB_CompareDWordSlice
VAR
    Differ : BOOL;
    SliceA AT "Data".ArrayA : ARRAY[0..1] OF DWORD;
    SliceB AT "Data".ArrayB : ARRAY[0..1] OF DWORD;
END_VAR
Differ := (SliceA[0] <> SliceB[0]) OR (SliceA[1] <> SliceB[1]);

The AT view requires the source array to be at least 8 bytes (which Array[0..7] of Char is) and the new view to start at a byte boundary that the target type aligns to. A DWORD-aligned view of an 8-byte array produces a 2-element DWORD array, hence the explicit index 0 and 1 in the compare.

The two element compares are compiled to a load-compare pair each. On the S7-1500 the operation is single-cycle per DWORD; on the S7-1200 the same code compiles to roughly 2-3 microseconds per compare. This is the fastest portable approach for fixed-length 8-element arrays.

Limitation: AT views are only valid on data blocks with non-optimized access, or on optimized blocks when the source variable is a tag of a structured type and the AT view is declared within the same instance scope. The TIA Portal compiler raises AT overlay is not allowed here if either condition is violated.

Solution 3 — Convert to STRING With Chars_TO_Strg

When the comparison result feeds downstream STRING logic, or when the field is logically a 7-bit ASCII string padded with a null, the cleanest pattern is to convert each CHAR array to a STRING once and then use the <> operator on the STRING tags.

The Siemens instruction Chars_TO_Strg copies an array of ASCII character bytes into a STRING. The instruction accepts only zero-based arrays (Array[0..n] of Char) and requires a destination STRING whose declared length is at least the source array length plus the implicit length byte.

VAR
    StrA   : STRING[8];
    StrB   : STRING[8];
    Differ : BOOL;
    SrcA   : ARRAY[0..7] OF CHAR := '12345678';
    SrcB   : ARRAY[0..7] OF CHAR := '1234567X';
END_VAR
Chars_TO_Strg(Chars := SrcA, pChars := 0, Count := 8, String => StrA);
Chars_TO_Strg(Chars := SrcB, pChars := 0, Count := 8, String => StrB);
Differ := StrA <> StrB;

The conversion performs 8 byte copies per call. The resulting STRING compare is a single cycle. This pattern is preferred when:

  • The arrays are already conceptually strings (terminator-padded, 7-bit ASCII).
  • The STRING result is reused elsewhere in the program (HMI tags, logging, recipe handling).
  • The block access setting is optimized and AT views are not viable.
Edge case: If the source array contains a $00 (NUL) byte before position 7, Chars_TO_Strg treats it as the end of the string and pads the remainder with $00. The resulting STRING length is shorter than 8, and the compare will report equal with any other array that has a NUL at the same position. Use the FOR loop pattern in this case.

Method Comparison

Pattern Compiler cost Runtime cost (8 bytes, S7-1516) Optimized blocks Handles NUL bytes Best use
FOR loop with CHAR compare Low ~8 µs Yes Yes Default choice, variable-length arrays
AT view (DWORD slice) Medium ~0.2 µs Restricted* Yes Fixed 8 bytes, high-speed cyclic check
Chars_TO_Strg + STRING compare Low ~3 µs Yes Truncates on $00 ASCII fields feeding HMI / recipes

*AT views on optimized blocks are allowed only when both views target tags in the same instance DB or static VAR section.

Optimized vs Non-Optimized Block Access

The original STL pattern worked because the developer could hard-code the byte offset of each CHAR array. TIA Portal DBs created with the default Optimized block access checkbox enabled do not expose byte offsets; the compiler reserves the right to reorder fields. Once a DB is marked optimized, all access must be symbolic. STL with absolute addresses is rejected at compile time.

Switching the DB to non-optimized restores the absolute addressing model but breaks compatibility with several newer SCL features, notably the AT overlay on local statics. The compiler warning reads Use of absolute addresses is not recommended for optimized blocks.

Practical guidance:

  1. If the project is greenfield, keep both arrays in optimized blocks and use the FOR loop or the Chars_TO_Strg pattern.
  2. If the project was migrated from STEP 7 V5.x and the original DBs are non-optimized, the AT view pattern is legal and gives the fastest result.
  3. Never mix optimized and non-optimized DBs in the same comparison. A symbolic reference from a non-optimized DB to an optimized one is rejected.

S7-1200 vs S7-1500 Runtime Behavior

Both controllers execute the FOR loop, AT view, and Chars_TO_Strg patterns correctly. Two differences matter in the field:

  • Cycle time. The S7-1214 typically runs the FOR loop at 12-18 µs for 8 elements; the S7-1516 runs the same code at 0.8-1.2 µs. In a 1 ms OB this is invisible; in a 100 µs OB on an S7-1212 it can dominate the cycle budget.
  • Firmware version. Chars_TO_Strg on the S7-1200 requires firmware V4.0 or later. Earlier firmware only supports the legacy STRING_TO_xxx family. Confirm the CPU type in the device configuration before relying on the conversion.
  • Instruction availability. The AT view overlay is supported on both families, but the alignment rules are stricter on the S7-1200. A DWORD overlay on a CHAR array is always legal; a LREAL overlay on a CHAR array of 7 bytes is rejected at compile time.

Verifying the Comparison Logic

Commissioning checklist:

  1. Force "Data".ArrayA to a known pattern (e.g., 'AAAAAAAA') and "Data".ArrayB to a different pattern ('BBBBBBBB'). Confirm Differ goes TRUE in the SCL editor's online monitor.
  2. Force a single byte to differ (e.g., ArrayA[3] := 'X'). Confirm the comparison still detects the difference.
  3. Copy ArrayA to ArrayB in a watch table. Confirm Differ goes FALSE on the next cycle.
  4. Trigger the edge pulse Edge by toggling the comparison. Confirm the pulse lasts exactly one cycle in the trace.
  5. For STRING-based patterns, monitor StrA and StrB in the watch table and confirm the length byte is set to 8.
  6. Verify the OB1 cycle time has not increased by more than 5% after enabling the compare. A 100% increase signals the FOR loop is the dominant cost and the unrolled or AT-view variant should be considered.

For automated regression, add a test FB that runs the comparison under 4-6 known input pairs and asserts the expected output. Store the results in a DB the HMI can read, so QA can replay the test after every firmware update.

Common Pitfalls and Migration Notes

  • One-based vs zero-based arrays. The loop bounds in Solution 1 assume the lower bound is 0. For Array[1..8] of Char, the loop must run i := 1 TO 8. Forgetting this is the single most common bug when porting from S7-300 to S7-1500.
  • STRING declared length. A STRING[8] tag holds 9 bytes (8 data + 1 length). If the source CHAR array is 8 bytes, the destination STRING must be at least STRING[8]; using STRING[7] truncates the last character silently.
  • Optimized block editing. Once a DB is marked optimized, the compiler may insert padding between adjacent fields. The original STL byte offsets no longer map to the new offsets, even though the field order looks the same in the editor.
  • Null terminator handling. The Chars_TO_Strg pattern assumes a full 8-byte payload. If the array is a C-style string with a trailing $00 at position 7, the resulting STRING length is whatever the first $00 sits at. Use the FOR loop if the payload is binary (for example, a 64-bit serial number).
  • Re-initialization on restart. Retentive CHAR arrays keep their last value on a warm restart. If the comparison is part of a startup handshake, force both arrays to a known pattern before the first compare.
  • STEP 7 V5.x migration. When importing a STEP 7 V5.x project into TIA Portal, the SCL source is ported automatically but the DB access settings default to optimized. If the original STL referenced absolute byte offsets inside the DB, those references become invalid. Re-implement the comparison in SCL as soon as the migration is complete, and audit every absolute address in the imported project.
  • Multi-instance DBs. When the two arrays live in different instance DBs of the same FB, the AT view pattern is rejected. Use the FOR loop or Chars_TO_Strg in this case.

FAQ

Why does SCL reject my STL code that loaded DWORDs and added them?

STL uses an implicit accumulator model (ACCU1, ACCU2) with absolute address syntax DBx.dbdx. SCL is a high-level typed language with no accumulators; every expression must resolve to typed symbols. Statements like L DBx.dbdx, +D, and T A are not valid SCL syntax and produce the "Invalid type of address" compiler error.

Can I compare two Array[0..7] of Char in a single line in SCL?

Only if the array is first converted to a STRING with Chars_TO_Strg. The native CHAR array compare is element-by-element through a FOR loop, or a 2-element compare using an AT view that overlays the array as ARRAY[0..1] OF DWORD. The single-line "Data".ArrayA = "Data".ArrayB is not supported for CHAR arrays.

Which method is fastest: FOR loop, AT view, or string conversion?

On the S7-1500, the AT view pattern runs in roughly 0.2 microseconds for an 8-byte array, the Chars_TO_Strg + STRING compare runs in roughly 3 microseconds, and the FOR loop runs in roughly 8 microseconds. On the S7-1200 the absolute times are 5-10x longer but the order is identical. For fixed-length arrays in a high-speed OB, use the AT view. For variable-length arrays or ASCII fields feeding HMI, use the FOR loop or Chars_TO_Strg.

Does block access optimization affect array comparison in SCL?

Yes. Optimized blocks prohibit absolute addressing and restrict where AT views can be declared. The Chars_TO_Strg and FOR loop patterns work on both optimized and non-optimized blocks. The AT view pattern works on non-optimized blocks and on optimized blocks only when both the source and the view are in the same instance DB or local static section.

How do I compare two CHAR arrays of different lengths in SCL?

Bound the FOR loop by the shorter array's length, or use Chars_TO_Strg on both with the same Count parameter equal to the shorter length. After conversion, a STRING compare handles the remainder of the longer array being padded with $00. For exact-length comparison, declare a STRING whose length equals the maximum and inspect the length byte after the call.

Back to blog