Resolving MB_MODE in MB_CLIENT for Modbus TCP on S7-1200

David Krause18 min read
ModbusSiemensTroubleshooting
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: MB_CLIENT on SIMATIC S7-1200 for Modbus TCP

The MB_CLIENT instruction on SIMATIC S7-1200 CPUs provides Modbus TCP client functionality when the CPU is connected to a Modbus TCP server via the PROFINET interface. The block ships with the standard TIA Portal library and has been present, with parameter changes, from the original S7-1200 library through the V4.0 libraries and into the V2.1/V3.x libraries for the S7-1200 G2 platform used in TIA Portal V20/V21.

Three input parameters define every Modbus transaction: MB_MODE, MB_DATA_ADDR, and MB_DATA_LEN (the input pin is named DATA_LEN on the older V2.x library; semantics are identical). The slave's behaviour and the on-the-wire function code are driven entirely by these three values, along with the DATA_PTR address. Misconfiguring any of them produces a successful TCP exchange with clean status, but the application sees garbage. The garbage is almost always one of three patterns: 16#03, 16#FF, or a stuck value. This article documents the exact root cause of a stuck read of 16#03 from a single coil at Modbus address 1004, the mapping between MB_MODE and the Modbus function codes, and a verification procedure that catches address and length mismatches at commissioning.

Problem: Reading 16#03 Instead of 1 from Modbus Address 1004

A SIMATIC S7-1200 CPU programmed with TIA Portal V14 was configured as a Modbus TCP client to poll a single read/write coil at Modbus register address 1004. The slave's own documentation described the register as:

1004 - Single Coil R/W - Read flag: 1 = new transponder detected, 0 = flag reset

Symptoms reported in the field:

  • MB_CLIENT output DONE pulsed TRUE; ERROR stayed FALSE; STATUS returned 16#0000 on completion. No Modbus exception code was raised.
  • The data buffer passed to DATA_PTR contained the value 16#03 (decimal 3) regardless of the actual coil state on the slave.
  • Writing to the same register using MB_MODE = 1 with the same address worked correctly and the slave acknowledged the new state.
  • Reading with MB_MODE = 0 produced the 16#03 value but reading the slave directly with a Modbus scanner tool (or by toggling the coil from the slave's own configuration utility) showed the coil was actually 1 or 0 cleanly.

The clean status is the critical clue. The slave is responding, but the client is assembling bits from more than one coil into the same byte or word, OR-ing them together. The application logic sees a value of 3 and cannot tell which bit is the real one.

Root Cause: MB_DATA_LEN Set to 2 on a Single-Coil Read

The instance DB of the calling FB had MB_DATA_LEN = 2. With length 2 the instruction issues a Modbus Read Coils request for two consecutive bits starting at the requested address. In the field case that means reading coils 1004 and 1005 in one PDU. The slave packs the response as a single byte containing bit 0 = coil 1004 and bit 1 = coil 1005. If the neighbour coil (1005) is energised, the byte becomes binary 11 = decimal 3 = hex 16#03. If only coil 1004 is set, the byte is 10 = decimal 2 = hex 16#02. If both are 0, the byte is 0. The pattern the application sees is therefore a function of the entire pair of coils, not the single coil it intended to read.

Two contributing factors compounded the symptom:

  1. Oversized length. Reading or writing more registers than the application logic intends is the textbook cause of bit-OR results. Modbus function codes 01 (Read Coils), 02 (Read Discrete Inputs), 03 (Read Holding Registers), and 04 (Read Input Registers) all pack bits or words contiguously in the response payload. Any address the application does not need to read will still appear in the buffer and be visible to the code if the buffer is read at the wrong offset.
  2. Use of TEMP data for DATA_PTR. Temporary variables in S7-1200 FBs and FCs are re-initialised on every cycle. When MB_CLIENT is called inside an FB whose instance DB is not used for the receive buffer, the address passed to DATA_PTR can be invalidated between the trigger and the DONE condition. The on-the-wire transaction is still correct, but the buffer read by the application can be stale, zeroed, or interleaved with other TEMP storage. A dedicated instance data block (DB) for the receive buffer is the recommended pattern. The combination of an oversized length and a volatile buffer is what turned a simple read-length bug into a persistent 16#03 read.

