— Endianness, 32-bit Floats, Strings & Advanced Representations
(Module 4 · Modbus Data Model & Function Codes)
Learning objectives
After you finish this chapter you will be able to …
- Pack and unpack every common data type—signed/unsigned integers, IEEE-754 floats, 64-bit doubles, ASCII strings, BCD—into the 16-bit register lattice Modbus provides.
- Identify and correct the four real-world byte/word-swap patterns (“ABCD”, “BADC”, “CDAB”, “DCBA”).
- Explain the difference between little-endian bytes and little-endian words—and why careless mixing causes 256× or 65 536× errors.
- Implement robust conversion code in Python, C/C++, and IEC-61131 that never breaks across CPUs.
- Audit an existing register map and spot hidden pitfalls (bit packing, signed vs unsigned, mixed scaling).
12.1 Why data representation matters
Modbus is transport-layer simple: everything is a 16-bit word (or a single bit). Real-world values rarely fit this neat box:
- Temperatures need one decimal place (25 .3 °C).
- Total energy may need 64-bit accumulation.
- Firmware version or date stamp is text.
Unless master and slave agree exactly how multi-word values are stacked, the SCADA may show nonsense—often 256× off (byte swap) or 65 536× off (word swap).
12.2 Unsigned & signed integers
Width | Registers | Range | Typical use |
---|---|---|---|
16-bit uint | 1 | 0 … 65 535 | Pulse counters, set-points |
16-bit int (two’s comp) | 1 | –32 768 … 32 767 | ± current, temperature offset |
32-bit uint | 2 | 0 … 4 294 967 295 | Total pulses, kWh totals |
32-bit int | 2 | –2 147 483 648 … + 2 147 483 647 | ± position, ΔP |
Packing order – default Modbus convention is Big-endian word, Big-endian byte (“AB CD”).
Hi word sent first, hi byte of each word sent first.
▶ Tip — If the datasheet does not mention word order, assume the standard “ABCD”.
12.3 IEEE-754 floating-point
12.3.1 32-bit float layout
Sign 1 bit | Exponent 8 bit | Fraction 23 bit
12.3.2 Four swap patterns you’ll meet in the field
Word order | Byte order | Nickname | Example hex for 123.456 f |
---|---|---|---|
A B C D | big-endian | ABCD (standard) | 42 F6 E9 79 |
B A D C | big-endian | BADC | F6 42 79 E9 |
C D A B | big-endian | CDAB | E9 79 42 F6 |
D C B A | big-endian | DCBA | 79 E9 F6 42 |
Byte-swap is rare; word-swap is common on Rockwell, GE, some ABB drives.
12.3.3 Python helper — Listing 1
import struct
def decode_float32(reg_hi, reg_lo, pattern="ABCD"):
words = [reg_hi, reg_lo]
if pattern == "BADC":
words = [(reg_hi << 8) | (reg_hi >> 8),
(reg_lo << 8) | (reg_lo >> 8)]
elif pattern == "CDAB":
words = [reg_lo, reg_hi]
elif pattern == "DCBA":
words = [((reg_lo << 8) | (reg_lo >> 8)),
((reg_hi << 8) | (reg_hi >> 8))]
raw = struct.pack('>HH', *words)
return struct.unpack('>f', raw)[0]
12.4 64-bit double & long-long
Requires four consecutive registers.
Parameter | Float64 | Uint64 |
---|---|---|
Registers | 4 | 4 |
Word-order options | Same four patterns as float32 | Same |
⚠ Performance note — many 8-bit MCUs cannot decode float64 natively; they may map the value to two 32-bit floats (hi, lo) instead.
12.5 Scaling strategies
Strategy | Example | Pros | Cons |
---|---|---|---|
Implicit decimal | 253 ⇒ 25.3 °C | Integers only, no FP | Limited resolution |
Fixed-point Qn.m | Q8.8 => 0x1234 ⇒ 0x12.34 | Cheap math | Needs doc |
Engineering units ×1000 | 101325 Pa ⇒ 101 kPa | Clear | Overflows sooner |
IEEE-754 float | 25.3 → 0x41CA 999A | Wide range | Requires FP lib |
Use a single scaling convention per table; mix-and-match leads to operator errors.
12.6 Strings & ASCII blobs
Method | Detail |
---|---|
2 chars per register | Register Hi-byte = first char |
Pascal style | First reg = length |
Null-terminated (C) | 0x00 sentinel |
Always allocate an even number of characters; pad unused bytes with 0x20
(space) or 0x00
.
12.7 Bit-level access inside registers
Use FC01/02 for true coils; but if memory limited, you can pack 16 status bits into one holding register:
Bit | Meaning |
---|---|
0 | LimitSW_A |
1 | LimitSW_B |
… | … |
15 | System_ready |
Master must read full word and mask bits:
status = client.read_holding_registers(100, 1).registers[0]
if status & (1 << 0):
print("Limit A hit")
Downside: cannot write single bits without Read-Mod-Write-Write race condition—use cautiously.
12.8 Binary-Coded Decimal (BCD)
2 decimal digits per byte.
Type | Example value | Register hex |
---|---|---|
Date YYYYMMDD | 2025-06-07 | 0x20 25, 0x06 07 |
Packed counter | 12345678 | 0x12 34, 0x56 78 |
Validation rule — nibble > 9 = data corruption.
12.9 Troubleshooting table
Symptom | Likely cause | On-the-wire clue | Field fix |
---|---|---|---|
256× too large | Byte swap | Hi-byte/Lo-byte reversed in capture | Swap bytes in code |
65 536× too large | Word swap | Word order reversed | Swap register order |
Negative value when should be unsigned | Signed vs unsigned read | 0xFFFF FF38 displayed –200 | Interpret as uint or extend map |
Random chars in string | Odd-length + missing padding | Second half of reg overwritten | Pad with 0x00 or 0x20 |
12.10 Design checklist for device vendors
- Document every field: address, type, units, scale, word order, R/W.
- Keep multi-word values even-aligned (start on odd human numbers — 40001, 40003).
- Include sample raw frames in the datasheet.
- Provide open-source decoding snippet (C or Python) to remove ambiguity.
- Reserve extra registers now; firmware upgrades later will thank you.
Chapter recap
- Modbus transports only bits + 16-bit words; everything else is a packing problem.
- Know the ABCD → DCBA taxonomy and build swap guards into your code.
- IEEE-754 floats give clarity, but require explicit word-order statement.
- Strings, BCD, and bit-packed flags are space-savers—use judiciously.
- Publish an unambiguous register book; it is the eternal contract between device and every system that will ever talk to it.
Assets to create
ID | Visual idea |
---|---|
Fig-12-1 | Word & byte order matrix (ABCD/BADC/CDAB/DCBA) |
Fig-12-2 | Float32 bit-field diagram (sign, exponent, mantissa) |
Fig-12-3 | BCD vs packed-int comparison graphic |
Listing-1 | Python swap-aware float decoder |
Worksheet | Excel: input raw registers → live decode (downloadable) |
Next: Chapter 13 – Modbus Exception Responses: Understanding & Handling Errors. We’ll dissect every exception code, map it to root-causes, and build a solid retry/back-off strategy that keeps masters from flooding the bus when something goes wrong.