Modbus RTU Float Values: Resolving Endian Byte/Word Swap

David Krause18 min read
ModbusSiemensTutorial / 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

Overview

Modbus RTU was defined as a 16-bit register protocol, but most analog process values from PLCs, drives, flow meters, and power meters are 32-bit IEEE 754 single-precision floating-point numbers. A float occupies two adjacent Modbus registers, and the way those two 16-bit words (and the two bytes inside each word) are placed on the wire depends entirely on the byte-order convention of the slave device. When the SCADA or HMI master interprets the register pair with the wrong convention, the decoded value is garbage: typically a sign-flipped, exponent-corrupted number that may appear to fluctuate across positive and negative ranges, or simply as a small value that does not track the process variable.

This reference explains the IEEE 754 single-precision bit layout, the four possible word/byte permutations over Modbus, how to identify which permutation a specific slave uses by reading raw registers, and the concrete configuration changes required in WinCC OA, WinCC Professional, TIA Portal, Ignition, FactoryTalk View SE/ME, and Codesys-based controllers. Verification procedures and a symptom-decoding matrix are included.

Engineering note: Modbus itself does not define a float data type. Function codes 03, 04, 06, and 16 (0x10) carry 16-bit register payloads only. Multi-register values are an application-layer convention; the master is solely responsible for combining the two 16-bit words into a 32-bit IEEE 754 value. This is why the byte-order question is a master-side configuration problem, not a slave firmware problem.

IEEE 754 Single-Precision Bit Layout

An IEEE 754 single-precision float is laid out as follows:

Bit Range Width Field Description
31 1 bit Sign (S) 0 = positive, 1 = negative
30 - 23 8 bits Exponent (E) Biased by +127, range -126 to +127
22 - 0 23 bits Mantissa (M) Fractional part with implicit leading 1

Total width: 32 bits = 4 bytes = exactly two Modbus 16-bit registers. The value of the float is computed as:

value = (-1)^S × 2^(E-127) × (1.M)

When the mantissa is all zeros and the exponent is 0x7F8 (E = 128), the value is ±infinity. When E = 0 and M = 0, the value is ±0. When E = 255 and M ≠ 0, the value is NaN.

Modbus PDU Register Model

Modbus function codes that carry 16-bit register data:

Function Code Name Access
01 Read Coils Read discrete bits
02 Read Discrete Inputs Read discrete bits
03 Read Holding Registers Read 16-bit words
04 Read Input Registers Read 16-bit words
05 Write Single Coil Write one bit
06 Write Single Register Write one 16-bit word
15 (0x0F) Write Multiple Coils Write N bits
16 (0x10) Write Multiple Registers Write N 16-bit words

A 32-bit float requires two consecutive registers in either the holding or input register space, addressed as N and N+1. See the Modbus Tools protocol reference and the Wikipedia Modbus entry for the full PDU specification. See also Schneider Electric FAQ FA168406 on proper Modbus installation, and the National Instruments in-depth Modbus protocol article for additional framing on master/slave roles and transaction timing.

Endianness Fundamentals

Endianness is the order in which a multi-byte scalar is decomposed into bytes for serial transmission. There are two independent axes:

  1. Word order (which 16-bit register holds the high half of the 32-bit value): big-endian places the high word in the lower-numbered register; little-endian places the high word in the higher-numbered register.
  2. Byte order within each word (which byte of the 16-bit word is transmitted first over the UART): big-endian transmits the high byte of the word first; little-endian transmits the low byte of the word first.

These two axes can be combined independently, producing four possible permutations of the four bytes of a float. The permutations are conventionally named by the order in which the four bytes appear in consecutive Modbus registers, byte-by-byte:

