S7-1500 Global DB to Instance DB: Mixed Data Type Transfer

David Krause27 min read
SiemensTIA PortalTutorial / How-to
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

Problem Definition: Mixed-Type Data Transfer Between Global and Instance DBs on S7-1500

On S7-1500 CPUs (and on S7-1200 V4.0 and later, since the same instruction set is shared), moving a contiguous slice of data from a global data block (DB) into an instance DB of a function block (FB) becomes non-trivial as soon as the source area contains mixed data types (BOOL, INT, DINT, REAL, WORD, BYTE) and the destination structure uses a different layout. The classical S7-300/S7-400 pattern that paired BLKMOV with a hand-built 10-byte ANY pointer in STL has no direct equivalent in TIA Portal once optimized block access is enabled, which is the default for any new DB created in current TIA Portal versions.

This is a recurring engineering problem for anyone building a Modbus TCP or Modbus RTU gateway on top of the MB_CLIENT instruction. A typical slave exposes a holding-register area that mixes 16-bit, 32-bit, and floating-point values on the same register map. The gateway must deposit the raw register payload (read as an ARRAY OF BYTE or ARRAY OF WORD by MB_CLIENT) into a known location of a global DB, and then expose a typed view of that data through the FB instance DB so the application logic, the HMI, and the OPC UA server can read symbolic members. The same pattern appears in MQTT gateways, vendor-specific serial protocols, and any handler where the wire format is bytes/words and the application format is a structured type.

Key constraint: The MB_CLIENT instruction reads or writes a contiguous block of holding registers as a 16-bit register image. To re-interpret that register image as INT, DINT, REAL, or BOOL, the gateway must perform a typed copy out of the wire image. MOVE_BLK_VARIANT cannot do this reinterpretation because it requires identical source and destination element types, and POKE_BLK cannot write into instance DBs that use optimized block access. BLKMOV (LAD/FBD legacy form) and the SCL MOVE_BLK are still available in TIA Portal but require the source and destination to be the same element type, or, for the legacy BLKMOV, require non-optimized access so that an absolute byte offset exists.

Prerequisites and Engineering Environment

To reproduce the patterns in this article you need the following:

  • STEP 7 (TIA Portal) V18 or later, with the S7-1500 CPU support package installed. The instruction behaviour described here matches the unified TIA Portal generation that ships BLKMOV, MOVE_BLK, MOVE_BLK_VARIANT, PEEK, POKE, Variant, and Serialize/Deserialize as standard instructions.
  • One S7-1500 CPU (any current family) or an S7-PLCSIM V18+ instance for offline validation.
  • The MB_CLIENT instruction, found in the "Communication > Other" palette of the instruction tree. MB_CLIENT and MB_SERVER are part of the standard TIA Portal instruction set; no additional library is required.
  • A global DB configured as the Modbus register image. The data type of the image is typically ARRAY[0..N] OF BYTE or ARRAY[0..N] OF WORD, sized to the largest single slice the gateway must read.
  • A function block with an instance DB that defines the typed view of the register area. The typed view is typically a UDT so the same definition can be reused across multiple instances.
  • A working understanding of the "Optimized block access" block attribute, the difference between symbolic and absolute addressing on S7-1500, and the IEC type-cast functions (WORD_TO_INT, DWORD_TO_DINT, DWORD_TO_REAL, etc.).

Before you start, read the official Siemens references that govern access modes and the canonical data transfer pattern:

Why Standard Instructions Fail on Optimized Blocks

Three of the most common "move a slice of DB" instructions on S7-1500 are not interchangeable when the source and destination have different element types, or when one of them is an instance DB with optimized access. The table below summarises the failure mode you will hit in TIA Portal V18+ and the documented workaround.