MB_MODE Parameter Reference

MB_MODE selects the Modbus function code and the direction (read or write). The mapping has been stable from the original S7-1200 library through the V4.0 (S7-1200) and V3.x (S7-1500) libraries and into the V2.1/V3.x libraries for the S7-1200 G2 platform. Reference: MB_MODE, MB_DATA_ADDR and DATA_LEN parameters - S7-1200 - TIA Portal V21 documentation and MB_MODE, MB_DATA_ADDR and MB_DATA_LEN parameters - S7-1200 / S7-1500 - TIA Portal V20 documentation.

MB_MODE Direction Modbus Function Code Typical Use
0 Read 01, 02, 03, or 04 (selected by MB_DATA_ADDR range) Read Coils (0xxxx), Discrete Inputs (1xxxx), Holding Registers (4xxxx), or Input Registers (3xxxx) from the slave.
1 Write 05 or 06 Write a single coil (function 05) when MB_DATA_LEN = 1 and the address is in the 0xxxx range, or write a single holding register (function 06) when MB_DATA_LEN = 1 and the address is in the 4xxxx range.
2 Write 15 (0x0F) or 16 (0x10) Write multiple coils (15) or multiple holding registers (16). The number of elements is defined by MB_DATA_LEN.
123 Read/Write combined 23 (0x17) Read/Write Multiple Holding Registers. Reads MB_DATA_LEN words starting at MB_DATA_ADDR and writes MB_DATA_LEN words from the second half of the buffer in a single transaction.

Important: when the Modbus function 23 is used, the MB_MODE parameter must be exactly 123. The value is not arbitrary; it is the function-code selector for the transaction. Reference: MB_CLIENT instruction - S7-1200 manual collection.

MB_DATA_ADDR: Address Format and Slave Offset Rules

MB_DATA_ADDR carries the Modbus register number as entered in the project. The S7-1200 does not automatically prepend the Modicon 4xxxx / 3xxxx / 1xxxx / 0xxxx prefix and it does subtract 1 from the entered value before putting it on the wire. The convention used by the slave determines the value to enter. The S7-1200 documentation recognises four address families:

MB_DATA_ADDR Range (entered in project) Modbus Function Code Issued Access Type Element Width
00001 to 09999 01 (Read Coils) / 05, 15 (Write Coils) Coil (bit) data 1 bit per element
10001 to 19999 02 (Read Discrete Inputs) Discrete input (bit) data, read-only 1 bit per element
30001 to 39999 04 (Read Input Registers) Input register (word) data, read-only 16 bits per element
40001 to 49999 03 (Read Holding Registers) / 06, 16, 23 (Write Holding Registers) Holding register (word) data 16 bits per element

If the slave documentation numbers registers from zero (a common convention on Phoenix Contact, Wago, and many Chinese slaves), the value entered in MB_DATA_ADDR must be the slave's logical address plus 1. If the slave uses Modicon 5-digit addressing and the manual says "register 1004" with no prefix, it almost always means holding register 1004 and the project value must be 41004. Always verify with a packet capture on TCP port 502 before assuming the data is wrong at the application level. A Wireshark decode of the request PDU shows the exact function code and address the S7-1200 is sending.

MB_DATA_LEN: Why 1 Is Almost Always the Right Start

MB_DATA_LEN (input pin DATA_LEN on V2.x libraries) is the number of Modbus elements to read or write in a single transaction. For a single coil or single holding register, the correct value is 1. The element width depends on the function code selected by MB_DATA_ADDR:

  • For function 01/02 (coil / discrete), each element is 1 bit. MB_DATA_LEN = 1 reads 1 bit, MB_DATA_LEN = 8 reads 8 bits packed into 1 byte, MB_DATA_LEN = 16 reads 16 bits packed into 1 word.
  • For function 03/04 (register), each element is 1 word (16 bits). MB_DATA_LEN = 1 reads 1 word = 2 bytes, MB_DATA_LEN = 10 reads 10 words = 20 bytes.

