S7-1200 Modbus REAL Word Swap Byte Order Conversion in TIA Portal

David Krause13 min read
S7-1200SiemensTutorial / 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

1. Problem Overview

When the S7-1200 reads a 32-bit floating-point value (REAL / IEEE 754 single precision) from a Modbus RTU or Modbus TCP slave, the received value often appears nonsense. The high and low 16-bit words are reversed: the register labeled as the least significant word contains the high byte of the float, and vice versa. The float itself is not corrupted; only the byte order in which the four payload bytes arrive at the PLC is reversed compared to how the S7-1200 stores REAL internally.

This symptom is dominant on instruments that follow the Modbus "word-swapped" or "32-bit float, byte and word swap" convention, including many heat meters (Kamstrup, Landis+Gyr, Itron), energy meters, and HVAC field devices. The slave transmits the IEEE 754 representation of the float with the high 16-bit word transmitted first (network byte order, big-endian word sequencing), while the S7-1200 expects the low byte of the REAL first in memory (little-endian byte layout). The byte order mismatch is two bytes wide, not one.

Note: The S7-1200 Swap instruction from the basic instruction set operates on WORD, INT, and DWORD. It does not accept REAL. The instruction set documentation explicitly states REAL is not supported as an input, because swapping the bit pattern of a float is not a meaningful IEEE 754 operation. The byte-swap must be performed on the four underlying bytes and the result re-interpreted as REAL.

2. Root Cause: IEEE 754 vs Modbus Word Order

REAL in STEP 7 follows the IEEE 754 single-precision layout. The S7-1200 stores the 32 bits in little-endian order: the least significant byte (LSB) is at the lowest memory address. A REAL occupies four consecutive bytes. When the value 0x42C80000 (= 100.0) is held in memory starting at %MD100, byte MB100 = 0x00, MB101 = 0x00, MB102 = 0xC8, MB103 = 0x42.

Modbus, on the other hand, delivers a 32-bit register pair in the order the slave was configured to emit. The three common variants are:

Variant Register order Byte order Most common on
Standard Modbus float (ABCD) Hi word first, then Lo word, big-endian bytes inside each word Big-endian word sequencing Modicon / Schneider legacy
Word-swapped (CDAB) Lo word first, then Hi word, big-endian bytes inside each word Word swap only Many heat meters, Kamstrup, ABB
Byte-swapped (DCBA) Lo word first, Lo byte first Full little-endian Some Chinese power meters
Reversed (BADC) Hi word first, Lo word, but each word is byte-swapped Byte swap inside words Older Allen-Bradley SLC

The instruction set of S7-1200 covers this with the SWAP block for WORD, which performs a full 16-bit byte swap, and a manual DWord construction approach for 32-bit data. Because REAL is not a permitted input, the canonical field-proven technique is to operate on the four bytes that make up the float, reorder them, and re-interpret the result.

3. Prerequisites

  1. CPU firmware V4.0 or later recommended. AT overlay on optimized blocks is supported from firmware V4.0 (for S7-1200) and V4.2 onward for full SCL overlay semantics. Refer to the S7-1200 Programmable Controller System Manual.
  2. TIA Portal V13 SP1 or later. The code below was developed and field-verified in TIA V13 / V15 / V16. See S7-1200 SCL Programming and Operating Manual.
  3. Modbus RTU master block MB_MASTER (instruction library "MODBUS") or Modbus TCP blocks MB_CLIENT / MB_SERVER. See S7-1200 Modbus RTU / TCP Manual.
  4. A function block (FB) with a static section, or a global DB declared with the non-optimized (or "with classic access") attribute so that byte-level AT overlay is permitted.
  5. Wireshark or the TIA online trace for verification.
Critical: AT overlay on a STRUCT or byte view inside a DB requires the DB to be non-optimized. If the DB is set to "Optimized block access" (default for new TIA Portal DBs), the compiler will reject the AT declaration with error 0x8092 ("The AT construct is not permitted for optimized data"). Disable optimization in the DB properties, or place the AT overlay inside a non-optimized section.

4. Solution Method 1: AT Overlay on a Non-Optimized DB

The cleanest method is to declare a four-byte array as an AT view over the same memory that already holds the raw receive buffer. The buffer is a byte array filled by the Modbus master block. The AT view re-interprets the same memory as a REAL after the bytes have been manually reordered.

DB layout (non-optimized):

