Problem Statement: WinCC Reads Characters from the Wrong Byte Offset
When configuring a WinCC text tag pointed at a Siemens S7 data block, engineers frequently observe that the visible characters begin two bytes after the configured start address. The configured pointer reads DBB2200, but the first character displayed on the HMI is the byte located at DBB2202. This is not a tag-addressing bug, a communication fault, or pointer corruption. It is the documented behavior of the S7 STRING data type, which carries a two-byte header in front of the actual character payload.
Most often this symptom appears when the user defines the WinCC tag as a generic Text / CHAR array type and then points it at a STRING variable in the PLC. The HMI driver, expecting raw CHAR bytes, simply steps past the header bytes and returns the user data, and the operator sees the string "shifted" by two characters. The correct fix is to align the WinCC tag data type with the PLC data type: declare a WinCC tag of type String and point it at the same S7 STRING variable. WinCC will then decode the two-byte header and the character payload correctly.
STRING is not a C-style null-terminated character array. The first two bytes are metadata. Reading the tag as CHAR discards that metadata and shifts the visible string by two bytes, and the header bytes themselves are usually displayed as small numeric boxes or junk characters before the real text begins.Root Cause: S7 STRING Two-Byte Header Structure
The S7 STRING data type is defined in the Siemens programming manuals for S7-300/400 and S7-1200/1500 controllers. It reserves a 2-byte header followed by a fixed-size character buffer. The header contains the maximum string length and the current (valid) string length. The character payload follows immediately after the header. The total memory footprint is therefore MaxLen + 2 bytes, where MaxLen is the maximum number of characters the string can hold (default 254, valid range 0..254).
The byte map of an S7 STRING variable located at offset N in a data block is shown below. In the example, N = 2200, MaxLen = 16, ActLen = 12, and the current value of the string is "Hello, world".
When the HMI driver is told to read a CHAR array starting at DBB2200, it copies bytes beginning at DBB2200 into the tag buffer. The first byte copied is MaxLen (16, displayed as ASCII 0x10 which is a non-printable data-link-escape character). The second byte copied is ActLen (12, displayed as ASCII 0x0C, a form feed). The byte at DBB2202 is the first character of the actual string, which is what the operator sees on the screen as the readable text. The two header bytes are non-printable and are typically rendered as small boxes, the two-byte "shift" the user perceives.
| Byte offset | Name | Type | Range | Meaning |
|---|---|---|---|---|
| DBB[N+0] | MaxLen | BYTE | 0..254 | Maximum number of characters the buffer can hold. Set at compile time in the DB declaration. |
| DBB[N+1] | ActLen | BYTE | 0..MaxLen | Current valid character count. Updated on every write by the CPU firmware. |
| DBB[N+2..N+2+MaxLen-1] | Char[i] | BYTE (CHAR) | 0x00..0xFF | Character payload. Bytes beyond ActLen are typically 0x00 but are not part of the visible string. |
The 2-byte header is the same on every S7 CPU that supports STRING: S7-300, S7-400, S7-1200, S7-1500, and the WinAC RTX soft-PLC. The header is also identical between STEP 7 V5.x and TIA Portal, so a STRING written by an S7-300 with classic STEP 7 is correctly interpreted by an S7-1500 HMI configured in TIA Portal V18.
S7 STRING vs WSTRING vs CHAR Array - Comparison
Siemens offers three string-like data types in the S7 family. Choosing the right one is essential before binding a WinCC tag.
| Property | STRING | WSTRING | CHAR / BYTE array |
|---|---|---|---|
| CPU support | S7-300, S7-400, S7-1200, S7-1500, WinAC | S7-1500, S7-1200 FW 4.4+ | All S7 CPUs |
| Header size | 2 bytes | 4 bytes (2 bytes max + 2 bytes current length) | 0 bytes |
| Encoding | 8-bit ASCII / Latin-1 | UTF-16 (UCS-2 little-endian) | Raw bytes |
| Max character count | 254 | 16382 | Unbounded (limited by DB size) |
| Total memory footprint | 2 + MaxLen bytes | 4 + 2*MaxLen bytes | MaxLen bytes |
| Null terminator | No (length is header-based) | No (length is header-based) | No (length must be tracked externally) |
| WinCC tag data type | String | WString | Text (8-bit) - read-only display |
| Standard string library | LEN, LEFT, RIGHT, MID, CONCAT, INSERT, DELETE, REPLACE, FIND, Chars_TO_STRING | WLEN, WLEFT, WRIGHT, WMID, WCONCAT, WINSERT, WDELETE, WREPLACE, WFIND | None - manual byte operations |
Use STRING for typical operator messages, recipe names, order numbers, and barcodes that fit in 254 ASCII characters. Use WSTRING for any text that must contain non-ASCII Unicode (e.g., Cyrillic, CJK, accented Western European). Use a raw CHAR / BYTE array only when the upstream protocol (Modbus, ASCII scanner) gives you bytes that you will normalize in the PLC before display.
STRING[254] in the PLC and then point a CHAR[254] tag at its base address in WinCC. The WinCC tag will show the 254 MaxLen byte (0xFE), the 254 ActLen byte (0xFE), and the first 252 characters - missing the last two characters of the actual string and starting with two non-printable numeric bytes. This is the same off-by-two symptom at a different scale.Why WinCC Treats Text Tags and String Tags Differently
WinCC (TIA Portal and the legacy WinCC V7.x / WinCC flexible 2008) supports the S7 STRING data type natively. The driver knows about the 2-byte header, reads ActLen from the second byte, and decodes only the first ActLen characters of the payload. This is true for both the integrated HMI in TIA Portal (WinCC Comfort / Advanced / Professional) and the standalone SCADA WinCC V7.x.
If, however, the tag is configured as a Text tag of length L pointed at a raw DBB address, WinCC treats it as a flat byte array. There is no header awareness. Every byte is treated as a character and the entire L bytes are displayed. This is the configuration that produces the 2-byte shift.
Per the official Siemens support FAQ "How do you display an S7 tag of the CHAR type in WinCC?", WinCC does not provide a direct CHAR-array display path for S7 PLCs. The recommended path is to use a STRING tag in WinCC that points at an S7 STRING in the PLC. The same FAQ documents the Chars_TO_STRING function block for converting legacy CHAR arrays into a STRING at the PLC side.
Prerequisites
- STEP 7 V5.5 SP4 or TIA Portal V15.1 or later installed, with an active license for the S7-300/400 or S7-1200/1500 toolchain.
- WinCC Comfort / Advanced / Professional (TIA Portal) or WinCC V7.5 SP2 / WinCC flexible 2008 SP5 on the engineering station.
- An S7-300, S7-400, S7-1200, or S7-1500 PLC with a configured Ethernet / PROFIBUS / MPI HMI connection. The HMI connection must be online and tested with the PLC (e.g., a small BOOL tag toggles correctly).
- An S7 data block (DB) containing a
STRING[n]variable. The user fills the string from the application code or from a barcode reader / Modbus gateway. - Operator authorization to compile and download both the PLC and HMI projects.
- TIA Portal Online & Diagnostics or STEP 7 V5.5 PLCSIM to verify the STRING byte values before going to runtime.
Step-by-Step: Configure a WinCC STRING Tag Against an S7 STRING Variable (TIA Portal)
-
Declare the STRING in the PLC. In TIA Portal, open the source DB and add a new tag:
VAR sOrderNumber : STRING[16]; // 2 header bytes + 16 characters of payload END_VAR -
Populate the STRING from the application code. Use standard SCL string assignment or the
CONCAT,INT_TO_STRING, orChars_TO_STRINGhelpers from the Standard Library → String + Char palette:"dbMsg".sOrderNumber := 'Order#12345'; "dbMsg".sOrderNumber := CONCAT(IN1 := 'Order#', IN2 := INT_TO_STRING(iCounter)); -
Compile and download the PLC program to the target CPU. The DB offset is reported in the project tree (e.g.,
DB100.DBX200.0 STRING[16]). On S7-1200/1500 with optimized block access, the symbol is"dbMsg".sOrderNumberand the absolute offset is informational only. -
Add an HMI tag in TIA Portal. In the HMI project, right-click HMI Tags → Default tag table → Add new tag. Set the following:
- Name:
sOrderNumber - Data type: String (TIA Portal maps the PLC STRING to a String tag automatically when the HMI connection uses S7 communication). For WSTRING, choose WString.
- Connection: the configured S7 connection to the PLC (e.g., HMI_Connection_1).
- PLC tag: browse to
dbMsg.sOrderNumberor enter the absolute addressDB100.DBB200 STRING[16](offset of the STRING in the DB plus the bracket length). - Acquisition cycle: 1 s (or matching the update rate of the upstream data source).
- Name:
- Bind the tag to an I/O field, output field, or text element on the desired screen. The displayed value will be the full ActLen-character payload, with the header bytes automatically hidden by the driver.
- Compile and download the HMI project. Trigger a value change from the PLC (e.g., toggle a BOOL that increments the order number) and confirm the HMI updates with the correct characters starting at the first payload byte.
Step-by-Step: Legacy WinCC V7 / WinCC flexible Configuration
- Open WinCC Explorer (WinCC V7.x) or WinCC flexible and select the S7 connection that links to the target PLC.
- In the tag management, edit the tag you want to display. Change the Data type from Text to String (WinCC flexible) or String with the same length as the PLC STRING (WinCC V7).
- Set the Address to the absolute PLC address:
DB100 DBB 200 STRING[16]. The address parser handles the 2-byte header automatically. If you enterDB100 DBB 200without the length specifier, the driver falls back to flat-byte mode and reproduces the original symptom. - Bind the tag to an I/O field, output field, or WinCC OnlineTrendControl text column.
- Save the project, compile the OS, and trigger a full HMI download.
Alternative: Converting an Array of CHAR / BYTE to STRING in the PLC
If the source data is a raw byte array (e.g., a Modbus register image, a barcode scanner, or a legacy data block that was migrated from a third-party controller) you can normalize it into a STRING in the PLC using the standard string library blocks. On S7-1200 / S7-1500 use Chars_TO_STRING from the Standard Library → String + Char palette:
// SCL example for S7-1500
{ S7_Optimized_Access := 'TRUE' }
FUNCTION "fbNormalizeMsg" : Void
VAR
i : INT;
sText : STRING[32];
END_VAR
BEGIN
sText := '';
FOR i := 0 TO 31 DO
// fold only printable ASCII; skip control bytes
IF "rawBytes"[i] > 16#1F AND "rawBytes"[i] < 16#7F THEN
sText := CONCAT(IN1 := sText,
IN2 := CHAR_TO_STRING(BYTE_TO_CHAR("rawBytes"[i])));
END_IF;
END_FOR;
"dbMsg".sOrderNumber := sText;
END_FUNCTION
For a direct CHAR-array to STRING copy, use the higher-level helper:
"dbMsg".sOrderNumber := Chars_TO_STRING(
chars := "dbSource".rawBytes,
count := 16);
On S7-300 / S7-400 the equivalent is the IEC standard library FC block from Standard Library → IEC Function Blocks:
CALL "TAKE_CHAR" // copy N chars from a STRING into a CHAR array
IN := "dbSource".sRawString
OUT := "dbTarget".rawBytes
LEN := 16
RET_VAL := "iRet";
CALL "STRING_TO_CHARS" // inverse: CHAR array -> STRING
IN := "dbTarget".rawBytes
OUT := "dbSource".sRawString
LEN := 16
RET_VAL := "iRet";
Whichever method is used, the HMI tag is then bound to the STRING variable in the DB, not to the original byte array. This isolates the HMI from the raw byte layout and makes the project resilient to changes in the upstream data source.
Symbolic vs Absolute Addressing on S7-1200 / S7-1500
On S7-1200 and S7-1500 with optimized block access enabled, the absolute byte offsets shown by the TIA Portal compiler are not stable. The compiler reorders tags for alignment, and the actual offsets can shift between two consecutive compilations if the DB layout changes. The recommended practice for STRING tags is to use symbolic addressing in the HMI connection:
- In the HMI tag editor, click the ... button next to the PLC tag field.
- Browse the PLC project tree, expand the DB, and select the STRING tag by name (e.g.,
"dbMsg".sOrderNumber). - The HMI connection resolves the symbol to the correct absolute offset at runtime, including the 2-byte header. The tag is not affected by reorderings of the source DB.
If symbolic addressing is unavailable (e.g., a third-party HMI that does not support the S7 symbolic protocol), use absolute addressing but with the full DB number + byte offset + STRING length specifier. The STRING length tells the driver that the 2-byte header is present, even though the address points at the start of the variable.
DB n DBB x STRING[m] notation.Verification and Acceptance Test
- Online in TIA Portal, open the source DB in the watch table / VAT. Confirm that MaxLen at
DBB[N]equals the declared STRING length and ActLen atDBB[N+1]matches the visible character count. - Display the bytes
DBB[N]..DBB[N+5]in HEX and ASCII. Verify byte 0 = MaxLen, byte 1 = ActLen, bytes 2+ = first characters of the string. - On the HMI, force the STRING to a known value (e.g.,
'TEST 12345'from the VAT) and confirm the I/O field shows exactly 10 characters starting at the first payload byte, with no leading or trailing junk. - Force ActLen to a value larger than MaxLen in the VAT (e.g., MaxLen = 16, force ActLen = 250). The CPU firmware will clip the value to MaxLen on the next write, confirming the driver is honoring the header.
- Switch the WinCC tag data type back to Text to reproduce the original 2-byte shift. Switch back to String to confirm the fix.
- Hot-restart the PLC (MRES or power cycle) and verify the STRING tag is re-initialized to the DB declaration default. A non-optimized DB may need a startup assignment to clear stale ActLen bytes.
- Run a sustained 24-hour test with the production recipe / order number to confirm the tag survives MRES, station restart, and recipe change events without corrupting the visible string.
Troubleshooting Matrix
| Symptom | Likely cause | Diagnostic | Remedy |
|---|---|---|---|
| HMI shows the string shifted by 2 characters, with leading junk | WinCC tag is Text/CHAR but PLC tag is STRING | Right-click the HMI tag → Properties → Data type | Change the HMI tag to String; the driver now strips the 2-byte header |
| HMI shows truncated string (last 2 characters missing) | PLC STRING MaxLen is N but HMI tag length is N-2 | Check both lengths | Match the HMI tag length to the PLC STRING MaxLen |
| HMI shows empty string even though PLC STRING is populated | WinCC tag address starts at DBB[N+2] instead of DBB[N]
|
Inspect the absolute address | Point the tag at the start of the STRING variable, not the character payload |
| HMI shows a single "?" for every character | PLC uses WSTRING (UTF-16) but HMI tag is configured as STRING | Check the PLC data type | Change the HMI tag to WString; WSTRING has a 4-byte header and double-byte characters |
| HMI shows 0x10 0x0C then correct characters | MaxLen=16, ActLen=16 displayed before payload because tag is Text | Switch tag type | Use String data type in WinCC |
| HMI shows scrambled characters after PLC restart | Remanent STRING not initialized; ActLen carries garbage | Force ActLen=0 at startup | Assign a default value in OB100: "dbMsg".sOrderNumber := '';
|
| HMI stops updating mid-string | Optimized block access enabled and tag is bound to an absolute address that no longer matches | Check DB attribute Optimized block access | Use the symbolic name in the HMI connection, not the absolute address |
| WinCC flexible shows correct value, WinCC Professional shows junk | Project migrated without updating tag data type | Compare tag tables | Re-run the migration wizard; manually set String for all STRING tags |
| HMI shows string with extra characters appended | ActLen byte is corrupted (e.g., uninitialized memory) | Inspect the DB in the VAT with HEX display | Re-initialize the DB or assign the STRING in OB100 to clear the header |
| STRING is correct on the HMI but reports wrong length in the recipe CSV | Recipe export uses the declared MaxLen, not the actual ActLen | Open the CSV in a text editor | Use a script in the recipe to re-initialize the STRING before save |
Edge Cases and Field-Proven Caveats
- Optimized block access on S7-1200 / S7-1500. When the DB is configured with Optimized block access, the absolute byte offsets shown by the compiler are symbolic. The S7 communication driver resolves them automatically when you select the symbolic tag in the HMI connection. Always prefer symbolic addressing for STRING tags to avoid manually offsetting the 2-byte header.
-
STRING[1]. A
STRING[1]uses 3 bytes total (2 header + 1 char). If you point aCHAR[3]tag at it you will read[MaxLen][ActLen]['X'], not the single character. WinCC configuration of a STRING[1] is supported but the field is too small to be useful for any non-trivial display. - WSTRING on S7-1500. The wide-string data type uses a 4-byte header (max char count as WORD = 2 bytes, current char count as WORD = 2 bytes) followed by up to 16382 UTF-16 characters (32764 bytes). The HMI tag must be WString; pairing it with a String tag will not work and will produce a runtime alarm on the HMI.
-
STRING parameter passing in FB/FC interfaces. When passing a STRING to a function block, the formal parameter is declared
INof typeSTRING. Inside the FB, the local copy has the same 2-byte header; do not strip it manually. TheLENfunction returns the ActLen value, not the MaxLen value. - Multi-instance DBs and STRINGs. A STRING member of an instance DB inherits the 2-byte header from the FB static section. If the FB is multi-instanced, the offsets are relative to the start of the multi-instance block. Symbolic access from the HMI side eliminates any confusion.
- Recipes. WinCC recipe elements that bind to a STRING must use the STRING data type; the recipe CSV export/import will encode the value without the header. On import, the ActLen is computed from the CSV cell length, not from any pre-allocated MaxLen value.
- Historical archiving. Tag logging of a STRING tag works in WinCC Professional; on smaller Comfort panels the STRING length that can be archived is limited by the panel's project memory. For long strings, use the WString data type or split the message into segments.
- STRING in alarms. Alarm texts in WinCC Professional support STRING tags as text parameters, but the alarm message buffer is 32 bytes. If the STRING exceeds 32 bytes, only the first 32 characters are displayed in the alarm view.
- String truncation safety. The S7 CPU firmware guarantees that a write to a STRING will never overflow the MaxLen boundary. The driver cannot write more characters than MaxLen. If your upstream code tries to assign a longer string, the assignment is silently truncated.
- Endianness of the header. The MaxLen and ActLen bytes are single bytes, so endianness is not a concern. The 4-byte WSTRING header is little-endian on all S7-1500 CPUs.
-
String in arrays. An
ARRAY[0..9] OF STRING[20]occupies10 * (2+20) = 220bytes in the DB. HMI tags that bind to individual elements must use absolute offsetDBB[N + i * 22]or symbolic access. - PUT/GET access. A PUT from a remote S7-1200/1500 to a STRING on the local PLC will write the payload bytes correctly, but the caller must also write the ActLen byte. The PUT instruction does not auto-update ActLen. Use S7 communication with full read/write semantics (PUT/GET or BSEND/BRCV) and write the header manually for safety.
Performance and Acquisition Cycle Considerations
The acquisition cycle of the WinCC tag determines how often the HMI polls the STRING from the PLC. For operator messages, a 1 s cycle is typical. For tag logging, the cycle can be reduced to 100 ms if the string changes rapidly (e.g., a moving barcode). The 2-byte header adds a fixed overhead of 2 bytes per poll, negligible for typical networks. However, on a wide-area PROFIBUS network with hundreds of STRING tags, the cumulative payload can add up. Use the WinCC tag group statistics to identify the heaviest contributors and consider whether the string length is appropriate for the application.
For very long strings (close to 254 chars), consider compressing the data in the PLC and decompressing on the HMI side using a script. The compression ratio for typical operator messages is around 30 %, which can reduce the per-poll payload by 70 bytes and free bandwidth for other tags.
Security Considerations
STRING tags displayed on the HMI are operator-readable but not operator-writable by default. If the tag is used as an input field (e.g., a recipe parameter), restrict write access to authorized user groups in the WinCC user administration. A malicious operator with write access can inject control characters (0x00..0x1F) into the STRING, which can break downstream protocols that expect pure ASCII. Strip control characters in the PLC before the STRING is consumed by the next stage:
// Strip control characters on S7-1500
FOR i := 1 TO LEN("dbMsg".sOrderNumber) DO
IF MID("dbMsg".sOrderNumber, i, 1) < CHAR#16#20 THEN
"dbMsg".sOrderNumber := REPLACE(IN1 := "dbMsg".sOrderNumber,
IN2 := '',
L := 1,
P := i);
i := i - 1;
END_IF;
END_FOR;
FAQ
Why does my WinCC text tag read DBB2202 when I configured DBB2200?
The first two bytes of an S7 STRING are header bytes (MaxLen, ActLen). A WinCC Text tag treats the address as a raw byte array and copies every byte, including the header. The first character of the string sits at DBB[N+2], so what you see is "off by two." Switch the WinCC tag data type to String and the driver will hide the header automatically.
How large is the S7 STRING header in S7-300, S7-400, S7-1200, and S7-1500?
The header is always 2 bytes: the maximum length (1 byte) and the current length (1 byte). On S7-1500 the WSTRING header is 4 bytes. See Table 2 for the full limits and the standard string library functions.
Can I read a CHAR array from the PLC and display it directly on WinCC?
WinCC does not provide a direct CHAR-array display path for S7 PLCs. Per Siemens FAQ 22015649, convert the CHAR array to a STRING inside the PLC using Chars_TO_STRING (S7-1200/1500) or the IEC standard FC blocks (S7-300/400), then bind the WinCC tag to that STRING variable.
What happens if I bind a WinCC String tag to a WSTRING in the PLC?
The driver will read only the first 2 bytes of the 4-byte WSTRING header and treat the remaining bytes as characters. The visible string will be wrong and a runtime alarm will be raised. Always match the HMI data type to the PLC data type: String for STRING, WString for WSTRING.
Why does my HMI show an empty string after a CPU restart even though the DB is non-remanent?
STRING tags in non-optimized DBs are not automatically re-initialized by the firmware. The current-length byte may carry a stale value from the previous cycle. Force an assignment in OB100 / the startup OB: "dbMsg".sOrderNumber := '';. For optimized DBs on S7-1200/1500, the STRING is reset to the declared default automatically.
Is there a TIA Portal function block that converts a CHAR array to STRING?
Yes. Chars_TO_STRING from the Standard Library → String + Char palette takes an array of CHAR (or BYTE) and a count, and produces a STRING with the correct 2-byte header. The companion block String_TO_Chars does the inverse. Both are documented in the S7-1200/1500 programming manual available from the Siemens Industry Online Support.