Instruction Mixed element types? Optimized instance DB destination? Workaround if it fails
BLKMOV (LAD/FBD legacy form) No — source and destination element type must match (S7-300/400 semantics). No — requires an absolute byte offset, which optimized access removes. Use MOVE_BLK_VARIANT with same type, or build a Variant pointer in SCL and read element-by-element.
MOVE_BLK (SCL) No — same as BLKMOV, same-type elements only. Yes — operates via symbolic addressing. Use a temporary ARRAY OF BYTE / WORD and apply type casts in SCL.
MOVE_BLK_VARIANT (S7-1500, TIA Portal V13+) No — the COUNT and SRC/DST element types are validated at compile time and must be the same. Mixed BOOL/INT/REAL is not permitted. Yes — symbolic, works on optimized instance DBs. Stage the data into a same-type temporary (for example, ARRAY OF BYTE) and apply a type-casting layer in SCL.
POKE_BLK (S7-1500, TIA Portal V13+) No — writes a raw byte image, but the destination must be a global DB or a non-optimized area. No — TIA Portal V18+ rejects POKE_BLK targets in optimized instance DBs because the absolute offset is not exposed. Use PEEK/POKE on a global shadow DB and copy element-by-element into the instance DB through symbolic access, or use Serialize/Deserialize.
Serialize / Deserialize (S7-1500, TIA Portal V14+) Yes — re-interprets a byte image as any structured PLC data type (UDT) and vice versa. Yes — both ends are symbolic. If the slice is not aligned to a full UDT, use a wrapper UDT and copy the relevant subset after the round trip.
PEEK / POKE (S7-1500, TIA Portal V13+) Yes — read/write a single BYTE/WORD/DWORD at an absolute offset. Partially — works only on the source side, and only if the target ARRAY OF BYTE in the global DB has a stable layout. Mirror the data into a global shadow DB and route element-by-element to the instance DB through symbolic writes.

The decisive attribute is the "Optimized block access" checkbox on the DB properties. When it is enabled (default for new DBs in current TIA Portal versions), the compiler is free to re-order the symbolic members and no stable byte offset is exposed. When it is disabled, the DB falls back to the S7-300/S7-400 layout with stable byte offsets, at the cost of losing some of the S7-1500 symbolic-only features (download-without-reinitialise, partial download of changed members, and certain HMI/OPC UA optimisations).

Engineering trade-off: Disabling optimized block access on a global DB purely to allow BLKMOV is rarely the right answer for a production gateway. You lose the ability to download individual DB members without re-initialising, and you force the S7-1500 symbolic memory layout to fall back to the absolute-offset layout. The patterns in the rest of this article keep the optimized attribute on both the global and the instance DB and resolve the type mismatch in code.

Solution 1 — BLKMOV with Non-Optimized Block Access

If you must use the legacy BLKMOV in LAD/FBD on S7-1500, the documented path is to disable optimized block access on both DBs and provide a contiguous ARRAY OF BYTE/WORD/DWORD slice at a known absolute offset. The S7-300/S7-400 STL pattern that built a 10-byte ANY pointer manually still works in TIA Portal SCL if you declare the source and destination as non-optimized and use the same element type.

Configuration steps

  1. Open the global DB properties in TIA Portal and clear the "Optimized block access" checkbox under "Attributes".
  2. Compile the project. The compiler now assigns a fixed byte offset to every member; record the start offset of the slice you want to copy.
  3. Open the FB that owns the instance DB and clear "Optimized block access" on the instance DB as well, so the instance DB also has a stable layout.
  4. Drop a BLKMOV block from the "Move operations" palette. Wire the source to the byte slice in the global DB and the destination to a same-size slice in the instance DB. Both slices must be the same elementary type (BYTE, WORD, or DWORD).
  5. Compile and download. Verify the slice was copied by reading back the instance DB member from the watch table.

Because BLKMOV requires identical element types, it is a viable answer only when the source area is already laid out as bytes/words. In a Modbus gateway where the holding register area is read into an ARRAY OF WORD or ARRAY OF BYTE, BLKMOV is a clean fit. In a mixed-type scenario where the source DB already mixes INT, DINT, REAL, and BOOL, BLKMOV is not the answer — the next three solutions are.

Legacy ANY pointer construction (for reference)

The 10-byte ANY pointer that S7-300/400 STL programmers built by hand is no longer the recommended path on S7-1500, but the structure is still valid for non-optimized blocks if you choose to use it. A VARIANT is the modern equivalent and is checked at compile time, which is why the discussion above recommends it over hand-built ANY pointers.