DATA_BLOCK "DB_ModbusRaw"
{ S7_Optimized_Access := 'FALSE' }
AUTHOR : 'FO'
FAMILY : 'Modbus'
VERSION : 0.1
  STRUCT
   rxBuffer : ARRAY[0..255] OF BYTE;   // raw Modbus receive buffer
   rxCount  : INT;                     // valid byte count from MB_MASTER
  END_STRUCT;
END_DATA_BLOCK

In an FB, declare a temporary that reinterprets a slice of the buffer as a REAL after the bytes have been physically moved into the correct order:

FUNCTION_BLOCK "FB_HeatMeter"
VAR
   rawDword   : DWORD;        // raw 32-bit pattern as received
   rawBytes   : AT %MD0 : ARRAY[0..3] OF BYTE;   // pseudo - shown as concept
   swapped    : REAL;
END_VAR
BEGIN
   // Step 1: assemble the 4 received bytes into a DWORD using AT overlay
   // rawBytes[0] = first received byte (low address of the float on the wire)
   // rawBytes[3] = last received byte  (high address of the float on the wire)

   // Step 2: re-order the four bytes to IEEE 754 little-endian
   // The standard "word swap" only needs the two 16-bit halves exchanged.
   // rawBytes[0] <- rawBytes[2]
   // rawBytes[1] <- rawBytes[3]
   // rawBytes[2] <- rawBytes[0]  (use a temp to avoid overwrite)
   // rawBytes[3] <- rawBytes[1]
END_FUNCTION_BLOCK

5. Solution Method 2: AT Overlay in the FB Static Section

This is the most field-proven pattern. The static section holds the four bytes and an AT overlay as a REAL.

FUNCTION_BLOCK "FB_ModbusFloat"
VAR
   // Raw bytes as received from the slave (order: B0, B1, B2, B3)
   b0 : BYTE;
   b1 : BYTE;
   b2 : BYTE;
   b3 : BYTE;
   // AT overlay reinterprets the same memory as REAL
   rValue : AT %MD0 : REAL;   // note: %MD0 must point to the four bytes above
                              // The compiler enforces byte alignment.
END_VAR
BEGIN
   // rValue is now a synonym of the 4 bytes above.
   // The Modbus receive routine must write into b0..b3 in the order they arrive.
END_FUNCTION_BLOCK

The above does not perform a swap; it only declares the same memory twice. The byte-reordering is done either in the receive routine (move the wire bytes into b2, b3, b0, b1 instead of b0, b1, b2, b3) or in a separate swap FB. This pattern is what the S7-1200 SCL manual calls an "AT construct" and is the recommended approach for typed reinterpretation of memory.

6. Solution Method 3: SCL Slicing of a Byte Array

Slicing is fully supported in SCL for S7-1200. It allows extracting a single byte from a variable of a larger type. The example below uses slicing to read the four bytes of the raw 32-bit value and reassemble them in the correct IEEE 754 little-endian order before casting the result to REAL.

FUNCTION "FC_SwapModbusFloat" : VOID
VAR_INPUT
   rawByte0 : BYTE;   // first byte received from Modbus
   rawByte1 : BYTE;
   rawByte2 : BYTE;
   rawByte3 : BYTE;
END_VAR
VAR_OUTPUT
   result   : REAL;
END_VAR
VAR_TEMP
   dw        : DWORD;
   b0, b1, b2, b3 : BYTE;
END_VAR
BEGIN
   // "Word swap" (CDAB -> ABCD): swap the two 16-bit halves.
   b0 := rawByte2;   // B2  goes to byte 0 of the DWORD
   b1 := rawByte3;   // B3  goes to byte 1 of the DWORD
   b2 := rawByte0;   // B0  goes to byte 2 of the DWORD
   b3 := rawByte1;   // B1  goes to byte 3 of the DWORD

   // Build DWORD from the four bytes using SCL byte slicing
   dw.%B0  := b0;
   dw.%B1  := b1;
   dw.%B2  := b2;
   dw.%B3  := b3;

   // Cast DWORD pattern to REAL (re-interpret, no math)
   result := DWORD_TO_REAL(dw);
END_FUNCTION

The casting DWORD_TO_REAL is a bitwise re-interpretation: the compiler generates no arithmetic code, only a memory move. The S7-1200 SCL manual confirms that type conversion between bit-equivalent types (DWORD <-> REAL, INT <-> WORD) does not change the bit pattern. The result is the IEEE 754 value the slave originally transmitted.

7. Solution Method 4: PLC Tag Byte Access (Merkers, %M Area)

If the real-time data is held in the %M (Merker) area, you can directly address the individual bytes with the %MB prefix. The PLC tag table in TIA Portal can declare a REAL tag at %MD100 and you can manipulate the four %MB100..%MB103 bytes with simple byte MOVE instructions.

