← Articles
Modbus/7 min read/ views

Modbus Exception Codes: The Device Answered, It Just Said No

Reading Modbus exception responses on the wire, telling a refused request from a dead link, and chasing illegal-address and gateway causes.

ModbusSCADATroubleshootingNetworkingChecklists

Half the "Modbus is broken" tickets I get aren't broken links at all. The device answered. It just answered with a no.

That difference is worth internalizing before you touch a cable. A timeout means silence — the request went out and nothing usable came back. An exception response means the slave received your frame, parsed it, and deliberately rejected it. If your driver logs Illegal Data Address or Gateway Target Device Failed to Respond, you are already past the physical layer. Chasing termination resistors and RS-485 polarity at that point is wasted afternoon.

What an exception actually looks like on the wire

The Modbus Application Protocol Specification (V1.1b3, section 7) is blunt about this. A normal response echoes the function code you sent. An exception response sets the high bit of that function code and appends a single exception-code byte.

So if you send function 0x03 (Read Holding Registers) and the device rejects it, the response function code comes back as 0x83 — that's 0x03 | 0x80. Read Coils 0x01 fails as 0x81. Write Multiple Registers 0x10 fails as 0x90. The byte after that is the exception code.

A rejected holding-register read looks like this on the RTU wire:

Request:  01 03 00 13 00 0A C5 CD
Response: 01 83 02 C0 F1

01 unit id, 83 = read-holding-registers-with-error, 02 = Illegal Data Address, then the CRC. Once you can read that by eye, most of the diagnosis is done before you open a manual. If your SCADA driver only surfaces a text string and hides the raw frame, get a capture — Wireshark decodes Modbus/TCP natively, and any half-decent serial driver has a raw comms log you can turn on.

The exception codes, and how much to trust them

  • 01 Illegal Function — the device doesn't support that function code for that object, or at all.
  • 02 Illegal Data Address — the address or range falls outside the implemented map.
  • 03 Illegal Data Value — the value, length, or write payload isn't acceptable. This is about the request structure, not your process value being out of range (that's a device-internal decision, if it's enforced at all).
  • 04 Slave Device Failure — the frame was fine, the device failed internally handling it.
  • 06 Slave Device Busy — needs more time, or is mid-operation. Retry later.
  • 0A Gateway Path Unavailable — a gateway couldn't build a path to the downstream network.
  • 0B Gateway Target Device Failed to Respond — the gateway forwarded it; the downstream device stayed silent.

Trust these loosely. Plenty of low-end firmware returns 02 for nearly every bad request — wrong function, wrong length, wrong address, all collapsed into one code. So don't over-read the specific number. Read the fact that a code came back: something on that network answered, and it processed your unit id far enough to disagree with you.

Confirm the request before you blame the map

Start from the frame the driver put on the wire, not the tag name on the HMI. Tag names bury the offset, the word order, and the function-code choice. Before anything else, write down: the function code, the starting address as sent, the quantity, the unit id, and the poll group it belongs to. Five items, and most problems fall out once they're visible.

The classic trap is address notation. A vendor table prints 40001; the actual PDU carries protocol address 0. Some drivers want the documented register number and subtract for you, some want the raw zero-based address and don't. Guessing here burns hours. Confirm against one register you already read successfully, or read the capture — the address is right there in the frame.

Block optimization is the most common 02

Drivers combine adjacent tags into one large read to cut round-trips. Good for throughput, and the usual source of an Illegal Data Address on a map that "worked yesterday when I tested one register." Say you have:

  • 40020 pressure
  • 40021 temperature
  • 40030 status word

Read singly, all three are fine. Let the driver merge them into one read from 40020 through 40030 and the device rejects the whole block, because 4002240029 were never implemented. The fix is never a network change. Split the poll block, or cap the driver's maximum block size so it stops spanning the gap.

Check the device's own ceiling too. The spec allows 125 holding registers per read (0x7D), but a cheap RTU controller or a serial device behind a gateway may choke well below that. When in doubt, drop the max block to something conservative like 32 registers and see if the exceptions stop.

Writes fail for reasons reads never do

Write exceptions carry more baggage — permissions, run mode, interlocks, scaling. Before you decide a write is broken, confirm the address is actually documented as writable, that you're using the function code the device expects (06 single vs 16 multiple register, 05 single vs 15 multiple coil), and that the device is in remote/comms-enabled mode if it demands one.

An Illegal Data Value on a write is often a setpoint outside the accepted engineering range, not a comms defect at all. And Illegal Function on a write frequently means the device only implements 16 for that region even though 06 works elsewhere — I've seen exactly this on drive parameter blocks that insist on multi-register writes.

Gateways: suspect the unit id

A Modbus TCP-to-RTU gateway adds a whole failure surface. The TCP side stays green while the serial side is overloaded, miswired, or pointed at the wrong slave. Confirm the gateway isn't quietly remapping the unit id your driver sends. Check baud, parity, stop bits, and termination on the RTU leg. And count your masters — an engineering laptop, a historian, and two HMIs all polling through one gateway will produce retries and busy exceptions that look like a device fault.

This is exactly where the gateway-specific codes earn their keep. 0A points you at routing and gateway config; 0B points you downstream at the RTU segment or the slave itself. That's a real fork in the troubleshooting path, handed to you for free.

When the map is fine but the runtime isn't

A register that reads clean on a single manual poll can start throwing exceptions the moment the full runtime spins up every poll group at once. Slow devices return 06 busy, drop frames, or time out behind the gateway. Reduce variables before you touch the map: disable non-essential poll groups, test one device and one function code at a time, and raise the timeout and inter-request delay for slow serial devices — 300–500 ms of turnaround delay is not unusual for old RTU gear.

If the exceptions vanish when you slow the poll rate, that's your answer — but don't leave the setting as a lucky guess. Record the scan rate, timeout, retry count, and registers-per-block that actually held, so the next person doesn't rediscover the device's capacity from scratch. And keep fast HMI display tags out of the same poll group as slow maintenance diagnostics; there's no reason to read a firmware-revision register at your trend rate.

The whole job, in the end, is lining up three views that should agree and usually don't: what the manual says the map is, what the driver actually put on the wire, and what the device sent back. The exception code is the shortest line between them — so read it as an answer, not as a failure.