Modbus Basics Modbus

Modbus is older than most building automation. Modicon released it in 1979 so that a programmable controller and a host could trade a handful of values over a serial line, and the same protocol — barely changed — now carries readings between half the BMS-adjacent gear on a job site. Variable-frequency drives, power meters, BTU meters, packaged units with their own controllers, plenty of small specialty kit nobody else integrates: any of them might speak it. If you integrate equipment, you'll meet it. This page is the explainer the Modbus Register Viewer footnote keeps pointing toward.

What Modbus is, and isn't

At its core, Modbus is two things: a small set of function codes that say "read these locations" or "write this location," and a tiny data model of locations to read and write. The transport — whether the message runs over a serial line (Modbus RTU) or an Ethernet TCP connection (Modbus TCP) — changes the framing around the message but not the function codes or the data model. Either flavor, the same nine or ten function codes are doing the work.

What Modbus is not is self-describing. A BACnet object knows its own type, units, scale, and name; a Modbus register is just sixteen bits. Nothing on the wire says holding register 12 is in tenths of a degree Fahrenheit, or that the value 524 means 52.4 °F. That mapping lives only in the vendor's documentation. Modbus is dumb on purpose: the protocol stays simple, and the device's manual carries the load. Lose the manual and you have data without meaning.

The other thing Modbus assumes is one client talking to one or more servers. (Older docs say master/slave; the function codes and the wire are unchanged, only the names have moved on.) Every conversation starts when the client sends a request to a specific server's address; the server returns a response, then waits silently for the next request. Servers don't initiate — there is no asynchronous "by the way, this just changed" notification. If a sequence cares how fast it sees a new value, the client has to poll fast enough to catch it.

Modbus client-server message flow Two boxes side by side. The left box is labeled Client; the right box is labeled Server. An upper arrow runs from the client to the server, labeled request — for example, function code 03 to read ten holding registers starting at zero. A lower arrow runs from the server back to the client, labeled response — ten register values. The server does not speak on its own; the client polls and the server answers. Client (the master) Server (the slave) request "FC03 read 10 regs from 0" response "10 register values" the client polls; the server only ever answers

The four data tables

The data model has four tables, sorted along two dimensions: what each item is (a single bit or a 16-bit word), and whether the client can write it or only read it. The two single-bit tables — coils and discrete inputs — predate the registers; they map back to relay-style controllers, where "coil" meant the energizing coil of a control relay the master could write, and "discrete input" was a contact closure it could only read. The two 16-bit tables — input registers and holding registers — are where the analog values live.

Coils · 1-bit · read/write

A writable bit. Enables, overrides, commanded states. Read with FC01; write with FC05 (one) or FC15 (many). Rare in modern BMS gear; common in older industrial controllers.

Discrete inputs · 1-bit · read-only

A read-only bit. Switch positions, status flags, freezestats, alarm bits. Read with FC02. The server reports them; the client cannot change them.

Input registers · 16-bit · read-only

A read-only 16-bit value. Sensor readings, measured values, computed feedback. Read with FC04. On a VFD, the speed feedback typically lives here.

Holding registers · 16-bit · read/write

A read/write 16-bit value. Setpoints, commands, configuration. Read with FC03; write with FC06 (one) or FC16 (many). On a VFD, the speed command lives here.

Four tables, named for forty-year-old reasons, addressed independently — register 0 in the input table and register 0 in the holding table are different storage locations. The address alone doesn't tell you which table; the function code that names which table to act on does. That's where the next section starts.

Function codes — reading and writing

Each table gets two function codes: one to read it, and (where applicable) one to write it. Read function codes can return up to a few hundred items in one request — the protocol caps coils at 2000 per read and registers at 125, but most devices set tighter limits in their docs. Writes come in two flavors, single and multiple, so a single-register setpoint update doesn't drag a whole block along with it.

