Two jobs on one page. Decode takes bytes off the wire and tells you what they say. Build goes the other way: pick a function code, fill in the fields, get a frame with a valid CRC you can paste into a serial terminal or a test client.
The decoder handles RTU and TCP, and it works out which one it is looking at on its own.
—
Builds the master request. Address and quantity go out as sent on the wire, so a PLC register listed as 40001 is address 0 here.
Paste the request as well as the response
The response frame alone cannot tell you which registers you are looking at.
A read holding registers response is a function code, a byte count and the data. That is all. The start address lives in the request, and the slave never repeats it. So a response of 01 03 04 00 7B 00 C8 means “here are two registers”, and nothing more. Which two is anyone’s guess.
Paste the request in the top field and the decoder carries the start address and quantity across. Registers then come back with their real addresses instead of starting at zero. It also cross-checks the pair: a request to slave 1 with an answer from slave 3, or a request for function 3 answered by function 4, gets flagged.
If you only have the response, type the start address in the field below it and the table numbers itself from there.
How the transport is detected
RTU and TCP carry the same PDU. What differs is the wrapper.
| RTU | TCP | |
|---|---|---|
| Before the function code | 1 address octet | 7-octet MBAP header |
| After the data | 2 CRC octets | nothing |
| Frame boundary | 3.5 character silence | length field in the MBAP header |
| Addressing | slave address 1 to 247 | unit ID, often 255 or 0 through a gateway |
Auto-detect checks the CRC first. If the last two octets are a valid CRC-16 over everything before them, it is RTU. Only if that fails does it look for the MBAP shape.
That order matters. An ordinary read request, 01 03 00 00 00 02 C4 0B, happens to look like a valid MBAP header: protocol ID zero, length two, eight octets total. Tools that test for TCP first will call it a TCP frame and decode garbage. Testing the CRC first avoids the whole class of mistake, and a corrupted RTU frame then reports a CRC mismatch instead of being quietly relabelled.
You can override the choice if you know what you have.
Reading the byte map
Every frame is drawn as coloured segments with the field name under each one.
Blue is the framing: slave address and CRC on RTU, the MBAP fields on TCP. Teal is the PDU: function code and the fields that belong to it. Amber is data. Red is trouble — a failed CRC, a wrong length field, or octets left over after the function’s fields are accounted for.
Those leftover octets are worth attention. If a read response says byte count 4 and carries six data octets, something upstream is padding or the frame is two frames run together. The map shows exactly where the extra bytes start.
Word order, the thing that wastes the most time
Modbus moves 16-bit registers. A 32-bit float needs two of them, and the standard does not say which register goes first. Every vendor picked something.
The decoder gives you all four orders under names people actually use:
| Name | Also called | Register order | Byte order in each register |
|---|---|---|---|
| ABCD | big-endian | high word first | high byte first |
| CDAB | word swap, mid-little | low word first | high byte first |
| BADC | byte swap, mid-big | high word first | low byte first |
| DCBA | little-endian | low word first | low byte first |
Switch the selector and the table redraws without re-pasting. The raw hex column stays next to each value, so you can see which arrangement produces a sensible number. A temperature reading of 74.2 in one order and 1.8e+32 in another tells you which one the device uses in about three seconds.
The same selector handles 16-bit signed and unsigned, hex, binary, ASCII, and 32 and 64-bit integers and doubles.
Drop the decoded registers into the Modbus Float Converter to see all four word orders side by side. It resolves FLOAT32, INT32, and INT64, applies a scale factor, and encodes a value back to registers when you need to write one.
Exception responses
When a slave cannot serve a request it sets the top bit of the function code and returns one data octet. Function 3 becomes 0x83, and the octet after it says why.
| Code | Name | What to check |
|---|---|---|
| 1 | Illegal function | The slave does not implement that function code at all |
| 2 | Illegal data address | The address does not exist, or the range runs past the end of the block |
| 3 | Illegal data value | A value in the request is out of range. Often a quantity above the limit |
| 4 | Slave device failure | Something failed inside the device while handling the request |
| 5 | Acknowledge | Accepted, but it will take time. Poll for the result |
| 6 | Slave device busy | Retry later |
| 7 | Negative acknowledge | The programming function cannot be performed |
| 8 | Memory parity error | Extended file record area failed its check |
| 10 | Gateway path unavailable | The gateway has no route to that unit ID |
| 11 | Gateway target failed to respond | The route exists, the device behind it does not answer |
Codes 10 and 11 only come from a gateway, and they are the difference between “wrong unit ID” and “the serial device is offline”. Worth knowing which one you got before you drive to site.
The decoder names the base function alongside the exception, because 0x83 on its own is easy to misread as an unknown function code.
Quantity limits the frame will not tell you about
Reaching the limit does not corrupt anything. It just produces an exception 3 that some clients report as a timeout.
| Function | Limit per request |
|---|---|
| 1, 2 — read coils or discrete inputs | 2000 bits |
| 3, 4 — read holding or input registers | 125 registers |
| 15 — write multiple coils | 1968 coils |
| 16 — write multiple registers | 123 registers |
The decoder checks the quantity against these, and the builder refuses to generate a request above them.
Building a frame
Pick RTU or TCP, set the slave address and function code, fill the fields, press Build. You get the frame in hex with the CRC already appended, or the MBAP header already sized.
The builder writes master requests, which is what you need for a bench test. Addresses go out as they appear on the wire, so a holding register documented as 40001 is address 0 here. That offset is the single most common reason a test read comes back as exception 2.
Send to decoder pushes the frame you just built into the decode tab and runs it. Useful for confirming a frame reads back the way you intended before you send it to a live device.
FAQ
Does anything leave my browser?
No. The decoding and the CRC run in the page. There is no server call and nothing is stored.
Which CRC does Modbus RTU use?
CRC-16 with polynomial 0xA001, initial value 0xFFFF, no final inversion, transmitted low byte first. It covers the slave address, the function code and all data, and it is not used at all in Modbus TCP, where TCP’s own checksum does the job.
Why does my frame decode as TCP when it is serial?
It should not, since the CRC is tested first. If it happens, the CRC is failing. Set the transport by hand and look at the CRC line: if the calculated value differs from the one in the frame, you have a real error, not a detection problem.
Can it decode Modbus ASCII?
Not yet. ASCII frames start with a colon, carry hex digits as text and end with an LRC and CRLF. Convert the frame to binary and the PDU part decodes normally.
How do I know if a frame is a request or a response?
For functions 5, 6 and 22 you cannot. The response is a byte-for-byte echo of the request. The tool says “request or echoed response” rather than guessing. For functions 1 and 2 a four-octet payload is also ambiguous — it could be start address and quantity, or byte count 3 with three data octets. Pasting both halves of the exchange resolves it.
What unit ID should I use over TCP?
For a native TCP device the unit ID is usually ignored, and 255 or 1 both work. Through a gateway to a serial line it is the serial slave address, and it matters. Exception 11 means the gateway tried and got nothing back.
Two devices answer the same poll. Is that visible here?
Not from a single frame. It shows up as CRC errors on a serial capture, because the two responses overlap on the wire. Consistent CRC failures on a multi-drop line with clean wiring usually means a duplicate address.
Does the register table handle strings?
Yes. Choose 16-bit ASCII and each register shows as two characters. Device name and firmware version blocks read straight out.