Permutation Register N bytes Register N+1 bytes Also called Typical vendors
ABCD [A][B] [C][D] Big-endian, big-endian words Modicon, Schneider M340, ABB AC500, most legacy PLCs
BADC [B][A] [D][C] Byte-swap, big-endian words Many Chinese instruments, some Wago 750 couplers, certain Schneider PM5000 series
CDAB [C][D] [A][B] Word-swap, big-endian bytes Wago 750-xxxx with float, Beckhoff EL/ELM terminals, some ABB drives, Phoenix Contact Axioline
DCBA [D][C] [B][A] Little-endian, little-endian Some Codesys v3 controllers, some MicroLogix/CompactLogix native Modbus, certain sensor heads

where A = bits 31-24, B = bits 23-16, C = bits 15-8, D = bits 7-0 of the IEEE 754 value.

Inline Diagram: Byte Mapping

IEEE 754 float, value = 1.0 = 0x3F800000 A = 0x3F | B = 0x80 | C = 0x00 | D = 0x00 ABCD (big-endian big-endian): A=3F B=80 C=00 D=00 Reg N high byte Reg N low byte Reg N+1 high byte Reg N+1 low byte CDAB (word-swap, big-endian bytes): C=00 D=00 A=3F B=80 Reg N Reg N+1 Register N = 0x3F80 (high word) Register N+1 = 0x0000 (low word) Register N = 0x0000 (low word) Register N+1 = 0x3F80 (high word) Same float (1.0), different wire order. The master must match the slave.

Symptom-Decoding Matrix

When a float reads incorrectly, the symptom is highly diagnostic. Force a known reference value (1.0, 100.0, 273.15) into the slave and observe how the master interprets it:

Observed master value (slave = 1.0) Observed master value (slave = 100.0) Likely permutation Action
1.0 100.0 ABCD (correct) No swap, this is the natural decode
Very small ~1e-39 or 0 Very small ~7e-43 CDAB Swap the two 16-bit words (word-swap)
Large negative ~ -1.7e+38 or NaN Large negative ~ -1.5e+34 BADC Swap bytes within each word (byte-swap)
Different small value, no obvious pattern Different small value DCBA Swap words and bytes (full reversal)
Sign flips when sign bit of high word changes Same ABCD with sign bit in wrong word CDAB most likely
Value reads correctly but trend is jittery near zero Same Denormal or scale mismatch Check engineering units and scaling factor

Manual Hex Decoding Procedure

Use this procedure on any Modbus master to identify the slave's permutation. It does not require any float conversion—only a Modbus poll that returns the two 16-bit register values in hexadecimal.

  1. Force a known float into the slave. The canonical reference is 1.0 = 0x3F800000, because the byte pattern is easy to recognize.
  2. Read the two consecutive Modbus registers and write down their values in hex.
  3. Concatenate the four bytes in the order received, treating each 16-bit register as high-byte, low-byte.
  4. Match the resulting 32-bit hex pattern against the table below.
Reg N Reg N+1 Byte order Decode as
0x3F80 0x0000 ABCD 1.0 (correct, no swap)
0x0000 0x3F80 CDAB 1.0 after word-swap
0x803F 0x0000 BADC 1.0 after byte-swap within each word
0x0000 0x803F DCBA 1.0 after full reversal

Repeat the test with 2.0 = 0x40000000, -1.0 = 0xBF800000, and 100.0 = 0x42C80000 to confirm. Negative values are particularly diagnostic: the sign bit (bit 31) of -1.0 is set in byte A (= 0xBF); if you observe the sign in the wrong word, the permutation is CDAB or DCBA.

Step-by-Step Fix in WinCC OA (Siemens SCADA)

The original symptom in this scenario was reading tag address 578 as float in WinCC OA, receiving a nonsensical value spanning negative to positive while integer reads worked correctly. The fix is to add the appropriate word-swap transformation on the SCADA side.

Prerequisites

  • WinCC OA version 3.16 or later (3.18+ recommended for the Modbus driver improvements).
  • The Modbus RTU driver ModDrv installed and licensed.
  • A known reference value writable on the slave (or a Modbus simulator such as modbus-server, diagslave, or a Python pymodbus server).

