Part II · Chapter 9
Exceptions and Diagnostics
Most of the time a request succeeds. This chapter is about the rest of the time: the complete list of exception codes, what each is really telling you, and the diagnostic tools that let you test a serial line and read a device's own error counters.
By the end of this chapter you can
- Read any exception response and name the fault behind it.
- Go from symptom (data, exception, or silence) to the right place to look.
- Run a loopback test with the Diagnostics function (0x08).
- Read a device's communication counters and say what they point to.
The exception response, once more
When a server understands a request but can't carry it out, it replies with an exception response. It takes the original function code, sets the high bit (adds 0x80), and follows it with one exception code byte naming the problem. A failed Read Holding Registers (0x03) comes back as 0x83; a failed Write Single Coil (0x05) as 0x85. No normal function code uses that high bit, so its presence is an unmistakable signal that what follows is an error report, not data.
An exception response is always this short: address, marked function code, exception code, CRC. The spec's own example, from server 17, is 11 83 02 C1 34.
The exception codes
A modest list covers everything a device can report. The first three are by far the most common day to day; the gateway codes appear only when a gateway sits in the path.
| Code | Name | What it tells you |
|---|---|---|
| 01 | Illegal Function | This device doesn't implement that function code |
| 02 | Illegal Data Address | That address doesn't exist here: check your addressing |
| 03 | Illegal Data Value | The quantity or value is outside the allowed range |
| 04 | Server Device Failure | An unrecoverable fault occurred inside the device |
| 05 | Acknowledge | Accepted, but it needs time: poll again shortly |
| 06 | Server Device Busy | Busy now: resend the request later |
| 08 | Memory Parity Error | A memory error during a file-record operation |
| 0A | Gateway Path Unavailable | A gateway has no path to the target: misconfigured or overloaded |
| 0B | Gateway Target Device Failed to Respond | The gateway sent the request on, but the target never replied (often shortened to "Gateway No Response") |
Three of these deserve a closer word.
- Illegal Data Address (02) is the beginner's constant companion. It almost always means an addressing mistake: a forgotten reference-number conversion, a count that runs past the end of a table, or the wrong table entirely.
- Illegal Data Value (03) doesn't mean "I dislike this number." It means the quantity or value broke this request's rules: 200 registers when the limit is 125, or something other than 0xFF00 / 0x0000 written to a coil.
- Acknowledge (05) and Server Device Busy (06) aren't failures at all. They're timing signals: wait and try again rather than give up.
From symptom to cause
Combine the three outcomes from Chapter 3 with these codes and you have a fast first-pass diagnosis for almost any Modbus problem:
- Normal response: the path works. Wrong-looking data is a scaling or interpretation question for the register map.
- Exception: the device is alive and listening, so look at what you asked. 01: it lacks that function. 02: fix the address. 03: fix the quantity or value.
- Silence: the device isn't hearing you, so look at the connection: wiring, unit address, serial settings, power.
This one decision is the spine of Modbus troubleshooting, and Chapter 16 fleshes it out with tools and worked cases. The point to carry forward: Modbus is unusually diagnosable. The protocol nearly always tells you which of three worlds your problem lives in, and that alone eliminates most of the guesswork.
The Diagnostics function
Beyond exceptions, serial Modbus has a dedicated Diagnostics function, code 0x08, for testing a link and reading a device's internal health counters. It's a serial-line tool: over TCP the network provides its own diagnostics, but on RS-485 it's genuinely useful. After the function code comes a two-byte sub-function that selects the test, then a two-byte data field.
The most immediately useful sub-function is the simplest. Sub-function 0x0000, Return Query Data, is a loopback test: the client sends some arbitrary data and the server must send the exact same data straight back.
If the echo matches, you've proven the full round trip: the wiring, the serial settings, the addressing, and both devices' ability to frame and check a message. That's wonderfully reassuring when you're bringing a new bus to life, because it isolates the communication path from any question about register maps or data meaning.
The diagnostic counters
A serial Modbus device quietly keeps counters about its own communication, and other Diagnostics sub-functions read them. They turn "the bus is flaky" into hard numbers. The most telling:
| Sub-function | Counter | Why it matters |
|---|---|---|
| 0x000B | Bus message count | Total frames the device saw on the bus |
| 0x000C | Bus communication error count | Frames with a bad CRC: noise or wiring |
| 0x000D | Bus exception error count | Requests it answered with an exception |
| 0x000E | Server message count | Frames actually addressed to this device |
| 0x000A | (Clear counters) | Resets the counters to start a clean test |
To read a counter, send its sub-function with a data field of 00 00; the response carries the count in that field. Reading the CRC error count from server 1, and a reply reporting 37 errors:
Read together, these counters are diagnostic gold:
- A steadily climbing communication error count points straight at electrical trouble (noise, a missing termination resistor, a marginal connection) because it counts frames that arrived with a broken CRC.
- A high exception error count points instead at a software or configuration problem: the device receives frames cleanly but refuses them.
- Comparing the bus message count with the server message count shows how much of the bus traffic is even meant for this device.
A common workflow: clear the counters with sub-function 0x000A (01 08 00 0A 00 00 C0 09), run the system for a while, then read them back to see exactly what went wrong and how often. The counters are 16 bits wide and wrap after 65,535, so read them often enough on a busy bus.
Counter interpreter
Enter the counts you read back after clearing the counters and letting the system run.
Read Exception Status
One more small serial function rounds out the toolkit: Read Exception Status, code 0x07. The request is just the address and function code; the response returns a single byte of eight device-specific status bits. What those bits mean is defined by each device (over-temperature, a calibration fault, a low battery), so you need the manual to read them.
Here the status byte 6D is 0110 1101: bits 0, 2, 3, 5 and 6 are set, and only the device manual says what each one means. The value of the function is its brevity: a quick "are you healthy?" poll that costs almost nothing, useful for keeping a light eye on a device between fuller reads. (The name is historical: these status bits have nothing to do with exception responses.)
RTU frame decoder
Examples to paste: 01 83 02 C0 F1, 01 08 00 0C 00 25 E1 D3, 01 08 00 0A 00 00 C0 09, 01 07 6D E3 DD, 01 85 03 02 91, 11 03 06 AE 41 56 52 43 40 49 AD, 01 01 01 0D 90 4D, 01 10 00 10 00 02 04 00 0A 01 02 52 F0. Change one digit to see a CRC failure.
Check your understanding
1. A device replies 01 86 03 …. What happened?
0x86 − 0x80 = 0x06, Write Single Register. Exception 03 means the value broke the device's rules, for example it's out of range for that register.
2. A request comes back with exception 06, Server Device Busy. What should the client do?
05 and 06 are timing signals, not failures. The device heard you and is asking you to try again shortly.
3. A loopback test (sub-function 0x0000) returns the exact data you sent. What has it proven?
A matching echo proves the whole communication path. It says nothing about register meanings, which is exactly why it's useful for isolating the link.
4. After clearing the counters, the CRC error count climbs steadily while the exception count stays at zero. Where do you look?
Bad CRCs mean frames are being damaged in transit. Exceptions would point to configuration; here the requests are fine but the wire isn't.