S7 STRING Comparison: Fixing FC10 EQ_STRNG with Excel Data

David Krause11 min read
S7-300SiemensTroubleshooting
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

1. Problem Overview

The classic Siemens S7 STRING comparison failure occurs when a STRING tag populated from an external source — most often an Excel cell read through WinCC Flexible VBScript — never returns a TRUE result at FC10 EQ_STRNG even though the visible ASCII characters are identical. The OUT pin stays FALSE while a parallel FC10 call fed with a hard-coded STRING constant works correctly. The fault is not in FC10, not in the comparator logic, and not in the ASCII payload; it is a structural mismatch of the S7 STRING container.

S7 STRING is not a NUL-terminated C string, not a Pascal short string, and not a .NET System.String. It is a fixed-format container whose first two bytes are metadata. If the metadata is wrong, any equality test that honours those metadata bytes (which is the correct behaviour of FC10) will report inequality, regardless of the visible content. Excel and WinCC Flexible VBScript only deliver raw Unicode or ANSI character sequences; they do not prefix the payload with the S7 header.

Field-proven symptom: FC10 returns FALSE on every call, the operator display in WinCC Flexible shows the text identically, and any locally written constant of the same value passes the test. The two numbers at the beginning of the S7 STRING tag, when monitored in STEP 7 in hex, read 00 00 instead of the expected FE nn (decimal 254 and the actual character count).

2. S7 STRING Memory Layout

The S7-300/S7-400 STRING data type occupies 2 + n bytes where n is the declared maximum length. For S7-1200/1500 the upper bound is 255 characters; for S7-300/400 it is 254 characters per STRING declaration. The layout is strictly positional:

Byte Offset Content Typical Hex Value Purpose
0 Maximum length FE (254) Declared upper bound of the string. Set at declaration time, not at runtime.
1 Actual length 00FE Number of valid ASCII characters currently stored starting at byte 2.
2 … 2+n-1 ASCII characters 207E Character payload. Unused trailing bytes are undefined; FC10 only compares up to the actual-length count.

For STRING[n] where n ≤ 254, byte 0 is the literal value n, not FE. Many programmers always store 254 in byte 0 because STEP 7 defaults STRING declarations to STRING[254]; but if the declaration is STRING[20], the maximum-length byte must read 14 (hex), not FE. Using the wrong maximum-length byte can produce a valid equality test for short strings but will truncate or break on the next string that exceeds the declared maximum.

ASCII printable range: Valid S7 STRING payload bytes are 0x20 to 0x7E for displayable characters and 0x00 to 0x1F for control codes. The "square" or "tofu" glyph that WinCC Flexible editors and the STEP 7 variable table display for byte 0 and byte 1 is the editor's substitute character because 0xFE and 0x00 to 0xFE are outside the printable ASCII window.

3. FC10 EQ_STRNG Reference

FC10 EQ_STRNG is part of the IEC standard library (also exposed as the renamed block EQ_STRING in some STEP 7 versions). It performs a byte-accurate comparison of two STRING variables and is the canonical function for S7-300/400 string equality.

Parameter Declaration Data Type Description
S1 INPUT STRING First string. Passed by ANY pointer; underlying storage is read through the ANY descriptor.
S2 INPUT STRING Second string. Same ANY-pointer passing convention.
RET_VAL OUTPUT BOOL TRUE if and only if the actual-length byte AND all character bytes match.

The comparison algorithm inside FC10 is, in pseudocode:

len1 = byte_at(S1, 1)
len2 = byte_at(S2, 1)
IF len1 != len2 THEN RET_VAL = FALSE
ELSE compare byte_at(S1, 2..2+len1-1) with byte_at(S2, 2..2+len2-1)
     IF all equal THEN RET_VAL = TRUE ELSE RET_VAL = FALSE

Because the actual-length byte is the first gate, two strings with identical visible content but different length bytes are not equal. This is the precise root cause of the Excel-import failure: the imported tag carries length byte 0x00 because VBScript never wrote anything to that location.

4. Root Cause Analysis

Trace the data path from Excel to FC10 to localise the failure:

  1. Excel cell: Contains ANSI or Unicode text such as MOTOR_OK. No S7 header.
  2. WinCC Flexible VBScript: Reads the cell with Cell.Value and assigns to an HMI tag. The tag has STRING[254] type, but the VBScript writes only the character payload starting at byte 2 of the PLC mirror; byte 0 and byte 1 of the tag mirror are left at their initialised state — typically 00 00.
  3. PLC tag buffer: Receives the character bytes but with actual-length byte = 0.
  4. FC10 comparison: Compares two strings, one with actual-length = 0 and one with actual-length = 8. Result is FALSE.

