Overview
The "Illegal parameter assignment" compiler error is one of the most common stumbling blocks when developers first assign and compare STRING values inside Siemens S7-SCL functions and function blocks. The error message is misleading: the SCL editor often highlights a downstream line (for example, the line that stores the result of a comparison) while the real problem is the way an INPUT parameter of type STRING is used in an expression. The compiler refuses to treat the input parameter as a writable l-value for the comparison and aborts the compile with a generic parameter-assignment diagnostic.
This article documents the exact behavior of the SCL STRING data type, explains why the error appears, and shows the canonical workarounds accepted by TIA Portal V16, V17, V18, V19, V20, and V21. The patterns apply across the S7-1200, S7-1500, S7-300, and S7-400 CPU families and are the same in STEP 7 Classic and TIA Portal.
The STRING Data Type in S7-SCL
According to the TIA Portal V21 STRING documentation, the STRING data type stores a character string of up to 254 characters in a contiguous memory area. The full memory layout occupies 256 bytes per string regardless of the declared length:
-
Byte 0: Maximum length of the string (for
STRING[20], byte 0 = 20). - Byte 1: Actual length currently in use (length word of the current contents).
- Bytes 2 to 255: Character data, up to 254 ASCII characters.
When you declare STRING[20], you are reserving up to 20 useful characters. The compiler still reserves the full 256-byte shadow because the maximum length byte (byte 0) must be able to express 254 in a single unsigned byte. If you declare STRING without a length specifier, the default is 254 characters.
| Declaration | Useful characters | Memory reserved | Header bytes |
|---|---|---|---|
STRING |
254 | 256 bytes | 2 |
STRING[80] |
80 | 256 bytes | 2 |
STRING[20] |
20 | 256 bytes | 2 |
STRING[1] |
1 | 256 bytes | 2 |
The two header bytes matter for the error described here because they encode the data structure that SCL must update atomically. Any operation that has to read the length, modify the contents, and write the length back requires a true writeable l-value. A VAR_INPUT parameter of type STRING is, in SCL semantics, a pointer to the caller's storage — it is not a local copy that the block can treat as its own l-value for compound operations.
Problem Details: When the Error Appears
The diagnostic surfaces in two common shapes. Both produce the same root cause.
Shape 1 — Direct literal assignment to a TEMP string
FUNCTION FC100 : Void
VAR_TEMP
MyString : STRING[20];
END_VAR
MyString := 'Hello'; // <<< Compiler error: illegal parameter assignment
Counter-intuitively, even though MyString is a local VAR_TEMP block, the SCL compiler will reject direct assignment of a string literal in some firmware versions. The accepted pattern is to wrap the literal in an explicit variable or use a known-working initialization path (covered later in the Solution section).
Shape 2 — Direct comparison of an INPUT with a TEMP
FUNCTION FC100 : Void
VAR_INPUT
NewString : STRING[20];
END_VAR
VAR_TEMP
MyString : STRING[20];
END_VAR
VAR_OUTPUT
Result : BOOL;
END_VAR
MyString := 'hello';
Result := FALSE;
IF NewString = MyString THEN // <<< Error appears here in many versions
Result := TRUE;
END_IF;
;
The compiler cursor usually lands on the line that finally stores the comparison result (Result := TRUE;) or on the IF statement itself, but the real defect is the direct comparison of the input parameter NewString with the local variable MyString.
VAR_INPUT STRING parameters when this error appears.Root Cause: Why SCL Rejects the Operation
The Siemens S7-SCL Working with Strings reference document explains the rules for parameter assignment of strings. Three compiler-internal mechanisms combine to produce the error:
-
Input parameters are read-only references. A
VAR_INPUTof typeSTRINGis passed into the block as a pointer to the caller's data area. The block cannot legally write to that storage in the way that it can write to its ownVAR_TEMParea. -
String comparison is implemented as a block call. The SCL compiler emits an implicit call to a string comparison FB (for example,
EQ_STRINGor its IEC-equivalent) for every=,<>,<,>,<=, or>=operator on STRING. That call expects a copy it can hold during the compare and write back to. -
String literals are constant. A literal like
'hello'is materialized into a temporary data area in the work memory of the block. The compare FB cannot use anINPUTpointer on one side and a constant materialization on the other without an explicit copy step on theINPUTside.
The combination — read-only input, compare-as-block-call, constant literal — forces the compiler to refuse the implicit copy. Its answer is the generic "Illegal parameter assignment" diagnostic. The error is technically correct: the block is trying to use an INPUT in a context that requires a writable l-value.
Solution: The Copy-to-Temp Pattern
The accepted workaround is to copy every VAR_INPUT string into a local VAR_TEMP string before using it in any expression, comparison, or assignment. This makes the local copy a true l-value that the comparison FB can manage.
FUNCTION FC100 : Void
VAR_INPUT
NewString : STRING[20];
END_VAR
VAR_TEMP
MyString2 : STRING[20]; // working copy of the input
MyString : STRING[20]; // the literal target
END_VAR
VAR_OUTPUT
Result : BOOL;
END_VAR
MyString := 'hello';
MyString2 := NewString; // <-- copy input to local TEMP first
Result := FALSE;
IF MyString2 = MyString THEN // <-- now both sides are writable TEMP
Result := TRUE;
END_IF;
;
The intermediate MyString2 := NewString; line is the entire fix. Once both sides of the comparison are local, the SCL compiler is happy and the error disappears.
Alternative Patterns
Beyond the simple copy-to-temp, several patterns satisfy the SCL compiler for STRING manipulation. Choose based on the data flow of your function or FB.
Pattern A: InOut instead of Input
If the caller can provide a writable reference, declare the parameter as VAR_IN_OUT instead of VAR_INPUT. InOut parameters are passed by reference and SCL treats them as writable, so they can be used directly in comparison expressions.
FUNCTION FC100 : Void
VAR_IN_OUT
NewString : STRING[20];
END_VAR
VAR_TEMP
MyString : STRING[20];
END_VAR
VAR_OUTPUT
Result : BOOL;
END_VAR
MyString := 'hello';
IF NewString = MyString THEN
Result := TRUE;
ELSE
Result := FALSE;
END_IF;
;
VAR_IN_OUT requires the caller to pass a writable variable, not a literal. The caller code must change as well. This is the cleanest pattern for an FB that needs to manipulate the input string.Pattern B: Temp for the literal
When the comparison involves a string literal that the compiler rejects, place the literal in its own VAR_TEMP and assign it through a block call (EQ_STRING) explicitly. This is rarely needed in modern TIA Portal versions but is the most defensive approach for legacy STEP 7 V5.x code.
VAR_TEMP
sRef : STRING[20];
sMatch : STRING[20];
bEqual : BOOL;
END_VAR
sRef := NewString;
sMatch := 'hello';
bEqual := FALSE;
IF sRef = sMatch THEN
bEqual := TRUE;
END_IF;
Pattern C: Use the IEC string FCs explicitly
For maximum portability, call the IEC 61131-3 string function blocks directly. The SCL compiler in TIA Portal V16+ recognizes the following system FBs for STRING handling:
| System FB/FC | Function | Use case |
|---|---|---|
EQ_STRING |
Equality test | Direct = on STRING |
NE_STRING |
Inequality test | Direct <> on STRING |
GT_STRING |
Greater than | Direct > on STRING |
GE_STRING |
Greater or equal | Direct >= on STRING |
LT_STRING |
Less than | Direct < on STRING |
LE_STRING |
Less or equal | Direct <= on STRING |
LEN |
String length | Returns the actual length byte |
CONCAT |
Concatenate two strings | Combine two STRINGs |
LEFT, RIGHT, MID
|
Substring extraction | Pull a portion of a STRING |
DELETE, INSERT, REPLACE
|
Substring mutation | Modify a STRING in place |
FIND |
Substring search | Locate a pattern |
Explicit calls to these blocks always succeed, even when the SCL operator shortcut would not. They are the safest fallback when porting code between TIA Portal versions.
Common STRING Operations and Their Pitfalls
Beyond comparison, several operations have similar restrictions in SCL. Understanding them prevents the next "illegal parameter assignment" surprise.
Pitfall 1: Concatenation of more than two strings
The SCL shortcut for concatenation is CONCAT (formerly + in older STEP 7 versions). It accepts only two source strings per call. To build a longer string, chain calls or use a temporary.
VAR_TEMP
sDate : STRING[12];
sTime : STRING[10];
sDateT : STRING[24]; // intermediate
sResult : STRING[50];
END_VAR
sDate := '2024-05-21';
sTime := '14:33:07';
sDateT := CONCAT(IN1 := sDate, IN2 := ' '); // "2024-05-21 "
sResult := CONCAT(IN1 := sDateT, IN2 := sTime); // "2024-05-21 14:33:07"
A direct sResult := sDate + ' ' + sTime; may compile in newer TIA Portal versions but fails in STEP 7 V5.5 SPx. Use the explicit chained pattern for cross-version portability.
Pitfall 2: Numeric to STRING conversion
The IEC standard functions INT_TO_STRING, REAL_TO_STRING, DINT_TO_STRING, and LREAL_TO_STRING convert numeric values to STRING. In LAD/FBD they strip a leading + automatically, but in SCL some versions append a + in front of positive values because the underlying format is signed. The accepted fix is to concatenate with a leading space and trim, or use a custom conversion routine.
VAR_TEMP
iVal : INT;
sRaw : STRING[12];
sFinal : STRING[12];
END_VAR
iVal := 42;
sRaw := INT_TO_STRING(iVal); // may yield "+42" in SCL
IF LEFT(IN := sRaw, N := 1) = '+' THEN
sFinal := RIGHT(IN := sRaw, L := LEN(sRaw) - 1);
ELSE
sFinal := sRaw;
END_IF;
Pitfall 3: Assignment of one STRING to another of different declared length
SCL will accept the assignment but will silently truncate the source if it is longer than the declared length of the target. Always size the target STRING[n] to at least the longest possible source string plus a small margin for terminator handling.
VAR_TEMP
sSource : STRING[254];
sShort : STRING[20];
END_VAR
sSource := 'A long message that does not fit in twenty characters';
sShort := sSource; // compiles, truncates to 20 chars
String Comparison Best Practices
String comparison in SCL is case-sensitive, lexicographic by ASCII code, and stops at the length of the shorter string. Empty strings compare equal to empty strings. The comparison does not trim trailing whitespace, so a value with a trailing space will not equal the same value without one.
-
Always initialize the result variable. SCL does not auto-initialize
VAR_TEMP; the initial value is undefined. SetResult := FALSE;(orTRUE) before theIFblock. -
Copy INPUT strings to TEMP first. The pattern shown above is non-negotiable for
VAR_INPUTSTRING parameters. -
Use
LEFT(s, 1)orRIGHT(s, 1)only with TEMP copies. Substring FBs also require writable l-values. -
Trim before compare if input is human-typed. HMI input strings frequently carry a trailing
$00or space that will defeat an exact compare. -
Use a constant block for the comparison literal. If the literal never changes, declare it in a
CONSTsection of a global DB to keep the working memory layout predictable.
Edge Cases and Field-Proven Caveats
| Scenario | Symptom | Cause | Fix |
|---|---|---|---|
| Direct assignment of string literal to TEMP | "Illegal parameter assignment" | Compiler rule on literal-to-temp in some firmware | Use a CONST block, or assign through concatenation: s := ''; s := CONCAT(IN1 := s, IN2 := 'hello');
|
| Compare of INPUT to literal in IF | Error cursor on the line after the IF | INPUT not copyable for compare FB | Copy INPUT to TEMP first |
| Compare inside multi-instance FB | Same error in nested FB call | Multi-instance uses the same l-value rules | Apply the copy-to-temp pattern in the inner FB as well |
| Compare in optimized block with "Set ENO automatically" | Compiler accepts, runtime misbehaves | EN/ENO short-circuit interferes with the compare FB | Disable ENO auto or wrap the compare in IF ... THEN with explicit assignment |
| String literal contains a single quote | Syntax error in SCL | Single-quote is the string delimiter | Escape with $' or use CHAR(39) |
Empty string '' compared with a non-empty string |
Always returns FALSE or Result := FALSE
|
Empty string has length 0; non-empty has length > 0 | Expected; this is correct IEC behavior |
| STRING length 254 with content of 254 chars | Truncation or runtime fault | Buffer is 256 bytes; content of 254 fills bytes 2-255 exactly | Reduce content to 253 chars to leave a safety byte |
Verification Procedure
After applying the copy-to-temp pattern, verify the fix with the following procedure in TIA Portal V16 or later:
- Compile the block. In the project tree, right-click the FC/FB and select Compile > Software (rebuild all). The compile window must show 0 errors, 0 warnings related to the changed block.
-
Check the block interface. Open the block and confirm the
VAR_TEMPsection contains the working copy and that it has the same declared length as the correspondingVAR_INPUT. - Download to the PLC. Use Online > Download to device with "Download to device" set to "All" to push the rebuilt block.
-
Monitor online. Open the block in Monitor/Modify mode and force the
NewStringinput to a known value (for example,'hello'). Confirm thatResultgoesTRUEon the next cycle. -
Test the negative case. Force
NewString := 'world';and confirmResultstaysFALSE. - Test edge strings. Force an empty string, a 20-character string, and a string of length 254 to confirm the buffer never overflows.
- Test the SCL watch table. Add the block's instance DB (for an FB) or use a watch table to force values; this catches issues that offline simulation cannot reproduce, such as ENO short-circuiting.
- Run the PLC diagnostic buffer check. Open Online > Diagnostics > Diagnostics buffer and confirm no new diagnostic events appear after the test run.
Performance and Memory Notes
Each VAR_TEMP string of length 254 reserves 256 bytes on the stack. The PLC's local stack size is finite (typically 1 to 4 KB on S7-1200/1500, larger on S7-300/400). A function with many string temps can exhaust the local stack and trigger an OB121 programming error. Best practices:
- Reuse a single TEMP
sWork : STRING[254];for multiple intermediate operations rather than declaring one per step. - Use
STAT(FB static variables) for strings that must persist across cycles. - Avoid declaring
STRING[254]when the actual data is bounded — a 20-character target for a 20-character source uses the same 256 bytes but is clearer.
Related Official Documentation
The official Siemens references that govern the behavior above are:
- TIA Portal V21 STRING data type — defines the 254-character limit and 256-byte reservation.
- S7-SCL Working with Strings (Siemens Industry Online Support) — details the parameter assignment rules and the 256-byte layout.
FAQ
Why does SCL refuse MyString := 'Hello'; on a TEMP variable?
It depends on the TIA Portal version. In some firmware versions the SCL compiler treats a string literal as a constant that must be copied into a writable l-value, and it may emit "Illegal parameter assignment" when it cannot guarantee the copy. The accepted fix is to copy the literal in two steps using CONCAT, to declare the literal in a CONST DB, or to use an FB instance with the literal as the initial value.
Does the workaround apply to WString (Unicode) as well?
Yes. WString is passed and compared by the same l-value rules, with a 512-byte buffer for up to 254 wide characters. Use a local VAR_TEMP wsWork : WSTRING[254];, copy the VAR_INPUT into it, and only then perform the comparison or substring operation.
Can I avoid the extra copy by using VAR_INOUT?
Yes. VAR_IN_OUT is passed by reference and SCL treats it as writable, so direct comparison works. The trade-off is that the caller must pass a real variable (not a literal), and any in-block modification writes back to the caller's storage.
Is the comparison case-sensitive?
Yes. SCL string comparison uses byte-wise ASCII ordering. 'Hello' and 'hello' compare as not equal. To do a case-insensitive compare, convert both sides to upper or lower case with a custom routine, or use the IEC function UPPER_STRING / LOWER_STRING if your TIA Portal version includes them.
Why does the error cursor point to a different line than the actual problem?
The SCL compiler emits the diagnostic for the operation that required the writeable l-value, which may be one or two statements downstream of the original cause. When "Illegal parameter assignment" appears, scan upward for any VAR_INPUT STRING used in an IF, :=, or substring expression, and apply the copy-to-temp pattern there.