View all lessons
Part 1
Fundamentals
Part 2
Modbus RTU
- Lesson 5RS485 explained: A, B, common and termination20 min
- Lesson 6Baud rate and parity: why 8E1 is the default16 min
- Lesson 7The Modbus RTU frame byte by byte, plus CRC18 min
- Lesson 8Multiple Modbus devices on one RS485 bus16 min
- Lesson 9Read your first Modbus device with mbpoll22 min
- Lesson 10Modbus RTU troubleshooting: symptom to cause20 min
Part 3
Modbus TCP
Part 4
Advanced
- Lesson 16Writing to a Modbus device without breaking it18 min
- Lesson 17Word order and floats: same bytes, other value22 min
- Lesson 18Calculate your poll interval and bus load18 min
- Lesson 19Modbus security: the protocol will not help16 min
- Lesson 20Modbus integration: PLC, Home Assistant, cloud20 min
- Lesson 21Modbus commissioning checklist and cheat sheet18 min
Word order and floats: same bytes, other value
Modbus word order decides whether you read 230.5 or pure nonsense. Learn the four permutations, IEEE 754 floats, scale factors and sentinel values fast.
What this lesson covers
- The four permutations in which two registers form a float32
- The plausibility test that finds the right word order
- Sentinel values, and why an energy counter does not fit in a float32
Read first: Modbus addressing: from 40001 to the wire, How to read a Modbus datasheet, step by step
Modbus word order decides whether the same four bytes read as 230.5 volts or as 2.3 times ten to the power 23. The standard fixes the byte order inside one register and says nothing at all about the sequence of two registers, so every value wider than 16 bits rests on a vendor agreement. After this lesson you can pull a float32 out of two registers, try all four permutations, and pick the right one on plausibility instead of jargon.
What the standard does and does not define
Read one register holding 0x1234 and the device puts 12 on the wire first, then 34. That is written down: Modbus uses a big-endian representation, so the most significant byte goes first and the first byte of each register carries the high order bits. Now read two registers that together hold a 32-bit value. Which one carries the high half? The specification does not say. It defines four data blocks of bits and 16-bit registers and stops there, so a float32 or a uint32 is a vendor agreement, not a Modbus type. The one little-endian field in the protocol is the CRC of an RTU frame, low byte first.
Building a float32 from two registers
The datasheet line says float32 over two registers, a line you read the way reading a Modbus datasheet sets out. Those two holding registers come back as 0x4366 and 0x8000. Concatenate them in the order they arrived and you hold 0x43668000, four bytes 43 66 80 00.
| Byte | Value | Role in IEEE 754 float32 |
|---|---|---|
| A | 43 | sign bit 0, start of the 8 exponent bits |
| B | 66 | end of the exponent, start of the 23 mantissa bits |
| C | 80 | mantissa |
| D | 00 | mantissa |
IEEE 754 splits those 32 bits into 1 sign bit, 8 exponent bits and 23 mantissa bits, with an exponent bias of 127. The exponent field here is 134, so the exponent is 7. The mantissa is 6,717,440 out of 8,388,608, which is 0.80078125. That gives 1.80078125 times 128, so 230.5. A mains voltage, so the order was right.
The rule behind it: two registers carry one 32-bit number, the four bytes are one IEEE 754 value, and the only open question is which register comes first.
Change one thing and read an energy counter instead. 1,234,567 Wh as an int32 is 0x0012D687, so register n holds 0x0012 and register n+1 holds 0xD687. Same procedure, different type, and that difference matters more than it looks.
The four permutations, and their names
Four bytes can be ordered four ways that anyone actually implements.
| Order | Common name | Register n | Register n+1 | Read as float32 |
|---|---|---|---|---|
| ABCD | big-endian | 0x4366 | 0x8000 | 230.5 |
| BADC | byte swap | 0x6643 | 0x0080 | 2.3 x 10^23 |
| CDAB | word swap | 0x8000 | 0x4366 | -2.4 x 10^-41 |
| DCBA | little-endian | 0x0080 | 0x6643 | 1.2 x 10^-38 |
The names describe what moves. Big-endian is high word first, high byte first. Word swap puts the low word first, byte swap reverses the bytes inside each word, and little-endian reverses everything. In the Home Assistant Modbus integration you reach all four: no swap key is ABCD, swap: byte gives BADC, swap: word gives CDAB, and swap: word_byte gives DCBA.
Vendors sit on both sides. SolarEdge documents flatly that its registers are big-endian, most significant values at the lowest address. Eastron ships most significant register first but lets you change it through a Register Order parameter: write the float 2141.0 into it and the meter deduces from the bytes it received which order your client uses.
The plausibility test instead of jargon
You do not need the acronyms. Pick a quantity whose order of magnitude you already know, a mains voltage near 230 V or a frequency near 50 Hz, read it in each order, and keep the only reading that lands where you expect.
Test on a voltage, never on a counter. With a float32 a wrong order screams at you. With an int32 all four orders look like a believable meter reading, and the same 1,234,567 Wh from the previous section proves it.
| Order | Register n | Register n+1 | Read as int32 |
|---|---|---|---|
| ABCD | 0x0012 | 0xD687 | 1,234,567 |
| BADC | 0x1200 | 0x87D6 | 302,024,662 |
| CDAB | 0xD687 | 0x0012 | -695,795,694 |
| DCBA | 0x87D6 | 0x1200 | -2,016,013,824 |
Some tools do that comparison for you: ModbusCloud Diagnostics works out in its Register Explorer which byte order gives the most plausible value. On a counter the judgement stays yours, because all four orders look believable there.
Scale factors living in their own register
SunSpec avoids floats in its integer maps. It puts a whole number in one register and the power of ten in a second register beside it, a signed 16-bit value of type sunssf with a range of -10 to 10. The real value is the raw value times ten to the power of that scale factor.
On an SMA inverter, datasheet number 40200 (1-based) holds active power and 40201 its scale factor, fixed at 1, so a raw 550 is 5,500 W. Datasheet number 40202 holds the grid frequency, scale factor at 40203 set to -2, so a raw 5001 is 50.01 Hz. Datasheet number 40217 holds DC power with a scale factor of 2: skip it and you report 57 W on a string delivering 5,700 W. Do not apply the rule blindly, though: 40219, the internal temperature, has no scale factor at all. How SunSpec models and scale factors are laid out is the reference version, and what a register map lists per value covers the columns a datasheet owes you.
When a number is not a measurement
A register with nothing in it does not come back empty. It comes back with a fixed sentinel that looks like a reading.
| Type | Not implemented |
|---|---|
| int16 and sunssf | 0x8000 |
| uint16, enum16, bitfield16 | 0xFFFF |
| int32 | 0x80000000 |
| float32 | 0x7FC00000 |
Daikin uses readable decimals instead: 32767 means the device does not know this register, 32766 means unavailable in this configuration, 32765 means the value has not loaded yet. A Daikin temperature register returning 32767 is not 327.67 degrees.
Why an energy counter does not belong in a float
A float32 has 24 bits of effective significance, so above 2 to the power 24, which is 16,777,216, it can no longer represent every whole number and the step size becomes 2. In a float32, 16,777,216 and 16,777,217 are the same number. A counter in Wh therefore goes grainy above roughly 16.78 MWh, which is why meters report kWh. Even in kWh it shows: at 1,000,000 kWh the smallest step a float32 can display is 0.0625 kWh.
Use a uint32 or a uint64 with a scale factor for anything that only climbs. Watch the far end too: a uint32 counter in Wh read as a signed int32 turns negative at 2,147,483,648 Wh, roughly 2.15 GWh, after years of correct readings.
Common mistakes
Confusing byte order with word order. Inside a register the order is fixed, between registers it is not. Turning on "byte swap" in your client when you need "word swap" changes the bytes inside each register and leaves the halves where they were, so the value stays wrong and you conclude the device is broken.
Trusting the jargon instead of the measurement. Two tools call the same permutation by different names, and a datasheet may name none of them. The plausibility test on a voltage always works, the terminology does not.
Putting a sentinel in a graph. 0x7FC00000 on a Fronius and 0x80000000 on an SMA mean "no value". Store them as numbers and you get a spike of billions on your dashboard plus an alarm nobody can explain. Filter them in the mapping, not in the chart.
Reading a kWh counter as a float32. Above 16,777,216 you lose precision, and at a million kWh the smallest step is 0.0625 kWh, so daily consumption differences stop adding up.
Get hands-on
You read the same two registers three times and decide for yourself which outcome can be right. Both mbpoll and pymodbus are free to use, including commercially.
- 1
Read the raw registers
Take two registers your datasheet says form one 32-bit value and print them as hex, so you see what the device sent rather than what the tool assumed.
-r 100is mbpoll's 1-based reference; add-0if your datasheet gives PDU addresses. - 2
Read the same pair as a float
Change only the data type and let the tool do the conversion for you.
- 3
Flip the word order
Repeat the float read with
-Badded. Same bytes, other interpretation, and one of the two answers is nonsense. - 4
Judge on plausibility
Write both outcomes down next to the quantity you are measuring. A voltage near 230 or a frequency near 50 settles it in one look.
- 5
Repeat it in Python
pymodbus does the same conversion in one call, which is what your integration will end up doing.
- 6
Record the combination
Note brand, model, data type and word order in your project file. For this model you never have to look it up again.
#1. the raw registers, as hex
mbpoll -m rtu -a 1 -b 9600 -P none -t 4:hex -r 100 -c 2 -1 /dev/ttyUSB0
#2. the same registers as a float32
mbpoll -m rtu -a 1 -b 9600 -P none -t 4:float -r 100 -c 2 -1 /dev/ttyUSB0
#3. the same registers, other word order
mbpoll -m rtu -a 1 -b 9600 -P none -t 4:float -B -r 100 -c 2 -1 /dev/ttyUSB0
value = client.convert_from_registers(
rr.registers, data_type=client.DATATYPE.FLOAT32, word_order="big"
)
Run that second time with word_order="little" and compare.
Without hardware. Use the pymodbus simulator from practising on a Modbus simulator. In its default dataset, PDU address 6 holds a float32 of around 404 spread over two registers that climbs on every read. Try both word orders and keep the one that produces a rising, plausible series.
Expected result: two interpretations of one byte pair, one of them plausible, and a line in your project file that saves the next engineer an afternoon.
Summary
- Modbus fixes the byte order inside a register and says nothing about the sequence of two registers, so every 32-bit value rests on a vendor agreement.
- The bytes
43 66 80 00read as 230.5 in ABCD order and as three unusable numbers in the other three orders. - With an int32 all four permutations look believable, so settle the word order on a voltage, never on a counter.
- A scale factor may live in its own register, as SunSpec does with
sunssf, and you filter sentinels such as0x8000first. - A float32 stops representing every whole number above 16,777,216, so a large energy counter belongs in a uint32 or uint64.
Check yourself
Four questions about this lesson. Every answer comes with an explanation.
Question 1 of 4
Want to see how it works?
The ModbusCloud Gateway reads the devices from this course without you programming a single register.