Modbus Decoding Modbus

Modbus Basics covered getting a response back at all — what device to ask, what function code to use, what an exception means. This page is the companion: what to do with the sixteen bits when they arrive. Modbus is famous (notorious) for handing back a number that's somehow wrong even after the read succeeded — 40001 isn't where the vendor docs said it would be, a temperature reads −12 instead of 533, a 32-bit float comes back gibberish. None of those are protocol bugs; they're interpretation choices the protocol leaves to you. This page walks through the four common ones and how they go sideways.

The 5-digit numbering trap

Open a vendor's Modbus integration table and you'll see addresses like 40001, 30005, 10012, 00007. Plug 40001 into your tool, hit read, get an exception. The off-by-one is real, and it's not a bug. It's a documentation convention older than most people debugging it.

The leading digit names which data table the address lives in — 0 for coils, 1 for discrete inputs, 3 for input registers, 4 for holding registers. It is not part of the wire address. The remaining four digits are the 1-based index inside that table. So 40001 means the first holding register — which on the wire is holding-register address 0. 40123 means the 123rd holding register — wire address 122. The off-by-one comes from the 1-based numbering meeting the wire's 0-based addressing.

Translating 5-digit vendor numbering to wire addresses Two columns. The left column lists three example addresses as a vendor would write them: 40001, 40123, and 30005. The leading digit of each is highlighted in the accent color and labeled "table prefix." The remaining four digits are the 1-based index inside that table. The right column shows the wire-address translation: 40001 becomes "holding table, address 0"; 40123 becomes "holding table, address 122"; 30005 becomes "input table, address 4." A note reminds the reader that the wire address is the documented number minus one. vendor docs say on the wire 40001 holding table address 0 40123 holding table address 122 30005 input table address 4 prefix names the table; wire address = (documented number − 1)

Two related conventions to know about:

  • Some vendors drop the prefix and the +1 entirely — a "Modicon-style" address table lists wire addresses straight: 0, 1, 2. The function code names the table; no leading digit needed. The same physical register may appear as 40001 in one vendor's manual and 0 in another's.
  • When the register count exceeds 9999, some vendors switch to a 6-digit "extended" form (400001 for the first holding register). Same convention, one more digit of headroom.

Rule of thumb on the bench: when a vendor's address starts with 4 or 3 and has four digits after it, it's almost certainly 5-digit Modbus numbering. The wire address is the documented number minus one, with the leading digit pointing you at FC03/FC16 (holdings) or FC04 (inputs).

Signed vs unsigned interpretation

The right register answers, the response carries sixteen bits, and now you have to choose what those bits mean. The protocol stores them; it doesn't tell you whether to read them as a positive count from 0 to 65535 or a signed count from −32768 to +32767. Same bits, different intent.

A single 16-bit register, two valid interpretations The hex value 0xFFF3 is shown as a 16-bit binary number. An arrow splits into two branches. The left branch labels it "unsigned, 65523." The right branch labels it "signed two's complement, −13" with a note that the high bit being 1 means negative. 0xFFF3 1111 1111 1111 0011 unsigned (UINT16) 65523 signed (INT16) −13 high bit is 1 → signed reading is negative; unsigned reading is large

Two's complement is the rule the signed interpretation follows: if the highest bit (the "sign bit") is 0, the number is what the bit pattern says. If the sign bit is 1, the number is the bit pattern minus 65536. So 0xFFF3 (the example above) is either 65523 read unsigned, or 65523 − 65536 = −13 read signed.

The clue lives in the vendor docs: INT16 means signed, UINT16 means unsigned. When the docs don't say, the values in use tell the story. The signature of a negative-as-unsigned misread is values jumping near 65535 — a sensor reading 65521 isn't measuring 65521 of anything, it's a signed −15 being read with the wrong type. Same trap on a smaller scale with offset values that swing around zero.

Signed values show up in building controls anywhere a reading can go negative: outdoor-air temperatures, signed deltas, offset values, anything referenced to a baseline. Unsigned values are typical for counters, runtimes, percentages, setpoints, and packed bit-flag words.

32-bit values and the four byte orders

A 16-bit register can hold a temperature with a tenth of a degree of resolution, a 0–100 % setpoint, or a small counter. It cannot hold a 32-bit floating-point number, a 32-bit signed integer, or a four-byte timestamp. When the vendor wants to communicate one of those, they map it across two consecutive 16-bit registers — and there is no 32-bit- anything in the Modbus data model to enforce a byte order. So four orderings exist in the wild, all of them in current shipments, none of them the "standard."

Pick a four-byte value with bytes labeled A B C D in transmission order — 0x12 0x34 0x56 0x78 for a worked example. Each register holds two of those bytes. The four orderings are the four ways a device may have laid them into the two registers:

The four Modbus byte orderings for a 32-bit value A 32-bit value with bytes labeled A, B, C, D in transmission order is shown four ways across two consecutive registers. ABCD is byte order A-B in the first register, C-D in the second — the strict Modbus standard. CDAB swaps word order: C-D in the first register, A-B in the second — the Modicon word-swap. BADC swaps bytes within each word: B-A in the first register, D-C in the second. DCBA does both — D-C in the first register, B-A in the second. register N register N+1 ABCD A B C D strict-Modbus CDAB C D A B Modicon swap BADC B A D C byte-in-word DCBA D C B A full reverse network order: A · B · C · D — devices lay it into two 16-bit registers four ways

ABCD · strict-Modbus

Big-endian word, big-endian byte. Bytes lie in the two registers in their natural transmission order. The closest thing to a Modbus "standard"; common on modern IP-side gear and platform controllers.

CDAB · Modicon word-swap

Little-endian word, big-endian byte. Each register's bytes are in network order, but the two registers are swapped. Legacy Modicon PLCs exposed an internal little-endian word layout to the wire this way.

BADC · byte-in-word swap

Big-endian word, little-endian byte. Word order preserved, byte order within each word reversed. Less common; turns up on some specialty meters and older industrial gear.

DCBA · full reverse

Little-endian word, little-endian byte. The whole 32-bit value is byte-reversed. Common on x86-derived devices that store the value in CPU-native form and forward it raw.

How to find which one a device uses: pick a register pair where you know the expected value — a configured setpoint, a calibration constant, a baseline reading. Read the two registers. The four reconstructions produce four wildly different decimal values; one of them matches the expected, and that's the device's ordering. The Modbus Register Viewer tool's 32-bit Pair tab shows all four side-by-side for exactly this exercise — you don't have to compute them by hand.

Once you've identified the ordering for a device, it holds across every 32-bit value on that device. Manufacturers don't mix orderings within a product. So this is figure-out-once, document-it-on-the-job work, not a per-register decision.

Scaling and engineering units

The last piece — and the one Modbus is most famously dumb about — is the jump from the integer stored in the register to a value with units. A holding register holds a number; the protocol doesn't know whether that number is in tenths of a degree, in percent times 10, in raw ADC counts, or in something else entirely. The vendor decides; the vendor's docs are the only thing that knows.

Applying a scale factor to a raw register value Three boxes left to right. The left box shows the raw register value 523. An arrow labeled "× 0.1 (per docs)" leads to the middle position. The right box shows the engineering value 52.3 degrees Fahrenheit. A caption notes that the protocol stores the raw integer; the units and scale live only in the vendor's manual. raw register 523 × 0.1 scale from the vendor docs engineering 52.3 °F the protocol stores the integer; the units and scale live only in the manual

The patterns to recognize:

  • Implicit decimal point. The most common pattern. The vendor stores the value × 10 (or × 100) to preserve one or two decimal places in an integer register. A reading of 523 with the docs noting scale 0.1, units °F means 52.3 °F. A reading of 1024 with scale 0.01 means 10.24.
  • Offset + scale. Less common, but real. The stored value is (actual − offset) × scale. The docs name both numbers; both have to be applied. Older equipment that exposed calibration values directly tends to use this.
  • Native unit. Some values are stored directly in the units the docs claim. An event counter doesn't need scaling; an integer setpoint in percent often doesn't either.
  • Packed multi-field encoding. A status word may pack several flags into one register's bits, with the docs naming what each bit means. The Modbus Register Viewer tool's binary readout is built for these.

Scaling is where the "vendor docs are load-bearing" framing from Modbus Basics gets concrete. A raw register without its scale tag is meaningless — and a register read with the wrong scale tag is worse than meaningless. The classic failure is treating an unsigned register that's actually signed-and-scaled. A temperature reads 6553.5 (a 0.1-scaled raw 65535) when it's actually −0.1 (raw −1 read signed, scaled by 0.1). The wrong number looks plausible enough to log without flagging; the real value is a sensor at the bottom of its range. Both interpretation choices have to be right.

What this page didn't cover

Two questions stay out of scope, both deliberately:

  • Modbus RTU on the wire — RS-485 multidrop wiring, baud rates, parity, the two-byte CRC computation, the inter-character framing timeout. Most BMS techs touch this layer only at commissioning; a third Modbus page may land here if demand surfaces.
  • The integration story — how a Niagara gateway or an EBO field controller maps Modbus points into the BAS object space, how poll rates are set, how stale-data flags propagate. Platform-specific work that belongs on the eventual Niagara / EBO pages, not here.

Between the two pages: a device address, the right function code, the right register, the right sign interpretation, the right byte order, and the right scale to engineering units. That's the BMS integration loop, end to end — enough to read a vendor's Modbus table, write the config, and trust the number on the graphic. The practitioner-grade interactive surface (signed/unsigned toggle, the four-order viewer, decimal/hex/binary readouts) lives on the Modbus Register Viewer tool, one click away.

← Back to Education