// Network 1: byte swap a float at %MD100 (word swap only)
// Source: 4 bytes already in %MB100..%MB103 in the wire order
// Target: same address, but the two 16-bit halves are exchanged
LAR1  P##src;          // not needed; shown for clarity
// Using absolute LAD/FBD moves:
MOVE  %MB102 -> tempMB0;
MOVE  %MB103 -> tempMB1;
MOVE  %MB100 -> %MB102;
MOVE  %MB101 -> %MB103;
MOVE  tempMB0 -> %MB100;
MOVE  tempMB1 -> %MB101;

This is the exact pattern confirmed in the field: "byte access to real when it is defined as a Plc tag in %M memory works perfect!" Direct byte MOVE works on the Merker area because the PLC tag table exposes both the DWORD view and the underlying byte views at the same address.

Important: Direct byte access inside a DB is not always possible because TIA Portal DBs default to optimized access. The %MB access pattern only works for PLC tags (Merker, inputs, outputs) or for non-optimized DBs with absolute addressing. The S7-1200 System Manual section "Differences between optimized and non-optimized data" details the rules.

8. Full SCL Example: Reading a Kamstrup-Style Heat Meter

The following example ties everything together. It reads holding register 0x0004 from a Modbus RTU slave, which returns the heat energy as a CDAB float (word-swapped IEEE 754), and converts it using the SCL slicing method.

FUNCTION_BLOCK "FB_HeatEnergy"
VAR CONSTANT
   MB_ADDR_ENERGY : WORD := 16#0004;   // holding register 4 (0x0004)
END_VAR
VAR
   mbInstance    : MB_MASTER;          // Modbus RTU master instance DB
   mbDone        : BOOL;
   mbError       : BOOL;
   mbStatus      : WORD;
   rxData        : ARRAY[0..3] OF BYTE; // receives 4 bytes (2 registers)
   energy_dw     : DWORD;
   energy_kWh    : REAL;
   swap_helper   : ARRAY[0..3] OF BYTE;
END_VAR
BEGIN
   // Issue Modbus request
   mbInstance(REQ  := TRUE,
              MB_ADDR := MB_ADDR_ENERGY,
              MODE   := 0,            // 0 = Read Holding Register
              DATA_ADDR := 0,         // offset into rxData is 0
              DATA_LEN := 2,          // 2 registers = 4 bytes = 1 REAL
              DONE => mbDone,
              ERROR => mbError,
              STATUS => mbStatus,
              DATA_PTR := rxData);

   IF mbDone AND NOT mbError THEN
      // Word-swap: the wire delivered B0 B1 B2 B3 where B0..B1 is the LOW word.
      // The S7-1200 expects the LOW byte of the float at the LOW memory address.
      swap_helper[0] := rxData[2];
      swap_helper[1] := rxData[3];
      swap_helper[2] := rxData[0];
      swap_helper[3] := rxData[1];

      // Cast the bit pattern to REAL
      energy_dw.%B0  := swap_helper[0];
      energy_dw.%B1  := swap_helper[1];
      energy_dw.%B2  := swap_helper[2];
      energy_dw.%B3  := swap_helper[3];
      energy_kWh     := DWORD_TO_REAL(energy_dw);
   END_IF;
END_FUNCTION_BLOCK

9. Verification

Verification must cover three layers: the raw Modbus wire data, the in-PLC byte order, and the resulting REAL value.

  1. Wire-level: Use a Modbus scanner (e.g., the free Modbus Poll or a Wireshark capture on the TCP variant) to read the same register pair. The scanner shows the float with the slave's native byte order. Compare the four hex bytes; the swap you apply in TIA must map the scanner's CDAB into ABCD.
  2. PLC tag monitor: In TIA Portal, add the four rxData bytes, the energy_dw DWORD, and the energy_kWh REAL to a watch table. Force the RX data with a known IEEE 754 value, for example 100.0 = 0x42C80000. After the swap, energy_dw should display 0x42C80000 (low byte first) and energy_kWh should read 100.0.
  3. Edge cases: Test with a negative value (-1.5 = 0xBFC00000), a denormal, and a value that exceeds the slave's actual range. The swap must not change sign, exponent, or mantissa, only the order they sit in memory.

A practical sanity check: if the raw bytes on the wire are 00 00 C8 42 (CDAB of 100.0), then after swap they should be 42 C8 00 00 (ABCD) in the REAL. 42C80000 in IEEE 754 is exactly 100.0.