The buffer at DATA_PTR must be large enough for the worst-case read or write. A safe formula is SIZE_OF(DATA_PTR) >= MB_DATA_LEN * element_width_in_bytes. For coils at length 1 through 8, a single byte is sufficient. For coils at length 9 through 16, a 2-byte WORD is the minimum. The slave packs the response LSB first; the bit at Modbus address N lands in bit N mod 8 of byte N div 8 of the response.

Setting MB_DATA_LEN to a value larger than the number of registers the slave exposes on that logical address produces the exact symptom seen in the field case: 16#03 = binary 11 = both bits 0 and 1 set, regardless of which one the application actually wants. There is no error, no exception, no STATUS code, just wrong data. The only way to catch this is to validate the byte count and the bits in the response against what the slave is supposed to hold.

DATA_PTR: Why a Dedicated DB Beats a TEMP Variable

MB_CLIENT writes the received Modbus data into the area starting at the address given by DATA_PTR. The instruction does not allocate this memory; the project must reserve it. Three patterns are common in the field and they behave differently:

Pattern Storage Class Behaviour with MB_CLIENT Recommendation
Instance DB of the calling FB Static, retained or non-retained Stable address for the full PLC cycle. Symbolic access available. Use this for any polled register, setpoint, or command word owned by one FB.
Global DB with named tags Static, shared Stable address. Visible in HMI and other blocks. Use this for HMI-visible data and for data shared between multiple FBs.
TEMP in the calling FB/FC Re-initialised each cycle Pointer can be stale after the first scan and may overlap other TEMP. Causes 16#03, 16#FF, or 0 corruption on multi-cycle polls. Do not use as DATA_PTR for MB_CLIENT.
Merkers (M area) Static Stable, but conflicts with HMI / other logic and survives power-cycle only if configured retained. Avoid for new projects; keep for legacy conversions only.

For the field case, the fix was to create a new instance DB called MB_Buffer_DB with a tag RxCoil : BOOL; and to wire MB_Buffer_DB.RxCoil to the DATA_PTR input of MB_CLIENT. With the new buffer plus MB_DATA_LEN = 1, the read became deterministic and matched the slave state bit-for-bit.

Step-by-Step Resolution

  1. Open the FB or FC that contains the MB_CLIENT instance in TIA Portal.
  2. Open the instance DB and locate the MB_DATA_LEN (or DATA_LEN) tag.
  3. Change MB_DATA_LEN to 1 for a single coil or single register transaction.
  4. Insert a new global DB (right-click Program blocks > Add new block > Data block). Name it MB_Buffer. Keep "Optimised block access" enabled (the S7-1200 default since firmware V4.0); symbolic DATA_PTR works on optimised blocks.
  5. In the new DB, create the data area. For a single coil use RxCoil : BOOL;. For a single holding register use RxReg : WORD;. For a 16-bit coil read use RxCoils : WORD; sized to 1 word per 16 coils.
  6. Wire MB_Buffer.RxCoil (or the appropriate tag) to the DATA_PTR input of MB_CLIENT.
  7. Compile and download to the CPU.
  8. Go Online and force a coil state on the slave using its configurator.
  9. Monitor MB_Buffer.RxCoil in the watch table. The tag must follow the slave state 1:1 with one cycle of lag, no bit-OR from neighbouring registers.

Verification Procedure: Wireshark Decode and Status Polling