The pairs that come up most in building controls are FC03 / FC16 for holding registers (read a setpoint or a command, write a setpoint or a command), and FC04 for input registers (read a sensor). FC06 appears when only one register is being changed at a time. The bit-table function codes — FC01, FC02, FC05, FC15 — show up less often in BMS work and more in older industrial gear. The full list lives on the Modbus Register Viewer tool's reference panel; this page treats it as a lookup rather than reprinting it.

Every request follows the same shape. On Modbus RTU, the byte sequence is the server's address (which device on the bus), the function code (what to do), a data field whose contents depend on the function (which register, how many, what value), and a two-byte CRC at the end for line-error detection. Modbus TCP wraps the same function code + data payload in a small TCP header and drops the CRC — TCP already handles error detection. The two transports speak the same language; only the envelope changes.

An FC03 read request on Modbus RTU Eight labeled bytes in a row representing an FC03 read request on Modbus RTU. Byte one is the server address, here 0x01 — device one on the bus. Byte two is the function code, here 0x03 — read holding registers. Bytes three and four are the starting register address as a 16-bit value, here zero. Bytes five and six are the quantity of registers to read, here ten. Bytes seven and eight are the CRC for error detection. A bracket groups bytes three and four under the label "starting address" and bytes five and six under "quantity." 0x01 server device #1 0x03 FC read holdings 0x00 0x00 0x00 0x0A CRC CRC CRC (2 B) RTU only starting address (0) quantity (10) "on device #1, read 10 holding registers starting at address 0"

The response carries the same server address and function code echoed back, a byte count, and the requested values back-to-back. Two-byte starting address and quantity in the request, multi-byte payload in the response — that's most of what a Modbus request looks like on the wire.

When something goes wrong — exception responses

When the server can't or won't honor a request, it returns an exception response instead of the data. The shape is deliberately simple: it echoes the function code back with the high bit set (FC 0x03 comes back as 0x83), followed by a one-byte exception code that names what went wrong.

A Modbus exception response Three labeled bytes in a row representing a Modbus exception response. Byte one is the server address, 0x01. Byte two is the original function code with the high bit set, here 0x83 instead of 0x03 — the high-bit marker is the universal exception signal. Byte three is the exception code, here 0x02 — illegal data address. An arrow points at the high-bit byte with the label "high bit set." 0x01 server 0x83 FC | 0x80 0x02 exception high bit set FC 0x03 → 0x83 "the FC03 request you sent failed — illegal data address"

The exception codes that show up most often in building controls:

  • 0x01 Illegal Function. The server doesn't implement this function code at all. A read-only sensor slave with no write support, hit with FC06 (Write Single Register), returns this — the function itself isn't in its table. Contrast 0x02 below, where the function is supported and only the address is wrong.
  • 0x02 Illegal Data Address. The function code is fine; the address is outside the device's documented range. A vendor manual that documents holding registers 0–127 and a client asking for register 200 produces this.
  • 0x03 Illegal Data Value. Address is fine, the value being written is not. Writing 200 to a 0–100 % setpoint register is the canonical example.
  • 0x04 Server Failure. An internal device error — a sensor fault, an internal comms problem on a multi-board unit, a coprocessor that didn't answer. The device is telling you it can't produce a meaningful answer.

Higher codes (0x05 through 0x0B) cover acknowledge- and-retry, target-device-busy, and gateway-routing variants. Most BMS techs will see 0x02 and 0x03 more than the rest, and 0x01 whenever a function code is mistyped or a register has been mapped to the wrong table.

A response that arrives with the function code echoed and no high bit set is a normal response — the bytes after it are the data that was asked for. The high-bit-set echo is the universal "look at the next byte, that's the error" signal.

What this page didn't cover

That's the message shape and the data model — enough to read a vendor's Modbus integration table, build a request, and recognize an exception when one comes back. The next question is the one the Modbus Register Viewer tool was built around: the response came back successfully, so what do those sixteen bits actually mean? The 5-digit register numbering convention (40001 in the manual is holding register 0 on the wire), signed vs unsigned interpretation, 32-bit values that span two registers with four possible byte orderings, and the scale factor that turns a raw integer into engineering units — Modbus Decoding is the companion page that walks through each.

← Back to Education