The Siemens S7-1200 has built-in Modbus TCP support. No additional communication modules are needed. The CPU handles Modbus TCP natively through its PROFINET Ethernet port.
You configure it using two instruction blocks in TIA Portal:
- MB_SERVER — makes the S7-1200 act as a Modbus TCP server (slave). Other devices read and write its registers.
- MB_CLIENT — makes the S7-1200 act as a Modbus TCP client (master). It polls other Modbus devices like energy meters, VFDs, and sensors.
A single S7-1200 can run both roles at the same time — serving data to SCADA while polling field devices.
Despite the simplicity, engineers regularly run into problems: connection IDs not configured, holding register data blocks not sized correctly, MB_CLIENT requests overlapping, or Modbus addresses not matching the device register map.
This guide walks through both configurations with real TIA Portal settings, parameter explanations, and troubleshooting.
In This Guide
1. Requirements
| Item | Minimum Version |
|---|---|
| TIA Portal | V13 SP2 or later (V16+ recommended) |
| S7-1200 CPU | Firmware V4.0 or later |
| Communication module | Not required — built into CPU |
| Max simultaneous connections | 8 (server) / depends on CPU for client |
The Modbus TCP instructions (MB_CLIENT and MB_SERVER) are found in TIA Portal under:
Instructions → Communication → Others → MODBUS TCP
2. How Modbus TCP Works on the S7-1200
| Mode | Instruction | PLC Role | Port | What Happens |
|---|---|---|---|---|
| Server | MB_SERVER | Slave — listens for requests | 502 (default) | External clients (SCADA, HMI, other PLCs) connect to the S7-1200 and read/write its holding registers |
| Client | MB_CLIENT | Master — sends requests | Ephemeral | The S7-1200 connects to external Modbus devices (meters, VFDs, sensors) and reads/writes their registers |
Both instructions must be called cyclically in OB1 (the main program). They are not one-shot calls — they run continuously every scan cycle.
3. Step-by-Step: Configure S7-1200 as Modbus TCP Server
Step 1. Create a Data Block for Holding Registers
Create a new Global Data Block (e.g., DB10 “MB_HoldingReg”).
Add an array:
| Name | Data Type | Comment |
|---|---|---|
| HoldReg | Array [0..99] of Word | 100 holding registers |
This array is the data that external Modbus clients will read and write. Holding register 0 in Modbus maps to HoldReg[0], register 1 maps to HoldReg[1], etc.
⚠️ Important: Disable “Optimized block access” on this DB. Right-click the DB → Properties → Attributes → uncheck “Optimized block access.” Modbus requires fixed byte offsets, which optimized access does not guarantee.
Step 2. Add MB_SERVER to OB1
Drag the MB_SERVER instruction into your OB1 network from the instruction palette.
TIA Portal will prompt you to create an instance data block. Accept the default (e.g., DB11 “MB_SERVER_DB”).
Step 3. Configure MB_SERVER Parameters
| Parameter | Value | Meaning |
|---|---|---|
| DISCONNECT | FALSE | Keep connection open |
| CONNECT_ID | 1 | Unique connection identifier (1–8) |
| IP_PORT | 502 | TCP port to listen on |
| MB_HOLD_REG | P#DB10.DBX0.0 WORD 100 | Pointer to the holding register data block |
Step 4. Download and Test
Download the program to the PLC. Use a Modbus TCP client tool (ModbusPoll, QModMaster, or your SCADA system) to connect to the PLC’s IP address on port 502. Read holding registers starting at address 0.
If you write a value to register 5 from the client tool, it should appear in DB10.HoldReg[5] in TIA Portal’s online monitor.
4. MB_SERVER Parameters Explained
| Parameter | Type | Description |
|---|---|---|
| DISCONNECT | Bool | TRUE = close the current connection. FALSE = normal operation. |
| CONNECT_ID | UInt | Unique ID for this server connection (1–8). Each simultaneous client needs a different CONNECT_ID. |
| IP_PORT | UInt | TCP port to listen on. Default: 502. |
| MB_HOLD_REG | Variant / Pointer | Pointer to the holding register area (data block + offset + length). |
| NDR | Bool (output) | New Data Ready — TRUE for one scan after a client writes data. |
| DR | Bool (output) | Data Read — TRUE for one scan after a client reads data. |
| ERROR | Bool (output) | TRUE if an error occurred. |
| STATUS | Word (output) | Error code. See Siemens documentation for codes. |
Handling Multiple Clients
To support multiple simultaneous Modbus TCP clients, you need one MB_SERVER call per client connection, each with a different CONNECT_ID but pointing to the same holding register data block.
Example: 3 SCADA clients connecting simultaneously → 3 MB_SERVER instances with CONNECT_ID = 1, 2, 3.
5. Mapping Holding Registers to PLC Data
The MB_HOLD_REG parameter defines which PLC data is exposed as Modbus holding registers.
Direct Mapping
| Modbus Register Address | PLC Location | Value |
|---|---|---|
| 0 | DB10.HoldReg[0] | Motor speed setpoint |
| 1 | DB10.HoldReg[1] | Temperature reading |
| 2 | DB10.HoldReg[2] | Pressure reading |
| 10 | DB10.HoldReg[10] | Alarm status (bitmask) |
Writing Process Data to Holding Registers
In your PLC program, copy process values into the holding register array so external clients can read them:
DB10.HoldReg[0] := INT_TO_WORD(MotorSpeed);
DB10.HoldReg[1] := INT_TO_WORD(Temperature * 10); // Scale factor 0.1
DB10.HoldReg[2] := INT_TO_WORD(Pressure * 100); // Scale factor 0.01
Reading Commands from Holding Registers
When a SCADA client writes a value to a holding register, your program reads it:
IF DB11.NDR THEN // New data received from client
SpeedSetpoint := WORD_TO_INT(DB10.HoldReg[50]);
END_IF;
6. Step-by-Step: Configure S7-1200 as Modbus TCP Client
Step 1. Create a Connection Data Block
Create a new Global DB (e.g., DB20 “MB_ConnParam”) with the TCON_IP_v4 structure:
| Field | Value | Meaning |
|---|---|---|
| InterfaceId | 64 | HW identifier of the PROFINET interface (check in device configuration) |
| ID | 5 | Connection ID (must be unique) |
| ConnectionType | 11 | TCP (always 11 for Modbus TCP) |
| ActiveEstablished | TRUE | PLC initiates the connection |
| RemoteAddress.ADDR[1] | 192 | First octet of target IP |
| RemoteAddress.ADDR[2] | 168 | Second octet |
| RemoteAddress.ADDR[3] | 1 | Third octet |
| RemoteAddress.ADDR[4] | 100 | Fourth octet → 192.168.1.100 |
| RemotePort | 502 | Target device Modbus port |
| LocalPort | 0 | Auto-assign (let PLC choose) |
Step 2. Create a Data Block for Received Data
Create DB21 “MB_ClientData” with an array to store read values:
| Name | Data Type |
|---|---|
| ReadData | Array [0..49] of Word |
Step 3. Add MB_CLIENT to OB1
Drag MB_CLIENT from the instruction palette into OB1. Create the instance DB (e.g., DB22).
Step 4. Configure MB_CLIENT Parameters
| Parameter | Value | Meaning |
|---|---|---|
| REQ | Trigger signal (pulse) | TRUE for one scan to send the request |
| MB_MODE | 0 | 0 = read, 1 = write |
| MB_DATA_ADDR | 40001 | Modbus register address to read (1-based) |
| MB_DATA_LEN | 10 | Number of registers to read |
| CONNECT | DB20 | Pointer to TCON_IP_v4 connection structure |
| MB_DATA_PTR | P#DB21.DBX0.0 WORD 10 | Pointer to store received data |
Step 5. Trigger the Request
The REQ input must be pulsed (TRUE for one scan) to send a Modbus request. Use a timer or a clock memory bit:
// Trigger MB_CLIENT every 1 second using Clock_1Hz
MB_CLIENT_Instance.REQ := Clock_1Hz;
Wait for DONE = TRUE before sending the next request.
7. MB_CLIENT Parameters Explained
| Parameter | Type | Description |
|---|---|---|
| REQ | Bool | Rising edge triggers a Modbus request |
| DISCONNECT | Bool | TRUE = disconnect from server |
| MB_MODE | USInt | 0 = read, 1 = write, 2 = diagnostics |
| MB_DATA_ADDR | UDInt | Modbus register address (1-based: 40001 = first holding register) |
| MB_DATA_LEN | UInt | Number of registers/coils to read or write |
| CONNECT | TCON_IP_v4 | Connection structure with target IP, port, connection ID |
| MB_DATA_PTR | Variant / Pointer | Pointer to the data area for read/write |
| DONE | Bool (output) | TRUE when request completed successfully |
| BUSY | Bool (output) | TRUE while request is in progress |
| ERROR | Bool (output) | TRUE if request failed |
| STATUS | Word (output) | Error code |
MB_MODE Values
| MB_MODE | Action | Modbus Function Code |
|---|---|---|
| 0 | Read holding registers | FC 03 |
| 0 | Read input registers (if MB_DATA_ADDR starts at 30001) | FC 04 |
| 0 | Read coils (if MB_DATA_ADDR starts at 1) | FC 01 |
| 1 | Write holding registers | FC 06 (single) / FC 16 (multiple) |
| 1 | Write coils (if MB_DATA_ADDR starts at 1) | FC 05 (single) / FC 15 (multiple) |
| 2 | Diagnostics | FC 08 |
The function code is selected automatically based on MB_DATA_ADDR and MB_DATA_LEN. You do not choose FC 03 or FC 16 manually — TIA Portal determines this from the address range.
8. Reading Multiple Devices Sequentially
The S7-1200 can only process one MB_CLIENT request at a time per connection. You cannot send the next request until the current one finishes (DONE = TRUE or ERROR = TRUE).
The Problem
If you have 5 energy meters to poll, you cannot call MB_CLIENT 5 times simultaneously. They must be sequential.
The Solution: State Machine
Use a step counter to cycle through devices:
CASE Step OF
0: // Read Meter 1 - Voltage
MB_CLIENT_Instance.REQ := TRUE;
MB_CLIENT_Instance.MB_DATA_ADDR := 40001;
MB_CLIENT_Instance.MB_DATA_LEN := 3;
// Point CONNECT to Meter 1 connection DB
IF MB_CLIENT_Instance.DONE THEN Step := 1; END_IF;
1: // Read Meter 1 - Power
MB_CLIENT_Instance.REQ := TRUE;
MB_CLIENT_Instance.MB_DATA_ADDR := 40010;
MB_CLIENT_Instance.MB_DATA_LEN := 4;
IF MB_CLIENT_Instance.DONE THEN Step := 2; END_IF;
2: // Read Meter 2 - Voltage
// Change CONNECT to Meter 2 connection DB
MB_CLIENT_Instance.REQ := TRUE;
MB_CLIENT_Instance.MB_DATA_ADDR := 40001;
MB_CLIENT_Instance.MB_DATA_LEN := 3;
IF MB_CLIENT_Instance.DONE THEN Step := 3; END_IF;
// ... continue for all devices
5: // Restart cycle
Step := 0;
END_CASE;
Each step waits for DONE before moving to the next. This ensures only one request is active at any time.
9. Data Type Handling Between Modbus and S7
Modbus registers are 16-bit words. S7-1200 data types vary. You need to convert.
| Modbus Data | S7 Data Type | Conversion |
|---|---|---|
| 1 register (16-bit unsigned) | Word / UInt | Direct — no conversion needed |
| 1 register (16-bit signed) | Int | WORD_TO_INT() |
| 2 registers (32-bit float) | Real | Combine 2 words into DWord, then DWORD_TO_REAL() |
| 2 registers (32-bit unsigned) | DInt / UDInt | Combine 2 words into DWord |
| Scaled value (×0.1) | Real | INT_TO_REAL(value) * 0.1 |
32-Bit Float Example
Energy meter register 40010–40011 contains a FLOAT32 active power value.
// Combine two 16-bit words into a 32-bit float
TempDWord := SHL(WORD_TO_DWORD(DB21.ReadData[10]), 16)
OR WORD_TO_DWORD(DB21.ReadData[11]);
ActivePower := DWORD_TO_REAL(TempDWord);
⚠️ Warning: Byte order varies by device. Some meters use big-endian (AB CD), others use word-swapped (CD AB). If the float value looks wrong, swap the two words. See: Modbus Register Map Explained
10. Connection Management and TCON_IP_v4
Every MB_CLIENT and MB_SERVER connection needs a TCON_IP_v4 structure. Key fields:
| Field | Description | Notes |
|---|---|---|
| InterfaceId | Hardware ID of the PROFINET port | Find in Device Configuration → PROFINET interface → System constants |
| ID | Connection ID | Must be unique across all Modbus connections on this CPU (1–255) |
| ConnectionType | 11 | Always 11 for TCP |
| ActiveEstablished | TRUE = client (PLC initiates), FALSE = server (PLC listens) | |
| RemoteAddress.ADDR | Target IP address (4 bytes) | Only for client mode |
| RemotePort | 502 | Target device port |
| LocalPort | 0 for client (auto), 502 for server |
Finding the InterfaceId
In TIA Portal: Device Configuration → click on the PROFINET port → Properties → System constants tab → look for “Local~PROFINET_interface_1” → note the constant value (often 64 or 100).
11. Common Errors and How to Fix Them
| Error | STATUS Code | Cause | Fix |
|---|---|---|---|
| No connection | 0x80C8 | Target device not reachable | Check IP address, cable, and firewall. Ping the device. |
| Connection refused | 0x80C9 | Target device not listening on port 502 | Verify Modbus TCP is enabled on the target device. |
| Timeout | 0x80C7 | Device did not respond in time | Increase timeout. Check if device is overloaded. |
| Parameter error | 0x8188 | Wrong data type or pointer | Verify MB_HOLD_REG or MB_DATA_PTR pointer syntax. Disable optimized block access. |
| Duplicate connection ID | 0x80B2 | Two MB_CLIENT/SERVER instances use the same CONNECT_ID | Assign unique IDs to every connection. |
| Invalid register address | Exception 02 | MB_DATA_ADDR does not exist on the target device | Check the device register map. Verify 1-based vs 0-based addressing. |
| Request overlap | BUSY stays TRUE | New REQ sent before previous request completed | Wait for DONE or ERROR before triggering the next request. |
12. Testing Your Configuration
Step 1. Test Server Mode
- Download the program to the S7-1200.
- Open ModbusPoll or QModMaster on your PC.
- Connect to the PLC’s IP on port 502.
- Read holding registers starting at address 0.
- Write a value to register 5 and verify it appears in DB10 in TIA Portal’s online monitor.
Step 2. Test Client Mode
- Run a Modbus Slave simulator on your PC (e.g., ModRSsim2).
- Set the simulator to listen on port 502 with known register values.
- Download the MB_CLIENT program to the S7-1200.
- Trigger a read request.
- Verify received values match the simulator.
Step 3. Verify with Wireshark
Capture on port 502 using filter: tcp.port == 502
You should see:
- TCP SYN → SYN-ACK → ACK (connection setup)
- Modbus requests from the PLC (if client mode)
- Modbus responses from the device
- MBAP header with correct Transaction ID, Unit ID, and function code
💡 Tip: Use the free Modbus Frame Decoder Tool to decode any Modbus TCP frame captured in Wireshark.
Summary
The S7-1200 handles Modbus TCP natively — no extra hardware needed. The two instructions are:
- MB_SERVER — exposes PLC data as holding registers for external clients (SCADA, HMI)
- MB_CLIENT — polls external Modbus devices (meters, VFDs, sensors)
The key things to get right:
- Disable “Optimized block access” on all Modbus data blocks
- Use unique CONNECT_ID for every connection
- Wait for DONE before sending the next MB_CLIENT request
- Match MB_DATA_ADDR to the device register map (1-based addressing)
- Handle byte order carefully for 32-bit float values
- Test with simulator tools before connecting to real devices