The square-symbol characters that appear at the start of the tag in WinCC Flexible are the editor's attempt to render the header bytes as glyphs. Two squares followed by the ASCII payload are the visual signature of a correct S7 STRING; one square plus payload plus a second square (where the operator placed a constant by hand-copy) signals a corrupted header. Always inspect the tag in HEX using the STEP 7 variable table or a VAT online view; the printed character view hides the problem.

5. Solution 1 — Inject Header Bytes via STL in the PLC

The cleanest engineering solution is to let VBScript write the character payload to a raw byte block, then run a small STL routine on each cycle or on a change-of-state trigger to populate the S7 STRING header from the received length. This isolates the WINCC side from the S7 metadata format.

Assume DB100 contains a STRING[254] at byte offset 0.0 and that VBScript populates an ARRAY[0..253] OF BYTE at DB100.DBX2.0 with the ASCII payload. The length is implicit in the array write; the PLC counts it.

// STL fragment to normalise STRING header after VBScript write
// Inputs:  DB100.DBX0.0  STRING[254] target
//          DB100.DBX2.0  ARRAY[0..253] OF BYTE payload buffer
// Local:   AR1 pointer to target header
//          #iLen          scanned length

      L     P##DB100.strTarget    // load ANY pointer of STRING
      LAR1                          // AR1 = address of max-length byte
      L     B [AR1, P#1.0]         // load actual-length byte (still 0)
      L     0
      ==I
      JC    END                     // skip if no data yet

      L     0
      T     #iLen                   // iLen = 0
NEXT: L     #iLen
      L     254
      >=I
      JC    WRITELEN                // cap at 254
      L     DB [AR1, P#2.0]         // load payload character at offset (iLen + 2)
      L     0
      ==I
      JC    WRITELEN                // NUL terminator found -> done
      L     #iLen
      +     1
      T     #iLen
      JU    NEXT

WRITELEN:
      L     #iLen
      T     B [AR1, P#1.0]          // write actual-length byte
      L     254
      T     B [AR1, P#0.0]          // write maximum-length byte (constant 254)
END:  NOP   0

This routine walks the payload bytes from offset 2 onward until it hits either the declared maximum (254) or a 0x00 terminator, then writes the count back to byte 1. It assumes the VBScript pads the remainder of the array with 0x00, which is the natural state of a fresh data block on a cold restart.

6. Solution 2 — Construct the S7 STRING Inside VBScript

For small strings (≤ 254 characters) the VBScript itself can prepend the two header bytes and write the full 256-byte block to the tag. WinCC Flexible VBScript supports raw byte assignment via HMIRuntime.Tags when the tag is declared as a raw array; if the tag is a STRING, write to the underlying buffer tag instead.

' WinCC Flexible VBScript — write S7 STRING header + payload
Dim sText, iLen, sRaw, iByte
sText = SmartTags("ExcelCell").Value     ' read raw cell content
iLen  = Len(sText)
If iLen > 254 Then iLen = 254

' Build 256-byte buffer: byte0 = 254 (max), byte1 = iLen, bytes 2.. = ASCII
ReDim arrBytes(255)
arrBytes(0) = 254                        ' maximum length
arrBytes(1) = CByte(iLen)                ' actual length
For iByte = 1 To iLen
    arrBytes(iByte + 1) = Asc(Mid(sText, iByte, 1))
Next
' Pad remaining bytes with 0
For iByte = iLen + 2 To 255
    arrBytes(iByte) = 0
Next

' Write to a RAW BYTE tag that mirrors the STRING area of DB100
SmartTags("DB100_StrRaw").Value = arrBytes

Declare the receiving tag in WinCC Flexible as a raw byte array of length 256 that maps onto the same DB offset as the STRING tag. The PLC sees a fully-formed STRING on the next read cycle and FC10 returns the correct result.

7. Solution 3 — Replace FC10 with a Manual Compare on the Payload

If changing the import path is not feasible, replace FC10 with a manual byte compare that ignores byte 0 (max length) and uses an externally computed length. This is a workaround, not a fix — the STRING tag itself remains malformed and other functions such as FC11 NE_STRNG, FC26 LEN_STRNG, and any operator-panel display call will still misbehave.

// STL — manual equality ignoring max-length byte, comparing payload bytes
// only when lengths match
      L     B [AR1, P#1.0]        // length of S1
      L     B [AR2, P#1.0]        // length of S2
      <>I
      JC    NOTEQUAL
      T     #iLen                  // shared length
LOOP: L     #iLen
      L     0
      <=I
      JC    EQUAL                  // lengths matched AND loop exited clean
      L     #iLen
      +     1
      T     #iLen
      L     B [AR1, P#1.0]         // compare payload
      L     B [AR2, P#1.0]
      <>I
      JC    NOTEQUAL
      JU    LOOP
EQUAL:  SET
      SAVE
      CLR
      SAVE
NOTEQUAL: ...

Better engineering practice is to keep FC10 and fix the tag.

8. Verification Procedure

  1. Open the STEP 7 variable table, add the STRING tag, enable Display format → Hex, and trigger a single transfer from VBScript.
  2. Confirm byte 0 = FE (or the declared maximum) and byte 1 = the ASCII character count of the payload.
  3. Force FC10 with the imported tag and a hand-typed STRING constant of identical visible content. Observe RET_VAL = TRUE.
  4. Modify the Excel cell to a value longer than 254 characters and confirm that FC10 still behaves correctly because the truncation logic clamps the actual-length byte to 254.
  5. Trigger a WinCC Flexible restart, observe that the STRING header does not survive because the cell is re-read on cold start. This is desirable for deterministic behaviour; if persistence is required, write the header bytes in OB100 once and have VBScript skip byte 0/byte 1.

9. Edge Cases and Field-Proven Caveats

Scenario Symptom Resolution
Excel cell contains non-ASCII (e.g. Cyrillic, CJK) VBScript uses Unicode, byte count diverges from character count. Convert to ANSI before assigning to the tag; or migrate to WSTRING on S7-1500.
String declaration is STRING[20], but max-length byte was set to 254 FC10 still works for short strings but LEN_STRNG and concatenation blocks overflow the buffer. Write the declared maximum from the data block information, not a hard-coded 254.
VBScript runs while HMI is in change-of-state, tag update races the PLC scan Length byte reads as the previous cycle's value. Move the header-injection routine into the same OB that reads the tag, or use a request/ack handshake.
S7-1200/1500 STRING vs legacy STRING S7-1500 STRING has byte 0 = 254 for STRING[254] but the STRING data type semantics differ slightly. Use S7-String (WSTRING) or verify against the TIA Portal help for the target CPU firmware.
Operator edits the tag value from WinCC Flexible online Operator input via the panel writes the header bytes correctly; only scripted writes fail. Standardise all STRING writes through a single FB so header maintenance is centralised.

10. Alternative Comparison Functions in the IEC Library

Beyond FC10 EQ_STRNG, the standard library exposes:

Block Function Behaviour
FC11 NE_STRNG TRUE if S1 ≠ S2, identical byte-level rules.
FC9 EQ_CHAR Compares single CHARACTER values.
FC26 LEN_STRNG Returns actual length as INT.
FC30 MAX_LEN Returns declared max length of a STRING input.
FC31 RIGHT, FC32 LEFT, FC33 MID Sub-string extraction; all rely on actual-length byte being correct.

Any function that consults the actual-length byte will misbehave when the byte is 0. Fix the header once at the data ingress and every downstream block is automatically corrected.

11. Quick Diagnostic Checklist

  • Open the STRING tag in HEX view. Byte 0 = FE? Byte 1 = character count?
  • If byte 1 = 0, the upstream writer is not setting the length. Identify whether the writer is VBScript, an OPC client, or a partner CPU.
  • Test with a manually typed STRING constant. If FC10 returns TRUE for the constant, the comparator and the constant path are healthy.
  • Move the imported data through a transient ARRAY OF BYTE, scan the array, and write the length byte from the PLC side. This is the most robust pattern in production systems.
  • For multi-language strings, switch to WSTRING on S7-1500 or pre-encode to ANSI on the HMI side.

12. FAQ

Why does my S7 STRING show two square symbols at the start in WinCC Flexible?

The two squares are the editor's substitute glyph for the maximum-length byte (byte 0, typically 0xFE) and the actual-length byte (byte 1). They are control values, not printable characters. To see the real values, open the tag in STEP 7 with the display format set to Hex.

FC10 returns FALSE even though both strings look identical on the panel — what is wrong?

The actual-length byte of one of the strings is 0 or wrong. FC10 EQ_STRNG compares the actual-length byte first; if it differs, the function returns FALSE without examining the payload. Re-write byte 1 with the true character count of the payload.

How do I count the string length in STL without LEN_STRNG?

Use a loop starting at offset 2 of the STRING, increment a counter until you read 0x00 or reach the maximum-length byte, then write the counter back to byte 1. The snippet in Section 5 shows a complete implementation.

Can I use FC10 with strings declared as STRING[20]?

Yes. The maximum-length byte (byte 0) must equal the declared maximum — 20 (0x14) for STRING[20], not 254. FC10 honours this and will refuse to compare strings whose actual length exceeds the declared maximum of the destination.

Does this apply to S7-1200 and S7-1500 as well?

S7-1200/1500 use the same STRING layout. For multi-byte character sets, switch to WSTRING, which uses a 4-byte header (max length as word, actual length as word) followed by UTF-16 characters. EQ_STRNG works on STRING only; for WSTRING use the TIA Portal library equivalent or the standard "=" operator in SCL.

Back to blog