Byte offset Contents (10-byte ANY) Description
0..1 0x10, 0x01 Syntax ID for S7 ANY pointer.
2..3 Data type code 0x04 = WORD, 0x05 = INT, 0x06 = DWORD, 0x07 = DINT, 0x08 = REAL, 0x02 = BYTE.
4..5 Count Number of elements of the indicated type.
6..7 DB number 0 for non-DB areas.
8..11 Byte offset Byte offset of the first element within the DB.

On S7-1500, building this in SCL requires the area to be non-optimized so that the byte offset is known, and the result is a PVOID pointer that can be passed to a BLKMOV call. For most engineers, the safer route is to skip the ANY pointer entirely and use the Variant and Serialize/Deserialize patterns described below.

Solution 2 — MOVE_BLK_VARIANT with a Type-Conversion Layer

The robust S7-1500 answer is to keep optimized block access on, use MOVE_BLK_VARIANT for the actual block copy, and place a type-conversion layer in SCL between the wire format (ARRAY OF BYTE/WORD) and the typed view (a UDT or STRUCT). The conversion is element-by-element and uses the standard IEC type-cast functions.

The approach has three stages:

  1. MB_CLIENT reads or writes the raw Modbus register payload into a same-type ARRAY OF WORD or ARRAY OF BYTE in a global DB. No type mismatch here, because MB_CLIENT always treats the holding register area as 16-bit registers.
  2. The gateway FB receives, as an INOUT or as a Variant parameter, the symbolic reference to that global DB array, and the symbolic reference to its instance DB typed view.
  3. Inside the FB, an SCL loop walks the typed-view members, computes the byte offset of each member in the wire-format array, and copies the right number of bytes with PEEK (read) or POKE (write to a global shadow). For an instance DB that is optimized, use symbolic read/write for the destination and PEEK on the source.

SCL pattern for the conversion loop

The snippet below illustrates the canonical SCL shape. It assumes the wire format is an ARRAY[0..199] OF BYTE in the global DB and the typed view is a UDT of mixed members in the instance DB. Error checking is omitted for clarity; in production code add status returns for the Modbus transaction, the slice bounds, and the byte alignment of each member.

FUNCTION_BLOCK "Modbus_Register_Mapper"
VAR
    // Input: symbolic reference to the global DB register image
    iStartRegister : INT;     // Modbus start address of this slice
    iRegisterCount : INT;     // Number of 16-bit registers in this slice
    arrWireImage : ARRAY[0..199] OF BYTE;   // Symbolic reference resolved at FB call
    // Output: typed view stored in the instance DB
    stTypedView : "UDT_ModbusSlice";
END_VAR
BEGIN
    // 1. Compute the byte offset of the first register in the global wire image
    //    and the number of bytes to read.
    //    Each Modbus holding register occupies 2 bytes, big-endian on the wire.
    //    Total bytes = iRegisterCount * 2.
    //    Word-aligned copy from offset iStartRegister * 2.

    // 2. Walk the typed-view members and copy the right byte count
    //    from the global wire image into each member, with byte-swap
    //    for 16-bit and 32-bit values because Modbus is big-endian and
    //    S7-1500 is little-endian.

    // Example: read a 16-bit value (INT) at register offset 0
    stTypedView.nVoltage := WORD_TO_INT(
        SWAP_WORD(PEEK_WORD(area:=arrWireImage, byteOffset:=0))
    );

    // Example: read a 32-bit value (REAL) at register offset 2
    stTypedView.rCurrent := DWORD_TO_REAL(
        SWAP_DWORD(PEEK_DWORD(area:=arrWireImage, byteOffset:=4))
    );

    // Example: read a 32-bit signed integer (DINT) at register offset 6
    stTypedView.nCounter := DWORD_TO_DINT(
        SWAP_DWORD(PEEK_DWORD(area:=arrWireImage, byteOffset:=12))
    );
END_FUNCTION_BLOCK
Endianness: Modbus RTU and Modbus TCP are big-endian on the wire, but the S7-1500 stores multi-byte values in little-endian byte order. Any 16-bit or 32-bit value read out of the wire image must be byte-swapped before the IEC type cast is applied. The S7-1500 instruction set provides SWAP_WORD and SWAP_DWORD for exactly this purpose.

Why PEEK works on the source but POKE on the destination