Configuration

  1. Open the PARA module and navigate to the RTU device driver configuration.
  2. Locate the Driver panel for the affected connection. Confirm the COM port, baud rate, parity, stop bits, and slave (unit) ID.
  3. Open the DP (Data Point) editor for the analog input tag at address 578.
  4. Set the data point type to FLOAT (32-bit IEEE 754). Do not select INT or WORD; that will truncate the 32-bit value and produce the integer-looks-correct-but-float-looks-wrong symptom.
  5. On the conversion tab, set the Swap attribute:
Observed Reg N / Reg N+1 pattern WinCC OA swap selector
0x3F80 / 0x0000 (ABCD) No swap (default)
0x0000 / 0x3F80 (CDAB) WORD_SWAP (swap the two 16-bit words)
0x803F / 0x0000 (BADC) BYTE_SWAP (swap bytes within each word)
0x0000 / 0x803F (DCBA) BOTH (full 32-bit reversal)
  1. Save the DP and re-activate the driver. The change should take effect within one poll cycle.
  2. Verify by reading the value back in the para value list or by adding the tag to a graphical view with a numeric widget.
Critical: The swap setting in WinCC OA is a property of the data point (DP), not the driver. If the same Modbus device uses different byte orders for different registers (some vendors do this for legacy compatibility), each DP must be configured individually. Do not change the driver-level byte order; that affects all registers on the link.

Step-by-Step Fix in TIA Portal (Siemens S7-1200 / S7-1500)

For a Siemens S7-1200/1500 acting as a Modbus RTU master via the MB_CLIENT or MB_MASTER instruction, or as a Modbus TCP master via MB_CLIENT in TCP mode, the same four permutations apply.

  1. Place the raw two-register read into a WORD array Data[0..1] using MB_MASTER with mode 0 (read holding registers) or 4 (read input registers).
  2. Combine the two words into a DWORD with explicit byte assembly. The S7 byte order is big-endian.

For CDAB slaves (word-swap):

