Overview
Exchanging process data between a Siemens SIMATIC S7-1200 CPU and a PC is one of the most common integration tasks in factory automation. The platform supports several native and standardized paths, but the right answer depends on three engineering variables:
- Data type and volume – INT, WORD, REAL, STRING, DB payloads, and update rate
- Firmware generation – V4.0 … V4.4 unlocks different feature sets (notably OPC UA Server requires V4.4+)
- PC-side stack – Python, C#/.NET, LabVIEW, or off-the-shelf HMI/SCADA
This reference covers the three production-grade paths most engineers use with a CPU 1214 DC/DC/RLY on firmware V4.3 with TIA Portal V15.1:
- Open User Communication (OUC) over TCP/IP using
TCON/TSEND/TRCV - Modbus TCP (S7-1200 as Modbus TCP Client or Server)
- OPC UA Server (firmware V4.4+)
Each path is documented with a working configuration, a parameter table, a fault matrix, and a PC-side integration hint.
Prerequisites
| Item | Recommended Value | Notes |
|---|---|---|
| CPU | S7-1200 CPU 1214 DC/DC/RLY (6ES7214-1AG40-0XB0 or later) | Integrated PROFINET interface |
| Firmware | V4.3 for TCP/Modbus; V4.4+ for OPC UA Server | Verify in Online & Diagnostics > Module Information |
| TIA Portal | V15.1 Update 5 or later (V16/V17 for OPC UA modeling) | Step7 V13.0.2 → V18.0.0 supports SCL export compatibility |
| PC stack | Python 3.8+ (Windows 7/10/11) or .NET 4.6+ | S7.Net / S7NetPlus for C#; python-snap7 or s7 library |
| Network | 192.168.0.0/24, CPU 192.168.0.1, PC 192.168.0.10 | Same subnet, no VLAN routing issues |
| Firewall | Allow TCP 102 (S7), 502 (Modbus), 4840 (OPC UA) | Windows Defender or corporate GPO |
Path Selection Matrix
| Criterion | TCON/TSEND/TRCV | Modbus TCP | OPC UA Server |
|---|---|---|---|
| Min. firmware | V4.0 | V4.0 | V4.4 |
| Data types | Raw bytes (manual encode INT/REAL/STRING) | Holding/Input registers, Coils (16-bit or 1-bit) | Native INT, REAL, BOOL, STRING, DateTime |
| Topology | S7-1200 active or passive partner | Client/Server model | Publish/Subscribe or Client/Server |
| Typical payload | 1 – 8192 bytes per call | 1 – 125 registers per request | Up to 100k nodes (practical: 200 – 2 000) |
| Cycle | 10 ms – 1000 ms (event-driven) | 50 ms – 5000 ms (polled) | 100 ms – 5 000 ms (subscription) |
| PC stack effort | Medium (parse byte stream) | Low (mature libraries) | Low (semantic info auto-mapped) |
| Security | None (TCP only) | None (TCP only) | Sign + Encrypt (X.509) |
Method 1 – Open User Communication (TCON / TSEND / TRCV)
Open User Communication (OUC) is the leanest path. The S7-1200 acts as a TCP client or server and exchanges raw byte buffers; the user program is responsible for the data layout. This is the path the original integrators selected for a 1214 DC/DC/RLY on firmware V4.3.
Hardware Configuration in TIA Portal
- Open Devices & Networks and select the CPU 1214 DC/DC/RLY.
- In the Properties > PROFINET interface pane, set:
- IP address:
192.168.0.1 - Subnet mask:
255.255.255.0 - Router: not used for direct PC link
- IP address:
- Enable Permit PUT/GET communication from remote partner only if you also use
PUT/GET– not required for OUC.
Connection Description (TCON)
Add an TCON instance DB (e.g. TCON_DB) and configure the connection block as follows:
| Parameter | Value (Active Client Example) | Value (Passive Server Example) |
|---|---|---|
| Block | TCON (FB65 in STEP 7 V15.1) | TCON (FB65) |
| Interface | PN interface (built-in) | PN interface (built-in) |
| Connection type | TCP (16#0B) | TCP (16#0B) |
| Active/Passive | Active connection establishment | Passive connection establishment |
| Local port | 0 (auto-assign) | 2000 |
| Remote IP | 192.168.0.10 |
0.0.0.0 |
| Remote port | 2000 |
0 (any) |
Data Buffer Block (SCL)
Create a global DB DataBuffer for the send and receive arrays. Reserve at least 4 bytes for INT, 4 bytes for REAL, 2 bytes for WORD, and N+2 bytes for STRING (max length byte + actual length byte + N characters).
DATA_BLOCK "DataBuffer"
{ S7_Optimized_Access := 'TRUE' }
VERSION : 0.1
NON_RETAIN
VAR
SendBuf : ARRAY[0..255] OF BYTE; // raw outbound buffer
RcvBuf : ARRAY[0..255] OF BYTE; // raw inbound buffer
i : INT; // scratch
rSpeed : REAL; // example REAL value
iCount : INT; // example INT value
wStatus : WORD; // example WORD value
sLabel : STRING[20]; // example STRING value
END_VAR
END_DATA_BLOCK
Encode INT, REAL, WORD, and STRING to a Byte Buffer (SCL)
// Pack a REAL, INT, WORD, and STRING into SendBuf starting at offset 0
"DataBuffer".SendBuf[0] := USINT_TO_BYTE(SHR(IN:=REAL_TO_DWORD("DataBuffer".rSpeed), N:=24) AND 16#FF);
"DataBuffer".SendBuf[1] := USINT_TO_BYTE(SHR(IN:=REAL_TO_DWORD("DataBuffer".rSpeed), N:=16) AND 16#FF);
"DataBuffer".SendBuf[2] := USINT_TO_BYTE(SHR(IN:=REAL_TO_DWORD("DataBuffer".rSpeed), N:=8) AND 16#FF);
"DataBuffer".SendBuf[3] := USINT_TO_BYTE( REAL_TO_DWORD("DataBuffer".rSpeed) AND 16#FF);
"DataBuffer".SendBuf[4] := USINT_TO_BYTE(SHR(IN:=INT_TO_WORD("DataBuffer".iCount), N:=8) AND 16#FF);
"DataBuffer".SendBuf[5] := USINT_TO_BYTE( INT_TO_WORD("DataBuffer".iCount) AND 16#FF);
"DataBuffer".SendBuf[6] := USINT_TO_BYTE(SHR(IN:="DataBuffer".wStatus, N:=8) AND 16#FF);
"DataBuffer".SendBuf[7] := USINT_TO_BYTE( "DataBuffer".wStatus AND 16#FF);
// STRING layout: byte 8 = max len, byte 9 = actual len, bytes 10..(10+actual-1) = chars
"DataBuffer".SendBuf[8] := 20; // max length
"DataBuffer".SendBuf[9] := INT_TO_BYTE(LEN("DataBuffer".sLabel));
FOR "DataBuffer".i := 0 TO LEN("DataBuffer".sLabel) - 1 DO
"DataBuffer".SendBuf[10 + "DataBuffer".i] := CHAR_TO_BYTE(MID("DataBuffer".sLabel, "DataBuffer".i + 1, 1));
END_FOR;
Send / Receive Sequence in OB1 (SCL)
// One-shot: establish the connection
IF "FirstRun" THEN
"TCON_DB".REQ := TRUE;
"TCON_DB".ID := 1;
"TCON_DB".CONNECT := "ConnParam"; // connection description VARIANT/DATA_BLOCK
"FirstRun" := FALSE;
END_IF;
"TCON_DB">(REQ := "TCON_DB".REQ, ID := 1);
// Trigger TSEND every 100 ms using a cyclic interrupt OB
IF "TSEND_DB".DONE OR "TSEND_DB".ERROR THEN
"TSEND_DB".REQ := FALSE;
END_IF;
"TSEND_DB"(REQ := NOT "TSEND_DB".BUSY,
ID := 1,
LEN := 30, // total payload bytes
DATA := "DataBuffer".SendBuf);
// TRCV – receive any length up to 256 bytes
"TRCV_DB"(EN_R := TRUE,
ID := 1,
LEN := 0, // 0 = accept any length
DATA := "DataBuffer".RcvBuf,
NDR => ,
LEN => ,
STATUS => "rcvStatus",
BUSY => ,
ERROR => );
TSEND from a Cyclic Interrupt OB (e.g. OB35 at 100 ms) rather than OB1 to avoid stalling the main scan. TRCV should run every OB1 pass to flush the receive buffer quickly.Method 2 – Modbus TCP
Modbus TCP rides on the same PROFINET port (X1) but uses port 502 and the Modbus Application Protocol. The S7-1200 can act as a Modbus TCP Server (one client) from firmware V4.0, and as a Modbus TCP Client from V4.1+ via the MB_CLIENT instruction.
Procedure
- Insert Instructions > Communication > Modbus TCP and choose
MB_SERVERorMB_CLIENT. - For the server role, configure:
- Local port: 502
-
MB_HOLD_REG: pointer to a data block sized to map the registers (e.g.
P#DB100.DBX0.0 WORD 100for 100 holding registers)
- Set the IP access list under Properties > Connection > Access list to limit which PCs can attach.
- Compile and download.
MB_SERVERruns in OB1 automatically once started withMB_MODE := 1.
Register Map Example
| Modbus Address | S7-1200 Tag | Type | Notes |
|---|---|---|---|
| 40001 | "ModbusDB".Speed_RPM | REAL (2 registers) | Holding register pair, big-endian word swap |
| 40003 | "ModbusDB".Count | INT (1 register) | 0 – 32767 / –32768 negative |
| 40004 | "ModbusDB".StatusBits | WORD (1 register) | Bit-packed flags |
| 40005 – 40014 | "ModbusDB".Label | STRING[20] (10 registers) | First register = max len, second = actual len |
SWAP in SCL or rely on the PC library to do the byte swap.Method 3 – OPC UA Server (Firmware V4.4+)
When firmware V4.4 is loaded on the S7-1200, the integrated PROFINET interface exposes an OPC UA Server with semantic information. This is the cleanest path if the PC stack supports OPC UA (Unified Automation, Ignition, Kepware, Node-RED, etc.).
Activation
- Update firmware to V4.4 or later. Use TIA Portal Online > Firmware Update; ensure the PLC is in STOP.
- Open Properties > OPC UA Server on the CPU and tick Activate OPC UA Server.
- Define a server interface by drag-and-drop of DB tags into the OPC UA Companion Specification view.
- Set the security policy –
Nonefor lab,Basic128Rsa15orBasic256Sha256with a server certificate for production.
Endpoint URL
opc.tcp://192.168.0.1:4840
Client browsing will then expose each tag with its native type (INT, REAL, BOOL, STRING, DateTime, structured record). No byte swapping or length prefixes are needed on the PC side.
PC-Side Integration
Python (TCP via OUC)
import socket, struct
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect(('192.168.0.1', 2000))
sock.send(b'\x00') # request frame or trigger
raw = sock.recv(256)
real_val, int_val, word_val = struct.unpack('>fiH', raw[:10])
str_len = raw[9]
text = raw[10:10+str_len].decode('ascii')
print(real_val, int_val, word_val, text)
Python (Modbus TCP with pymodbus 3.x)
from pymodbus.client import ModbusTcpClient
c = ModbusTcpClient('192.168.0.1', port=502)
rr = c.read_holding_registers(0, 14, slave=1)
print(rr.registers)
.NET (S7-1200 native S7 protocol)
Use S7NetPlus. This uses the Siemens S7 protocol on TCP/102 – a fourth option not based on OUC. It is suitable when the PC reads the same data blocks the HMI sees, but it must be enabled in TIA Portal: Properties > Connection > Permit access with PUT/GET.
Simulating the Link with PLCSIM
PLCSIM Advanced targets the S7-1500/ET 200SP CPU simulation and is not a fit for a 1214. For an S7-1200, use PLCSIM V13 – V18 (the regular PLCSIM) which can simulate the 1214 with firmware V4.x. If you need to expose PLCSIM to the host Ethernet adapter (e.g. for Python tests), NetToPLCSim bridges the simulated PLC to the PC's network card. Limitations:
- Only one simulated CPU per NetToPLCSim instance
- Connection drops every time you stop PLCSim – re-establish the partner connection
- OPCU UA Server activation is supported in PLCSIM V17+
Troubleshooting Matrix
| Symptom | Likely Cause | Diagnostic | Fix |
|---|---|---|---|
TSEND stays BUSY = 1 forever |
TCON connection not established |
Inspect STATUS of TCON – 16#80C4 means remote partner unreachable |
Ping the PC; check firewall on TCP 2000; verify IP/subnet |
TRCV errors with STATUS 16#8085 |
Length 0 with new firmware variants | Set LEN := 256 and evaluate RCV_LEN
|
Use a fixed length for the first 200 ms, then switch to LEN=0 |
| Modbus PC sees garbage REAL | Word order swap on multi-register values | Use pymodbus decode_32bit_float
|
Use word_order='big' or apply SWAP in PLC |
| OPC UA browse returns empty | Server interface not published | Right-click PLC > Compile > Download hardware configuration | Add tags under OPC UA > Server interface and recompile |
| Connection refused on TCP 502 | Another SCADA already opened it | Run netstat -ano | findstr 502
|
Kill the other client or change MB_SERVER port |
| Data arrives in wrong sequence | PC stack uses Nagle algorithm | Disable Nagle: sock.setsockopt(IPPROTO_TCP, TCP_NODELAY, 1)
|
Add length prefix in payload to frame messages |
| String truncated to one character | Forgot to write the actual length byte | Inspect byte 9 of payload | Update SCL code to write LEN(s) before the chars |
| Python winsocket error 10060 | PC firewall blocks reply | Disable Windows Defender Firewall temporarily | Create inbound rule for python.exe or open the TCP port |
Performance and Cycle-Time Notes
- OUC: TSEND/TRCV on a 1214 takes ~3 – 5 ms per call for 100 bytes. 100 ms OB35 cycle is safe up to 1 kB payload.
-
Modbus TCP: A
MB_CLIENTpoll of 100 registers takes 8 – 12 ms round-trip on a 100 Mbit/s link. - OPC UA: Subscription with 200 tags and 100 ms publishing interval consumes roughly 15 – 20 % of OB1 time on a 1214 – avoid sampling fast counters through OPC UA.
- For >1 kB per cycle, use
TSEND_C(FB1904) with the Send with length prefix option to fragment the payload transparently.
Security Hardening Checklist
- Disable Permit PUT/GET when using OUC or Modbus – it is not required for either.
- Restrict Modbus TCP access list to the engineering PC IP.
- For OPC UA, deploy a server certificate signed by the plant CA and enable
Basic256Sha256. - Isolate the engineering network in a VLAN; never expose port 102/502/4840 to the corporate LAN.
Recommended Path by Scenario
| Scenario | Recommended Path | Why |
|---|---|---|
| Lab / prototype, ≤ 200 tags, Python on PC | TCON/TSEND/TRCV | Lowest hardware dependency; firmware V4.0 is enough |
| Production SCADA, multi-vendor, polling 100 – 500 registers | Modbus TCP | Mature libraries, deterministic, easy to firewall |
| IIoT gateway, semantic info required, future-proof | OPC UA Server (V4.4+) | Native types, security, no manual encoding |
| PC runs existing Siemens HMI / WinCC on the same subnet | Native S7 over TCP/102 (S7NetPlus) | Reuses HMI variables, no extra configuration |
What is the minimum firmware for OPC UA Server on S7-1200?
OPC UA Server on the integrated PROFINET interface requires firmware V4.4 or later. Earlier versions expose the S7 protocol on TCP/102 only; they do not support OPC UA.
Do I need the PUT/GET option enabled for TCON/TSEND/TRCV?
No. Open User Communication uses the TCON/TSEND/TRCV instruction family and is independent of PUT/GET. Leave PUT/GET disabled to reduce the attack surface.
Can PLCSIM Advanced simulate a 1214 for this task?
No. PLCSIM Advanced is limited to S7-1500 and ET 200SP CPUs. Use the regular PLCSIM V13–V18 for the 1214, and bridge it to the host network with NetToPLCSim if the PC must connect to a real TCP port.
What is the largest payload per TSEND call on a 1214 firmware V4.3?
Up to 8 192 bytes per call on the integrated PROFINET interface. The send/receive DB must be at least that size; for shorter messages the PC reads the actual length from the first two bytes of the payload.
Why does my REAL value arrive corrupted over Modbus TCP?
Modbus is big-endian and S7-1200 holding registers are word-addressed. A 32-bit REAL spans two registers with the high word at the lower address; the PC library must perform a word swap before treating the 32-bit value as a float. In pymodbus use client.convert_from_registers(rr.registers[:2], data_type=ModbusClient.DATATYPE.FLOAT32).