PEEK and POKE accept an area pointer and an absolute byte offset. PEEK is valid on an ARRAY OF BYTE in a global DB whether the DB is optimized or not, because the array is a contiguous byte sequence and TIA Portal will resolve the offset symbolically at compile time. POKE is not valid on an optimized instance DB because the destination must be writable at an absolute offset. The pattern is therefore always "PEEK from the global wire image, write symbolically into the instance DB".

Solution 3 — Variant Pointer Construction in SCL

If the target is a generic typed view rather than a UDT, the cleanest pattern is to pass a VARIANT into the FB and let the FB inspect the variant at run time. The Variant type in S7-1500 carries the run-time information needed to compute byte offsets, and SCL provides the system functions to interrogate it.

System functions for Variant introspection

Function Purpose Typical use
TypeOf() Returns the data type code of a Variant at run time. Validate the destination type before copying.
CountOfElements() Returns the number of elements in an array Variant. Bound check for the slice length.
ElementaryTypeOf() Returns the elementary type code of a Variant. Decide whether to apply SWAP_WORD or SWAP_DWORD.
IS_NULL(Variant) Tests whether the Variant has been assigned. Defensive check at the top of the FB.

Building a Variant in SCL

A Variant can be assigned in SCL by simply assigning a typed value or symbol to a Variant variable. The compiler resolves the type information at compile time and the runtime system resolves the offset on first use.

FUNCTION_BLOCK "Modbus_Variant_Mapper"
VAR_INPUT
    vWireImage : Variant;        // Symbolic reference to the global DB array
END_VAR
VAR_INOUT
    vTypedView : Variant;        // Symbolic reference to the instance DB typed view
END_VAR
VAR
    iElementType : INT;
    iElementCount : INT;
END_VAR
BEGIN
    // Defensive checks
    IF IS_NULL(vWireImage) OR IS_NULL(vTypedView) THEN
        RETURN;
    END_IF;

    // Inspect the source Variant
    iElementType := ElementaryTypeOf(vWireImage);
    iElementCount := CountOfElements(vWireImage);

    // Dispatch on elementary type and copy element-by-element
    CASE iElementType OF
        WORD_TYPE: // 0x04 — word image, apply SWAP_WORD
            // ... element-by-element copy with byte swap
        BYTE_TYPE: // 0x02 — raw byte image, no swap
            // ... element-by-element copy without byte swap
        DWORD_TYPE: // 0x06 — double word, apply SWAP_DWORD
            // ... element-by-element copy with double-word swap
    END_CASE;
END_FUNCTION_BLOCK

The Variant pattern is the closest modern equivalent to the legacy ANY pointer. The compile-time type checking is weaker than with strongly-typed parameters, so the FB must validate the Variant at run time before dereferencing it. The advantage is that one FB can serve multiple typed views without changes to its interface.

Solution 4 — Serialize/Deserialize for Structured Types

If the slice is exactly aligned with a UDT — that is, you can define a UDT whose byte layout matches the relevant portion of the Modbus register map — the Serialize and Deserialize instructions are the cleanest answer. They treat the source/destination as a Variant and re-interpret a byte image as any structured PLC data type, or vice versa, without the same-type restriction that blocks MOVE_BLK_VARIANT.

When Serialize/Deserialize is the right tool

  • The slice has a known start and length that maps cleanly to a UDT boundary.
  • The destination is a typed UDT, not a hand-built STRUCT in the instance DB.
  • You are happy to define one UDT per Modbus slave register map (typical for vendor-specific register maps).

When it is the wrong tool

  • The slice does not align to a UDT boundary — for example, you need registers 100..115 of a 16-bit area where 100..107 hold an INT, 108..111 hold a REAL, 112..113 hold a DINT, and 114..115 hold bits.
  • The destination is a complex pre-existing structure with padding or HMI-relevant members you do not want to round-trip.
  • The slice is very small (one or two registers). In that case, PEEK/POKE on a global mirror is simpler.

SCL pattern for Serialize/Deserialize

FUNCTION_BLOCK "Modbus_Slave_Map"
VAR
    arrWireImage : ARRAY[0..255] OF BYTE;       // Global DB, filled by MB_CLIENT
    stMap : "UDT_VendorRegisterMap";            // UDT matching the register layout
    bSerializeTrigger : BOOL;
    bDeserializeTrigger : BOOL;
    iStatus : INT;
