Passing MB_DATA_PTR as ANY Variant in S7-1500 MB_CLIENT
The MB_CLIENT instruction in TIA Portal is the workhorse block for Modbus TCP/RTU master communication on the S7-1500 and S7-1200. One of the most common engineering questions is how to pass a single instance of MB_CLIENT a buffer pointer (MB_DATA_PTR) that can hold either BOOL data (for Modbus coils / 0xxxx / 1xxxx address space) or WORD / INT data (for Modbus holding registers / 4xxxx address space) depending on the active function code. Because MB_DATA_PTR is strongly typed, the buffer selection has to be done at runtime through an ANY (or in newer firmware VARIANT) pointer, and there are several platform-specific constraints that must be respected.
VARIANT signature is selected). The technique does not work on legacy S7-300/S7-400 CPUs because the communication blocks there expect a fixed POINTER or ANY with a defined DB number.1. Overview of MB_CLIENT and MB_DATA_PTR
The MB_CLIENT block is generated when you insert a Modbus client connection under Devices & Networks → [CPU] → Properties → Communication → Modbus client. TIA Portal places an instance DB (for example MB_CLIENT_DB) and exposes the following inputs:
| Input | Type | Description |
|---|---|---|
REQ |
BOOL |
Rising edge triggers one transaction. |
MB_ID |
WORD |
Connection ID (matches the configured connection). |
MODE |
BYTE |
0 = read, 1 = write/read; bit 7 selects word vs bit mode. |
DATA_ADDR |
WORD |
Modbus start address. |
DATA_LEN |
WORD |
Number of coils or registers. |
DATA_PTR |
VARIANT / ANY
|
Pointer to the application buffer. |
DONE |
BOOL |
Set for one cycle on success. |
BUSY |
BOOL |
TRUE while the request is active. |
ERROR |
BOOL |
TRUE on fault. |
STATUS |
WORD |
Detailed status / error code. |
The signature of MB_DATA_PTR changed with TIA Portal V18: prior to V18 the input is declared VARIANT for S7-1500 and ANY for S7-1200. With V18 and later, both CPU families accept a VARIANT, which makes the polymorphism easier to handle from SCL. See the official TIA Portal documentation at Applications for pointers in the comparison S7-1200 / S7-1500 for the runtime semantics of VARIANT.
2. Prerequisites
- CPU S7-1500 (any firmware ≥ V1.8) or CPU S7-1200 (firmware ≥ V4.2 for
VARIANT, ≥ V4.0 with limitations). - TIA Portal V16 or later (V18+ recommended for full
VARIANTpolymorphism). - Modbus client connection configured under the CPU properties (TCP/RTU depending on CM/CP).
- One global data block (optimized or non-optimized) to host the
BOOLandWORDbuffers. Symbolic access is recommended. - Familiarity with the ANY/VARIANT pointer structure (byte offset, bit offset, area reference, DB number, length).
3. Why Conditional MB_DATA_PTR Is Required
A Modbus master reads two fundamentally different address spaces:
| Modbus object | FC code | Address space | Native PLC data type |
|---|---|---|---|
| Coil | 01 / 05 / 15 | 0xxxx | BOOL |
| Discrete Input | 02 | 1xxxx | BOOL |
| Holding Register | 03 / 06 / 16 | 4xxxx |
WORD, INT, REAL
|
| Input Register | 04 | 3xxxx |
WORD, INT
|
MB_CLIENT packs each coil into one bit and each register into one word. The block itself does not convert data, so the application buffer must match the data type of the FC selected in MODE. When the same master is used to talk to a drive that exposes both coils (digital outputs) and registers (setpoints / actual values), the buffer must switch between BOOL[] and WORD[] at runtime. Because SCL is strongly typed, you cannot assign a BOOL variable directly to a WORD-typed input. The compiler error you will see is "Incompatible types for input DATA_PTR: cannot convert BOOL to WORD" or runtime error "Access to object via ANY pointer is invalid" when the call is built.
4. The ANY Pointer Solution (S7-1500 Only)
The classic S7-1500 approach is to build a local ANY tag at runtime that targets either the BOOL buffer or the WORD buffer, and then pass that ANY to MB_DATA_PTR. The compiler accepts an ANY against a VARIANT-typed input as long as the source DB is symbolically referenced.
An ANY pointer on S7-1500 has the following 10-byte layout:
| Byte | Content | Example for P#DB10.DBX0.0 BYTE 20
|
|---|---|---|
| 0–1 | 10 hex (always) for S7-1500 DB | 16#10 16#00 |
| 2–3 | Length in bytes | 16#14 16#00 (20 dec) |
| 4–5 | DB number | 16#0A 16#00 (DB 10) |
| 6 | Area / memory class (16#84 = DB) | 16#84 |
| 7–8 | Byte offset (32 bit, low/high) | 16#00 16#00 16#00 |
| 9 | Bit offset (0–7) | 16#00 |
SCL exposes the shorthand P#<DB>.<Tag> which the compiler fills in automatically. You can also use P#<DB>.<Tag> BYTE n to force an explicit byte length, which is helpful for non-optimized DBs.
4.1 Building the ANY pointers
For an optimized DB (recommended) the symbolic name is enough:
// Local tags in the calling FB
VAR
anyCoilBuf : ANY; // Pointer to the BOOL coil buffer
anyWordBuf : ANY; // Pointer to the WORD register buffer
END_VAR
Then, depending on the required Modbus function:
IF #bReadCoils THEN
#anyCoilBuf := P#"ModbusData".CoilBuffer; // ARRAY OF BOOL
ELSE
#anyWordBuf := P#"ModbusData".WordBuffer; // ARRAY OF WORD
END_IF;
For a non-optimized DB the syntax sometimes requires the explicit BYTE n qualifier so the loader can resolve the offset:
#anyCoilBuf := P#"ModbusData".CoilBuffer BYTE 2; // 16 coils = 2 bytes
BOOL array, n must be the number of bytes the array occupies, not the number of coils. Use (number_of_coils + 7) / 8.5. VARIANT Pointer in TIA Portal V18 and Later
TIA Portal V18 introduced a unified VARIANT-typed MB_DATA_PTR for both S7-1500 and S7-1200. A VARIANT is a self-describing pointer: it carries not only the address but also the data type, length, and the area reference. This means the runtime can detect a type mismatch (e.g., passing a BOOL array where the block expects a WORD) and return STATUS = 16#8381 rather than crash.
The official Siemens documentation at TIA Portal V20 — Variant for S7-1200 / S7-1500 summarises the rules:
-
VARIANTaccepts any elementary, structured, UDT, FB, or array type as well asANY. - Type checks happen at the call site (compiler) and again at runtime.
- For FBs that only support
ANY, you can still feed aVARIANTbecause theVARIANTis implicitly converted.
In practice this means that with V18+ you can pass the symbolic tag name directly without building an ANY:
IF #bReadCoils THEN
"MB_CLIENT_DB".DATA_PTR := "ModbusData".CoilBuffer;
ELSE
"MB_CLIENT_DB".DATA_PTR := "ModbusData".WordBuffer;
END_IF;
6. Implementing Conditional Buffer Selection in SCL
Below is a complete, copy-and-adapt pattern for a flexible Modbus master. The instance of MB_CLIENT is created once and reused. Two buffers live in a separate global DB ModbusData.
6.1 Data block
DATA_BLOCK "ModbusData" NON_OPTIMIZED
{ S7_Optimize_Access := 'FALSE' }
VERSION : 0.1
STRUCT
CoilBuffer : ARRAY[0..31] OF BOOL; // 32 coils = 4 bytes
WordBuffer : ARRAY[0..31] OF WORD; // 32 holding registers
RealBuffer : ARRAY[0..15] OF REAL; // 32 registers interpreted as REAL
END_STRUCT;
END_DATA_BLOCK
6.2 Control block
FUNCTION_BLOCK "FB_ModbusMaster"
{ S7_Optimize_Access := 'TRUE' }
VERSION : 0.1
VAR_INPUT
bExecute : BOOL; // Rising edge starts a request
bModeCoils : BOOL; // TRUE = read 1-bit, FALSE = read 16-bit
wModbusAddr : WORD; // 0..65535
wLength : WORD; // # coils or # registers
END_VAR
VAR_OUTPUT
bDone : BOOL;
bBusy : BOOL;
bError : BOOL;
wStatus : WORD;
END_VAR
VAR
mbClient : MB_CLIENT;
anyCoil : ANY;
anyWord : ANY;
END_VAR
BEGIN
// ---- Step 1: Build the ANY pointer before the call ----
IF #bModeCoils THEN
#anyCoil := P#"ModbusData".CoilBuffer;
ELSE
#anyWord := P#"ModbusData".WordBuffer;
END_IF;
// ---- Step 2: Force a positive edge on REQ only when idle ----
#mbClient(REQ := #bExecute AND NOT #mbClient.BUSY);
// ---- Step 3: Forward the active pointer to MB_DATA_PTR ----
IF #bModeCoils THEN
"MB_CLIENT_DB".DATA_PTR := #anyCoil;
ELSE
"MB_CLIENT_DB".DATA_PTR := #anyWord;
END_IF;
// ---- Step 4: Map outputs ----
#bDone := #mbClient.DONE;
#bBusy := #mbClient.BUSY;
#bError := #mbClient.ERROR;
#wStatus := #mbClient.STATUS;
END_FUNCTION_BLOCK
6.3 Flow diagram
7. Optimized vs Non-Optimized DB Considerations
| Aspect | Optimized DB (default S7-1500) | Non-optimized DB (S7-1200 or classic block) |
|---|---|---|
| Symbolic access | Required, no absolute address | Supported, but symbolic preferred |
P#"DB".Tag usage |
Direct assignment works | May need BYTE n suffix |
RET_VAL of READ_DBL style |
Compiler builds ANY at runtime | Compiler builds ANY at compile time |
| Reset to initial values | Per-tag, byte-aligned | Whole-DB, byte-aligned |
| Watchdog safety | Can shadow DATA_PTR during OB1 cycle |
No shadowing, pointer is fixed |
P#"DB".Tag at compile time. When the buffer DB is non-optimised, every tag occupies a contiguous area starting at the DB base; offsets are stable but the absolute byte/bit offset has to be embedded in the ANY pointer. Always switch on "Optimized block access" for the buffer DB on S7-1500.
8. Alternative: Two MB_CLIENT Instances
For engineers that prefer a more explicit topology, two MB_CLIENT instance DBs can be instantiated—one bound to the coil buffer and one bound to the word buffer. Each request then becomes a call to the matching instance. The advantage is that the compiler can fully type-check the connection between DATA_PTR and the buffer; the disadvantage is the loss of fine-grained BUSY/DONE arbitration when both calls are needed in the same OB1 cycle.
// Two-instance approach
IF #bModeCoils THEN
"MB_CLIENT_DB_Coil"(REQ := #bExecute,
DATA_PTR := "ModbusData".CoilBuffer);
ELSE
"MB_CLIENT_DB_Word"(REQ := #bExecute,
DATA_PTR := "ModbusData".WordBuffer);
END_IF;
The same instance DB can also be called twice in a row as long as REQ is not re-triggered while BUSY = 1. Toggling the instance DB at runtime is not allowed because the connection parameters are bound at compile time.
9. Verification and Diagnostics
- Compile check – Compile the project. The compiler must not raise "Incompatible types for input DATA_PTR". If it does, the local ANY pointer was never assigned before the call.
-
Watch table – Place
MB_CLIENT_DB.DATA_PTRin a watch table. Confirm it points to the correct buffer area after the IF/ELSE branch. -
Online status – Trigger a coil read (FC 01) and confirm that
ModbusData.CoilBuffer[0]reflects the remote coil value. Trigger a register read (FC 03) and confirmModbusData.WordBuffer[0]. - Diagnostic buffer – Open the online & diagnostic view of the CPU and check for entry "Communication error" with STATUS code 16#8381 (data type mismatch). If you see this, the FC selection does not match the buffer type.
-
Trace – Record
MB_CLIENT_DB.REQ,.BUSY,.DONE,.STATUSand the buffer pointer to verify sequencing.
10. Common Fault Codes and Errors
| STATUS (hex) | Meaning | Typical cause | Remedy |
|---|---|---|---|
| 16#7000 | No request active | Idle state, normal | No action |
| 16#7001 | Request running | MB_CLIENT is busy | Wait for DONE |
| 16#8381 | Data type mismatch | FC = 01/02 but DATA_PTR is WORD, or vice versa | Rebuild ANY pointer, ensure FC matches buffer |
| 16#80A1 | Area length error | DATA_LEN exceeds buffer size | Reduce DATA_LEN or enlarge buffer |
| 16#80B1 | Pointer invalid | ANY pointer was never assigned | Initialize ANY before call |
| 16#8181 | Connection not configured | MB_ID does not match the configured connection | Verify Modbus connection in Devices & Networks |
| 16#8188 | Modbus exception 02 | Illegal data address from slave | Check DATA_ADDR and slave address map |
| 16#8189 | Modbus exception 03 | Illegal data value | Check DATA_LEN vs. slave capacity |
MB_CLIENT expects. For example, calling with DATA_PTR := "DB".BoolTag while MODE requests word data will trigger this diagnostic. The fix is to assign the correct ANY pointer before each call.11. Reference Application: Modbus RTU Between S7-1500 and SINAMICS V20
The Siemens support entry 63696870 — S7-1200/S7-1500 connecting to SINAMICS V20 via Modbus RTU — contains a complete project, including a master implementation that uses MB_CLIENT with multiple buffers. The example illustrates the use of two separate instance DBs to drive a V20 drive (registers for setpoints and control words, coils for digital I/O). The same pattern transfers directly to a generic Modbus TCP/RTU master.
12. Notes on SCL Syntax for ANY Pointers
| Form | Use case |
|---|---|
P#"DB".Tag |
Optimized DB, automatic length |
P#"DB".Tag BYTE n |
Non-optimized DB, explicit length |
P#M0.0 BYTE 100 |
Bit memory area |
P#I0.0 WORD 4 |
Process input area |
P#Q0.0 BYTE 1 |
Process output area |
P#DB100.DBX0.0 BYTE 50 |
Absolute DB access |
P#DB100.DBX0.0 on optimised blocks. The compiler will refuse the assignment because the optimised block does not expose absolute offsets. Stick to symbolic references whenever the buffer DB is optimised.13. Field-Commissioning Checklist
- Confirm the Modbus slave address map: coils 0xxxx, inputs 1xxxx, registers 3xxxx/4xxxx.
- Define two buffer tags of matching types in a global DB; verify the buffer byte count matches
DATA_LEN. - Build the
ANYpointer in a function block before invokingMB_CLIENT; never inside an asynchronous OB. - Edge-trigger
REQonly whenBUSY = 0. - Capture
STATUSon everyDONEandERRORcycle; log it. - Use the trace to validate pointer transitions; confirm
DATA_PTRswings between the coil and word areas exactly when the FC changes. - Run a stress test with 1000+ transactions at the planned baud rate / TCP cycle time to surface any watchdog or buffer overflow issues.
14. FAQ
Can I use VARIANT instead of ANY on S7-1500?
Yes. With TIA Portal V18 and later, MB_CLIENT.DATA_PTR is declared VARIANT, which accepts both ANY and any direct symbolic tag. The runtime performs an additional type check and returns STATUS = 16#8381 on mismatch, which is safer than the classic ANY-only behaviour. Older projects can be migrated by retyping the input or wrapping the call in a multi-instance FB.
Why does the conditional ANY pointer technique not work on S7-1200 (firmware V4.0)?
S7-1200 firmware V4.0 only accepts POINTER-typed buffer inputs in the Modbus blocks; runtime ANY construction was added in V4.2 together with VARIANT support. On V4.0 you must either upgrade the CPU to V4.2+ or use two pre-configured instance DBs, one per data type.
Do I need two separate MB_CLIENT instances to read coils and registers?
No, a single instance is sufficient. The instance only holds the connection parameters; the buffer is selected per transaction through DATA_PTR. Make sure REQ is re-triggered only after BUSY returns to 0 and STATUS has been captured.
How do I fix the runtime error "Access in invalid" on MB_DATA_PTR?
This message means the buffer tag you assigned to DATA_PTR has the wrong data type for the active FC (coil vs register). Build a local ANY for each buffer type, assign the matching one inside an IF/ELSE block, and pass that local ANY to DATA_PTR. After the assignment recompile and watch the value with a watch table.
What happens if the buffer DB is optimised and I use absolute P# syntax?
The compiler rejects the assignment because optimised blocks do not expose absolute byte/bit offsets. Use the symbolic form P#"ModbusData".CoilBuffer or the direct symbolic tag, and enable "Optimized block access" on the buffer DB. The length will be inferred from the tag declaration.