Connecting S7-1200 PLC to PC Using VB.NET TCP Server Tutorial

David Krause13 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

Overview

This tutorial demonstrates how to build a point-to-point TCP link between a Siemens SIMATIC S7-1200 CPU (1212C, 1214C, 1215C, or 1217C) and a Windows PC running a Visual Basic .NET console application. The PC acts as a TCP server listening on a user-defined port, while the PLC uses the standard TSEND_C and TRCV_C instructions from the TIA Portal library to exchange raw byte arrays with the host. The example payload is a single byte in each direction: the PLC's IB0 is exposed to the application as the first received byte, and any value the user types into the VB input box is written to QB0 on the controller.

The technique uses the open ISO-on-TCP / TCP protocol stack that has shipped in every S7-1200 CPU since firmware release 2.0. It does not require the S7CommPlus symbolic-access path that newer HMIs use, so it remains usable on every firmware branch in the field today. For integrators who need a higher-level API (read/write by tag name, no manual byte slicing), the Snap7 open-source client/server library is documented at the end of this article as an alternative.

Field-proven caveat: S7-1200 firmware 2.0 introduced the TSEND_C/TRCV_C open user communication blocks. Firmware 1.x CPUs do not expose these blocks in TIA Portal, and the T_CONFIG block required to configure the on-board PROFINET port for TCP also only works from V2.0 forward. Always confirm the PLC firmware before commissioning the link.

Prerequisites

  1. CPU and firmware — SIMATIC S7-1200 CPU 1212C, 1214C, 1215C, 1217C, or 1214FC/FW. The 1212C DC/DC/DC, 1212C AC/DC/RLY, 1214C DC/DC/DC, and 1214C DC/DC/RLY variants all support the on-board PROFINET interface and the open communication blocks used here. The original example was built and tested on FW 3.0; the same code path is valid on FW 4.x and FW 5.x.
  2. TIA Portal — V13 SP1 or later, with the S7-1200 CPU HSP installed. For FW 4.6+ CPUs, use TIA V18 or V19 with the corresponding hardware support package.
  3. VB.NET development environment — Microsoft Visual Studio 2010 or later (Visual Basic Console Application template). The sample uses System.Net.Sockets.TcpListener, which is part of the .NET Framework 2.0+ baseline.
  4. Ethernet cabling — A PROFINET patch cable between the CPU's on-board Ethernet port and the PC Ethernet port, or both devices connected to the same unmanaged switch. Avoid routing the link through a managed switch that blocks unknown TCP ports.
  5. IP plan — Both devices on the same /24 subnet, for example PLC 192.168.0.1 and PC 192.168.0.5 with subnet mask 255.255.255.0.
  6. GET/PUT or open user communication enabled — In the PLC's device configuration, under Properties > General > Protection > Connection mechanisms, the checkbox Permit access with PUT/GET communication from remote partner is required only for S7-1500 and S7-1200 FW 4.x in read-via-PUT mode; the TSEND_C/TRCV_C path described here does not need it.

Project Architecture and Data Flow

The link is half-duplex-friendly: the PLC can be configured as the active connection partner (active connection establishment) and the PC as the passive listener, or vice versa. The VB example shown here uses the PC as the TCP server, so the PLC initiates the connection on power-up using TSEND_C with the CONT input tied to TRUE.

Direction Trigger PLC block VB code Data width
PLC → PC Cyclic, on change, or via REQ TSEND_C with DATA := P#IB0 BYTE 1 networkStream.Read(bytesFrom, 0, bytesFrom.Length) 1 byte (IB0)
PC → PLC Operator enters a value in InputBox TRCV_C with DATA := P#QB0 BYTE 1 networkStream.Write(sendBytes, 0, sendBytes.Length) 1 byte (QB0)

Each transmitted frame in this minimal example is exactly one byte. The PLC's TSEND_C block transmits the byte located at input byte 0 of the process image (IB0); on the PC side, the first received byte of the buffer represents that value. The application writes a user-supplied value into sendBytes(0), sends the full 256-byte buffer to the network stream, and the PLC's TRCV_C instruction places the first byte into QB0.