END_VAR
BEGIN
    // 1. Read 100 holding registers into arrWireImage[0..199]
    //    (omitted: the MB_CLIENT call)

    // 2. Deserialize the byte image into the typed UDT view
    Deserialize(
        REQ := bDeserializeTrigger,
        VARIANT := arrWireImage,
        POS := 0,
        DEST_VARIANT := stMap,
        RET_VAL := iStatus
    );

    // 3. Application logic uses stMap.nVoltage, stMap.rCurrent, etc.

    // 4. When writing back, Serialize the UDT into the byte image
    Serialize(
        REQ := bSerializeTrigger,
        VARIANT := stMap,
        POS := 0,
        DEST_VARIANT := arrWireImage,
        RET_VAL := iStatus
    );

    // 5. MB_CLIENT writes the byte image back to the slave
END_FUNCTION_BLOCK

The Serialize/Deserialize pattern keeps optimized block access on, works on instance DBs, and re-orders members freely because the byte layout is dictated by the UDT definition, not by the compiler's symbolic-layout choice. The cost is one UDT per Modbus slave register map and the byte-alignment discipline that comes with it.

Solution 5 — PEEK/POKE on a Global Shadow DB

When the destination is an instance DB with optimized block access and the source is a wire-format ARRAY OF BYTE/WORD in a global DB, the practical pattern is to mirror the instance-DB layout into a global "shadow" DB that has optimized access disabled, and to use PEEK/POKE on the shadow. POKE_BLK cannot write into the optimized instance DB directly, so the gateway then performs a symbolic write of each member from the shadow to the instance DB.

This is the pattern Siemens Support entry 49717873 effectively recommends when optimized block access is required on both ends. The performance cost is one extra copy and the maintenance cost is one extra shadow DB per typed view. For a small number of register slices the cost is negligible; for a high-frequency gateway with many slices, prefer Solution 2 or Solution 3.

SCL pattern for shadow DB

// Shadow DB: "DB_ModbusShadow", optimized access OFF, contains an
// ARRAY[0..255] OF BYTE that mirrors the wire image plus a layout
// matching the typed view at fixed byte offsets.
DATA_BLOCK "DB_ModbusShadow"
  STRUCT
      arrBytes : ARRAY[0..255] OF BYTE;
  END_STRUCT;
END_DATA_BLOCK

FUNCTION_BLOCK "Modbus_Shadow_Mapper"
VAR
    stShadow : "DB_ModbusShadow";
    stTypedView : "UDT_ModbusSlice";
END_VAR
BEGIN
    // 1. MB_CLIENT writes the holding register payload into stShadow.arrBytes
    //    (the global shadow DB is non-optimized, so MB_CLIENT is happy)

    // 2. Copy element-by-element from the shadow to the instance DB
    //    using symbolic access for the destination. The byte-swap and
    //    IEC type cast are the same as in Solution 2.
    stTypedView.nVoltage := WORD_TO_INT(
        SWAP_WORD(PEEK_WORD(area:=stShadow.arrBytes, byteOffset:=0))
    );
    stTypedView.rCurrent := DWORD_TO_REAL(
        SWAP_DWORD(PEEK_DWORD(area:=stShadow.arrBytes, byteOffset:=4))
    );
END_FUNCTION_BLOCK

Modbus MB_CLIENT Integration Pattern

The full FB that ties the patterns above together typically has the following interface and structure. MB_CLIENT in TIA Portal V18+ exposes a standard signature that includes REQ, MB_MODE (0 = read holding registers, 1 = write multiple registers), DATA_ADDR, DATA_LEN, DATA_PTR, CONNECT, plus the standard timeout and status outputs. Refer to the MB_CLIENT inline help in TIA Portal for the current signature in your project.

FB interface (sample)

Section Name Type Purpose
INPUT execute BOOL Trigger the slice read/write cycle.
INPUT mbMode USINT 0 = read holding registers, 1 = write multiple registers, 2/3 etc. per MB_CLIENT enum.
INPUT dataAddr UINT Modbus start address. MB_CLIENT uses 0-based addressing; +1 for the Modbus PDU if needed.
INPUT dataLen UINT Number of 16-bit registers in the slice.
INOUT wireImage VARIANT Reference to the global DB array that MB_CLIENT reads/writes.
INOUT typedView VARIANT Reference to the instance DB typed-view UDT.
OUTPUT done BOOL One-shot done flag.
OUTPUT busy BOOL Transaction in progress.
OUTPUT error BOOL Error flag.
OUTPUT statusId WORD MB_CLIENT status code.
OUTPUT statusText STRING Optional human-readable status.

