In this lesson

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.

One float32: 230.5 V0x42C80x0000High wordLow wordHigh word first (big-endian)0x42C80x0000Register nRegister n+1✓ reads 230.5Low word first (little-endian)0x00000x42C8✗ reads nonsense
Inside a register the order is fixed. Between two registers nothing is fixed, so the same pair reads as a voltage or as a number no meter ever produces.

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.

ByteValueRole in IEEE 754 float32
A43sign bit 0, start of the 8 exponent bits
B66end of the exponent, start of the 23 mantissa bits
C80mantissa
D00mantissa

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.

OrderCommon nameRegister nRegister n+1Read as float32
ABCDbig-endian0x43660x8000230.5
BADCbyte swap0x66430x00802.3 x 10^23
CDABword swap0x80000x4366-2.4 x 10^-41
DCBAlittle-endian0x00800x66431.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.

Four bytes from two holding registers43A66B80C00DWhat you expecta mains voltage, so around230 VABCD, big-endian230.5BADC, byte swap2.3 x 10^23CDAB, word swap-2.4 x 10^-41DCBA, little-endian1.2 x 10^-38Only ABCD gives a voltage a meter ever measures. Set the client tobig-endian.
Four interpretations of one byte pair, one plausible outcome. This is how you settle the word order without memorising a single acronym.

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.

OrderRegister nRegister n+1Read as int32
ABCD0x00120xD6871,234,567
BADC0x12000x87D6302,024,662
CDAB0xD6870x0012-695,795,694
DCBA0x87D60x1200-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.

40200550×10^402011=5500 W
40200
raw active power, int16
40201
scale factor W_SF, sunssf
5500 W
real value
SunSpec keeps the scale in a register of its own, right beside the measurement. Read only the value register and you are a factor of ten out.

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.

TypeNot implemented
int16 and sunssf0x8000
uint16, enum16, bitfield160xFFFF
int320x80000000
float320x7FC00000

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. 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 100 is mbpoll's 1-based reference; add -0 if your datasheet gives PDU addresses.

  2. 2

    Read the same pair as a float

    Change only the data type and let the tool do the conversion for you.

  3. 3

    Flip the word order

    Repeat the float read with -B added. Same bytes, other interpretation, and one of the two answers is nonsense.

  4. 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. 5

    Repeat it in Python

    pymodbus does the same conversion in one call, which is what your integration will end up doing.

  6. 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 00 read 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 as 0x8000 first.
  • 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
Two registers hold 0x4366 and 0x8000. Which value is that as a float32 with the high word first?
You read a mains voltage and get values in the order of ten to the power minus 41 instead of something near 230. What do you try first?
You read a meter standing at roughly 1,000,000 kWh as a float32. What is the consequence?
Why does the Modbus standard not define the word order between two registers?

Stuck on a project?

Describe what you see and where it goes wrong. We will look at it and tell you what we would do.

Further reading in the knowledge base