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.
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
- 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.
- 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.
- Modbus RTU master block
MB_MASTER(instruction library "MODBUS") or Modbus TCP blocksMB_CLIENT/MB_SERVER. See S7-1200 Modbus RTU / TCP Manual. - 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.
- Wireshark or the TIA online trace for verification.
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.
%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.
- 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.
-
PLC tag monitor: In TIA Portal, add the four
rxDatabytes, theenergy_dwDWORD, and theenergy_kWhREAL to a watch table. Force the RX data with a known IEEE 754 value, for example 100.0 = 0x42C80000. After the swap,energy_dwshould display 0x42C80000 (low byte first) andenergy_kWhshould read 100.0. - 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.