End-to-end sequence in OB1

  1. MB_CLIENT reads or writes the holding register payload (raw bytes/words) into the global DB wire image, then sets done=true and busy=false.
  2. The mapping FB is called with execute=true, the symbolic reference to the wire image, and the symbolic reference to the instance DB typed view.
  3. The mapping FB computes the byte offset of each typed-view member in the wire image, applies the endianness byte-swap, performs the IEC type cast, and writes the result symbolically into the instance DB.
  4. The HMI, OPC UA server, and application logic read the typed view from the instance DB using symbolic access.
  5. If a write back to the slave is required, the reverse path runs: typed view → byte-swap → wire image → MB_CLIENT write.

Register Layout and Byte Arithmetic

Every operation in the FB reduces to byte arithmetic on the wire image. The formulas below are the ones to keep in mind when validating the slice against the typed view.

Quantity Formula Notes
Bytes per Modbus holding register Nbytes = Nregisters × 2 Each register is 16 bits on the wire regardless of the typed-view size.
Byte offset of register r in the wire image Offset(r) = r × 2 Big-endian, word-aligned.
Byte count of a typed-view member of size B bytes B = ceil(member size in bits / 8) BOOL packs to 1 byte, INT to 2, DINT to 4, REAL to 4, LREAL to 8.
Number of 16-bit registers consumed by a typed-view member Nreg = ceil(B / 2) REAL occupies 2 registers, DINT occupies 2, LREAL occupies 4.
Total register count of the slice Ntotal = Σ Nreg,i over all typed-view members Must equal DATA_LEN passed to MB_CLIENT.
Validation tip: In the FB, compute the expected total register count from the typed view at run time, compare it to the configured DATA_LEN, and raise a clear error if the two do not match. This catches the most common commissioning mistake — declaring a slice of 10 registers but mapping a typed view that actually consumes 12 — before the Modbus slave returns an exception code.

Worked example

Consider a slave that exposes a holding register area at base 40001 (Modbus address 0) with the following layout:

Register Modbus address Type on the wire Typed-view member
40001 0 INT (1 register) nVoltage : INT
40002..40003 1..2 REAL (2 registers) rCurrent : REAL
40004..40005 3..4 DINT (2 registers) nCounter : DINT
40006 5 INT (1 register) nStatus : INT
40007..40008 6..7 REAL (2 registers) rPower : REAL

Total register count: 1 + 2 + 2 + 1 + 2 = 8. DATA_LEN = 8. The slice in the global DB wire image occupies bytes 0..15. The byte offsets in the wire image for each typed-view member are:

  • nVoltage: bytes 0..1 (offset 0, 2 bytes)
  • rCurrent: bytes 2..5 (offset 2, 4 bytes)
  • nCounter: bytes 6..9 (offset 6, 4 bytes)
  • nStatus: bytes 10..11 (offset 10, 2 bytes)
  • rPower: bytes 12..15 (offset 12, 4 bytes)

The SCL code for the read path becomes:

// Wire image: arrWireImage[0..15] of BYTE in the global DB
// Typed view: stTypedView of UDT_ModbusSlice in the instance DB

stTypedView.nVoltage := WORD_TO_INT(
    SWAP_WORD(PEEK_WORD(area:=arrWireImage, byteOffset:=0))
);
stTypedView.rCurrent := DWORD_TO_REAL(
    SWAP_DWORD(PEEK_DWORD(area:=arrWireImage, byteOffset:=2))
);
stTypedView.nCounter := DWORD_TO_DINT(
    SWAP_DWORD(PEEK_DWORD(area:=arrWireImage, byteOffset:=6))
);
stTypedView.nStatus := WORD_TO_INT(
    SWAP_WORD(PEEK_WORD(area:=arrWireImage, byteOffset:=10))
);
stTypedView.rPower := DWORD_TO_REAL(
    SWAP_DWORD(PEEK_DWORD(area:=arrWireImage, byteOffset:=12))
);

