Argus EMS · 21.09.2026 · 6 min read
The power meter is mounted, the link is up and every poll gets a reply, yet the dashboard shows 2.3×10²³ V instead of 230 V, the frequency sits at zero and the energy counter either freezes or jumps around. Crews often blame the cabling. But if the device answers, the physical layer is almost certainly fine; what has gone wrong is how the register map was interpreted. This article skips protocol basics and focuses on the four things in a map that decide whether a value is right: the data table and function code, the address base, the data type, and the byte and word order.
Each row in a vendor document describes one parameter: its name, address, table or function code, data type, register count, scale factor, engineering unit and access rights. Modbus defines four data tables, and each has its own read function:
| Table | Classic notation | Read function | Content |
|---|---|---|---|
| Coil | 0xxxx | FC01 | Single bit, read and write |
| Discrete input | 1xxxx | FC02 | Single bit, read only |
| Input register | 3xxxx | FC04 | 16-bit word, read only |
| Holding register | 4xxxx | FC03 | 16-bit word, read and write |
The protocol only moves 16-bit words. A 32-bit power reading or an IEEE 754 float spans two registers, and a 64-bit energy counter spans four. Querying the wrong table, holding instead of input, either returns an ILLEGAL DATA ADDRESS exception or, worse, silently returns a different parameter that happens to live at the same number.
Documentation writes addresses in three styles: classic notation (40001), a one-based register number (Register 1) and a zero-based protocol address (0x0000). On the wire, only the zero-based offset exists: 40001 means FC03 at address 0, and 40108 means address 107. Many software libraries are zero-based, while some SCADA tools expect the one-based number and subtract one internally. Subtract twice, or not at all, and every read lands exactly one register away.
An off-by-one error rarely shows up as a fault, because the device happily returns the neighbouring register. Single-register values turn into the adjacent parameter, while 32-bit values glue the low word of one parameter to the high word of the next and produce nonsense. We ran into this on site with a chiller whose building automation document listed one-based register numbers while the reader expected zero-based addresses; leaving water temperature only matched the controller display once the address was set to the register number minus one. Another manufacturer's document already used zero-based numbers, so subtracting one there would have caused the very same shift. The lesson is simple: never assume the convention, verify it for every device model.
| Data type | Registers | Typical use | Watch out for |
|---|---|---|---|
| UINT16 | 1 | Frequency, status codes | 0xFFFF may mean no data |
| INT16 | 1 | Power factor, temperature | Negative values use two's complement |
| UINT32 / INT32 | 2 | Voltage, current, active power | Word order and sign bit |
| FLOAT32 | 2 | Unscaled physical values | Byte and word order are critical |
| UINT64 / INT64 | 4 | Energy counters | Word order varies by vendor |
| ASCII | N | Serial number, model name | Two characters per register |
Mixing signed and unsigned types is a quiet source of trouble. A value sent as INT16 −15 reads as 65521 when decoded as UINT16. On the consumption side, negative power is already a red flag for a wiring fault such as reversed current transformer polarity; choosing the wrong type hides that warning behind a large positive number. For integer registers the real value is raw value × multiplier + offset: a tenth for voltage and a thousandth for power factor are common, and devices reporting Fahrenheit need both a multiplier and an offset.
Then there are invalid-value markers. Plenty of devices flag missing data with the extreme values of a type: 0xFFFF, 0x7FFF, 0x8000, or 0x80000000 for 32-bit fields. We have seen a generator controller send 0xFFFF7FFF for an invalid reading. Store those as numbers and your trend charts fill up with giant spikes.
The Modbus specification puts the high byte first inside a single register, but it says nothing about the order of words in a multi-register value. That is why four layouts appear in the field. Label the bytes of a 32-bit value A, B, C and D from most to least significant:
Take a FLOAT32 carrying 230.5 V, which is 0x43668000. If the device uses one of the four layouts and the software assumes ABCD, this is what comes out:
| Device layout | Registers received | Decoded assuming ABCD |
|---|---|---|
| ABCD | 0x4366 0x8000 | 230.5 |
| CDAB | 0x8000 0x4366 | −2.4×10⁻⁴¹ |
| BADC | 0x6643 0x0080 | 2.3×10²³ |
| DCBA | 0x0080 0x6643 | 1.2×10⁻³⁸ |
With floats, the wrong layout almost always gives an absurd result, so it gets caught quickly. Integer counters are sneakier: a word-swapped energy register shows a plausible but wrong number that leaps whenever the low word rolls over. Sixty-four-bit counters have no consensus either; one brand sends its four words high to low while another on the same site sends the low word first.
In Argus EMS every device model is described by a template. Each register row holds the start address, register count, data type (INT16, UINT16, INT32, UINT32, FLOAT32 and others), function code (holding or input), byte order, multiplier, offset, unit, and whether the value is instantaneous or cumulative. A verified map is written once and reused for every device of that model. The field agent readers on the site PC work with zero-based protocol addresses, decode 16, 32 and 64-bit values by type, and leave known invalid-value markers empty instead of turning them into numbers.
One feature that pays off during commissioning: a remote management command can read a device's raw registers with FC03 or FC04 and return them as decimal and hex dumps, so address and byte-order trials happen without a site visit. Readings collected over Modbus TCP and RTU are forwarded to the server via MQTT. If the connection drops, data is buffered in a local SQLite queue for up to 30 days and is never deleted before the server acknowledges it, while the server keeps raw readings for 13 months.
Related Content
Explore the system with your own data in a demo session.