After the configuration change, validate the entire read path with three checks. Each catches a different class of error.

  1. Status check. In a watch table, monitor MB_CLIENT.DONE, MB_CLIENT.ERROR, and MB_CLIENT.STATUS. DONE must pulse TRUE on completion, ERROR must stay FALSE, and STATUS must be 16#0000 on completion (or 16#7001 / 16#7002 while in progress).
  2. Data check. Toggle the slave coil from 0 to 1 and back. The buffer tag must transition 0 → 1 → 0 with a one-cycle lag. Any other pattern (3, 0xFF, stuck value) indicates the wrong address or wrong length is still selected.
  3. Wire check. Run Wireshark on the network segment with the display filter tcp.port == 502 and decode as Modbus/TCP. The request PDU function code must be 01 (read coils) for MB_MODE = 0 against a coil address. The response byte count, coil count, and byte values must match the slave state. A request PDU from the field case looks like 00 01 00 00 00 06 01 01 03 EB 00 02 (trans-id, proto-id, length, unit-id, function 01, address 0x03EB = 1003 on the wire, quantity 0x0002). The fact that quantity is 2 in the original request is the wire-level proof of the bug: the project requested two coils, and the slave correctly returned two coils. The S7-1200 is doing exactly what the project told it to do; the project is wrong.

Connection Setup: The CONNECT Parameter