Step 1 — Configure the PLC in TIA Portal

  1. Create a new TIA Portal project and add the S7-1200 CPU (for example, 6ES7214-1AG40-0XB0, the 1214C DC/DC/DC FW 4.x).
  2. Open Device configuration > PROFINET interface [X1] > Ethernet addresses and assign a static IP, e.g. 192.168.0.1 / 255.255.255.0. Disable the router settings unless a default gateway is required.
  3. Add a new Connection to the Connections tab of the CPU. Choose TCP connection, set the partner to Unspecified, and the local port to a free port (the VB sample uses 2000). Confirm the Active connection establishment checkbox — the PLC will open the socket on CPU RUN.
  4. In the program blocks, add TSEND_C (FB 186, from Communication > Open user communication) and wire:
    REQ    := TRUE            // trigger continuous send
    CONT   := TRUE            // keep connection alive
    CONNECT:= "Connection_1"  // the connection configured in step 3
    DATA   := P#IB0 BYTE 1    // transmit process image input byte 0
    LEN    := 1
  5. Add TRCV_C (FB 187) and wire:
    EN_R   := TRUE
    CONNECT:= "Connection_1"
    DATA   := P#QB0 BYTE 1    // receive into process image output byte 0
    LEN    := 1
    RCVD_LEN will report actual bytes received
  6. Compile, download the hardware configuration and the user program, and start the CPU in RUN.
About the DATA parameter: TIA Portal will refuse to compile TRCV_C if the DATA input is left empty. A common mistake reported by new users is wiring "Receive_DB".Data — this only works if you create a global DB named Receive_DB with a byte array tag called Data. For this tutorial the simplest correct wiring is the symbolic address P#QB0 BYTE 1 shown above.

Step 2 — Build the VB.NET TCP Server

The original Visual Basic example is a self-contained console module. The annotated version below highlights the inputs that the field engineer must change for each installation.

Imports Microsoft.VisualBasic
Imports System.Net
Imports System.IO
Imports System.Net.Sockets
Imports System.Text

Module Module1
    Public Sub Main()
        ' === CONFIGURE THESE FOR YOUR SITE ===
        Dim Port As Int32 = 2000                        ' must match PLC TSEND_C connection
        Dim LocalAddress As IPAddress = _
            IPAddress.Parse("192.168.0.5")               ' PC IP, same subnet as the CPU
        ' ====================================

        Dim serverSocket As New TcpListener(LocalAddress, Port)
        Dim J As Int32
        Dim bytesFrom(1024) As Byte                     ' receive buffer
        Dim clientSocket As TcpClient
        Dim StrJ As String
        Dim StrK As String

        serverSocket.Start()
        Console.WriteLine(" >> Server Started")
        clientSocket = serverSocket.AcceptTcpClient()
        Console.WriteLine(" >> Accept connection from client")

        While (True)
            Try
                Dim networkStream As NetworkStream = clientSocket.GetStream()

                ' PLC -> PC: read first byte (PLC IB0)
                J = networkStream.Read(bytesFrom, 0, bytesFrom.Length)
                If J > 0 Then
                    StrJ = Str(bytesFrom(0))
                    Console.WriteLine(" >> Data from client - " & StrJ)

                    ' PC -> PLC: prompt operator, send first byte (PLC QB0)
                    StrK = InputBox("Insert value", "0")
                    Dim sendBytes(255) As Byte
                    sendBytes(0) = Val(StrK)
                    networkStream.Write(sendBytes, 0, sendBytes.Length)
                    networkStream.Flush()
                    Console.WriteLine(" >> Server response " & StrK)
                End If
            Catch ex As Exception
                MsgBox(ex.ToString)
            End Try
        End While

        clientSocket.Close()
        serverSocket.Stop()
        Console.WriteLine(" >> exit")
        Console.ReadLine()
    End Sub

    Sub msg(ByVal mesg As String)
        mesg.Trim()
        Console.WriteLine(" >> " & mesg)
    End Sub
End Module

The four lines between the CONFIGURE banners are the only ones a commissioning engineer needs to change. The port must match the one declared on the PLC's TSEND_C connection, and the PC's IP must be reachable on the same subnet as the CPU. If both devices sit on different subnets you must add a default gateway and ensure no firewall is blocking the chosen TCP port on the PC (Windows Defender Firewall will, by default, silently drop unsolicited inbound TCP — see the troubleshooting matrix below).

Step 3 — Verify the Link

  1. Bring the CPU to RUN with the program downloaded.
  2. Open Online & diagnostics > Watch tables in TIA Portal and force IB0 to a known value (e.g. 0xAA). On the PC, launch the compiled VB application.
  3. The console should print Server Started, followed within a few hundred milliseconds by Accept connection from client as the PLC opens the socket.
  4. Type any decimal value 0–255 in the input box. The value must appear on QB0 in the watch table.
  5. Change IB0 in the watch table. The next read on the PC should display the new value in the console.

