1. Overview: S7-1200 ↔ PC TCP/IP Communication
Establishing a raw TCP/IP link between a Siemens SIMATIC S7-1200 controller and a PC-hosted application is one of the most common integration tasks for shop-floor data logging, SCADA bridging, and custom HMIs. Unlike S7 communication (ISO-on-TCP, RFC 1006, port 102), a raw TCP connection gives the engineer full control over the byte stream, framing, and message semantics, but also full responsibility for both sides of the socket state machine.
This reference documents the architecture, TIA Portal configuration, the four open-user-communication instructions (TCON, TSEND, TRCV, TDISCON), the standard C# async-server pattern, and the most common field-failure modes (status 0x7001, 0x7004, "busy but no data" and stuck Waiting for connection issues) seen when commissioning S7-1200 firmware V4.x with TIA Portal V13 and later.
TCON. Always confirm PLC properties → General → Firmware before selecting the instruction version in the program editor.2. Architecture: Who Is the Server, Who Is the Client?
TCP is symmetric once established, but connection initiation is asymmetric: the client issues connect() to the listening port of the server. Either device can be either role; the choice is purely a project decision. The two most common field topologies are:
| Topology | Server (listener) | Client (connector) | Typical use |
|---|---|---|---|
| A — PC polls PLC | S7-1200 (passive, port 2000) | C# / VB.NET application | HMI, SCADA tag server, data acquisition |
| B — PLC pushes data | PC async-server (port 2000) | S7-1200 (active, periodic TCON) |
Event-driven push, alarm logging, line tracking |
| C — Bidirectional | PC async-server (port 2000) | S7-1200 (active) — both TSEND and TRCV enabled |
Recipe download, remote command dispatch, telemetry |
For first-time commissioning, Topology B is recommended: the PC runs a TcpListener on a fixed port (commonly 2000, although any unused TCP port above 1024 is acceptable; 1024–49151 are registered, 49152–65535 are dynamic/private), and the S7-1200 issues TCON on a rising-edge trigger. This isolates the PC from the PLC's IP address and keeps the C# code identical to the canonical Microsoft async-server sample, which most engineers already have working on localhost.
3. Prerequisites
- CPU: SIMATIC S7-1200, firmware V4.0 or later (CPU 1211C, 1212C, 1214C, 1215C, 1217C, or 1214FC/1215FC). For firmware V3 targets, the older
TCON/TSEND/TRCVinstruction set applies; some parameter layouts differ. - Engineering: TIA Portal V13 SP1 minimum, V15.1 / V16 / V17 recommended for V4 firmware instruction library compatibility.
- Network: point-to-point Ethernet, managed switch, or routed LAN. Both devices on the same subnet, or with valid default gateway and ACL pass-through for the chosen port.
- PC: Windows 10/11 or Windows Server 2016+ with .NET Framework 4.6+ (for VB.NET sample) or .NET 6/7/8 (for modern C# async
TcpListener). Add the PC's IP to the Windows Firewall inbound rule for the chosen TCP port. - Open user-communication connection resources: S7-1200 supports up to 8 open
TCONconnections simultaneously (firmware V4). EachTSEND/TRCVpair counts as a separate connection ID.
4. S7-1200 Side: TIA Portal Configuration
4.1 Hardware configuration of the connection
- In the TIA Portal project tree, open Devices & Networks and select the S7-1200 CPU.
- Open Properties → PROFINET interface → Ethernet addresses and set a fixed IPv4 address (e.g.,
192.168.0.10) with subnet mask255.255.255.0. Disable Set IP address using a different method unless you are using DCP. - Insert a new Connection in the program: from the CPU's Connections editor, add an Open User Communication (TCP) connection. The partner IP is the PC's static IPv4 address (e.g.,
192.168.0.5); partner port is the port the PC is listening on (e.g.,2000). - Assign a unique Connection ID (e.g.,
1). This ID is referenced by all subsequentTCON/TSEND/TRCV/TDISCONcalls.
4.2 Data blocks for the connection descriptor
The TCON block requires a TCON_Param structure instance, typically declared inside a global DB. For V4 firmware, create a DB of type System data type → TCON_Param (or TCON_IP_v4 for IPv4) and populate it with the partner endpoint. Example for IPv4:
| Field | Symbol | Value (Topology B) | Note |
|---|---|---|---|
| InterfaceId | HW_ANY | 64 (default PROFINET interface) | Use the HW identifier of the CPU's PROFINET port |
| ID | CONN_OUC | 1 | Matches the connection ID from step 4.1.4 |
| ConnectionType | BYTE | 16#0B (TCP, 11 decimal) | For ISO-on-TCP use 16#0C |
| ActiveEstablished | BOOL | TRUE (Topology B) / FALSE (Topology A) | TRUE = PLC is the client, FALSE = PLC listens |
| RemoteAddress | IP_V4 (4 BYTE array) | 192.168.0.5 | PC's IPv4, little-endian byte order: [5,0,168,192] |
| RemotePort | UINT | 2000 | Big-endian word: 16#07D0 |
| LocalPort | UINT | 0 (auto-assign) or fixed (e.g., 2001) | If fixed, ensure it is not in use |
4.3 Program blocks
Three instruction blocks are required. Block call in OB1 (cyclic):
TCON (open the TCP connection):
-
REQ: rising edge establishes the connection. -
ID: connection ID1. -
CONNECT: reference to the TCON_Param DB instance. -
DONE: pulses TRUE on successful establishment. -
BUSY: TRUE while establishing. -
ERROR/STATUS: 16#0000 on success; check against the error table in §5 if non-zero.
TSEND (transmit a buffer to the PC):
-
REQ: trigger pulse (e.g., from a pushbutton or a one-second clock). -
ID: connection ID1. -
LEN: byte count to send (max 8192 per call on S7-1200 V4). -
DATA: aVariantpointer; typicallyP#DB20.DBX0.0 BYTE 100. -
DONE/BUSY/ERROR/STATUS: as above.
TRCV (receive bytes from the PC):
-
EN_R: set TRUE to keep the receiver armed; FALSE halts after the next call. -
ID: connection ID1. -
LEN: max bytes to accept per call. Use0to accept whatever the remote sends in one frame (recommended for framed protocols — see §7). -
DATA: aVariantpointer to a receive DB. -
NDR: new-data-received pulse. -
RCVD_LEN: actual byte count of the frame. -
BUSY: TRUE while a receive call is in progress. -
ERROR/STATUS: as above.
TDISCON (clean disconnect):
Call only on shutdown or a controlled link-down. Issuing TDISCON while the PC is still listening will close the socket gracefully; the next TCON rising edge will re-establish the link.
TCON every cycle. Its REQ input must be a rising edge (use a one-shot from a tag, or feed the output of a flip-flop). Calling TCON with REQ = TRUE held high re-issues the connect request on every cycle, generating status 0x7001 in the handshake phase and 0x7004 only after the partner accepts.5. Status Code Reference for TCON / TSEND / TRCV
Status words are 16-bit; the MSB distinguishes temporary (1) from fatal (0). Common field-observed codes:
| STATUS (hex) | Source | Meaning | Action |
|---|---|---|---|
| 0x0000 | any | Completed without error | Continue |
| 0x7001 | TCON | Connect request initiated; awaiting partner | Wait. Persistent 0x7001 = PC is not listening or firewall blocks the port. |
| 0x7002 | TCON | Connection setup in progress | Wait |
| 0x7004 | TCON | Connection established successfully | Connection ready; begin TSEND / TRCV |
| 0x7005 | TDISCON | Disconnect in progress | Wait |
| 0x7006 | TDISCON | Disconnection completed | Connection is fully closed |
| 0x7000 | TSEND/TRCV | Job accepted; idle | Trigger REQ to send / keep EN_R set to receive |
| 0x7001 | TSEND | Send job active; data is being transmitted | Wait; do not retrigger REQ until DONE or ERROR |
| 0x7002 | TRCV | Receive job active; data is being read | Wait |
| 0x8085 | TCON | Connection ID already in use | Reuse a unique ID per active link |
| 0x80A1 | TCON/TSEND | Connection or resource error | Check partner reachable; check connection ID; re-issue TDISCON then TCON |
| 0x80C3 | TCON | All local connection resources in use | Reduce active TCON count; S7-1200 has 8 max |
| 0x80C4 | TSEND/TRCV | Temporary communications error; partner not responding | Verify partner socket open, IP correct, no firewall |
| 0x80B0 | TRCV | Data length > LEN, or no data to read | Set LEN = 0 to consume any-size frame, or match DB size |
The exact code set varies by firmware; the V4 instruction library is the source of truth and is documented in the TIA Portal help (F1 on the block) and the S7-1200 Programmable Controller — System Manual, chapter on Open User Communication.
6. PC Side: C# Async-Server Implementation
The Microsoft async-server sample is the most reliable starting point. It is reproduced here with modifications for the S7-1200 use case (binary framing, graceful disconnect, and connection-state logging):
using System;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
public class S71200TcpServer
{
private readonly int _port;
private TcpListener _listener;
private CancellationTokenSource _cts;
public S71200TcpServer(int port) { _port = port; }
public void Start()
{
_cts = new CancellationTokenSource();
_listener = new TcpListener(IPAddress.Any, _port);
_listener.Start();
Console.WriteLine($"[Server] Listening on 0.0.0.0:{_port} ...");
Task.Run(() => AcceptLoopAsync(_cts.Token));
}
private async Task AcceptLoopAsync(CancellationToken ct)
{
while (!ct.IsCancellationRequested)
{
try
{
TcpClient client = await _listener.AcceptTcpClientAsync().ConfigureAwait(false);
Console.WriteLine($"[Server] PLC connected from {((IPEndPoint)client.Client.RemoteEndPoint).Address}");
_ = Task.Run(() => HandleClientAsync(client, ct), ct);
}
catch (ObjectDisposedException) { break; }
catch (Exception ex) { Console.WriteLine($"[Server] Accept error: {ex.Message}"); }
}
}
private async Task HandleClientAsync(TcpClient client, CancellationToken ct)
{
using (client)
using (NetworkStream ns = client.GetStream())
{
byte[] rx = new byte[4096];
try
{
while (client.Connected && !ct.IsCancellationRequested)
{
int n = await ns.ReadAsync(rx, 0, rx.Length, ct).ConfigureAwait(false);
if (n == 0) break; // PLC closed cleanly
// TODO: parse frame, write to log, optionally build a reply
string hex = BitConverter.ToString(rx, 0, n).Replace("-", " ");
Console.WriteLine($"[Server] RX {n} bytes: {hex}");
}
}
catch (OperationCanceledException) { }
catch (IOException) { }
catch (Exception ex) { Console.WriteLine($"[Server] Handler error: {ex.Message}"); }
finally
{
Console.WriteLine("[Server] PLC disconnected.");
}
}
}
public void Stop()
{
_cts?.Cancel();
_listener?.Stop();
}
}
Key engineering points:
- Bind to
IPAddress.Any(0.0.0.0) unless you need to restrict the listener to a single NIC. Listening on127.0.0.1will never accept an inbound connection from the PLC over the LAN. - Set the listener's
ExclusiveAddressUse = true(the default) and aServerSocketbacklog of 1 unless you intend to accept multiple PLCs on the same port. - Use
ConfigureAwait(false)in the accept / read loops to avoid deadlocking the WinForms or WPF UI thread if you later embed the server in a desktop application. - Always
DisposetheNetworkStreamandTcpClient; theusingblock above does both.
6.1 VB.NET equivalent (topology A — PC is client)
The original VB.NET sample from the frequently-cited Frederiksen guide puts the PC in client role, polling the S7-1200. The System.Net.Sockets.TcpClient API in VB.NET is functionally identical to the C# version above; the connect call is:
Dim client As New TcpClient()
client.Connect("192.168.0.10", 2000)
Dim stream As NetworkStream = client.GetStream()
Dim tx(15) As Byte
' ... fill tx with S7-tag values ...
stream.Write(tx, 0, tx.Length)
Dim rx(255) As Byte
Dim n As Integer = stream.Read(rx, 0, rx.Length)
The same precautions about host IP, port, and firewall apply.
7. Frame and Protocol Design
Raw TCP is a byte stream, not a message protocol. The PLC's TSEND/TRCV with LEN = 0 emits one call per TCP segment, but the application cannot rely on TCP segment boundaries equalling application-message boundaries. Always include an explicit frame format:
| Field | Length | Description |
|---|---|---|
| Start-of-frame marker | 2 bytes | 0xAA55 — fixed sentinel |
| Length | 2 bytes (uint16 LE) | Payload size, excludes header or CRC |
| Payload | N bytes | Application data (PLC tag snapshot, recipe, command) |
| CRC-16 | 2 bytes | CRC-16/CCITT-FALSE over the entire header+payload, or any polynomial the team adopts |
Alternatively, use a delimiter protocol: a header line BEGIN + comma-separated values + terminator <eof> or \n. Delimiter protocols are easier to debug from a terminal emulator but slower to parse. The S7-1200 side typically scans incoming bytes for a marker using a small state machine in a String-typed receive DB and a FIND instruction, then extracts the substring between markers.
0x1C — the field-separator character).8. Bidirectional Communication: Sending Commands to the PLC
A common follow-up requirement is to send a command from the PC to the PLC — for instance, to acknowledge a fault, set a recipe selector, or start a sequence. The implementation steps are:
- On the S7-1200, keep
TRCVpermanently armed (setEN_R = TRUE). The block completes once for every received frame, and pulsesNDRwith the byte count inRCVD_LEN. - Inside OB1, latch the NDR pulse into a "new data received" flag. The receiver DB is updated in place by
TRCV; copy the bytes into a parsed structure on the NDR rising edge. - Parse the frame (length, CRC, payload). If valid, dispatch commands to a state machine in the PLC — for example, set a bit, write a recipe word, or call a function block.
- Reply with
TSENDusing a separate send buffer that contains the command's acknowledgement.
On the C# side, write the reply frame into the same NetworkStream:
byte[] reply = BuildAckFrame(ackCode: 0x01);
await ns.WriteAsync(reply, 0, reply.Length).ConfigureAwait(false);
await ns.FlushAsync().ConfigureAwait(false);
Both TSEND and TRCV can be active simultaneously on the same connection ID once the TCON has reached DONE. There is no conflict — TCP is full-duplex.
9. Verification and Commissioning
Use the following checklist before declaring the link production-ready:
-
PC-side port open: from a Windows command prompt,
netstat -an | find ":2000"showsLISTENING. -
PLC TCP block status: monitor
TCON.DONEandTCON.STATUSin the TIA Portal watch table. ExpectDONE = TRUEandSTATUS = 0x0000immediately after the rising edge ofREQ. -
Connection state on the PC: after the S7-1200 connects, the C# server logs
PLC connected from 192.168.0.10.netstat -an | find "192.168.0.10"shows the connection inESTABLISHED. -
Send a known pattern from PLC: program a 1-second clock that sets
TSEND.REQ. Fill the send buffer with an incrementing counter. Confirm on the C# console that bytes arrive and that the count is monotonic. -
Send a known pattern from PC: reply from C# with a fixed buffer. Confirm
TRCV.NDRpulses in TIA Portal and thatRCVD_LENmatches the sent length. -
Soak test: run for 24–72 hours with periodic send and receive. Watch for
TSEND.ERROR/TRCV.ERRORlatching and theSTATUSfield. Re-arm withTDISCON+TCONon the rising edge of a dedicated "reconnect" tag. -
Disconnect survival: disable the PC's network adapter for 30 seconds. The PLC's
TCON.STATUSwill return0x80C4(or similar) within the OS-detected timeout. Re-enable the adapter; confirm the link re-establishes automatically if theTCONis being re-armed on a periodic trigger.
10. Troubleshooting Matrix
| Symptom | Likely root cause | Diagnostic | Fix |
|---|---|---|---|
| PC console stuck at "Waiting for connection from PLC" | PC is not listening; firewall blocks port 2000; S7-1200 cannot reach the PC IP |
netstat -an | find ":2000"; Test-NetConnection -Port 2000 from PLC engineering PC; ping PC from TIA Portal's Online & Diagnostics ping |
Open the firewall inbound rule, fix the PC's static IP, correct the RemoteAddress in the TCON_Param DB |
PLC TCON reports 0x7001 persistently |
PC not listening or wrong partner port | Watch TCON.STATUS in TIA; verify port match |
Match ports; ensure TcpListener.Start() ran |
PLC TCON reports 0x7004 but no data reaches PC |
Wrong byte order in RemoteAddress; send DB offset/length error; TSEND.REQ never pulsed |
Watch TSEND.DONE and TSEND.STATUS; verify send DB byte count in TIA; Wireshark on PC to confirm packets arrive |
Correct RemoteAddress (little-endian IP), assign TSEND.REQ to a trigger, ensure send DB is large enough |
PC receives TSEND data but TRCV on PLC side never sets NDR
|
PC not flushing the stream; PLC's EN_R not held TRUE; LEN too small for payload |
Watch TRCV.BUSY and TRCV.NDR; check C# code calls Flush() or FlushAsync()
|
Set EN_R = TRUE continuously; use LEN = 0 for variable-length frames; add Flush() after each Write
|
| Connection drops every few hours, no auto-reconnect | TCP keepalive missing on the C# side; TCON is one-shot; PLC firmware in idle-disconnect mode |
Watch TCON.STATUS after the drop; check switch port counters for CRC errors |
Implement a periodic TDISCON + TCON cycle in the PLC; set SocketOptionName.KeepAlive = true on the C# socket |
C# AcceptTcpClientAsync throws Address already in use
|
Previous instance still running, or a SERVER socket is hanging in CLOSE_WAIT
|
netstat -ano | find ":2000"; look for zombie processes |
Set _listener.Server.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, false); always Stop() on application exit |
| VB.NET sample throws random errors but continues | Async-void handlers, lack of try/catch on socket operations, DataAvailable polling race |
Wrap reads in Try/Catch, use ReadAsync with a CancellationToken
|
Refactor to Task-based handlers with a long-lived CancellationTokenSource
|
11. Security and Production Considerations
- Authentication: raw TCP carries no authentication. If the PC is on a shared LAN, expect anyone on the subnet to be able to issue commands. Add a session token or HMAC-signed payload in the frame.
-
Encryption: for sensitive payloads, run the link through TLS using the S7-1200's Secure Open User Communication instructions (
TCON_SEC,TUSEND,TURCV) — available from firmware V4.4 onward. These wrap the TCP socket with TLS 1.2 / 1.3 and require importing a partner certificate. -
Firewall: in production, restrict the inbound rule to the PLC's source IP.
New-NetFirewallRule -DisplayName "S7-1200" -Direction Inbound -LocalPort 2000 -RemoteAddress 192.168.0.10 -Protocol TCP -Action Allowis the PowerShell equivalent of the typical commissioning rule. -
Watchdog: the PLC should treat the absence of
TRCV.NDRfor > N seconds as a link fault and fall back to a safe state. The C# server should treat the absence ofTSENDdata for > M seconds as a heartbeat loss. - Port choice: avoid well-known ports (< 1024) and registered application ports. Pick a port above 49152 if collision is a concern.
12. Field-Proven Tips
- Use Wireshark on the PC with the filter
tcp.port == 2000to confirm the three-way handshake (SYN,SYN-ACK,ACK) and the data flow. A successfulSYN-ACKproves that the PC'sTcpListeneris responsive even if the application code is silently dropping bytes. - When testing on the engineering PC, disconnect the VPN. Some enterprise VPN stacks (Cisco AnyConnect, GlobalProtect) intercept and tunnel TCP traffic, masking whether the link is on the local LAN.
- Always set the Online & Diagnostics → Functions → Set time of day on the S7-1200 before commissioning. Time-stamping PLC logs is invaluable for post-event analysis.
- On first connect, send a fixed magic-word (e.g.,
0xA5A5) from PLC to PC and require the same in reverse. This rules out IP/port/byte-order mismatches before any application payload is parsed.
FAQ
Why does my C# async server stay on "Waiting for connection from PLC" indefinitely?
The PC is either not actually listening, blocked by Windows Firewall, or listening on the wrong IP. Verify with netstat -an | find ":2000" (should show 0.0.0.0:2000 LISTENING), add an inbound firewall rule for the chosen TCP port, and confirm the S7-1200's TCON_Param RemoteAddress matches the PC's IPv4 address exactly (little-endian byte order: 192.168.0.5 = [5,0,168,192]).
What does PLC status 0x7001 vs 0x7004 on TCON mean?
0x7001 means a connection request has been initiated and the PLC is waiting for the partner (the PC) to accept — the listener is not responding. 0x7004 means the TCP handshake completed and the link is established. Persistent 0x7001 indicates the PC is not listening, the port is wrong, or a firewall is silently dropping the SYN.
How many simultaneous TCP connections can an S7-1200 support?
Firmware V4 supports up to 8 active TCON connections per CPU, including both S7 and open user communication. A single connection can carry both TSEND and TRCV traffic, so 1 connection ID is sufficient for most bidirectional PC-PLC links. TCON status 0x80C3 means all 8 are in use.
Why does TSEND return DONE and BUSY but the PC receives nothing?
Almost always one of three issues: the S7-1200's RemoteAddress is in the wrong byte order, the C# NetworkStream is not being flushed after Write, or the C# listener is bound to 127.0.0.1 and never sees the LAN-bound packet. Verify with Wireshark on the PC: a successful three-way handshake rules out routing/firewall problems.
Do I need firmware V4 to use TCON, or can I stay on V3?
TCON, TSEND, TRCV, and TDISCON exist on firmware V3, but the parameter layout is older (no TCON_IP_v4 system type — IP is encoded manually as four bytes). If you are starting a new project, use V4.0 or later with the TIA Portal V15.1+ instruction library for cleaner parameter handling and to enable Secure Open User Communication (TLS) on V4.4+.