S7-1200 TCP/IP Communication with C#: TCON TSEND TRCV Setup Guide

David Krause16 min read
S7-1200SiemensTutorial / How-to
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. 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.

Engineer field note: The TIA Portal project must match the S7-1200 CPU firmware. CPU firmware V4 ships a newer instruction library than V3, and mixing them generates compile or status warnings on 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

  1. 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/TRCV instruction set applies; some parameter layouts differ.
  2. Engineering: TIA Portal V13 SP1 minimum, V15.1 / V16 / V17 recommended for V4 firmware instruction library compatibility.
  3. 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.
  4. 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.
  5. Open user-communication connection resources: S7-1200 supports up to 8 open TCON connections simultaneously (firmware V4). Each TSEND/TRCV pair counts as a separate connection ID.

4. S7-1200 Side: TIA Portal Configuration

4.1 Hardware configuration of the connection

  1. In the TIA Portal project tree, open Devices & Networks and select the S7-1200 CPU.
  2. Open Properties → PROFINET interface → Ethernet addresses and set a fixed IPv4 address (e.g., 192.168.0.10) with subnet mask 255.255.255.0. Disable Set IP address using a different method unless you are using DCP.
  3. 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).
  4. Assign a unique Connection ID (e.g., 1). This ID is referenced by all subsequent TCON/TSEND/TRCV/TDISCON calls.

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 ID 1.
  • 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 ID 1.
  • LEN: byte count to send (max 8192 per call on S7-1200 V4).
  • DATA: a Variant pointer; typically P#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 ID 1.
  • LEN: max bytes to accept per call. Use 0 to accept whatever the remote sends in one frame (recommended for framed protocols — see §7).
  • DATA: a Variant pointer 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.

Engineer field note: Do not call 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 on 127.0.0.1 will never accept an inbound connection from the PLC over the LAN.
  • Set the listener's ExclusiveAddressUse = true (the default) and a ServerSocket backlog 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 Dispose the NetworkStream and TcpClient; the using block 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.

Engineer field note: The popular <eof> terminator pattern works, but if a payload legitimately contains those characters the receiver will truncate. Either escape them in the payload, switch to a length-prefixed binary frame, or use a sentinel that cannot appear in the payload (e.g., ASCII 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:

  1. On the S7-1200, keep TRCV permanently armed (set EN_R = TRUE). The block completes once for every received frame, and pulses NDR with the byte count in RCVD_LEN.
  2. 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.
  3. 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.
  4. Reply with TSEND using 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:

  1. PC-side port open: from a Windows command prompt, netstat -an | find ":2000" shows LISTENING.
  2. PLC TCP block status: monitor TCON.DONE and TCON.STATUS in the TIA Portal watch table. Expect DONE = TRUE and STATUS = 0x0000 immediately after the rising edge of REQ.
  3. 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 in ESTABLISHED.
  4. 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.
  5. Send a known pattern from PC: reply from C# with a fixed buffer. Confirm TRCV.NDR pulses in TIA Portal and that RCVD_LEN matches the sent length.
  6. Soak test: run for 24–72 hours with periodic send and receive. Watch for TSEND.ERROR / TRCV.ERROR latching and the STATUS field. Re-arm with TDISCON + TCON on the rising edge of a dedicated "reconnect" tag.
  7. Disconnect survival: disable the PC's network adapter for 30 seconds. The PLC's TCON.STATUS will return 0x80C4 (or similar) within the OS-detected timeout. Re-enable the adapter; confirm the link re-establishes automatically if the TCON is 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 Allow is the PowerShell equivalent of the typical commissioning rule.
  • Watchdog: the PLC should treat the absence of TRCV.NDR for > N seconds as a link fault and fall back to a safe state. The C# server should treat the absence of TSEND data 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 == 2000 to confirm the three-way handshake (SYN, SYN-ACK, ACK) and the data flow. A successful SYN-ACK proves that the PC's TcpListener is 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+.

Back to blog