Problem: TCP_Recv Returns One Character When Sending Multi-Byte Strings
On a Siemens SIMATIC S7-200 SMART CPU (CPU SR20, SR30, SR40, SR60, ST20, ST30, ST40, ST60, CR40, or CR60), the Open User Communication (OUC) library blocks TCP_Connect, TCP_Send, and TCP_Recv are configured to communicate with a Windows terminal emulator. The TCP connection establishes successfully, the handshake completes (Status = 0x0002), and the Done bit pulses on the receive block. The defect appears as follows:
| PC send (HyperTerminal) | PLC VB buffer contents | Observed result |
|---|---|---|
1 |
VB100 = 0x31 |
OK |
17 (two characters) |
VB100 = 0x37 ('7') |
'1' lost |
ABC (three characters) |
VB100 = 0x43 ('C') |
Only last character captured |
The data path is correct; the loss is in application-side buffer management. The PLC receives one TCP segment per keystroke, and the user program is not buffering segments or detecting the end of a logical message.
Affected Hardware, Firmware, and Software
| Item | Value |
|---|---|
| CPU family | S7-200 SMART (firmware ≥ V2.0 required for Open User Communication) |
| Standard CPUs | CPU SR20, SR30, SR40, SR60 |
| Signal-board CPUs | CPU ST20, ST30, ST40, ST60 |
| Compact CPUs | CPU CR40, CR60 |
| Programming software | STEP 7 Micro/WIN SMART V2.5 or later |
| Library | Open User Communication (TCP, UDP, ISO-on-TCP, Modbus TCP, USS) |
| Maximum active OUC connections | 8 simultaneous (Connection IDs 1-8) |
| Ethernet interface | PROFINET port, 10/100 Mb/s, RJ45 |
Root Cause Analysis
Four distinct causes can produce the "single-character" symptom. Diagnose which one applies before applying a fix.
Cause 1 – TCP Segmentation Is Independent of Application Framing
TCP is a byte-stream protocol; it does not preserve message boundaries. When HyperTerminal transmits 17 with a line ending, the kernel may emit one segment 0x31 0x37 or two segments 0x31 and 0x37, depending on Nagle algorithm, terminal echo, and time between keystrokes. TCP_Recv returns whatever bytes are currently queued; it does not wait for a logical message end.
Cause 2 – Done Bit Is Not Polled Continuously
The OUC blocks use a standard EN/ENO pattern. The Done bit (Status = 0x7002) is set for one scan only when the internal job is complete. If user logic only latches the buffer on the first rising edge, subsequent segments overwrite the buffer before being read.
Cause 3 – ASCII Mode Mismatch Between HyperTerminal and PLC
HyperTerminal can be configured to send characters in different ways: raw per keystroke, on CR (carriage return 0x0D), on LF (0x0A), or on CR+LF (0x0D 0x0A). If the PC sends 17 followed by a line terminator, the PLC receives 0x31 0x37 0x0D 0x0A. If user code reads the buffer immediately and only writes the first byte to the destination, the rest is dropped on the next scan cycle.
Cause 4 – HyperTerminal Encoding Anomaly
HyperTerminal's character map is tied to the Windows "Country" codepage. On systems set to non-Latin codepages (e.g., Eastern European), characters in the 0x80-0xFF range can be silently translated. Use Hercules SETUP, Tera Term, or PuTTY with UTF-8 or ANSI Latin-1 codepage to eliminate the variable.
S7-200 SMART Open User Communication Block Reference
Three blocks are used in this scenario. Refer to the S7-200 SMART System Manual, section on Open User Communication for the complete parameter list and timing diagrams.
TCP_Connect (UTCP_Connect) Parameters
| Parameter | Type | Description |
|---|---|---|
EN |
BOOL | Enable / start connection request |
ConnID |
BYTE | Connection identifier (1-8), unique per OUC session |
IPaddr1..4 |
BYTE | Remote IP octets (e.g., 192, 168, 1, 10) |
RemPort |
WORD | Remote TCP port (23 = telnet, 5001 = Hercules TCP server default) |
LocPort |
WORD | Local TCP port (0 = assign automatically) |
Done |
BOOL | TRUE for one scan when connect completes |
Busy |
BOOL | TRUE while connect is in progress |
Error |
BOOL | TRUE if connect failed |
Status |
WORD | Error / status code |
TCP_Send (UTCP_Send) Parameters
| Parameter | Type | Description |
|---|---|---|
EN |
BOOL | Trigger send on rising edge |
ConnID |
BYTE | Must match the active ConnID
|
DataPtr |
DWORD | &VB pointer to data to send (e.g., &VB100) |
DataLen |
WORD | Number of bytes to transmit (1-200) |
Done / Busy / Error / Status |
BOOL / BYTE / WORD | Same semantics as TCP_Connect
|
TCP_Recv (UTCP_Recv) Parameters
| Parameter | Type | Description |
|---|---|---|
EN |
BOOL | Trigger receive (level-sensitive or rising edge with re-arm logic) |
ConnID |
BYTE | Must match the active ConnID
|
DataPtr |
DWORD | &VB pointer to receive buffer (e.g., &VB200) |
DataLen |
WORD | Max buffer size (1-200); PLC fills up to this many bytes |
Done |
BOOL | TRUE for one scan when bytes have been written to buffer |
Busy |
BOOL | TRUE while receive is in progress |
Error |
BOOL | TRUE on receive error |
Status |
WORD | Error / status code |
TCP_Recv.DataLen is the maximum number of bytes the PLC will accept into the buffer. The actual number of bytes received is not returned as an output. You must either (a) read the full buffer on every Done pulse and parse for a delimiter, or (b) implement a fixed-length protocol.TCP_Recv and TCP_Connect Status Codes
| Status (hex) | Meaning | Action |
|---|---|---|
0x0000 |
No error, no operation | OK |
0x0001 |
Connection in progress | Wait |
0x0002 |
Connection established | Proceed with send/recv |
0x0003 |
Connection terminated | Re-arm TCP_Connect
|
0x7000 |
No job active | Normal idle |
0x7001 |
Job starting (first scan with EN=1) | Wait |
0x7002 |
Job in progress, Done=1 when complete | Latch result on Done |
0x8001 |
Connection ID invalid | Re-initialize |
0x8002 |
Parameter error (DataPtr/DataLen out of range) | Validate pointer and length |
0x8003 |
Connection failed (remote refused/timeout) | Check remote IP/port/firewall |
0x8004 |
Local port already in use | Change LocPort or set to 0 |
0x8005 |
No resources (max 8 connections) | Close unused connections |
0x8006 |
Send buffer overflow | Reduce DataLen or rate |
0x8007 |
Receive buffer overflow | Increase DataLen or drain faster |
0x8008 |
Connection closed by remote | Re-arm TCP_Connect
|
0x8009 |
Communication timeout | Check network |
Step-by-Step Fix: Buffer Multi-Byte Messages Until Terminator
The robust pattern: always arm TCP_Recv with EN=1, and on every Done pulse append the buffer contents to a FIFO while scanning for a CR/LF or fixed-length terminator. The sequence below assumes CPU SR30, STEP 7 Micro/WIN SMART V2.5+, and a server port of 5001.
Prerequisites
- CPU S7-200 SMART with firmware ≥ V2.0 (check in System Information → CPU Information).
- STEP 7 Micro/WIN SMART V2.5 or later installed.
- Open User Communication library installed (default location:
C:\Program Files (x86)\Siemens\Automation\MicroWIN SMART\Lib\Open_User_Communication). - PC and PLC on the same subnet. CPU has a static IP (e.g., 192.168.1.50); the PC has a compatible static IP (e.g., 192.168.1.10).
- Hercules SETUP utility for reliable terminal emulation. HyperTerminal is deprecated on Windows 10/11 and has known codepage issues.
Procedure
-
Reserve V memory. Allocate a 200-byte receive scratch buffer
VB200..VB399and a 200-byte assembly bufferVB400..VB599. Also allocate a counterVW600for "bytes in assembly buffer" and a "terminator received" flagVB610.0. -
Configure TCP_Connect (one-shot). Set
ConnID = 1,IPaddr1..4 = 192, 168, 1, 10,RemPort = 5001,LocPort = 0. DriveENwith a one-shot from the first scan, gated by a connection-failed retry timer. -
Configure TCP_Recv (continuously armed). Set
ConnID = 1,DataPtr = &VB200,DataLen = 200. WireENtoSM0.0(always TRUE) AND a "not in error state" boolean. The block re-arms automatically after each Done pulse. -
Implement FIFO copy on Done. In a sub-routine triggered by the rising edge of
TCP_Recv.Done:- Copy
VB200..VB(200+DataLen-1)byte-by-byte toVB400+VW600. - Increment
VW600by the number of bytes copied (use a fixed assumption of 200 if actual length is unknown, and rely on the terminator to mark end). - Scan the freshly-copied range for
0x0D(CR) or0x0A(LF). When found, setVB610.0 = 1.
- Copy
-
Process complete message. In a network that tests
VB610.0, copy the assembly buffer to the application variable area, resetVW600 = 0, clearVB400..VB599, and resetVB610.0 = 0. -
Send response (optional). Drive
TCP_Sendwith a rising edge after processing. SetDataPtr = &VB700,DataLen= actual response length.
Sample Ladder Excerpt (Conceptual)
Network 1 - TCP_Connect (one-shot on first scan)
SM0.1 TCP_Connect_EN (EN)
ConnID=1
IP: 192.168.1.10:5001
LocPort=0
-> TCP_Connect.Done -> Set M0.0 (Connected)
-> TCP_Connect.Error -> Set M0.1 (ConnectError)
Network 2 - TCP_Recv continuously armed
SM0.0 TCP_Recv_EN (EN)
NOT M0.1 (gate on no error)
ConnID=1
DataPtr = &VB200
DataLen = 200
-> TCP_Recv.Done -> SBR_0 (Process_Recv)
Network 3 - SBR_0: copy and scan for terminator
FOR VW700 = 0 TO 199
VB[400 + VW600 + VW700] = VB[200 + VW700]
NEXT
VW600 = VW600 + 200
FOR VW700 = 0 TO 199
IF VB[400 + VW700] == 0x0D THEN
SET VB610.0
EXIT FOR
END_IF
NEXT
ASCII Framing Alternatives
| Strategy | PC send pattern | PLC detection | Pros | Cons |
|---|---|---|---|---|
| CR/LF terminator | Adds 0x0D 0x0A after every command |
Scan buffer for 0x0D or 0x0A | Simple, human-readable, works with terminal | 0x0D/0x0A cannot appear in payload |
| Fixed length | Pads to constant byte count | Count bytes in FIFO | Predictable timing | Wastes bandwidth on short messages |
| Length prefix | First byte = payload length | Read first byte, then N more | Efficient, no escaping needed | More complex logic |
| STX/ETX framing | 0x02 ... 0x03 |
Look for 0x02, then 0x03 | Industry-standard, many libraries | Needs byte-stuffing if 0x02/0x03 in data |
Replace HyperTerminal With Hercules SETUP
HyperTerminal Private Edition (last release 2008) is not shipped with Windows 10/11. It exhibits three problems on modern systems:
- Codepage substitution: characters above 0x7F are remapped based on Windows regional settings.
- No raw byte view: it forces an ANSI/UTF display, hiding protocol bytes.
- No persistent log: it is not possible to capture a session for post-mortem.
Hercules SETUP is a freeware utility by HW-Group. To configure a TCP server on port 5001:
- Launch Hercules, click the TCP Server tab.
- Set Port = 5001.
- Click Listen. The status panel reads Listening on 0.0.0.0 : 5001.
- Type
17<Enter>in the Send box and click Send. Switch to hex view to confirm the bytes31 37 0D 0A.
Alternatives: Tera Term (free, supports VT100 and hex view) or PuTTY with Connection type: Raw.
Verification Procedure
-
Watch
TCP_Recv.Donein a status table. It must pulse on every segment arrival, not just on the first. -
Monitor
VB200..VB220in a status table. After sending17from the PC, the buffer must read31 37 0D 0A(assuming CR+LF). -
Confirm
VW600grows with each segment. After threeDonepulses from a single line, the count must equal the number of bytes sent (typically 4 for17\r\n). -
Trigger on
VB610.0. Set a watch on this flag – it must transition to 1 the instant a CR/LF is seen in the assembly buffer. -
Wireshark cross-check. Capture the PC Ethernet port with Wireshark filter
tcp.port == 5001. Confirm segment boundaries match what the PLC receives. -
Load test. Send 100 commands of 20 characters each from a script (Python
socket,ncon Linux, or Hercules "send file"). All 100 must reach the application variable area intact.
Troubleshooting Matrix
| Symptom | Likely cause | Fix |
|---|---|---|
Connection never completes, Status=0x8003
|
Wrong remote IP/port, PC firewall blocking 5001 |
ping PC from PLC (use ping block), open Windows Firewall port 5001 |
Connection completes, but TCP_Recv.Done never pulses |
PC not sending data, or EN not continuously driven |
Drive EN with SM0.0 and a NOT-error gate; verify with Hercules "Send" button |
| Only first byte captured, others lost | Logic reads VB200 only; doesn't loop to VB200+N
|
Implement FIFO copy from VB200 to VB400+VW600 on every Done
|
| Receives garbage / wrong characters | Codepage mismatch (HyperTerminal) | Switch to Hercules or Tera Term in ASCII Latin-1/UTF-8 |
| Receives echoes of sent data | Local echo on, server echoes input | Disable echo in Hercules; design protocol to ignore echoed characters |
Status=0x8007 receive buffer overflow |
PLC not draining buffer fast enough | Increase FIFO size, shorten scan time, or implement socket flush on error |
| Works for low rate, fails at high rate | Scan time too long; blocks queue up | Lower priority of TCP_Recv execution, or use a one-shot EN with a "Drain Complete" flag |
Status=0x8004 on TCP_Connect
|
Local port already in use (e.g., by another OUC connection) | Use LocPort=0 (auto) or unique port per ConnID
|
| Bytes appear corrupted on PLC side | Endianness or data type mismatch in DataPtr
|
Use byte pointer &VBxxx, not &VWxxx or &VDxxx
|
Performance and Limit Reference
| Parameter | Value |
|---|---|
| Max active OUC connections per CPU | 8 |
| Connection ID range | 1-8 |
Max payload per TCP_Send / TCP_Recv
|
200 bytes |
| Theoretical throughput | ~8-10 MB/s on PROFINET port (depends on scan time and OUC load) |
| Recommended minimum scan time for full-rate TCP_Recv | ≤ 20 ms |
| Port range restrictions | Avoid privileged ports < 1024 unless required; avoid well-known ports (80, 443, 502, 44818, etc.) |
Safety and Liveness Checks
-
Watchdog on
TCP_Connectfailure. IfErrorremains TRUE for > 5 s, drop and re-arm with a 1 s delay to avoid thrashing the OUC state machine. -
Limit assembly buffer growth. If
VW600 > 190without a terminator detected, discard the buffer and reset the counter. This prevents an attacker (or noisy line) from filling V memory. - Validate payload. If a command frame has unknown length or unknown opcode, drop it and reply with NAK. Do not act on incomplete frames.
Why does TCP_Recv give me only one byte even though I sent multiple characters?
TCP is a byte stream with no inherent message boundaries. Each Done pulse from TCP_Recv returns whatever bytes were in the receive queue at that instant, often just one TCP segment. Implement a FIFO in user logic and look for a terminator (CR, LF, length prefix, or STX/ETX) to assemble complete messages.
Does TCP_Recv return the actual number of bytes received?
No. The DataLen input is the maximum buffer size (1-200), and the actual received byte count is not exposed as an output. Parse the buffer for a delimiter, or use a fixed-length protocol, to know when a message is complete.
Can I use HyperTerminal on Windows 10/11 for S7-200 SMART TCP testing?
HyperTerminal is deprecated and not shipped with Windows 10/11. Even HyperTerminal Private Edition 2008 has known codepage and hex-view limitations. Use Hercules SETUP, Tera Term, or PuTTY in raw mode for reliable, repeatable TCP testing.
How many OUC connections can a single S7-200 SMART CPU maintain?
Up to 8 simultaneous Open User Communication connections, with Connection IDs 1 through 8. Each consumes internal resources; plan the connection table carefully if you also need S7/PG communication, Modbus TCP server, and PUT/GET.
Which firmware version first added Open User Communication to the S7-200 SMART?
Open User Communication (TCP, UDP, ISO-on-TCP, Modbus TCP, USS) was added in S7-200 SMART CPU firmware V2.0 (released 2013). CPUs with firmware V1.x do not support the OUC library.