10. Troubleshooting Matrix

Symptom Likely cause Fix
REAL reads as 0.0 but Modbus shows non-zero Byte order not swapped, or wrong swap variant Try both CDAB and BADC; confirm with Wireshark
REAL reads as a tiny number (~1e-39) and is sign-dependent Bytes reversed end-to-end (DCBA) Reverse all four bytes, not just word swap
REAL oscillates between 0 and a huge value Byte access on optimized DB rejected silently Disable DB optimization, or use the static-section AT overlay
Compiler error 0x8092 "AT construct not permitted" Optimized block access is enabled DB properties -> Attributes -> Optimized block access = false
SWAP instruction rejected for REAL input REAL is not a supported input type Use the AT overlay or SCL byte slicing methods above
MB_MASTER returns status 16#8188 (timeout) Slave latency; not a swap problem Check cable, baud, parity, and Modbus address
Result inverts sign for every read One of the high two bytes is wrong Compare against the IEEE 754 sign bit (bit 31)

11. Performance and Safety Notes

The word-swap adds 4 byte-MOVE instructions and 1 type cast. On an S7-1214C, the cycle-time cost is below 5 microseconds and is negligible in any real Modbus application. The DWORD_TO_REAL conversion compiles to a simple memory assignment; no arithmetic unit is invoked.

If the REAL holds a measurement that drives a control loop, wrap the conversion with a validity check on the DONE and ERROR outputs of MB_MASTER. A failed read must not propagate the previous result as a valid new value; latch the error and hold the last good reading. The S7-1200 Modbus manual (entry ID 47756141) shows the recommended handshake pattern for production-grade installations.

For safety-critical applications, the swap logic should be implemented in a separate, dedicated FB and code-reviewed. A reversed byte order on a pressure or temperature transmitter can produce values that look plausible but are off by orders of magnitude, which is more dangerous than a clear out-of-range fault.

12. Frequently Asked Questions

Why does the SWAP instruction refuse to accept my REAL input on the S7-1200?

The SWAP instruction is defined for WORD, INT, and DWORD inputs only. A REAL is a 32-bit IEEE 754 floating-point type and swapping its bit pattern is not a defined IEEE 754 operation. The correct approach is to operate on the four underlying bytes of the REAL with an AT overlay or SCL byte slicing, then re-interpret the swapped bit pattern as REAL via a DWORD_TO_REAL cast.

Can I use the Modbus TCP MB_CLIENT block in the same way as MB_MASTER for swapping?

Yes. MB_CLIENT returns the same byte layout for 32-bit values. The same word-swap or byte-swap logic applies. The MB_CLIENT block documentation is in the same Modbus manual (entry ID 47756141) and confirms that the data pointer receives raw bytes in network order.

Is the byte-swap always a 16-bit word swap, or do I sometimes need a full byte reversal?

Most heat meters and energy meters use the CDAB variant, which is a pure word swap. Some Chinese power meters use DCBA, which is a full 4-byte reversal. Confirm the variant by reading a known value (e.g., 100.0) and inspecting the four raw bytes; the IEEE 754 of 100.0 is 0x42C80000, so compare against the wire bytes 00 00 C8 42 (CDAB) or 00 C8 00 42 (BADC) or 42 C8 00 00 (ABCD) or 00 00 C8 42 reversed becomes 42 C8 00 00 if fully reversed.

Does DWORD_TO_REAL actually perform a numeric conversion, or is it a bitwise cast?

It is a bitwise cast (memory re-interpretation). The S7-1200 SCL manual documents DWORD_TO_REAL as a conversion that does not alter the bit pattern. No arithmetic is performed, no rounding occurs, and the cycle-time cost is zero. The cast is exactly what you need after a byte swap.

My DB is "optimized block access" and the AT overlay fails to compile. How do I fix it?

Open the DB properties, select "Attributes", and uncheck "Optimized block access". The DB will then use the classic absolute addressing that allows AT overlay on a byte view. Note that disabling optimization disables some compiler optimizations and makes the DB use more memory; on S7-1200 firmware V4.2 and later, you can keep optimization and use a non-optimized section or a temporary in an FB static area as an alternative.

What TIA Portal version is required for the AT overlay on a real?

TIA Portal V13 SP1 introduced the full SCL AT construct that allows the pattern shown in this article. Earlier versions lack the AT syntax in SCL and force you to use absolute byte addressing or the MOVE-based approach. The S7-1200 SCL manual (entry ID 109751604) lists the exact version history.

Back to blog