Why Modbus byte order matters
Modbus is a polite, sixteen-bit-at-a-time protocol. A register is two bytes. The spec is explicit that those two bytes are transmitted big-endian on the wire - high byte first, low byte second - and every spec-compliant device follows that convention. That part is settled.
What is not settled, and what this post is about, is what happens when a device packs anything larger than 16 bits - a 32-bit float, a 32-bit integer, a 64-bit timestamp - across multiple registers. The Modbus Application Protocol Specification doesn't say. It defines a 16-bit register and stops there. So when you read two consecutive registers and ask "what 32-bit value does that represent?", the answer depends entirely on the vendor's convention. Four common conventions exist, all "Modbus-compliant" in the sense that each individual register is correctly big-endian on the wire. Only one of them gives you the value the device actually meant.
This is the single most common cause of "the readings are wrong" support cases in the field. The wiring is fine, the addressing is right, no exception comes back - but the temperature reads 1.34e-28 instead of 23.5. That's not a bug; it's a byte-order mismatch.
Field tip - free download coming soon. When we commission a new Modbus device we haven't worked with before, the first thing we drop in is a function block that decodes a register pair as all twelve possible interpretations at once - four float orderings, four signed-int32 orderings, and four unsigned-int32 orderings. We're packaging it as a free download for TwinCAT, Schneider Control Expert, Siemens TIA Portal, and Rockwell Studio 5000 so you can do the same - it lands in about a week, so bookmark this page and check back. Read on for the workflow in the meantime.
The four byte orderings at a glance
Naming convention: A, B, C, D are the four bytes of a 32-bit value, with A as the most-significant byte and D as the least. A four-letter code like CDAB describes the order those bytes appear on the wire as you read the two consecutive registers left to right.
| Order | Wire bytes | Word order | Byte order in each word | Also called |
|---|---|---|---|---|
ABCD | A B C D | Big-endian | Big-endian | Big-endian / "Modbus standard" |
CDAB | C D A B | Little-endian | Big-endian | Word-swapped, Mid-Big-Endian |
BADC | B A D C | Big-endian | Little-endian | Byte-swapped, Mid-Little-Endian |
DCBA | D C B A | Little-endian | Little-endian | Little-endian |
ABCD is the order most people expect given that Modbus is described as a big-endian protocol. It's also the order the official spec text most naturally implies. But there is no requirement, and plenty of devices ship one of the other three. The reason is historical: when 32-bit values became common in the late 1980s, the dominant CPU architecture had flipped to little-endian Intel, and a lot of vendors mapped the low word to the low register address out of habit. That convention (CDAB) propagated into many product lines that are still shipping today.
Mapped to the two physical registers, this is what each ordering actually looks like:
Logical 32-bit value (MSB → LSB): A B C D
┌── Register N ──┐ ┌── Register N+1 ──┐
│ hi byte │ lo │ │ hi byte │ lo │
└─────────┴──────┘ └─────────┴────────┘
ABCD A B C D big-endian (intuitive)
CDAB C D A B word-swapped (low word first)
BADC B A D C byte-swapped inside each register
DCBA D C B A full little-endian
The two register reads themselves are always big-endian over the wire (each register's high byte first, then its low byte - that's the part of Modbus that is fixed). The differences above are entirely about which of the four logical bytes the vendor chose to put in each of the four physical byte slots.
Worked example: π across two registers
The 32-bit IEEE 754 single-precision encoding of π is 0x40490FDB. Bytes: A=0x40, B=0x49, C=0x0F, D=0xDB.
If a device stores π in two consecutive holding registers and you read both, the four bytes you see on the wire - and what they naively decode to as a 32-bit big-endian float - depend on the device's ordering:
Three of those four decodes are nonsensical. That's the diagnostic signal: if your "temperature" register reads 2.2e-29 or 7.7e5, you don't have a broken sensor - you have the wrong byte order. Pick the ordering whose decode actually looks like a temperature, and that's your device's convention.
What the Modbus spec actually says
The Modbus Application Protocol Specification (v1.1b3, modbus.org/specs.php) is precise about two things:
- Individual register values are transmitted high-byte-first on the wire. A register holding
0x1234is sent as12then34. - A register is 16 bits. That's the only data type the protocol natively defines.
The spec is silent about everything bigger than 16 bits. Floats, 32-bit integers, 64-bit timestamps, strings, bitfields - none of those exist at the protocol layer. They are decoder conventions on top of a stream of 16-bit registers. The four orderings above all satisfy rule (1) at the per-register level; they only diverge in how the words are arranged across consecutive registers.
Practically, this means:
- "Is my device Modbus-compliant?" doesn't tell you anything about its byte order.
- Vendor documentation that says "IEEE 754 single-precision float" without specifying the ordering is incomplete - and unfortunately, most vendor documentation is exactly that incomplete.
- You cannot tell the ordering from a passive frame capture alone. You need either a known reference value, or you need to try all four and pick the sensible one.
How to identify your device's byte order in the field
The reliable workflow is empirical, not documented:
- Pick a register you can verify against the device's display. A temperature sensor's local readout, a power meter's screen value, a setpoint you wrote yourself. The point is to know what the correct decoded value should be.
- Read the two consecutive registers raw. Don't ask the SCADA driver to decode it - get the four bytes off the wire. (Coilware Modbus and Wireshark both show raw register bytes; PLC function blocks usually do too, with the right output configured.)
- Try all four orderings. Either by hand, with our function block (next section), or by letting Coilware Modbus decode all four at once in the data view.
- Pick the one that matches the device's displayed value. If three are wildly out of range and one matches to within rounding, that's your answer.
- Document the convention for the device and reuse it across the rest of the device's register map - in practice a single device uses one ordering throughout, even if the vendor documentation never spells it out.
One important caveat: this whole workflow assumes the register pair really is a 32-bit float or 32-bit integer. If it isn't - if the device packs two unrelated 16-bit values into the pair, or stores a fixed-point integer with a vendor-defined scaling, or encodes a packed bitfield - then all four byte orderings will look nonsensical, because the bit pattern was never meant to be interpreted as a 32-bit number in the first place. If you've cycled through all four orderings and none of them produces a sensible value, the next assumption to question is the data type itself, not the byte order. The two 16-bit registers may not represent one value at all.
Two telltales that you've got the wrong ordering on a float:
- Value is denormalised - extremely small (≤ 1e-30) or extremely large (≥ 1e30). Real physical quantities don't sit there.
- Sign bit is set on a value that physically can't be negative (an absolute pressure, a square footage).
If a float decode produces NaN or Inf, you're definitely in the wrong ordering.
Function block: decode all four orderings on the PLC
This is the function block we flagged at the top - the one we drop in first whenever we're commissioning a device with an unfamiliar register map. It does the four-way decode directly on the PLC: you give it two consecutive registers, it gives you back twelve outputs (four float interpretations, four signed-int32, four unsigned-int32). Drop it in next to your existing Modbus read, watch the outputs in the online view, and the one whose value matches the device's local display is the ordering you should use. Five minutes, no extra tooling, and you can leave it deployed as a verification block while you're working through the rest of the register map.
Free downloads - coming soon. We're packaging it for four platforms; the files land in about a week, so check back on this page:
- Beckhoff TwinCAT 3 -
FB_ModbusDecode4Way(coming soon) - Schneider Control Expert / EcoStruxure -
FB_ModbusDecode4Wayfor M340 / M580 (coming soon) - Siemens TIA Portal -
FB_ModbusDecode4Wayfor S7-1200 / S7-1500 (coming soon) - Rockwell Studio 5000 -
AOI_ModbusDecode4Wayfor CompactLogix / ControlLogix (coming soon)
Output names follow the same convention on every platform:
INPUT
reg_high : WORD // first / higher-address register
reg_low : WORD // second / lower-address register
OUTPUT
float_ABCD : REAL
float_CDAB : REAL
float_BADC : REAL
float_DCBA : REAL
dint_ABCD : DINT
dint_CDAB : DINT
dint_BADC : DINT
dint_DCBA : DINT
udint_ABCD : UDINT
udint_CDAB : UDINT
udint_BADC : UDINT
udint_DCBA : UDINT
The function block is read-only - it doesn't perform the Modbus read itself, so it sits cleanly next to your existing comms function blocks regardless of which one you use.
Coilware Modbus: all four orderings, side by side
If you'd rather not deploy code to the PLC just to discover the byte order, Coilware Modbus shows all four decodings of any holding-register pair in its data view, simultaneously. Read once, glance, pick the one that looks right. The same view works for 32-bit signed and unsigned integers, and a separate panel handles the eight orderings used for 64-bit values.
This is the fastest way to nail down the convention on a device you haven't worked with before - especially in commissioning, when you don't yet have a PLC program to host the function block above.
See /coilware-modbus for the data view.
Common vendor defaults (verify, don't assume)
The table below summarises orderings commonly observed on each platform. Treat it as a hint, not a promise - vendors change conventions between product lines, and individual modules can override the platform default. Always confirm against a known reference value before trusting a decode.
| Platform / family | Common float ordering | Notes |
|---|---|---|
| Schneider PM5xxx power meters | ABCD | Big-endian throughout; well-documented. |
| Schneider Sepam protection relays | ABCD | Same as above. |
| Siemens S7 (native CPU Modbus) | ABCD | Big-endian matches the CPU's own memory model. |
| Allen-Bradley CompactLogix MSG (Modbus AOI) | Configurable | Default depends on the AOI vendor; verify the ByteOrder parameter. |
| Phoenix Contact ILC / Inline | CDAB | Word-swapped is the historical default. |
| Many low-cost power meters / temperature TXs | Vendor-specific | Genuinely unpredictable - assume nothing. |
| WAGO 750-series Modbus couplers | CDAB | Word-swapped by default in many configurations. |
Frequently asked questions
What's the difference between Modbus byte order and word order?
They describe two separate decisions. Byte order is the order of the two bytes inside a single 16-bit register; Modbus fixes this as big-endian (high byte first) and every device follows it. Word order is the order of the two 16-bit registers when you pack a 32-bit value across them, and Modbus is silent about this. The four orderings (ABCD, CDAB, BADC, DCBA) cover the four possible combinations of word-swapping and byte-swapping you can apply to the four bytes.
Why is my Modbus float reading wrong?
The most likely cause is a byte-order mismatch between what the device sends and what your decoder expects. Read the two registers raw and try decoding the four bytes as ABCD, CDAB, BADC, and DCBA in turn - one of them will match the value the device displays locally. The other three will be NaN, denormalised (< 1e-30), or improbably large/small.
Does Modbus TCP use a different byte order from Modbus RTU?
No. The byte and word orderings are determined by the device, not the transport. A device that uses CDAB on serial Modbus RTU will use CDAB on Modbus TCP. The only thing that changes between transports is the framing around the registers (CRC vs MBAP header) - the register data itself is identical.
How do I read a 64-bit value from Modbus?
Same idea, just more orderings to try. A 64-bit value spans four consecutive registers and there are eight common orderings on the wire. The diagnostic workflow is identical: read all eight bytes raw, try each ordering, and pick the one that matches a known reference value. Coilware Modbus shows all eight 64-bit orderings in a dedicated panel for exactly this reason.
Can a single device use different byte orders for different registers?
It is technically possible and it does happen, but it's rare. In practice, once you've identified the device's ordering for one register you can usually apply it to the entire register map. The exception is when a device mixes "native" values (its own measurements) with "passthrough" values from a subsystem with a different convention - some gateways and multi-protocol devices do this.
Troubleshooting flowchart for wrong Modbus values
When a 32-bit Modbus value is reading wrong but communications look healthy (no exception, no timeout, both registers respond), work through the following in order:
Wrap-up
Modbus byte order is the single most common reason a healthy-looking comms loop returns wrong values. The protocol layer is fine; the decoder layer is undefined. Capture the four wire bytes, try the four orderings, and the right one falls out within a couple of minutes.
We're writing the Modbus Debugging Field Manual right now - a free ebook covering byte-order traps in much more depth (including the eight 64-bit orderings, fixed-point scaling, and the worst real-world device quirks we've found), plus the exception codes, gateway traps, and timing pitfalls that don't fit in a blog post. It's not out yet. Join the waitlist and we'll email you the day it ships.
For the related protocol-layer issues - what exception codes mean when they do come back, and how to triage a sick Modbus loop end-to-end - see Modbus exception codes explained.