The S7-1500 talks Modbus TCP straight out of its PROFINET port. No CP module, no license, no third-party library. You drop in two instructions — MB_CLIENT and MB_SERVER — point them at a connection structure, and you have a working Modbus link.
That said, the part that trips people up isn’t dropping the block in. It’s the connection structure, the MB_MODE number, and the addressing. Get one of those wrong and the block sits there flashing a status code at you.
This guide walks the whole path: the two roles, the connection backbone both blocks share, then client and server side by side, the MB_MODE-to-function-code mapping that catches almost everyone, and the status codes you’ll see when something’s off.
Table of Contents
Client or server: pick the role first
Before you touch a block, decide what the S7-1500 is doing.
A client (master) starts the conversation. It opens the TCP connection and sends requests — read these registers, write those. The other device answers. If your PLC needs to pull data from a meter or push setpoints to a drive, it’s the client. That’s MB_CLIENT.
A server (slave) waits. It holds a block of registers and answers whoever connects. If another system — a SCADA front-end, an IoT gateway, another PLC — needs to reach into your PLC’s data, your PLC is the server. That’s MB_SERVER.
One CPU can be both at the same time, on separate connections. The S7-1500 can be a client on one connection — writing registers to another device — and a server on a second, letting that device read from it. Each role gets its own instruction call, its own instance DB, and its own connection ID.
Master/slave and client/server mean the same thing here. Master = client, slave = server. The newer terms are the standard now, so that’s what TIA Portal uses.
Both instructions live in the Communication → Others → MODBUS TCP folder in the instruction tree. Drag one into OB1 (or a cyclic block), and TIA Portal asks you to create an instance data block. Say yes. Each call needs its own instance DB.
The connection backbone: TCON_IP_v4
Both blocks need a CONNECT parameter. This is where most “it won’t connect” problems start, so it’s worth understanding before anything else.
CONNECT points to a connection description. You have two options:
- TCON_IP_v4 — you fill in the IP and port parameters yourself, in a data block. The connection opens when the block runs. This is the flexible route and the one used in the examples below.
- TCON_Configured — you set up the connection in the hardware config first, and the block reuses it. Cleaner for fixed setups, less flexible.
Use TCON_IP_v4 while you’re learning. It keeps everything visible in one structure.
Create a global DB, add a variable of type TCON_IP_v4, and fill it in. Here’s what each field does.
| Field | Type | What it is |
|---|---|---|
| InterfaceId | HW_ANY | Hardware ID of the PROFINET port. Find it in Device Configuration → mark the interface → Properties → “HW Identifier”. Often 64. |
| ID | CONN_OUC | Connection reference, 1 to 4095. Must be unique per connection in the CPU. |
| ConnectionType | BYTE | 11 (0x0B) for TCP. Nothing else is valid here. |
| ActiveEstablished | BOOL | TRUE = this CPU opens the connection (client). FALSE = it waits (server). |
| RemoteAddress | ARRAY[1..4] of BYTE | The other device’s IP, one octet per byte. 192.168.0.2 becomes 192, 168, 0, 2. |
| RemotePort | UINT | Client: the server’s listening port. Server: the client’s port, or 0 to accept any client. |
| LocalPort | UINT | Client: your local port, or 0 for any. Server: the port you listen on. |
Two rules from Siemens that save you a wasted afternoon:
- Set
ConnectionTypeto TCP only. Other types aren’t allowed for Modbus. - Don’t use these TCP ports — they’re reserved: 20, 21, 25, 80, 102, 123, 5001, 34962, 34963, 34964. Port 102 in particular is the S7 protocol port, so grabbing it breaks PG access.
The standard Modbus TCP port is 502. Use it unless something else on the device already owns it.
One detail about the ID field: it’s the connection ID, and it must match between the connection structure and the block. Each MB_CLIENT and MB_SERVER instance needs its own unique ID. Two blocks sharing an ID is a classic cause of connections that open, drop, and reopen forever.
Setting up the S7-1500 as a Modbus TCP client (MB_CLIENT)
The client opens the connection and drives every transaction. Here’s the role of each input on MB_CLIENT.
| Parameter | Type | Role |
|---|---|---|
| REQ | BOOL | Triggers a request. On the S7-1500, pulse it — one rising edge per transaction. |
| DISCONNECT | BOOL | 0 = keep the connection open. 1 = close it. |
| MB_MODE | USINT | Read, write, or which function code. The big one — covered below. |
| MB_DATA_ADDR | UDINT | Start address of the data to read or write. Meaning depends on MB_MODE. |
| MB_DATA_LEN | UINT | How many words or bits this request handles. |
| MB_UNIT_ID | USINT | Modbus unit/slave ID. Watch this one — see below. |
| MB_DATA_PTR | VARIANT | Pointer to your data buffer. Use an optimized global DB with an array of WORD. |
| CONNECT | VARIANT | Pointer to your TCON_IP_v4 structure. |
Outputs: DONE pulses true for one scan when a request finishes clean. BUSY is true while a request is in flight. ERROR flags a problem, and STATUS gives the code that tells you what.
Trigger REQ as a pulse, not a level
This is the single most common S7-1500 client mistake, and it’s where S7-1500 behaves differently from the S7-1200.
On the S7-1500, give REQ a single-scan pulse: set it true, let the block run once, then drop it. Holding REQ high until DONE comes back — which is the habit from older platforms — leads to stalls, stuck BUSY, and status codes like 8200 or 818D. The block manages the Modbus transaction ID itself; you don’t help it by holding the request high.
A simple pattern: trip REQ on a rising edge (a button, a timer, a DONE-chained sequence), and use the block’s own DONE/ERROR to gate the next one.
(If you wrap MB_CLIENT inside your own FB, as the Siemens example does, the wrapper can expose a level-controlled request input and handle the edge internally. Either way, the raw MB_CLIENT underneath wants the pulse.)
Worked example: write 10 registers
The Siemens note has the S7-1500 client writing 10 holding registers to a server at 192.168.0.2, port 502, on connection ID 1. The settings:
MB_MODE= 116 → write holding registers (function code 16)MB_DATA_ADDR= 0 → start at remote register 0MB_DATA_LEN= 10 → ten registersMB_DATA_PTR→ a DB withholdingRegister : Array[0..65535] of Word
Register [0] in your buffer maps to remote Modbus address 0, [1] to 1, and so on. Straight one-to-one.
Watch MB_UNIT_ID — it defaults to 255
On the V4.0+ blocks the S7-1500 uses, MB_UNIT_ID defaults to 16#FF (255). Most Modbus servers expect a unit ID between 1 and 247, and many simulators and devices default to 1. Leave it at 255 against a device that wants 1, and you get no answer — usually showing as a stuck connection or an exception. If a brand-new client setup connects but never returns data, check this first.
Setting up the S7-1500 as a Modbus TCP server (MB_SERVER)
The server is simpler. It holds a register block and answers. You don’t trigger anything — it reacts to the client.
| Parameter | Type | Role |
|---|---|---|
| DISCONNECT | BOOL | 0 = accept connections and stay open. 1 = close and reset. |
| MB_HOLD_REG / dataBuffer | VARIANT | Pointer to the holding register area the client reads and writes. Must be larger than 2 bytes. |
| CONNECT | VARIANT | Pointer to your TCON_IP_v4 structure, with ActiveEstablished = FALSE. |
Outputs: NDR (“new data ready”) pulses true when the client writes new data into your buffer. DR (“data read”) pulses when the client reads from it. ERROR and STATUS work the same as on the client.
A client can reach the server buffer with function code 3 (read), 6 (write single), and 16 (write multiple). The buffer is a global DB with optimized access, holding an array of Word.
MB_MODE and addressing: the part everyone gets wrong
This is the centerpiece. MB_MODE, MB_DATA_ADDR, and MB_DATA_LEN work together to decide which Modbus function code goes out and what address it hits. There are two ways to drive it, and mixing them up is the root of most “it reads garbage” complaints.
Method 1 — direct function code (MB_MODE 1xx)
Set MB_MODE to a number in the 100s and you’re naming the Modbus function code directly. MB_DATA_ADDR is then the raw remote address, 0 to 65535. This is the clean, modern way.
| MB_MODE | Function code | Action | Address range | Length |
|---|---|---|---|---|
| 101 | FC1 | Read coils | 0–65535 | 1–2000 |
| 102 | FC2 | Read discrete inputs | 0–65535 | 1–2000 |
| 103 | FC3 | Read holding registers | 0–65535 | 1–125 |
| 104 | FC4 | Read input registers | 0–65535 | 1–125 |
| 105 | FC5 | Write single coil | 0–65535 | 1 |
| 106 | FC6 | Write single register | 0–65535 | 1 |
| 115 | FC15 | Write multiple coils | 0–65535 | 1–1968 |
| 116 | FC16 | Write multiple registers | 0–65535 | 1–123 |
Read the pattern: the last two digits are the function code. 103 → FC3. 116 → FC16. Once you see that, the table memorizes itself.
Method 2 — classic addressing (MB_MODE 0 or 1)
Set MB_MODE to 0 (read) or 1 (write), and now MB_DATA_ADDR carries a classic Modbus address in the 4xxxx / 0xxxx ranges. The block reads the address range to figure out the function code for you.
| MB_MODE | MB_DATA_ADDR | Resolves to | Action |
|---|---|---|---|
| 0 | 40001–49999 or 400001–465535 | FC3 | Read holding registers |
| 1 | 40001–49999 or 400001–465535 | FC16 | Write holding registers |
So MB_MODE = 0 with MB_DATA_ADDR = 40001 reads holding register 0. The 40001 offset is the old convention; the block strips it to get the on-the-wire address 0.
Which one to use
Pick one method per project and stick to it. The 1xx direct-function-code method is easier to reason about because the address is exactly what goes on the wire — no mental subtraction of 40001. The classic 0/1 method is handy if your device documentation lists 4xxxx addresses and you’d rather not convert.
The mistakes to avoid: feeding a 40001-style address into a 1xx mode (it’ll point 40001 registers past where you meant), or feeding a raw 0-based address into mode 0 (it’ll reject anything below 40001). If your reads come back as zeros or exceptions, this mismatch is the first thing to check.
Status and error codes: reading what the block tells you
When a transaction doesn’t behave, STATUS is the word that explains why. The trap is that not every non-zero status is an error — some just mean “still working.”
In-progress codes (not errors)
| STATUS | Meaning |
|---|---|
| 7000 | Block called, no request active |
| 7001 | First call, opening the TCP connection |
| 7002 | Connection open, request in progress |
| 7005 | Connection up, waiting for the Modbus response |
| 7006 | Data transfer running |
Seeing 7001 then 7005 cycle through is normal handshaking. Don’t treat them as faults. The real signal of success is DONE = true with STATUS = 0 (client), or NDR/DR pulsing on the server.
Real error codes you’ll actually hit
| STATUS | Likely cause | Fix |
|---|---|---|
| 8200 | Client busy / port in use, or REQ held high too long | Pulse REQ as a single scan; don’t fire overlapping requests on one connection |
| 809B | TCON_IP_v4 structure wrong | Match the structure exactly — no extra variables after LocalPort; check ConnectionType = 11, valid ID, correct IP |
| 80C8 | Wrong MB_UNIT_ID, no answer from device | Set MB_UNIT_ID to the device’s real unit ID (often 1), not the default 255 |
| 818D / 80A3 | Connection/REQ handling, often from holding REQ high | Same fix as 8200 — clean edge-triggered REQ |
| 8382 | Data length out of range | Keep MB_DATA_LEN within the limits in the MB_MODE table |
The 809B error deserves a flag of its own: the TCON_IP_v4 block expects a fixed byte layout. Add a stray variable after LocalPort in that DB and the block can’t parse it, and you get 809B even though every value looks right. Keep that structure clean and ordered exactly as defined.
Verify it on the wire with Wireshark
When the status codes and the data both look suspicious, stop guessing and watch the actual packets. Modbus TCP is plain and easy to read in Wireshark.
Filter to just Modbus traffic:
modbus
Or pin it to the TCP port:
tcp.port == 502
To follow one connection between two known devices:
ip.addr == 192.168.0.2 && tcp.port == 502
In a Modbus TCP packet you’ll see the MBAP header (transaction ID, protocol ID = 0, length, unit ID) followed by the PDU (function code + data). Three quick checks:
- Function code — does it match the
MB_MODEyou set? A read going out as FC3 when you wanted FC16 means your mode is wrong. - Unit ID — is it the device’s real ID, or a stray 255?
- Exception responses — a function code with the high bit set (e.g. FC3 → 0x83) is the server rejecting the request, and the next byte is the exception code (02 = illegal data address is the usual one, meaning you asked for a register the device doesn’t have).
Seeing the request go out but no response come back points at the network or the unit ID. Seeing an exception response means you reached the device but asked for the wrong thing — almost always an address or length issue.
Frequently asked questions
Does the S7-1500 need a license for Modbus TCP?
No. MB_CLIENT and MB_SERVER are standard instructions built into TIA Portal V14 and later, running on the CPU’s integrated PROFINET port. There’s no add-on package, license server, or extra hardware to buy for basic Modbus TCP.
Do I need a CP module like the CP1543-1?
Not for standard Modbus TCP. The integrated Ethernet port handles client and server roles. A CP module only comes into play when you need extra Ethernet ports, network separation, or the CP’s built-in firewall and security features.
Why does MB_CLIENT connect but never return data?
Most often MB_UNIT_ID is still at its default of 255 while the device expects 1. After that, check that MB_MODE and MB_DATA_ADDR use the same addressing method, and confirm the device actually has the registers you’re requesting.
What’s the difference between MB_MODE 103 and MB_MODE 0?
Both end up reading holding registers, but the addressing differs. MB_MODE 103 names function code 3 directly and takes a raw address (0–65535). MB_MODE 0 takes a classic address (40001-style) and works out the function code from the range. Don’t mix the two addressing styles.
Why is my MB_CLIENT stuck in BUSY?
Usually REQ is held high. On the S7-1500, trigger REQ as a single-scan pulse and let DONE/ERROR gate the next request. Firing overlapping requests on one connection, or sharing one connection ID across two blocks, will also stick it.
Can one S7-1500 be both client and server?
Yes. Call MB_CLIENT and MB_SERVER on separate connections, each with its own connection ID and instance DB. The Siemens reference example does exactly this on one CPU.
Is port 502 mandatory?
502 is the registered Modbus TCP port and the safe default. You can change it — set LocalPort on the server and RemotePort on the client to match — but avoid the reserved ports (20, 21, 25, 80, 102, 123, 5001, 34962–34964), especially 102, which is the S7 protocol port.
Quick reference
- Client =
MB_CLIENT, opens the connection,ActiveEstablished= TRUE, pulseREQ. - Server =
MB_SERVER, waits,ActiveEstablished= FALSE, watchNDR/DR. - Connection =
TCON_IP_v4,ConnectionType= 11, uniqueID, port 502. - MB_MODE = 1xx names the function code with raw addresses; 0/1 uses classic 4xxxx addresses.
- MB_UNIT_ID = set it to the device’s real ID (often 1), not the default 255.
- Status 7xxx = in progress;
DONE+STATUS0 = success; 8xxx = real error.
Get the connection structure and the MB_MODE mapping right and the rest of Modbus TCP on the S7-1500 is routine. The blocks are reliable once they’re fed clean parameters — most of the pain lives in those two places.