Firmware and Hardware Compatibility Matrix

S7-1200 CPU Order number (example) Min FW for TSEND_C/TRCV_C Tested in this guide
CPU 1212C DC/DC/DC 6ES7212-1AE40-0XB0 V2.0 Yes (V3.0 example CPU)
CPU 1212C DC/DC/RLY 6ES7212-1BE40-0XB0 V2.0 Yes
CPU 1214C DC/DC/DC 6ES7214-1AG40-0XB0 V2.0 Yes (most common variant in field)
CPU 1214C DC/DC/RLY 6ES7214-1BG40-0XB0 V2.0 Yes
CPU 1214FC 6ES7214-1AF40-0XB0 V2.0 Yes (fail-safe CPU)
CPU 1215C DC/DC/DC 6ES7215-1AG40-0XB0 V2.0 Yes
CPU 1217C DC/DC/DC 6ES7217-1AG40-0XB0 V2.0 Yes

For CPUs with firmware 4.x and newer, TIA Portal may report the open user communication blocks under a slightly different catalog path (e.g. Instructions > Communication > Open user communication > TSEND_C / TRCV_C V4.0). Both the V1.x and V4.x instruction variants implement the same TCP socket semantics.

Common Failures and Diagnostic Flow

Symptom on PC Likely root cause Diagnostic step Remedy
Console prints only Server Started; never reaches Accept connection from client PLC has not opened the socket Check TSEND_C.DONE, BUSY, ERROR, STATUS in a watch table. STATUS 0x0001 is WSAEADDRINUSE on the PLC; STATUS 0x7001 means the connection is still being established Verify the IP, port, and that the Active connection establishment box is checked on the PLC side
Server Started appears, then program hangs Windows firewall on the PC silently drops the inbound SYN Run Test-NetConnection -Port 2000 192.168.0.1 in PowerShell from the PC Create an inbound rule for TCP 2000 (or disable the firewall for the local network profile during commissioning)
Accept connection fires, but read returns 0 bytes continuously PLC TSEND_C never gets REQ triggered Force REQ in the watch table and observe DONE/ERROR Set REQ TRUE in OB1 or wire a rising-edge of BUSY back to REQ to retrigger
Write succeeds but PLC does not see the value TRCV_C.DATA is wired to the wrong address Inspect RCVD_LEN in the watch table; 0 means PLC never received the frame Wire DATA := P#QB0 BYTE 1 exactly, or use a dedicated receive DB
Connection drops after a few seconds Keep-alive not configured or partner closes the socket Check TSEND_C.STATUS after the drop Set CONT := TRUE and confirm the PC application does not call clientSocket.Close() in the loop
One user reported connection establishes from PLC side but PC Read never returns VB code passes bytesFrom.Length (1024) as the size argument while the remote only sends 1 byte — Read blocks until that many bytes arrive Inspect raw socket traffic with Wireshark (filter tcp.port == 2000) Pass 1 instead of bytesFrom.Length, or accumulate into a buffer with a length-prefixed framing protocol
Exception Object reference not set to an instance of an object after the first loop iteration PLC closed the socket; clientSocket was reused Wrap the Read call in a Try/Catch that re-invokes AcceptTcpClient() Re-accept the connection inside the catch block and resume the read loop

Port, IP, and Subnet Planning

Because the S7-1200 ships with a default IP of 0.0.0.0 (or 192.168.0.1 after the first TIA download), commissioning engineers often leave the controller on its default address and assign the PC a sibling address. The sample in this article uses 192.168.0.5 on the PC, which is the same pattern the original author used.

Parameter Recommended value Notes
PLC IP 192.168.0.1 Static, set via TIA Portal or the SIMATIC Automation Tool
PC IP 192.168.0.5 Static, configured in the Windows network adapter
Subnet mask 255.255.255.0 Identical on both devices
Port 2000 Free for this use; alternatives include 102, 2000, 2001, 2500
MTU 1500 Default; no tuning required for one-byte payload

Alternative — Snap7 for Symbol-Based Access

