Reading LReal (64-bit Float) from Modbus RTU on S7-1200 in TIA Portal V13
The Siemens S7-1200 with firmware V4.0 and TIA Portal V13 supports Modbus RTU master and slave communication through the MB_MASTER / MB_SLAVE instructions. The DATA_PTR parameter of these blocks points to a data area in a global data block (DB). The block copies raw bytes from the Modbus message into that area or reads bytes from it for outgoing messages. Reading 16-bit Word or Int values is straightforward: each holding or input register maps to one Word element. Reading a 64-bit floating-point value (S7 LReal, IEEE 754 double precision) is significantly more complex because the value occupies four 16-bit Modbus registers (eight bytes), and the byte order transmitted by the slave rarely matches the byte order the S7-1200 stores internally.
This tutorial explains how to configure a non-optimized DB, map a Modbus slave register window into a byte array, and convert those eight bytes into a usable LReal value using SCL. It also covers the MB_CLIENT alternative introduced in S7-1200 firmware V4.1, the byte-order decisions that must be made, and the diagnostic steps to verify the conversion end-to-end.
MODBUS_PNI block or to ET200S serial modules. S7-300/400 (TIA or STEP 7 V5) uses the same byte-handling pattern but ships MODBUS_PN master blocks with a different parameter layout.1. Prerequisites
| Item | Requirement |
|---|---|
| CPU | S7-1200 with firmware V4.0 or higher (6ES7 2xx-1xx30-0XB0 or later) |
| Engineering | TIA Portal V13 SP1 (or V13 + HSP for newer CPUs); S7-1200 program block "MODBUS (RTU)" from the instruction palette |
| Serial module | CM 1241 RS232 / RS485 (6ES7 241-1xxxx-0XB0) or CB 1241 RS485 (6ES7 241-0xxxx-0XB0) |
| Slave | Any Modbus RTU slave exposing 64-bit float (double) values; common examples: power meters, weighing instruments, energy analyzers, variable-frequency drives |
| Slave data sheet | Register map showing that the LReal value starts at holding register 5000 and occupies 4 consecutive 16-bit registers (5000, 5001, 5002, 5003); second value at 5004..5007 |
| Cable | RS485 two-wire with shield, terminated at both ends with 120 Ω when line length exceeds ~10 m |
Confirm the S7-1200 program version. Open the project in TIA Portal, right-click the CPU, select "Properties", and read the firmware version on the "General" tab. Firmware V4.0 is the first release that supports the MB_MASTER block; V4.1 added MB_CLIENT for PROFINET-side Modbus TCP. For Modbus RTU, MB_MASTER is the right block across V4.0..V4.6.
2. How Modbus RTU Encodes a 64-bit Float
Modbus RTU is a register-oriented protocol. Each holding register is exactly 16 bits. The protocol has no native concept of a "float"; the master reads N consecutive registers using function code 03 (Read Holding Registers) or 04 (Read Input Registers) and interprets the resulting bytes as the slave documents.
For a 64-bit IEEE 754 double the layout is:
- Total bytes: 8 (64 bits).
- Total registers: 4 (5000, 5001, 5002, 5003 for the first value; 5004..5007 for the second).
- Encoding: 1 sign bit, 11 exponent bits, 52 mantissa bits (per IEEE 754-2008 binary64).
- Value range: approximately ±1.7977 × 10308 with ~15–17 significant decimal digits.
The IEEE 754 standard specifies big-endian byte order for the encoded value: byte 0 is the most-significant byte, byte 7 the least-significant. However, Modbus RTU itself transmits bytes in the order they appear in the PDU, and many slaves present registers in one of three common "endian" conventions:
| Convention | Register sequence (starting at 5000) | Byte order in transmission | Common in |
|---|---|---|---|
| Big-endian (ABCD) | 5000=MSW_hi, 5001=MSW_lo, 5002=LSW_hi, 5003=LSW_lo | AB CD EF GH | Some power meters, network byte order |
| Modicon / little-endian word swap (CDAB) | 5000=LSW_lo, 5001=LSW_hi, 5002=MSW_lo, 5003=MSW_hi | CD AB GH EF | Schneider Electric, most Modicon-derived devices |
| Byte swap within words (BADC) | 5000=MSW_lo, 5001=MSW_hi, 5002=LSW_lo, 5003=LSW_hi | BA DC FE HG | Some VFDs and legacy instruments |
You must determine which convention the slave uses before writing conversion code. There is no universal answer; the slave's manual is authoritative. If the manual is silent, treat it as Modicon (CDAB) and verify against a known value (see Section 9).
3. Create the Non-Optimized Target Data Block
The DATA_PTR input on MB_MASTER must point to a non-optimized DB (also called "standard" or "non-symbolic" access). Optimized DBs do not guarantee a fixed memory layout; the compiler can reorder and pad members, so a byte array declared in an optimized DB is not guaranteed to receive bytes at the offsets the block expects.
- In the project tree, right-click "Program blocks" and choose "Add new block" → "Data block".
- Name it
DB_ModbusRx. Click OK. - Open the DB and disable the "Optimized block access" checkbox in the DB properties (right-click the DB title → Properties → Attributes).
- Declare a single member at the start of the DB:
DATA : Array[0..15] of Byte; // 16 bytes cover registers 5000..5007 (two 64-bit values) END_STRUCT; - Compile the block. The byte offsets are now fixed:
DB_ModbusRx.DATA[0]= byte at register 5000 high;DATA[7]= byte at register 5003 low;DATA[8..15]= bytes of the second LReal.
You may also add a separate DB_ModbusTx for write requests with the same byte-array layout, and additional DBs to hold the decoded LReal values. The decoded DB can be optimized; it only receives the converted result.
4. Configure the MB_MASTER Instruction
Drag "MB_MASTER" from the "Communication → Modbus (RTU)" palette into OB1. The instruction appears as a multi-instance block. Declare an instance DB or let TIA Portal create one automatically.
Wire the inputs as follows for a single 4-register read:
| Input | Data type | Value (this example) | Description |
|---|---|---|---|
| REQ | Bool | Trigger pulse from a clock or a positive edge | Start a new Modbus transaction |
| MB_ADDR | USInt | 1 | Modbus slave address 1..247 |
| MODE | USInt | 0 | 0 = read, 1 = write, 2 = read/write (one transaction) |
| DATA_ADDR | UInt | 5000 | Start address of the first holding register in the slave |
| DATA_LEN | UInt | 8 | Number of 16-bit registers to read; 4 registers per LReal, 2 values = 8 |
| DATA_PTR | Variant | P#DB_ModbusRx.DBX0.0 BYTE 16 | Pointer to the byte array in the non-optimized DB |
| DONE | Bool | → instance DB | TRUE for one cycle when the transaction completed without error |
| BUSY | Bool | → instance DB | TRUE while the request is in progress |
| ERROR | Bool | → instance DB | TRUE if the request failed |
| STATUS | Word | → instance DB | Error code; see Section 10 |
The block places eight bytes into DB_ModbusRx.DATA[0..7] on a successful read. It places the next four registers (5004..5007) into DATA[8..15] only if you read eight registers in one request (DATA_LEN = 8). For sequential polling, drive REQ with a rising edge from a clock bit that runs slower than the expected response time (typical 50–200 ms for RTU at 9600–19200 baud).
5. Decide the Byte Order
Before writing SCL, inspect the raw bytes of a known value. A practical workflow:
- Set the slave to output a known value, e.g.
0.123as the first LReal. - Run the S7-1200 program and force
DB_ModbusRxinto the watch table. - Format the first eight bytes in hex.
- Compare the byte sequence with the IEEE 754 encoding of the expected value.
The IEEE 754 binary64 encoding of 0.123 is 0x3FBF7CED916872B0. In big-endian byte order, the eight bytes are:
| Byte offset | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
|---|---|---|---|---|---|---|---|---|
| Big-endian (ABCD) | 3F | BF | 7C | ED | 91 | 68 | 72 | B0 |
| Modicon (CDAB) | 72 | B0 | 68 | 91 | ED | 7C | BF | 3F |
| Byte-swapped words (BADC) | BF | 3F | ED | 7C | 68 | 91 | B0 | 72 |
Whichever row matches your captured hex defines the conversion your SCL code must perform. For Modicon (CDAB), the S7-1200 receives bytes in the order 72, B0, 68, 91, ED, 7C, BF, 3F and must reassemble them into 3F BF 7C ED 91 68 72 B0 before reinterpreting as LReal.
6. SCL Conversion Code
Create an SCL source file and add a function block (e.g. FB_LRealConvert) that runs whenever DONE of MB_MASTER rises. The block reorders bytes using the AT overlay view and copies the result into a typed LReal variable.
FUNCTION_BLOCK "FB_LRealConvert"
{ S7_Optimized_Access := 'FALSE' }
VAR
bModiconSwap : Bool := TRUE; // set FALSE if slave is true big-endian
END_VAR
VAR_TEMP
tByte0, tByte1, tByte2, tByte3,
tByte4, tByte5, tByte6, tByte7 : Byte;
END_VAR
BEGIN
// First LReal: bytes 0..7 of the read buffer
IF "DB_ModbusRx".DATA[0] = 0 AND "DB_ModbusRx".DATA[1] = 0
AND "DB_ModbusRx".DATA[2] = 0 AND "DB_ModbusRx".DATA[3] = 0
AND "DB_ModbusRx".DATA[4] = 0 AND "DB_ModbusRx".DATA[5] = 0
AND "DB_ModbusRx".DATA[6] = 0 AND "DB_ModbusRx".DATA[7] = 0
THEN
"DB_Decoded".Value1 := 0.0;
ELSE
IF bModiconSwap THEN
// CDAB: most-significant word last
tByte0 := "DB_ModbusRx".DATA[6];
tByte1 := "DB_ModbusRx".DATA[7];
tByte2 := "DB_ModbusRx".DATA[4];
tByte3 := "DB_ModbusRx".DATA[5];
tByte4 := "DB_ModbusRx".DATA[2];
tByte5 := "DB_ModbusRx".DATA[3];
tByte6 := "DB_ModbusRx".DATA[0];
tByte7 := "DB_ModbusRx".DATA[1];
ELSE
// AB CD EF GH: true big-endian, no swap needed
tByte0 := "DB_ModbusRx".DATA[0];
tByte1 := "DB_ModbusRx".DATA[1];
tByte2 := "DB_ModbusRx".DATA[2];
tByte3 := "DB_ModbusRx".DATA[3];
tByte4 := "DB_ModbusRx".DATA[4];
tByte5 := "DB_ModbusRx".DATA[5];
tByte6 := "DB_ModbusRx".DATA[6];
tByte7 := "DB_ModbusRx".DATA[7];
END_IF;
"DB_Decoded".Value1 := DWORD_TO_LREAL(0) // placeholder; replaced below
;
END_IF;
// Reinterpret the eight bytes as LReal using an AT overlay
// The cleanest approach: a temp DWORD array + the LReal overlay
// -- implementation in Section 7 --
END_FUNCTION_BLOCK
The example above is illustrative; the cleanest production code uses an AT overlay on a DWord array to make the byte-to-double conversion explicit and vendor-independent. See Section 7.
7. Clean AT-Overlay Implementation
An AT overlay lets the compiler treat eight consecutive bytes as a LReal without copying or shifting. Declare it inside the VAR_TEMP section of an SCL block:
FUNCTION_BLOCK "FB_LRealConvert"
{ S7_Optimized_Access := 'FALSE' }
VAR CONST
cSwapWords : Bool := TRUE; // TRUE = Modicon CDAB, FALSE = big-endian
END_VAR
VAR
DoneTrig : Bool;
END_VAR
VAR_TEMP
aRaw : Array[0..7] of Byte;
aReordered : Array[0..7] of Byte;
dwPair0 : DWord AT aReordered[0] : ARRAY[0..1] OF DWORD;
rResult : LReal AT dwPair0[0];
iWord : Int;
END_VAR
BEGIN
// Copy raw bytes into reorder buffer
aRaw[0] := "DB_ModbusRx".DATA[0];
aRaw[1] := "DB_ModbusRx".DATA[1];
aRaw[2] := "DB_ModbusRx".DATA[2];
aRaw[3] := "DB_ModbusRx".DATA[3];
aRaw[4] := "DB_ModbusRx".DATA[4];
aRaw[5] := "DB_ModbusRx".DATA[5];
aRaw[6] := "DB_ModbusRx".DATA[6];
aRaw[7] := "DB_ModbusRx".DATA[7];
IF cSwapWords THEN
// CDAB: words are reversed: 2,3,0,1 then 6,7,4,5
aReordered[0] := aRaw[2];
aReordered[1] := aRaw[3];
aReordered[2] := aRaw[0];
aReordered[3] := aRaw[1];
aReordered[4] := aRaw[6];
aReordered[5] := aRaw[7];
aReordered[6] := aRaw[4];
aReordered[7] := aRaw[5];
ELSE
// Big-endian: no swap
FOR iWord := 0 TO 7 DO
aReordered[iWord] := aRaw[iWord];
END_FOR;
END_IF;
"DB_Decoded".Value1 := rResult;
// Repeat for the second LReal using a separate AT overlay
// that starts at aReordered[8] of a longer temp array if you need both values
END_FUNCTION_BLOCK
The AT declaration on aReordered tells the compiler to map the same eight bytes as a 64-bit float. The CPU performs a single load instruction; no byte-level shifting is required at runtime, which makes the block both compact and deterministic.
8. Alternative: MB_CLIENT for S7-1200 Firmware V4.1+
If the project is on a PROFINET network and the Modbus device is reachable as a Modbus TCP gateway, firmware V4.1 introduces the MB_CLIENT block. MB_CLIENT supports the same data types and adds typed access modes including REAL and LREAL via the DATA_PTR variant. The conversion flow above is still required for RTU; for TCP, you can use MB_CLIENT with a typed pointer and skip the byte-reorder code when the gateway already presents registers in big-endian order.
For S7-1200 V4.0 projects that must use RTU, MB_MASTER remains the only option. There is no firmware upgrade path to add MB_CLIENT RTU; if the design later needs Modbus RTU on a newer CPU, the conversion code in this article is portable as-is.
9. Verification
Validate the conversion end-to-end before commissioning:
- Force the slave to output a known constant (e.g.
1.0,0.0,-1.0). The IEEE 754 binary64 encoding of1.0is0x3FF0000000000000; of-1.0is0xBFF0000000000000. - Trigger a single read and watch
DB_ModbusRx.DATA[0..7]in hex. - Compare each captured byte against the IEEE 754 byte sequence for the expected value, in the convention the slave documents.
- Verify
DB_Decoded.Value1in the watch table matches the expected floating-point value to fullLRealprecision (15 digits). - Repeat with a negative value to confirm sign-bit handling.
- Repeat with a small value (e.g.
0.123) to confirm mantissa and exponent handling. - Verify the second value (
DB_Decoded.Value2) after reading eight registers in one request.
Document the result in a commissioning sheet. Capture the hex bytes the slave returns for each known value and the resulting LReal; the sheet becomes the reference when integrating additional slaves of the same model.
10. Troubleshooting Matrix
| Symptom | Likely cause | Fix |
|---|---|---|
| STATUS = 0x8081 / 0x80C8 / 0x80D2 — slave does not respond | Wiring, baud, parity, slave address | Match the CM 1241 port configuration to the slave; verify A/B polarity on RS485; terminate at 120 Ω |
| STATUS = 0x8380 — CRC error from slave | Electrical noise or mismatched baud | Lower baud to 9600; check shield grounding; reduce cable length |
| STATUS = 0x8381 — function code not supported | Slave does not support function code 03/04 at the requested address | Check slave register map; some slaves require function code 04 for input registers only |
| Bytes received but Value1 is "garbage" or sign-flipped | Wrong byte-order assumption | Capture hex for a known value, compare with the table in Section 2, change cSwapWords |
| Value1 = 0.0 for all reads | DATA_PTR points to an optimized DB; block silently writes to the wrong offsets | Disable "Optimized block access" on the target DB; recompile |
| DB_ModbusRx.DATA reads as zeros in the watch table but STATUS = 0 | DATA_LEN is smaller than expected or wrong DATA_ADDR | Confirm slave register numbering; some slaves are 1-based, others 0-based; adjust DATA_ADDR accordingly |
| Value drifts slightly on every read | Reading partial register window (less than 4 consecutive 16-bit registers) | Ensure DATA_LEN is a multiple of 4 and the window is exactly the registers the slave documentation specifies |
| Compiles but errors at download: "Pointer not allowed" | DATA_PTR specified as P#DB1.DBX0.0 INT 8 instead of BYTE 8 | Use the BYTE qualifier; MB_MASTER transfers raw bytes |
For the complete list of MB_MASTER status codes, refer to the TIA Portal online help under "MB_MASTER: Status parameter". Common codes are also documented in the S7-1200 System Manual, section on Modbus RTU instructions.
11. Common Patterns and Caveats
-
Polling cadence. Drive
REQwith a clock bit at least 50 ms wide and at least one Modbus frame time apart. At 9600 baud, 8N2, 8 bytes of payload, one frame is roughly 25 ms. Add 5–10 ms of margin for slave processing. - Single transaction vs. multiple. Reading both LReal values in a single 8-register read is faster (one round trip) and produces a coherent snapshot. Two separate 4-register reads can return mismatched values if the slave updates between them.
-
Atomicity. The decoded
LRealassignment is atomic on the S7-1200 becauseLRealis a native 64-bit type. The temporary buffer copy is not atomic; do not call the conversion block from multiple OBs at different priorities without enablingREA/WRprotection. -
Optimized DB constraint.
DATA_PTRrequires non-optimized access. The decoded DB can be optimized; only the buffer DB must be standard. - Endian documentation. A slave manual that says "IEEE 754 64-bit float" is ambiguous. It may mean big-endian (the IEEE 754 default) or it may mean the local Modicon order. Confirm with a register-level example in the manual.
-
NaN and Inf. If the slave can return
NaNor±Inf, the SCL conversion still produces a validLReal. Filter them in user code if downstream logic (HMI display, write-back to another device) cannot handle them. -
Endian swapping libraries. TIA Portal V15+ includes the "Swap" extended instructions (
SWAP_DWORD,SWAP_LWORD) under "Extended instructions → String + Char". On V13, write the byte reorder manually as in Section 7.
12. References and Standards
-
Siemens S7-1200 Programmable Controller System Manual (entry ID 109751516) — chapter on "Modbus RTU communication" covers
MB_MASTERandMB_SLAVEparameter definitions and status codes. - S7-1200 CM 1241 communication module manual (entry ID 58523220) — pin-out, supported baud rates, configuration of the serial port in TIA Portal.
- Modbus Application Protocol V1.1b3 (Modbus Organization, 2012) — function codes 03/04/16/23, PDU layout, exception responses.
- IEEE Std 754-2019 — Floating-Point Arithmetic — binary64 (double precision) layout, encoding of special values.
- Chipkin — How Real (Floating Point) and 32-bit Data is Encoded in Modbus RTU Messages — practical reference on byte order and scaling factors for 32-bit values, applicable pattern for 64-bit.
Frequently Asked Questions
Can MB_MASTER on S7-1200 read an LReal directly with a typed DATA_PTR?
No. MB_MASTER on firmware V4.0 transfers raw bytes regardless of the pointer type. The DATA_PTR must point to a byte array in a non-optimized DB; the application must convert the eight bytes into an LReal with the correct byte order.
What is the most common byte order for 64-bit Modbus float values?
Modicon word order (CDAB) is the most common among industrial slaves, but true big-endian (ABCD) is also widespread. Confirm with a register example in the slave's user manual; if the manual does not specify, capture hex bytes for a known constant and compare against the IEEE 754 encoding of that constant.
Why must the target DB be non-optimized?
MB_MASTER writes bytes at fixed offsets inside the data area pointed to by DATA_PTR. Optimized DBs do not guarantee fixed offsets because the compiler may reorder and pad members. Disable "Optimized block access" on the buffer DB; the decoded LReal target DB can stay optimized.
My STATUS word shows 0x80C8. What does that mean?
0x80C8 indicates "no response from slave within the timeout". The most common causes are wiring or address mismatch, baud/parity/stop-bit mismatch between the CM 1241 port and the slave, or the slave not being powered. Verify the slave address byte, swap A and B on the RS485 trunk, and confirm the port configuration in the CM 1241 device properties.
Can I read multiple LReal values in one MB_MASTER call?
Yes. Set DATA_LEN to 4 × N where N is the number of LReal values you want to read. The block reads the registers in a single Modbus transaction and writes the bytes contiguously into the buffer; your SCL code then splits the buffer into 8-byte groups and converts each group separately.