// Data[0] is the LOW word, Data[1] is the HIGH word
// Reassemble as big-endian DWORD then interpret as REAL
#rawDword := SHL(IN := WORD_TO_DWORD(#Data[1]), N := 16) 
           OR WORD_TO_DWORD(#Data[0]);
#realValue := DWORD_TO_REAL(#rawDword);

For BADC slaves (byte-swap within each word):

// Each word's bytes are swapped
#swapped0 := SHL(IN := WORD_TO_DWORD(#Data[0]), N := 8) 
           OR SHR(IN := WORD_TO_DWORD(#Data[0]), N := 8) & 16#00FF;
// Or use the ROR/WORD swap pattern
#realValue := DWORD_TO_REAL(SHL(IN := WORD_TO_DWORD(#swapped0), N := 16) 
                          OR WORD_TO_DWORD(#swapped0));

A more compact SCL approach is to use the standard library SWAP function or the IEC function SWAP_DWORD (only available in S7-1500) to perform the full 32-bit reversal in one call.

Step-by-Step Fix in Studio 5000 / Logix Designer (Allen-Bradley)

For CompactLogix or ControlLogix acting as a Modbus master using the MODBUS AOI (Add-On Instruction) or a MESSAGE instruction with CIP Modbus, the byte order is the Logix-native little-endian. The CIP REAL data type is stored as IEEE 754 in the tag memory.

  1. Read the two 16-bit registers into a SINT[4] array using a Modbus message with an Atomic data type of INT[2].
  2. Copy the four bytes into the REAL tag in the correct order using COP and a SINT overlay.

For CDAB slaves:

// Source: Modbus response in ModbusWords[0..1] (INT)
// Destination: ModbusReal (REAL)
ModbusBytes[0] := ModbusWords[1].0; // high byte of high word
ModbusBytes[1] := ModbusWords[1].1; // low byte of high word
ModbusBytes[2] := ModbusWords[0].0; // high byte of low word
ModbusBytes[3] := ModbusWords[0].1; // low byte of low word
COP(ModbusBytes[0], ModbusReal, 4);

Alternatively, use the SWAP_FILE AOI from the Rockwell Sample Code library, or write a small routine that performs the byte reordering explicitly.

Step-by-Step Fix in Ignition (Inductive Automation)

For Ignition 8.x with the Modbus TCP / RTU driver, the byte order is configured per-tag using the Tag Scaling or the Modbus Address Mapping property sheet.

  1. Open the tag configuration in the Designer.
  2. Set the data type to Float4.
  3. Set the Byte Order selector. Ignition exposes four presets: Big-endian (ABCD), Little-endian byte swap (BADC), Little-endian word swap (CDAB), Little-endian (DCBA).
  4. Apply and verify with a known reference value.

Step-by-Step Fix in FactoryTalk View SE / ME

FactoryTalk View uses the RSLinx OPC server for Modbus communication. The byte order is set in RSLinx Classic's Modbus driver configuration:

  1. Open RSLinx Classic, launch RSWho.
  2. Right-click the Modbus device and select Device Properties.
  3. On the Data Table tab, set the Word Order to one of: High word first (big-endian), Low word first (little-endian / word-swap), High word first, byte-swap, Low word first, byte-swap.
  4. Save and restart the RSLinx driver for the change to take effect.

Step-by-Step Fix in Codesys v3

Codesys uses the ModbusTCPSlave / ModbusRTUSlave library for slave and ModbusTCPClient / ModbusRTUClient for master. To exchange floats:

  1. Declare two adjacent WORD variables at the holding register address.
  2. Use the __MEMCMP and __MEMCPY utilities, or the DWORD_TO_REAL with a manual word swap, to assemble the float.
// CDAB slave example
wHigh := pModbusRTU^.wRegs[1]; // N+1 is high word
wLow  := pModbusRTU^.wRegs[0]; // N is low word
dwRaw := SHL(WORD_TO_DWORD(wHigh), 16) OR WORD_TO_DWORD(wLow);
rValue := DWORD_TO_REAL(dwRaw);

PLC-Side Considerations (Float Source Encoding)

The endianness problem originates at the slave, not the master. The slave's PLC must write the float into its Modbus holding register area in a way that the master can interpret. Common patterns:

Siemens S7-300 / S7-400 / S7-1200 / S7-1500 (STEP 7 / TIA Portal)

Siemens stores REAL in big-endian byte order. The standard block move from a DB REAL tag to a Modbus holding register area using BLKMOV or MOVE_BLK preserves the byte order. Therefore, the default Siemens encoding is ABCD (big-endian, big-endian words). To produce CDAB output, swap the words manually before writing to the Modbus area.

Allen-Bradley ControlLogix / CompactLogix

Logix stores REAL in little-endian (the LSB is at the lowest memory address). When the MODBUS AOI or a CIP-Modbus gateway exposes a Logix REAL to a Modbus master, the default encoding depends on the gateway. Most CIP-to-Modbus gateways (ProSoft, Spectrum, Moxa MGate) default to ABCD but can be configured for CDAB. Always verify with the gateway datasheet.

Schneider Electric Modicon M340 / M580

Modicon uses big-endian natively. Floats are stored in %MW holding register pairs as ABCD. The Unity Pro / EcoStruxure Control Expert READ_VAR and WRITE_VAR block can be configured to swap words (CDAB) or bytes (BADC) on per-request basis, controlled by the SWAP input parameter.

Vendor-Specific Float Handling Quick Reference

Vendor / Device Default float encoding How to change
Modicon M340 / M580 (Unity Pro) ABCD SWAP input on READ_VAR / WRITE_VAR
Siemens S7-1200 / S7-1500 ABCD (native) Manual word-swap in SCL
Allen-Bradley CompactLogix ABCD via CIP gateway default Gateway byte-order setting
Wago 750-xxx (e!COCKPIT / Codesys) CDAB Library FB swap parameter
Beckhoff EL/ELM Modbus TCP CDAB TcModbusSrv FB configuration
ABB AC500 ABCD Word-swap FB in Automation Builder
ABB ACS880 drive (fieldbus adapter) ABCD for cyclic, CDAB for some parameters Per-parameter setting in drive
Schneider PM5000 power meter ABCD (newer) / BADC (older) Firmware register table
Generic Chinese sensor (4-20 mA + Modbus) BADC (very common) Master-side byte-swap
Endress+Hauser Fieldgate ABCD Native

Edge Cases and Field-Proven Caveats

  1. Integer reads work, float reads do not. This is the strongest diagnostic. Integers fit in one 16-bit register and are not subject to word boundaries; floats span two registers and therefore expose the byte-order issue. If both 16-bit and 32-bit values decode correctly, the byte order is ABCD.
  2. Value reads correctly but with the wrong sign. The sign bit of the float is in the most-significant byte (byte A). If the sign flips when the real process variable crosses zero, the permutation is CDAB or DCBA—the high word is in register N+1, not N.
  3. Value reads as a denormalized number near zero. The mantissa may be decoded as the exponent, and vice versa. Apply word-swap.
  4. Value reads as a large negative number or NaN. The exponent field may be in the wrong word, producing exponent values above 255 (NaN) or below 0 (denormal). Apply word-swap or full reversal.
  5. Float reads correctly but writes are inverted. The master is interpreting the write correctly but the slave is decoding with the wrong byte order. Apply the swap on the write direction only, leaving read unchanged—some SCADA drivers allow this.
  6. Float read works, but second float in a multi-register block fails. Some vendors only apply the non-standard byte order to specific register ranges. Verify with the vendor's register map PDF.
  7. Modbus gateway adds its own swap. When a Modbus RTU device is bridged through a Modbus TCP gateway (Moxa, Advantech, HMS Anybus), the gateway may normalize to CDAB. Test with both the direct RTU and the gateway in the loop to isolate.
  8. Floating-point precision loss on scaling. If the float reads in the correct order but the value is off by a factor of 10, 100, or 0.1, the engineering unit scaling is wrong, not the byte order. Check the slave's register map for any scaling constant.
  9. NaN propagation. If the float is undefined on the slave (e.g., channel not connected), the slave may write a non-standard bit pattern. A floating-point NaN in IEEE 754 has exponent 0xFF and a non-zero mantissa. The master may display this as 0, as a question mark, or as a large negative value depending on its NaN handling.
  10. Time-of-day stamps. Some process variables use 32-bit Unix timestamps; these have the same endian problem as floats. The same procedure applies.

Verification Procedure

  1. Inject a known reference value. Use the slave's configuration tool or a Modbus simulator to force the value of the float at address 578 (or the relevant address). Recommended: 1.0, 100.0, -1.0, 273.15.
  2. Read the raw registers. Use a Modbus master tool (e.g., modpoll, modbus-cli, QModMaster, CAS Modbus Scanner) to read registers N and N+1 and write down the raw hex values.
  3. Identify the permutation. Match against the manual hex decoding table in this article.
  4. Apply the SCADA-side swap. Configure the DP / tag with the appropriate swap selector.
  5. Re-poll and compare. The master should now display 1.0 / 100.0 / -1.0 / 273.15 exactly (within IEEE 754 precision, which is roughly 7 decimal digits for single-precision).
  6. Repeat under load. Poll the tag at 100 ms or 1 s for 10 minutes and verify the value does not drift. Drift indicates either a swap in only one of read/write directions, or a multi-master contention issue.
  7. Sign and edge tests. Verify negative values read correctly. Verify the value at 0.0 (all four bytes = 0x00) reads as exactly 0.0, not as a small denormal.
  8. Range test. Verify the value at 1e6 and 1e-6 reads correctly. These values exercise both the high and low bytes of the mantissa, exposing any byte-level corruption.

Troubleshooting Matrix

Symptom Likely root cause Fix
Integer read works, float reads as 0 or NaN CDAB word-swap needed Swap two 16-bit words in master
Float reads with sign flipped relative to process CDAB or DCBA Word-swap
Float reads as very large number, fluctuating BADC byte-swap Swap bytes within each word
Float reads as small fluctuating value DCBA full reversal Swap both words and bytes
Float reads correctly, write goes to wrong register Read and write have different swap settings Apply swap to write direction only
Float reads correctly on simulator, wrong on real device Gateway reordering Disable gateway byte conversion or test direct RTU
Float reads correctly, then drifts over time Multi-master Modbus collision Reduce poll rate, add inter-frame delay
Float reads correctly, but value is 10x, 100x, 0.1x off Scaling factor mismatch Check engineering units in register map
Float reads correctly in engineering tool, wrong in SCADA SCADA driver default byte order differs from tool Configure SCADA swap to match tool

Best-Practices Checklist

  • Document the byte order in the device's register map. If the manufacturer is silent, contact them and get a written answer.
  • Configure the SCADA/PLC master with the exact permutation, not a guess.
  • Use a Modbus simulator during commissioning to test all four permutations in minutes.
  • For new installations, prefer slaves that document ABCD explicitly—this is the historical Modbus standard for multi-register values and is the default in Modicon, ABB, and Schneider documentation.
  • When integrating a Modbus TCP gateway between an RTU device and the master, verify the gateway's byte-order behavior. Most gateways default to CDAB on the TCP side.
  • Avoid mixing Modbus RTU and Modbus TCP for the same logical value within one project—the byte order may differ between the two transports.
  • Use the READ_VAR and WRITE_VAR SWAP parameter in Unity Pro / Control Expert to abstract the swap into the I/O scanner rather than into the application code.

Why do integer reads work but float reads fail in Modbus RTU?

Integers fit inside a single 16-bit Modbus register and are not affected by word-order conventions. IEEE 754 single-precision floats are 32 bits, occupying two adjacent 16-bit registers. When the master and slave disagree on whether the high word is in register N or N+1 (and whether each word's bytes are big-endian or little-endian), the assembled 32-bit value is incorrect and typically reads as a large number, a small number, or NaN. Configure the SCADA driver to swap words (CDAB), bytes (BADC), or both (DCBA) as required.

How do I find the correct byte order for a Modbus RTU slave?

Write a known reference value such as 1.0 to the slave and read the two consecutive 16-bit registers back in hexadecimal. The byte pattern for 1.0 is 0x3F800000. Match the high and low words you read against the four permutations: ABCD = 0x3F80 / 0x0000, CDAB = 0x0000 / 0x3F80, BADC = 0x803F / 0x0000, DCBA = 0x0000 / 0x803F. The pattern that reconstructs 1.0 is the slave's correct encoding.

What is the difference between CDAB and BADC byte order?

CDAB means the two 16-bit words are swapped (the high word is in register N+1 rather than N), but the bytes inside each word remain in big-endian order. BADC means each word is correct (high word in N, low word in N+1) but the bytes within each word are swapped (low byte first instead of high byte first). CDAB is the most common non-standard permutation; BADC is common in older Chinese sensor instruments and some Schneider PM5000 meters.

Does the same byte order apply to Modbus TCP and Modbus RTU?

The Modbus application layer (function codes, register model, data interpretation) is identical between RTU and TCP. The byte-order convention is a property of the slave device, not the transport, so the same four permutations apply. However, when a Modbus TCP gateway is inserted between an RTU device and a TCP master, the gateway may normalize or re-order the bytes. Always verify the byte order at the master side, not at the field device side, after the gateway is in the path.

Can the byte order be different for read and write on the same Modbus register?

In theory no, because the encoding is a property of how the data is stored on the slave. In practice, yes, when an asymmetric gateway or a custom Modbus wrapper is involved. Some SCADA drivers allow configuring the swap independently for read and write directions. If the read value is correct but the write is incorrect (or vice versa), configure the swap on the affected direction only and re-test with a known reference value.

Back to blog