Modbus Message Structure: From Request to Response
Imagine sending a letter with a specific format: sender address, recipient address, subject line, body, and signature. The recipient follows the same format to reply. Modbus messages work the same way—they have a strict structure that both master and slave devices must follow.
In this article, we’ll dissect complete Modbus transactions step-by-step, examining both request and response messages for different function codes. We’ll cover both RTU (serial) and TCP (Ethernet) formats, using concrete examples with hexadecimal representation to explain each field.
The Anatomy of a Modbus Message
All Modbus messages share common components, regardless of format:
| Component | Purpose | RTU/TCP |
|—————|————-|————-|
| Addressing | Identify source and destination | Both |
| Function | Specify the operation to perform | Both |
| Data | Contain request parameters or response values | Both |
| Integrity Check | Verify message wasn’t corrupted | RTU (CRC), TCP (TCP/IP Checksum) |
| Protocol Wrapper | Additional framing for network compatibility | TCP (MBAP Header) |
Modbus RTU Message Structure
RTU (Remote Terminal Unit) is the most common Modbus variant for serial communication. It uses binary format for compact, efficient transmission.
RTU Request Structure
| Field | Size (bytes) | Description |
|———–|—————–|—————–|
| Slave Address | 1 | Unique identifier (1-247) for target device |
| Function Code | 1 | Action to perform (e.g., 03 = Read Holding Registers) |
| Data | N | Request parameters (e.g., register address, quantity) |
| CRC Check | 2 | Cyclic Redundancy Check for error detection |
RTU Response Structure
| Field | Size (bytes) | Description |
|———–|—————–|—————–|
| Slave Address | 1 | Echoes the request address |
| Function Code | 1 | Echoes the request code (or exception code + 0x80) |
| Data | N | Response values or exception code |
| CRC Check | 2 | Cyclic Redundancy Check |
Modbus TCP Message Structure
TCP (Transmission Control Protocol) wraps Modbus messages in TCP/IP packets for Ethernet communication.
TCP Request Structure
| Field | Size (bytes) | Description |
|———–|—————–|—————–|
| MBAP Header | 7 | Modbus Application Protocol Header (transaction ID, unit ID, etc.) |
| Function Code | 1 | Action to perform |
| Data | N | Request parameters |
TCP Response Structure
| Field | Size (bytes) | Description |
|———–|—————–|—————–|
| MBAP Header | 7 | Echoes transaction ID and includes response length |
| Function Code | 1 | Echoes request code (or exception code + 0x80) |
| Data | N | Response values or exception code |
MBAP Header Breakdown
| Field | Size (bytes) | Description |
|———–|—————–|—————–|
| Transaction ID | 2 | Unique identifier for matching requests/responses |
| Protocol ID | 2 | Always 00 00 for Modbus TCP |
| Length | 2 | Number of bytes following (Unit ID + PDU length) |
| Unit ID | 1 | Legacy field for RTU compatibility (usually 00 or slave address) |
Complete Transaction Examples
Let’s examine complete Modbus transactions for common function codes, showing both RTU and TCP formats.
Example 1: Read Coils (Function Code 01)
Scenario: Master reads 4 coils (00001-00004) from slave device 5.
#### RTU Format
Request:
“`
Hex: [05] [01] [00] [00] [00] [04] [FD] [08]
“`
Breakdown:
- `FD 08`: CRC Check
Response:
“`
Hex: [05] [01] [01] [0A] [B8] [09]
“`
Breakdown:
- `B8 09`: CRC Check
#### TCP Format
Request:
“`
Hex: [00] [01] [00] [00] [00] [06] [05] [01] [00] [00] [00] [04]
“`
Breakdown:
- MBAP Header:
– `00 01`: Transaction ID 1
– `00 00`: Protocol ID 0 (Modbus)
– `00 06`: Length 6 (1 byte Unit ID + 5 bytes PDU)
– `05`: Unit ID 5
- `00 04`: Quantity of Coils
Response:
“`
Hex: [00] [01] [00] [00] [00] [03] [05] [01] [01] [0A]
“`
Breakdown:
- MBAP Header:
– `00 01`: Transaction ID 1 (echo)
– `00 00`: Protocol ID 0 (echo)
– `00 03`: Length 3 (1 byte Unit ID + 2 bytes PDU)
– `05`: Unit ID 5 (echo)
- `0A`: Coil States
Example 2: Read Holding Registers (Function Code 03)
Scenario: Master reads 2 holding registers (40001-40002) from slave device 10.
#### RTU Format
Request:
“`
Hex: [0A] [03] [00] [00] [00] [02] [C4] [0B]
“`
Breakdown:
- `C4 0B`: CRC Check
Response:
“`
Hex: [0A] [03] [04] [00] [64] [01] [F4] [31] [CA]
“`
Breakdown:
- `31 CA`: CRC Check
#### TCP Format
Request:
“`
Hex: [00] [02] [00] [00] [00] [06] [0A] [03] [00] [00] [00] [02]
“`
Breakdown:
- `00 02`: Quantity of Registers
Response:
“`
Hex: [00] [02] [00] [00] [00] [06] [0A] [03] [04] [00] [64] [01] [F4]
“`
Breakdown:
- `01 F4`: Register 40002 value
Example 3: Write Single Coil (Function Code 05)
Scenario: Master writes coil 00010 to ON (1) on slave device 7.
#### RTU Format
Request:
“`
Hex: [07] [05] [00] [09] [FF] [00] [8C] [3A]
“`
Breakdown:
- `8C 3A`: CRC Check
Response:
“`
Hex: [07] [05] [00] [09] [FF] [00] [8C] [3A]
“`
Breakdown: Slaves echo the write request as confirmation
#### TCP Format
Request:
“`
Hex: [00] [03] [00] [00] [00] [06] [07] [05] [00] [09] [FF] [00]
“`
Response:
“`
Hex: [00] [03] [00] [00] [00] [06] [07] [05] [00] [09] [FF] [00]
“`
Example 4: Write Single Register (Function Code 06)
Scenario: Master writes 2500 to register 40005 on slave device 3.
#### RTU Format
Request:
“`
Hex: [03] [06] [00] [04] [09] [C4] [09] [8B]
“`
Breakdown:
- `09 8B`: CRC Check
Response:
“`
Hex: [03] [06] [00] [04] [09] [C4] [09] [8B]
“`
Breakdown: Slaves echo the write request as confirmation
#### TCP Format
Request:
“`
Hex: [00] [04] [00] [00] [00] [06] [03] [06] [00] [04] [09] [C4]
“`
Response:
“`
Hex: [00] [04] [00] [00] [00] [06] [03] [06] [00] [04] [09] [C4]
“`
Example 5: Write Multiple Registers (Function Code 16)
Scenario: Master writes two values (100 and 200) to registers 40001-40002 on slave device 12.
#### RTU Format
Request:
“`
Hex: [0C] [10] [00] [00] [00] [02] [04] [00] [64] [00] [C8] [3E] [7A]
“`
Breakdown:
- `3E 7A`: CRC Check
Response:
“`
Hex: [0C] [10] [00] [00] [00] [02] [41] [C8]
“`
Breakdown:
- `41 C8`: CRC Check
#### TCP Format
Request:
“`
Hex: [00] [05] [00] [00] [00] [0B] [0C] [10] [00] [00] [00] [02] [04] [00] [64] [00] [C8]
“`
Breakdown:
- `00 C8`: Register 40002 value
Response:
“`
Hex: [00] [05] [00] [00] [00] [06] [0C] [10] [00] [00] [00] [02]
“`
Breakdown:
- `00 02`: Quantity of Registers
RTU vs TCP: Key Differences
| Feature | Modbus RTU | Modbus TCP |
|————-|—————-|—————-|
| Physical Layer | Serial (RS-485/RS-232) | Ethernet |
| Message Format | Binary, compact | MBAP header + binary PDU |
| Error Checking | CRC (2 bytes) | TCP/IP checksum |
| Addressing | Slave ID (1 byte) | IP address + Port 502 |
| Multiple Masters | Not supported | Supported |
| Speed | 9600-115,200 bps | 10 Mbps-10 Gbps |
| Distance | Max 1,200 meters | Global via internet |
| Device Limit | 32 devices per network | Limited by IP addresses |
Practical Insights for Troubleshooting
1. CRC Mismatches: Always verify CRC calculations when debugging RTU communication
2. Transaction ID Matching: Ensure responses match requests using transaction IDs in TCP
3. Function Code Echo: Slaves always echo the function code in responses
4. Byte Count Validation: Verify that the byte count in responses matches expected data length
5. Zero-Based Addressing: Remember that internal addresses start at 0, while documentation uses 1-based
Common Mistakes to Avoid
- Case Sensitivity: RTU is case-insensitive, but hex display often uses uppercase
Conclusion: Mastering Modbus Messages
Understanding Modbus message structure is like learning a new language—once you know the grammar and vocabulary, you can communicate effectively. By dissecting complete transactions and understanding each field’s purpose, you’ll be able to:
- Develop custom Modbus applications
Whether you’re working with RTU or TCP, the core principles remain the same: strict structure, clear addressing, specific functions, and data integrity checks. With the examples provided, you now have a solid foundation to analyze and work with Modbus messages in real-world applications.
Remember that practice makes perfect—use Modbus testing tools to experiment with different message formats and observe how devices respond. This hands-on experience will deepen your understanding and make you a more effective Modbus troubleshooter and developer.