Modbus parser: how to decode a Modbus frame
A Modbus frame is a device address (the unit ID), a function code and data. Modbus TCP, RTU and ASCII wrap those same three parts differently, and parsing a frame means reading the fields in order and checking the checksum. Paste your hex below to see it done.
Modbus frame decoder
The decoder needs JavaScript, which is turned off in this browser. The guide below it works without JavaScript.
Paste a frame as hex. Everything is calculated in your browser: the bytes you paste are never sent anywhere or stored.
Spaces, commas, 0x prefixes and new lines are fine. An ASCII frame can be pasted as text starting with a colon. Press Ctrl+Enter to decode.
A request and its response share the function code and can look alike, so a frame cannot always be classed from its bytes alone. When the decoder says a frame fits both, choose the direction yourself. Function codes 01 to 06, 15, 16, 22, 23 and exception responses are decoded; for any other function code the decoder shows the raw data and does not guess.
What is inside a Modbus frame
Every Modbus message has the same core, the PDU (protocol data unit): a one-byte function code followed by data whose layout depends on that function. The transport adds what it needs around the PDU. Modbus TCP puts a 7-byte header in front of the PDU. Modbus RTU puts the unit ID in front and a CRC-16 behind. Modbus ASCII does the same as RTU but spells every byte as two text characters and uses an LRC instead of the CRC. The sections below show each wrapper with one worked example, and every example reads the same 3 registers, so you can compare them. The function code table lists what each code does.
All multi-byte numbers in the PDU are sent high byte first. The one exception is the RTU CRC, which is sent low byte first.
Modbus TCP frame structure
A Modbus TCP frame starts with the MBAP header (Modbus Application Protocol header): a 2-byte transaction ID, a 2-byte protocol ID that must be 0, a 2-byte length and the 1-byte unit ID. The length counts the bytes after it, so it is the unit ID plus the PDU. TCP already protects the data in transit, so the frame has no CRC. Here is a request for 3 holding registers starting at address 16, and the reply.
| Field | Hex | Meaning |
|---|---|---|
| Transaction ID | 00 01 | 1. The client picks it; the device copies it into the reply so the two can be matched. |
| Protocol ID | 00 00 | Always 0 for Modbus. |
| Length | 00 06 | 6 bytes follow: unit ID (1) + function code (1) + 4 bytes of data. |
| Unit ID | 01 | Device address. Devices reached directly by IP address often ignore it. |
| Function code | 03 | Read Holding Registers. |
| Start address | 00 10 | 16, counted from 0. |
| Quantity | 00 03 | 3 registers. |
| Field | Hex | Meaning |
|---|---|---|
| Transaction ID | 00 01 | Same as in the request. |
| Protocol ID | 00 00 | 0. |
| Length | 00 09 | 9 bytes follow: unit ID, function code, byte count and 6 data bytes. |
| Unit ID | 01 | Same as in the request. |
| Function code | 03 | Same as in the request. The high bit is not set, so this is not an exception. |
| Byte count | 06 | 6 data bytes follow. |
| Register 1 | 00 FA | 250. |
| Register 2 | 00 01 | 1. |
| Register 3 | 13 88 | 5000. |
To parse a TCP frame, check that the protocol ID is 0 and that the length field equals the number of bytes that follow it. A TCP stream can deliver several frames back to back, or half a frame, and the length field is how you find the border between them.
Modbus RTU frame example
A Modbus RTU frame has no header and no length field. It is the unit ID, the function code, the data and a 2-byte CRC-16 calculated over everything before it. The receiver finds the end of the frame from a silence of at least 3.5 character times on the line: at 9600 baud with 11 bits per character that is about 4 ms, and above 19200 baud the specification fixes it at 1.75 ms. A pause of more than 1.5 character times inside a frame marks it as broken.
| Field | Hex | Meaning |
|---|---|---|
| Unit ID | 01 | Device address 1. |
| Function code | 03 | Read Holding Registers. |
| Start address | 00 10 | 16. |
| Quantity | 00 03 | 3 registers. |
| CRC-16 | 04 0E | The CRC value is 0x0E04, sent low byte first. |
| Field | Hex | Meaning |
|---|---|---|
| Unit ID | 01 | Device address 1. |
| Function code | 03 | Read Holding Registers. |
| Byte count | 06 | 6 data bytes. |
| Registers | 00 FA 00 01 13 88 | 250, 1 and 5000. |
| CRC-16 | A5 F7 | The CRC value is 0xF7A5, sent low byte first. |
The CRC starts at 0xFFFF, folds in each byte with XOR and shifts right eight times per byte, applying the polynomial 0xA001 whenever the lowest bit is set. The result is sent low byte first, which is why the value 0x0E04 appears as 04 0E on the wire. When a frame fails its CRC check, first try swapping the two CRC bytes; if that helps, the tool that built the frame has the order wrong. On RS-485, a converter that echoes your request puts those bytes in front of the reply, which is covered in RS-485 echo and RTS.
Modbus ASCII frame
Modbus ASCII is used on some older serial devices. The frame starts with a colon, writes every byte as two hex characters, ends with an LRC (longitudinal redundancy check) and finishes with a carriage return and a line feed. The LRC is the sum of the data bytes, negated and cut to one byte. Every byte takes two characters, so the 7 bytes of this frame (unit ID, function, 4 data bytes and the LRC) become 14 characters, plus the colon and the line end.
| Field | Hex | Meaning |
|---|---|---|
| Start | 3A | A colon. |
| Unit ID | 30 31 | The characters 01. |
| Function and data | 30 33 30 30 31 30 30 30 30 33 | The characters 0300100003: function 03, address 0010, quantity 0003. |
| LRC | 45 39 | The characters E9. The bytes 01 03 00 10 00 03 add up to 0x17, and the negated sum is 0xE9. |
| End | 0D 0A | Carriage return and line feed. |
Exception responses
When a device understands a request but cannot carry it out, it answers with the request's function code plus 0x80, which sets the highest bit, followed by a single exception code. A read with function 03 is answered with 83, and a write with function 16 with 90. The PDU of an exception is always 2 bytes. It also proves that the device is alive and the frame was valid, which makes it a different problem from a timeout. As an example, a read of one register at address 36864 (0x9000) is sent as 01 03 90 00 00 01 A9 0A, and a device with nothing at that address replies:
| Field | Hex | Meaning |
|---|---|---|
| Unit ID | 01 | Same as in the request. |
| Function code | 83 | 03 + 80: the exception response to function 03. |
| Exception code | 02 | Illegal Data Address: the device has nothing at that address. |
| CRC-16 | C0 F1 | The CRC value is 0xF1C0, sent low byte first. |
| Code | Name | Usual meaning |
|---|---|---|
01 | Illegal Function | The device does not support that function code. |
02 | Illegal Data Address | The address, or the address plus quantity, is outside what the device offers. |
03 | Illegal Data Value | A value or the quantity is not allowed, for example a byte count that does not fit. |
04 | Server Device Failure | The device tried the request and failed. |
06 | Server Device Busy | The device is busy; try again later. |
0A | Gateway Path Unavailable | A gateway could not route the request. |
0B | Gateway Target Device Failed to Respond | A gateway got no answer from the device behind it. |
The full list with causes and fixes is on the Modbus exception codes page.
Reading the data field
After the function code, the layout depends on the function and on the direction. The common ones are in the table. A response to a read starts with a byte count, then the values: two bytes per register, high byte first. Function 06 is echoed unchanged, and the reply to function 16 repeats the address and the quantity but not the values. Addresses in the PDU count from 0; see Modbus register addressing if a manual lists 40001-style numbers.
| Function | Request | Response |
|---|---|---|
03 and 04 | Start address (2 bytes), quantity (2 bytes). | Byte count (1 byte), then 2 bytes per register, high byte first. |
06 | Register address (2), value (2). | An exact copy of the request. |
10 (16) | Start address (2), quantity (2), byte count (1), then 2 bytes per register. | Start address (2) and quantity (2), without the values. |
A register has only 16 bits, so a 32-bit integer or a float uses two registers, and devices differ on which register holds the high word and on the byte order inside each word. The frame cannot tell you; the device manual does. The Modbus register poller page explains how to show the same registers in all four byte and word orders.
How to check a Modbus frame
Length. Is the frame long enough for its function, and on TCP does the length field match the bytes that follow it?
Protocol ID. On TCP it must be 0. Anything else means the data is not Modbus TCP.
Unit ID. Does it match the device you addressed? A reply from another unit means a second device or a gateway is answering.
Function code. Is the high bit set? If so, the next byte is an exception code and the data checks below do not apply.
Byte count against quantity. For registers the byte count is twice the quantity; for coils it is the quantity divided by 8, rounded up.
CRC or LRC. Recalculate it over the right bytes and compare. TCP frames have none.
Reading frames in Busscope
The traffic panel in Busscope Master (F4) lists every frame that was sent and received, as hex with a time stamp and a Tx or Rx marker. It shows the complete frame as it went over the link: the MBAP header on TCP, the CRC on RTU, and on ASCII the colon, the characters and the line end as hex bytes starting with 3A. The panel shows raw bytes and does not split them into fields. To read a frame, copy its hex into the decoder above. The decoder is a free helper on this website, not a part of the Busscope program. The Modbus TCP tester and Modbus RTU tester pages describe the program itself.
Frequently asked questions
What is a Modbus parser?
A Modbus parser reads the bytes of a Modbus frame and splits them into fields: the unit ID (device address), the function code and the data, plus the header or checksum that the transport adds. The decoder on this page does that in your browser for Modbus TCP, RTU and ASCII frames.
How do I decode a Modbus RTU frame by hand?
Read the first byte as the unit ID and the second as the function code. The last two bytes are the CRC-16, sent low byte first, and everything between the function code and the CRC is the data. Calculate the CRC over all bytes before it and compare.
Why does the CRC of my Modbus RTU frame not match?
Common causes are swapped CRC bytes (the low byte is sent first), a CRC calculated over the wrong range (it covers unit ID, function code and data, but not itself), a frame that was cut off or merged with the next one, and an RS-485 echo that puts your own request in front of the reply. Modbus TCP frames have no CRC.
Can I tell a request from a response by looking at the bytes?
Not always. The function code is the same in both directions. For most functions the length and the byte count give the direction away, but responses to functions 05, 06 and 22 repeat the request exactly, and a few request and response layouts can match by chance. Choose the direction in the decoder when auto-detect is ambiguous.
Does the decoder upload my frames?
No. The decoder is JavaScript that runs in your browser. It makes no network requests, stores nothing and has no tracking, so the bytes you paste stay on your computer.
Try Busscope
Install Busscope and run it without a license key to see whether it fits your work. Evaluation mode is for trying the tool, not for production or commercial use.