If your application needs to read and write S7-1200 tags by name (for example "DB1".Motor_Speed) without writing the TSEND_C/TRCV_C ladder and slicing bytes on the PC, the open-source Snap7 library is the most widely deployed alternative. Snap7 implements the S7CommPlus / S7-300/400 protocol and exposes a C/C++/C#/VB.NET/Python/Java API:

  • Client.Connect() opens a connection to the PLC's IP, port 102.
  • Client.ReadArea(S7AreaDB, 1, 0, 1, buffer) reads a block of bytes from a data block.
  • Client.WriteArea(S7AreaDB, 1, 0, 1, buffer) writes back.
  • Client.ReadSZL() introspects the CPU's diagnostic buffer.

Snap7 supports S7-1200 firmware 2.0 through 4.x in basic mode, and firmware 4.0+ with the so-called optimized symbolic access if the Permit access with PUT/GET communication from remote partner tick is set under Protection > Connection mechanisms in the device configuration. The Raspberry Pi port of Snap7 is well suited to small headless gateway projects where a Windows PC is not available.

License note: Snap7 is distributed under the MIT license and may be embedded in commercial products. Always rebuild the binaries from the official source distribution linked above rather than from a third-party mirror to avoid tampered DLLs.

Frame Layout and the First-Byte Convention

The example intentionally collapses the entire payload into a single byte. In production, expand the buffer to a structured frame, for example:

Offset 0    : command code (READ = 0x01, WRITE = 0x02)
Offset 1    : DB number
Offset 2-3  : byte offset, little-endian
Offset 4-5  : length, little-endian
Offset 6..  : payload (for WRITE) or echo of read data (for READ)

This pattern is what the popular Weintek S7-1200/S7-1500 S7CommPlus symbolic addressing guide documents for HMI-to-PLC integration, and is a robust starting point for any custom driver.

Security and Hardening Notes

  • Disable the link outside commissioning windows. The on-board PROFINET port accepts up to 8 open user communication connections, but every active TSEND_C connection reserves a resource. Set CONT := FALSE when not in use.
  • Restrict the PC's reachability. Place the PC and the PLC in a private VLAN, and use the Windows Firewall to allow inbound TCP only from the PLC's IP, not from the wider corporate network.
  • Disable unused PLC services. In Protection > Connection mechanisms, leave Permit access with PUT/GET communication from remote partner unchecked unless a third-party HMI or another S7 controller genuinely needs it. This blocks S7 read/write attempts by attackers who have reached the same VLAN.
  • Sign and checksum the payload. The single-byte example in this article has no integrity check. In production, prepend a CRC-16 or CRC-32 to the frame and validate on both sides.

Frequently Asked Questions

What is the minimum S7-1200 firmware that supports TSEND_C and TRCV_C?

Firmware V2.0 is the minimum. FW 1.x CPUs ship with TIA Portal V11 SP2 block libraries that do not contain the open user communication blocks. The example in this article was built and tested on firmware V3.0 and works unchanged on FW 4.x and 5.x CPUs.

Why does the VB program stop at "Server Started" and never reach the Accept line?

Three causes account for almost every field report of this symptom: (1) the PLC has not opened the socket — verify TSEND_C.STATUS, (2) Windows Defender Firewall is silently dropping the inbound SYN — disable it for the private profile or add an inbound rule for TCP 2000, or (3) the PC's IP address and the PLC's IP address are on different subnets. Run Test-NetConnection -Port 2000 192.168.0.1 from PowerShell to confirm reachability before debugging the application.

Does the connection work with a 1214C CPU and not just a 1212C?

Yes. Both the 1212C and the 1214C share the same PROFINET interface and the same TSEND_C/TRCV_C instruction set. The only differences between the two CPUs are the I/O count, the work memory size, and the number of supported connections, none of which affect a single TCP socket.

Why does the TRCV_C block refuse to compile without a DATA parameter?

TIA Portal requires every TRCV_C call to specify a destination buffer at compile time. The simplest valid wiring is a direct process-image pointer such as P#QB0 BYTE 1. If you prefer to receive into a DB, create a global DB with a byte array tag and reference it as "Receive_DB".Data — the string Receive_DB alone will not compile.

Is there a way to read S7-1200 tag values by name instead of by byte offset?

Yes — use the Snap7 open-source client library, which exposes Client.ReadArea and Client.WriteArea for byte-level access and, on FW 4.x+ with PUT/GET enabled, supports symbolic access to optimized data blocks. Snap7 is distributed under the MIT license and has ports for Windows, Linux, and the Raspberry Pi.

Back to blog