Resolving Unwanted Symbol Prefix in S7-1200 TSEND Telegrams
The TIA Portal TSEND, TRCV, and TCON instructions on the SIMATIC S7-1200 are the canonical way to exchange framed telegrams over ISO-on-TCP (RFC 1006), TCP, and UDP. When a scanner, scale, vision sensor, or other serial-over-Ethernet device ignores incoming requests, the most common root cause is an invisible byte (typically STX = 0x02, NUL = 0x00, or a stray fill byte from the String data type) that the controller prepends to the user payload. This reference explains the root cause, the buffer-type change that eliminates the artifact, and the verification procedure.
1. Problem Description
An S7-1200 firmware V4.x CPU is configured with a TCON connection (ISO-on-TCP, connection ID 1) to a third-party laser scanner. The user program writes the request string into a data block, calls TSEND with LEN = 0, and observes the bytes reaching the scanner with a Hercules terminal in TCP server mode. The first byte on the wire is not what was loaded into the data block; it is either:
- An
STX(0x02) that the PLC apparently inserts automatically. - A
NUL(0x00) immediately followed by the first user character. - The high byte of the first String character (a wide-character
WSTRINGresidue) doubled to the front of the buffer.
The scanner's protocol parser rejects the framed request, returns no reply, and TRCV stays in BUSY with status 16#7002. TSEND reports DONE = FALSE, ERROR = FALSE, status 16#7000 (job in progress).
2. Root Cause: Optimized Block Access and String Packing
The Siemens S7-1200 System Manual states that data blocks created with the default "Optimized" attribute store each tag at a byte-aligned offset chosen by the compiler. When the user loads the request into a tag of type STRING or WSTRING, the runtime view exposed through P#DBxx.DBX0.0 BYTES n includes the two-byte length prefix that precedes the actual character payload.
| Offset in DB | WSTRING[20] Memory Layout | STRING[20] Memory Layout |
|---|---|---|
| +0.0 | MaxLen (WORD) = 20 | MaxLen (BYTE) = 20 |
| +2.0 | CurLen (WORD) = actual char count | CurLen (BYTE) = actual char count |
| +4.0 | Char 1 (WORD, 2 bytes) | Char 1 (BYTE) |
| +6.0 | Char 2 (WORD, 2 bytes) | Char 2 (BYTE) |
| ... | ... | ... |
When TSEND is fed a P# pointer or ANY pointer whose source byte count includes these prefix bytes, the first two bytes transmitted on the wire are 0x14 0x00 (the maximum length) instead of the expected user data. The scanner's read() syscall interprets those bytes as garbage and either discards the packet or sits waiting for a valid header.
A second contributor is the LEN = 0 special case. According to the TIA Portal online help for the TSEND instruction, when LEN = 0 the instruction transmits the entire declared length of the source area (SRCBLK). For an optimized STRING[20] tag this includes the administrative header, not just the meaningful payload. The fix is to either pass a precise LEN matching the actual character count or to switch the buffer to ARRAY OF BYTE / ARRAY OF CHAR which has no header.
3. The TCON / TSEND / TRCV Instruction Set
All three instructions live in the "Communication" group of the TIA Portal program elements and target the S7-1200 / S7-1500 CPU firmware V4.0 and higher. The connection itself must be configured separately via TCON which loads the connection description (IP, port, protocol, ID) from a dedicated TCON_IP_RFC or TCON_IP_V4 data block tag.
| Instruction | Function | Key Inputs | Status Output |
|---|---|---|---|
| TCON | Establish / re-establish connection | REQ, CONNECT, ID | DONE, BUSY, ERROR, STATUS |
| TSEND | Send data over established connection | REQ, ID, LEN, DATA | DONE, BUSY, ERROR, STATUS |
| TRCV | Receive data over established connection | EN_R, ID, LEN, DATA | NDR, BUSY, ERROR, STATUS, RCVD_LEN |
| TDISCON | Tear down connection | REQ, ID | DONE, BUSY, ERROR, STATUS |
The DATA parameter on TSEND accepts any of the following forms when symbolic addressing is used:
-
%DBxx.DBw0orP#DBxx.DBX0.0 BYTE 20— absolute, byte-granular pointer. - Symbolic tag of type
STRING,WSTRING,ARRAY OF BYTE, orARRAY OF CHAR.
4. Status Code Reference
The hexadecimal status codes returned on the STATUS pin of TSEND and TRCV map directly to the Open Modbus / TCP stack shared by the SIMATIC communication blocks. The most frequently observed values during telegram debugging are:
| STATUS (hex) | Meaning | Recommended Action |
|---|---|---|
| 0x0000 | No job active; instruction idle. | No action. |
| 0x7000 | Job started, processing in progress (TSEND / TCON busy). | Wait; do not retrigger REQ until BUSY clears. |
| 0x7001 | Job started, processing in progress (TRCV). | Wait for NDR or ERROR. |
| 0x7002 | TRCV: connection established, waiting for data. | Normal idle state after TRCV_EN_R = TRUE. |
| 0x8085 | LEN parameter invalid (zero, negative, or larger than source area). | Correct LEN or use LEN=0 only with full-length area. |
| 0x80A1 | Connection not established (TCON never ran or failed). | Re-run TCON; inspect CONNECT data block. |
| 0x80C3 | Connection terminated by remote partner. | Re-establish with TCON after delay. |
| 0x80C4 | Connection lost (cable, partner down). | Check physical layer; enable keep-alive. |
5. Buffer Data Type Selection
The choice of buffer data type has the largest single impact on whether the wire payload matches the protocol specification.
| Buffer Type | Overhead Bytes | Character Size | Recommended for |
|---|---|---|---|
| STRING[n] | 2 (MaxLen + CurLen) | 1 byte / char (ASCII / Latin-1) | Plain ASCII, no binary, length always < 256. |
| WSTRING[n] | 4 (MaxLen-WORD + CurLen-WORD) | 2 bytes / char (UTF-16) | Unicode strings, Asian characters. |
| ARRAY[0..n] OF BYTE | 0 | 1 byte / element | Binary protocols, scanners, sensors, Modbus TCP PDUs. |
| ARRAY[0..n] OF CHAR | 0 | 1 byte / char | ASCII strings with embedded NUL or control codes. |
For a laser scanner exchanging binary requests such as 02 04 00 01 00 02 ... the only correct choice is ARRAY OF BYTE. The byte sequence can be assembled byte by byte using hexadecimal literals:
// Data block "CommBuf" (optimized, retain=no)
DATA_BLOCK CommBuf
STRUCT
TxBuf : ARRAY[0..63] OF BYTE; // 64-byte transmit buffer
RxBuf : ARRAY[0..127] OF BYTE; // 128-byte receive buffer
TxLen : INT; // actual payload length
END_STRUCT;
END_DATA_BLOCK
Loading the buffer in SCL:
// Example: build a 6-byte request to the scanner
CommBuf.TxBuf[0] := 16#02; // STX
CommBuf.TxBuf[1] := 16#04; // Command: read measurement
CommBuf.TxBuf[2] := 16#00; // High byte of register address
CommBuf.TxBuf[3] := 16#01; // Low byte of register address
CommBuf.TxBuf[4] := 16#00; // High byte of word count
CommBuf.TxBuf[5] := 16#02; // Low byte of word count
CommBuf.TxLen := 6;
// Trigger send
TSEND_Instance.REQ := TRUE;
TSEND_Instance.ID := 1; // TCON connection ID
TSEND_Instance.LEN := CommBuf.TxLen;
TSEND_Instance.DATA := CommBuf.TxBuf;
TSEND_Instance();
6. The LEN Parameter in Detail
The behavior of LEN on TSEND follows the rules below:
- If
LEN > 0, exactlyLENbytes are transmitted starting at theDATApointer. - If
LEN = 0, the entire effective length of the source area is transmitted. For aSTRINGtag this is the actual character count (header bytes are skipped because the symbolic pointer already points at the character payload). - If
LEN = 0on an absolute pointer such asP#DB10.DBX0.0 BYTE 64, all 64 bytes — including any unmodified area beyondTxLen— are transmitted.
Always set LEN explicitly to the actual payload length. Leaving it at zero with an ARRAY OF BYTE that has not been fully populated produces a frame padded with the array's initial value (usually zero) and may extend the telegram past the scanner's expected length.
7. Step-by-Step Conversion Procedure
7.1 Prerequisites
- STEP 7 / TIA Portal V16 or later (any version V14 SP1+ supports the modern instruction set).
- S7-1200 CPU firmware V4.2 or later (CPU 1211C / 1212C / 1214C / 1215C / 1217C supported; firmware V4.0 limited to ISO-on-TCP only).
- Hercules SETUP utility (manufacturer page) for capturing the bytes on a laptop on the same subnet.
- A separately configured
TCON_IP_V4/TCON_IP_RFCDB tag with active connection ID.
7.2 Conversion Steps
- Open the project in TIA Portal and navigate to the program block that contains
TSEND. - Right-click the existing data block that holds the request string and select "Add new tag". Create
TxBufasArray[0..63] of ByteandTxLenasInt. - Delete or comment out the old
STRINGtag that was previously used as theDATAsource. - Replace the string-concatenation logic with explicit byte assignments as shown in section 5. If the request is built dynamically, use a
CASEladder on the message ID and assign each byte individually. - On the
TSENDinstance, changeDATAto the symbolic name of the newTxBuftag and setLENto theTxLenvariable. - Compile (Ctrl+B) and download to the CPU. The project should compile without "Incompatible data types" warnings.
7.3 Verification
- Start Hercules in TCP Server mode on port 2111 (or whichever port matches the scanner's native port). Enable the "Display incoming data as hex" option.
- Trigger
TSENDfrom the watch table by forcingTSEND_Instance.REQ := TRUE. - Confirm the first byte arriving on Hercules is
02(STX) for a scanner protocol that uses STX framing, or matches the first byte of the manually assembled payload. - Verify the byte count matches
CommBuf.TxLen. If Hercules shows extra trailing zeros, the LEN parameter is too high. - Inspect the
STATUSoutput ofTSENDin the watch table. After completion it should display16#0000for one scan before reverting to16#7000on the next call.
8. Common Telegram Framing Mistakes
| Symptom | Likely Cause | Correction |
|---|---|---|
Extra 0x14 0x00 at start of every frame. |
WSTRING length header leaked into buffer. | Switch to ARRAY OF BYTE; pass pointer past the length header. |
| Trailing zero padding on every frame. | LEN = 0 on absolute pointer to ARRAY OF BYTE. | Set LEN = exact payload length. |
| Byte-swapped characters (e.g. 'A' becomes '0x41 0x00'). | WSTRING used where STRING expected. | Use STRING or ARRAY OF CHAR for ASCII-only protocols. |
| Frame accepted but CRC mismatch. | CRC calculated on string header bytes that were transmitted. | Recalculate CRC on the same buffer passed to TSEND. |
| TRCV reports 0x80C4 after a few seconds. | Scanner closing idle connections. | Implement application-layer keep-alive or re-arm TCON on TDISCON. |
| Bytes swapped in pairs. | Endian mismatch between PLC and scanner (big-endian device). | Use SWAP on WORD/DWORD fields or build payload in network order. |
9. Diagnostics with Watch Tables
Open an online watch table against the running CPU and add the following tags:
// Watch table "TSEND_Diag"
TSEND_Instance.REQ // trigger
TSEND_Instance.BUSY // job running
TSEND_Instance.DONE // success latched
TSEND_Instance.ERROR // error latched
TSEND_Instance.STATUS // hex status
TSEND_Instance.LEN // bytes transmitted
CommBuf.TxBuf[0..15] // raw bytes, hex display
CommBuf.TxLen
To watch the bytes in hex format, right-click the array row, select "Modify", and choose "Hexadecimal". Monitoring the array row online forces the CPU to publish the live buffer contents every cycle.
10. Alternative: Block Move Instead of Array of BYTE
If the application logic already populates a STRING tag, the simplest fix without restructuring the buffer is to point TSEND.DATA at the character payload past the length header. With the data block "Comm" containing ReqString : STRING[40] at offset 0, the character payload begins at offset 2:
TSEND_Instance.DATA := P#"Comm".ReqString[2] BYTE 38;
TSEND_Instance.LEN := "Comm".ReqString.LEN; // S7-1200 STRING.LEN system attribute
This approach is useful when migrating an existing program that already uses STRING, but for new code the ARRAY OF BYTE pattern is preferred because it has no header and supports binary payloads cleanly.
11. Safety Considerations
When the S7-1200 controls a safety-related process, communication to a laser scanner used for hazardous-area perimeter monitoring must be classified according to IEC 61508 / IEC 62061. The standard TCP/ISO-on-TCP stack on the S7-1200 CPU does not satisfy SIL 2 or SIL 3 by itself; a separate safety bus (PROFIsafe over PROFINET) is required. The techniques in this document apply to non-safety scanner diagnostic data only.
12. Field-Commissioning Checklist
- Compile project with "All warnings as errors" enabled; resolve every warning before download.
- Verify the
TCONdata block'sActiveEstablishedflag matches the scanner's mode (client vs. server). - Capture a Hercules trace of the first 100 frames to confirm no extra prefix bytes.
- Disable the PLC's NTP synchronization before capturing to avoid clock drift in logs.
- Document the buffer layout, byte order, and CRC polynomial in the project functional specification.
- Add a
TRCVtimeout (e.g., 5 s) that resets theTSENDstate machine if no reply arrives.
Frequently Asked Questions
Why does the first byte on the wire differ from what I wrote into the DB?
You are most likely sending a STRING or WSTRING tag whose two-byte or four-byte length header is included in the transmission. Switch the buffer to ARRAY OF BYTE or pass the pointer past the length field at offset +2 for STRING or +4 for WSTRING.
What does status 16#7000 mean on TSEND?
It means the job is currently executing; BUSY is TRUE. Wait for DONE or ERROR to latch and then read the next STATUS value. The TSEND instruction must not be re-triggered with REQ = TRUE while BUSY is high.
Is LEN = 0 safe to use on TSEND?
Only when the source area is a symbolic STRING/WSTRING tag and you intend to send the entire current contents. For ARRAY OF BYTE with absolute pointers, LEN = 0 transmits the full declared array length, which usually adds unwanted trailing zeros.
Why is TRCV stuck on 16#7002?
Status 0x7002 is the normal idle state for TRCV after a connection has been established but before any data has arrived. Confirm the connection is open (TCON DONE = TRUE, STATUS 0x0000) and that the scanner is actually transmitting. Use Hercules to verify traffic in both directions.
Can I use WSTRING with TSEND?
Yes, but every character occupies two bytes and the WSTRING header is four bytes. If the remote device expects single-byte ASCII characters, every other byte will be 0x00 and the message will be garbled. Use STRING (single-byte) or ARRAY OF CHAR for ASCII protocols.