MB_CLIENT takes a CONNECT input of type TCON_IP_V4. The S7-1200 maintains a fixed pool of PROFINET connections; an MB_CLIENT instance consumes one connection ID for the lifetime of the project. Common configuration pitfalls:

  • Duplicate connection ID. Two blocks cannot share the same connection ID; the second download will fail with a resource conflict.
  • Wrong TSAP. For S7-1200 to S7-1200 communication the TSAP_C is the partner's TSAP, but for Modbus TCP to a non-Siemens slave the TSAP is typically not used. Set both TSAP_C and TSAP_S to 16#0000 or follow the slave's documentation; an unrecognised TSAP can cause the slave to drop the connection.
  • Wrong port. The IANA-assigned Modbus TCP port is 502. Some slaves listen on a different port (e.g., 5000, 5005, or 8888); verify with the slave's documentation or a port scan.
  • Multiple Modbus clients on one CPU. The S7-1200 supports up to 8 active MB_CLIENT connections (subject to the CPU's open-communication-connection limit; firmware V4.0 and later allows 8, earlier firmware allows fewer). Each one needs its own connection ID and its own TCON_IP_V4 instance.

Multi-Register Reads and Byte Ordering

When MB_DATA_LEN > 1 on a holding-register poll (MB_DATA_ADDR in the 4xxxx range), the slave returns MB_DATA_LEN words in big-endian word order. The S7-1200 stores words in little-endian byte order on the wire, so the library swaps the bytes. If the application needs to feed a DWORD or REAL across two consecutive holding registers, the buffer must be a 4-byte tag and the application must read the buffer as a 32-bit type:

// MB_Buffer must be at least 4 bytes for a DWORD / REAL
MB_Buffer.TempReal : REAL; // bytes 0..3 = registers N and N+1
MB_Buffer.TxDword  : DWORD;

For a multi-word read of mixed types, the standard pattern is a structured DB with named fields at fixed offsets. Avoid reusing the same buffer for read and write simultaneously, because the second half of a function-23 (MB_MODE = 123) read overlaps the first half of the write payload.

Structured-Text Code Example

The following snippet shows a typical MB_CLIENT trigger block in TIA Portal V14-V20. It is the same logic that fixed the field case, written as a reusable FB pattern.

// FB "MB_Poller" - read 1 coil from slave address 1004
// DATA_PTR -> "MB_Buffer".RxCoil (BOOL, static instance DB)

IF "StartPoll" AND NOT "MB_Buffer".Busy THEN
    "MB_Buffer".REQ        := TRUE;
    "MB_Buffer".MB_MODE     := 0;          // Read
    "MB_Buffer".MB_DATA_ADDR := 1004;      // coil 1004 (Modicon 0xxxx)
    "MB_Buffer".MB_DATA_LEN  := 1;          // 1 bit
    "MB_Buffer".CONNECT     := "MyTcpConn";// TCON_IP_V4 instance
    "MB_Buffer".Busy         := TRUE;
END_IF;

IF "MB_Buffer".DONE THEN
    "MB_Buffer".REQ     := FALSE;
    "MB_Buffer".Busy    := FALSE;
    "CoilSeen"         := "MB_Buffer".RxCoil; // 0 or 1, never 3
    "PollCounter"      := "PollCounter" + 1;
END_IF;

IF "MB_Buffer".ERROR THEN
    "MB_Buffer".REQ    := FALSE;
    "MB_Buffer".Busy   := FALSE;
    "LastErrorStatus" := "MB_Buffer".STATUS; // e.g. 16#8380
END_IF;

The REQ input is edge-triggered. Calling MB_CLIENT with REQ held high runs the request exactly once and clears DONE on the next call. The standard pattern is to set REQ on a positive edge of a poll timer (e.g., 100 ms) and clear it when DONE or ERROR fires.

Library Version and Firmware Notes

The MB_CLIENT instruction exists in three library families on the S7-1200 platform:

  • Library versions V2.x to V3.x (original S7-1200). Supported on CPU firmware V1.0 through V4.x. The length input is DATA_LEN on the FBD pin; the internal instance DB tag is MB_DATA_LEN. This is the library used in the field case (TIA Portal V14).
  • Library version V4.0 and later (S7-1200) / V3.x and later (S7-1500). The length input was renamed to MB_DATA_LEN on the pin for consistency with MB_MODE and MB_DATA_ADDR. Behaviour is unchanged; only the symbolic name differs.
  • Library versions V2.1 and V3.x (S7-1200 G2 second-generation CPUs). Used for the S7-1200 G2 platform (TIA Portal V21 environment). Same parameter semantics as V4.0; same MB_MODE, MB_DATA_ADDR, and MB_DATA_LEN names.

Switching the project to a newer TIA Portal version does not require changing any MB_MODE, MB_DATA_ADDR, or MB_DATA_LEN values, only the symbolic name of the length input. Mixing V2.x and V4.x blocks in the same project is not supported; the FB signature is incompatible and TIA Portal will refuse the download. Reference: MB_MODE, MB_DATA_ADDR and MB_DATA_LEN parameters - TIA Portal V20.

Troubleshooting Matrix

Observed Symptom Most Likely Cause Fix
DONE = TRUE, ERROR = FALSE, value is 16#03 for a 1-bit read MB_DATA_LEN too large; reading bit 0 and bit 1 together Set MB_DATA_LEN = 1; verify buffer is sized for the function-code element width
DONE = TRUE, value stuck at 0 with no error Wrong address family (e.g., 1004 entered as 1004 instead of 41004 for a holding register) Add the 4xxxx / 3xxxx / 1xxxx / 0xxxx prefix; cross-check with a Wireshark Modbus/TCP decode
DONE = TRUE, value stuck at 16#FFFF or 16#FF TEMP DATA_PTR or pointer invalidated by the optimiser Move DATA_PTR to a static instance or global DB; reload the program
ERROR = TRUE, STATUS = 16#8380 Slave did not respond within the timeout; or wrong IP / port / slot Verify TSAP_C and TSAP_S, slave IP, port 502, and the connection ID; check the slave's connection limit
STATUS = 16#80C8 TCP connection broken or partner closed the socket Check the slave's maximum connection count; verify keep-alive; power-cycle the slave
Modbus exception 02 (Illegal Data Address) returned by the slave MB_DATA_ADDR outside the slave's address map; or wrong family prefix Read the slave's address map; confirm 4xxxx vs 3xxxx vs 1xxxx vs 0xxxx convention
Modbus exception 03 (Illegal Data Value) MB_DATA_LEN combined with MB_DATA_ADDR exceeds the slave's available range Reduce MB_DATA_LEN or move the start address to a valid block
Reads succeed once after restart, then 16#03 forever Slaves with TIA Portal V14 + early CPU firmware sometimes lose the connection state; or retain/initialise issue in the buffer DB Update CPU firmware to V4.2 or later; mark the buffer DB as non-retained with explicit initial values
STATUS = 16#80C9 Connection ID already in use by another block Assign a unique connection ID to each MB_CLIENT instance and rebuild all TCON_IP_V4 blocks
Wireshark shows the request, no response, then STATUS = 16#80C8 Slave firewall or VLAN dropping the packet; or slave is busy and discards the request Disable the slave's firewall for commissioning; verify the slave is not in configuration mode; check the round-trip time

Field-Commissioning Checklist

  1. Confirm the CPU firmware is V4.0 or later. Older V1.x firmware had limited Modbus TCP support and was missing function codes 15 and 16.
  2. Confirm the project uses one consistent MB_CLIENT library version. Avoid mixing V2.x and V4.x blocks in the same project; the FB signature is incompatible.
  3. Reserve the Modbus connection: use one of the S7-1200's open PROFINET connections, not the HMI connection. HMI connections on a Comfort Panel and MB_CLIENT connections on the CPU share the same connection pool.
  4. Define DATA_PTR in a static DB. Do not use TEMP or M area for the receive buffer.
  5. Set MB_DATA_LEN = 1 for the first test, even if the final application needs 16 or 32 elements. Validate the single-element case, then scale up.
  6. Watch Wireshark on port 502 to confirm the request and response match the project values.
  7. After the first transaction is clean, increase MB_DATA_LEN in steps and re-verify. Watch for Modbus exception 02 (illegal data address) when the length crosses a slave block boundary.
  8. Document the chosen MB_MODE, MB_DATA_ADDR, MB_DATA_LEN, slave IP, port, and TSAP in the project comments. Future maintenance engineers will not have the field context.
Safety note: a stuck-at-1 or stuck-at-0 read caused by a misconfigured MB_DATA_LEN can defeat safety interlocks that depend on a single coil. Validate every Modbus-driven safety-relevant signal with an end-to-end test, not only a status check. A DONE = TRUE result is not the same as a correct result.

FAQ

Why does my S7-1200 MB_CLIENT return 16#03 when the slave coil is supposed to be 1?

MB_DATA_LEN is reading more than one coil and the neighbouring coil is set, so the two bits are packed as binary 11 = decimal 3. Set MB_DATA_LEN to 1 and move DATA_PTR to a static instance DB to fix the symptom. Reference: TIA Portal V20 MB_CLIENT documentation.

What is the value of MB_MODE for reading a Modbus register?

MB_MODE = 0 for any read. The actual Modbus function code (01, 02, 03, or 04) is selected automatically from the MB_DATA_ADDR range. For a holding register poll, use MB_DATA_ADDR between 40001 and 49999 with MB_MODE = 0.

What is MB_MODE = 123 used for on MB_CLIENT?

MB_MODE = 123 selects Modbus function 23 (Read/Write Multiple Holding Registers, 0x17). The first half of the buffer is the read target, the second half is the write payload, and both halves must be MB_DATA_LEN words long. Reference: MB_CLIENT instruction manual.

Can I use a TEMP variable for DATA_PTR on MB_CLIENT?

No. TEMP storage in S7-1200 FBs and FCs is re-initialised every scan, and the address handed to MB_CLIENT can become stale or overlap with other TEMP variables. Use a static instance DB or a global DB. This is the second root cause of the 16#03 corruption symptom in the field case.

Does MB_DATA_ADDR = 1004 mean register 1004 on the wire?

No. The S7-1200 sends the value of MB_DATA_ADDR minus 1 on the wire, and it does not auto-apply the Modicon 4xxxx / 3xxxx / 1xxxx / 0xxxx prefix. If your slave manual says "holding register 1004" and uses Modicon convention, enter 41004 in MB_DATA_ADDR. Confirm the on-the-wire value with a Wireshark capture on TCP port 502.

What is the difference between DATA_LEN and MB_DATA_LEN on MB_CLIENT?

None, semantically. The V2.x and V3.x libraries expose the input pin as DATA_LEN; the V4.0 (S7-1200) and V3.x (S7-1500) libraries and the V2.1/V3.x libraries for S7-1200 G2 renamed it to MB_DATA_LEN for consistency. Migrating the project to a newer TIA Portal version requires only a re-symbol mapping, not a value change.

Back to blog