The write path uses POKE_WORD / POKE_DWORD with the same byte offsets and reverses the byte-swap, then calls MB_CLIENT with mbMode = 1 to write the buffer back to the slave.

Verification and Diagnostics

Verification has three layers: compile-time, online with a watch table, and functional against the live Modbus slave.

Compile-time checks

  • Confirm that MOVE_BLK_VARIANT compiles only when source and destination element types match. If it does not, the compiler emits a type-mismatch error in the "Compile" tab of the inspector window.
  • Confirm that BLKMOV is available in the "Move operations" palette of the current TIA Portal version. If it has been removed in a future version, the compile will report "Unknown instruction".
  • Confirm that POKE_BLK is rejected by the compiler when the destination is an optimized instance DB. The diagnostic appears in the inspector as "Destination area not writable" or similar, depending on the TIA Portal version.
  • Confirm that the MB_CLIENT DATA_PTR accepts the Variant pointing into an optimized global DB. If the array is too small to hold DATA_LEN registers, the compile will report a length error.

Online checks (watch table)

  1. Force a single read cycle by setting the MB_CLIENT REQ bit to TRUE in a watch table. Watch the BUSY bit transition to TRUE and back to FALSE.
  2. Read back the wire image in the global DB. Confirm that the expected number of bytes has been written by the MB_CLIENT instruction.
  3. Read back the typed view in the instance DB. Compare each member to the corresponding registers in the wire image, accounting for endianness.
  4. Repeat for a write cycle. Verify the slave echoes back the expected values, or use a third-party Modbus master tool on a PC to poll the same registers and confirm the new values.

Functional checks against the live slave

Use a third-party Modbus master tool (Modbus Poll, CAS Modbus Scanner, pymodbus, or a libmodbus-based script) to poll the same register range and confirm the gateway is reading the expected values. The most common commissioning-time discrepancies are catalogued below.

Symptom Likely cause Fix
All values read as 0 MB_CLIENT connected to the wrong slave, wrong IP/port, or wrong DATA_ADDR. Verify the CONNECT parameter, the slave IP, the port (default 502 for Modbus TCP), and the address base.
Values appear byte-swapped Endianness fix-up skipped. SWAP_WORD / SWAP_DWORD missing before the IEC type cast. Add the appropriate SWAP instruction before each type cast.
REAL values are tiny non-zero numbers like 1.4E-45 Byte order issue or wrong byte offset. Often caused by reading a 32-bit REAL as two 16-bit words and casting individually. Re-check the byte-offset formula and confirm PEEK_DWORD is used for 32-bit members.
Values are correct in the wire image but wrong in the instance DB Symbolic write to the wrong instance DB member, or offset arithmetic off by one member. Walk the typed view in the watch table and compare member by member against the wire image.
MB_CLIENT reports an error status word Slave rejected the request or the connection failed. Read the MB_CLIENT inline help for the exact status code, and check the slave manual for the supported function codes at the requested address.
Modbus status non-zero on the wire Slave returned a Modbus exception (illegal data address, illegal function, slave device failure). Check the slave address map and the function code used by MB_CLIENT.

PLCSIM validation

Before connecting to a live Modbus slave, validate the gateway against a simulated slave in S7-PLCSIM. PLCSIM allows the mapping FB to be exercised against a known register image, and the byte-swap logic can be verified without a physical slave. The recommended sequence is:

  1. Create a small test program that pre-loads a global DB with known byte values (for example, 0x1234, 0x56789ABC, 0x7FFFFFFF).
  2. Run the mapping FB against the test wire image and verify that the typed view in the instance DB matches the expected INT / REAL / DINT values after the byte-swap.
  3. Reverse the test: load the typed view with known values, run the write path, and verify the wire image bytes are exactly what MB_CLIENT would send on the wire.
  4. Once the byte arithmetic is verified, connect to a real slave or to a third-party Modbus simulator.

Edge Cases and Field-Proven Caveats

These are issues that show up in commissioning and that the instruction help alone does not flag.

  • Modbus address base. Some slaves document register addresses in 1-based Modbus convention (40001 = first holding register). MB_CLIENT in TIA Portal uses 0-based addressing. The FB must translate between the two to keep the address arithmetic correct.
  • Byte count vs. register count. MB_CLIENT DATA_LEN is in 16-bit registers, but the global wire image is sized in bytes. If the wire image is an ARRAY OF WORD, the index is DATA_LEN; if it is an ARRAY OF BYTE, the index is DATA_LEN × 2.
  • Word alignment in the typed view. If the typed view mixes a 16-bit member followed by a 32-bit member, the byte-swap must be applied per member, not per register. A 32-bit member starting at an odd register offset on the slave side will arrive at a word-aligned offset in the wire image, but the typed view will consume 4 bytes from that point — make sure the byte arithmetic in the FB matches the typed-view layout, not the register layout.
  • BOOL packing. Modbus has no native bit type. A "coil" or single-bit status is typically returned as a 16-bit register with 0 or 1. The mapping FB must extract the relevant bit from the 16-bit word before assigning it to a BOOL member in the typed view.
  • STRING handling. Some Modbus slaves expose string registers as 16-bit words, one character per register (high byte) or two characters per register (ASCII packed). The mapping FB must convert the raw register image into a STRING member of the typed view explicitly; there is no automatic type cast for STRING from BYTE/WORD.
  • Multi-slice gateways. A single MB_CLIENT connection to a single slave can read or write only one contiguous register slice at a time. For a slave with multiple non-contiguous register areas, the gateway needs a state machine that issues multiple MB_CLIENT calls in sequence and accumulates the typed views across them.

Frequently Asked Questions

Does BLKMOV still work on S7-1500 in TIA Portal?

Yes. BLKMOV is still available in the "Move operations" palette in TIA Portal V18 and later, but it requires non-optimized block access on the source and destination DBs. For mixed-type data, prefer MOVE_BLK_VARIANT, Serialize/Deserialize, or PEEK/POKE on a global shadow DB so you can keep optimized block access on. See the official pattern in Siemens Support entry 49717873.

Why does MOVE_BLK_VARIANT reject my mixed-type copy?

MOVE_BLK_VARIANT validates the source and destination element types at compile time and requires them to be identical. It will not copy an ARRAY OF INT into an ARRAY OF REAL, nor a UDT into a different UDT. To re-interpret a byte image as a structured type, use Serialize/Deserialize instead, or apply PEEK/POKE on the wire image and write the typed view member-by-member with the appropriate byte-swap and IEC type cast.

Why does POKE_BLK refuse to write into my instance DB?

POKE_BLK writes a raw byte image at an absolute offset, and TIA Portal V18+ rejects POKE_BLK targets in optimized instance DBs because no stable absolute offset exists. Mirror the layout into a global shadow DB and use POKE there, then copy element-by-element into the instance DB through symbolic access, or use a Serialize/Deserialize round trip with a wrapper UDT that matches the wire layout.

Do I have to byte-swap values read out of a Modbus register?

Yes. Modbus RTU and Modbus TCP are big-endian on the wire, and the S7-1500 stores multi-byte values in little-endian byte order. Apply SWAP_WORD to 16-bit values and SWAP_DWORD to 32-bit values before the IEC type cast. Forgetting this swap is the single most common reason REAL values come back as denormal numbers such as 1.4E-45 instead of the expected floating-point value.

Can the global DB have optimized block access and still feed MB_CLIENT?

Yes. MB_CLIENT accepts a Variant pointing into an optimized global DB as the DATA_PTR. The optimization attribute affects how symbolic addresses are resolved, not whether MB_CLIENT can read or write the array. Keep optimized access on for the global DB and the instance DB, and resolve the type mismatch in SCL by combining PEEK on the source with symbolic writes into the instance DB, or by using the Serialize/Deserialize pattern with a UDT that matches the wire layout.

How do I compute the wire-image byte offset for a typed-view member?

Sum the byte sizes of all preceding typed-view members. BOOL = 1 byte, INT = 2 bytes, DINT = 4 bytes, REAL = 4 bytes, LREAL = 8 bytes. Then add the start-register offset of the slice, multiplied by 2, to translate from Modbus register address to wire-image byte offset. The total DATA_LEN passed to MB_CLIENT must equal the total register count of the slice, which is the sum of ceil(B/2) over all typed-view members.

Back to blog