Detailed Tutorial on Analyzing Modbus Communication Protocol Messages
I.Overview of the Modbus Protocol
Modbus is a serial communication protocol developed by Modicon (now Schneider Electric) in 1979 for communication between programmable logic controllers (PLCs). Over more than four decades of evolution, it has become one of the de facto standard communication protocols in the field of industrial automation.
Key Features:
- Master/Slave architecture: A single master host with multiple slaves, where the master initiates requests and slaves respond passively.
- Open protocol: No licensing fees; any manufacturer can implement it free of charge.
- Multiple transmission modes: RTU (binary), ASCII (text), TCP (Ethernet).
- Supports various physical layers: RS485 (most common), RS232, TCP/IP (Ethernet).
- Clear data model: Four object types – coils, discrete inputs, input registers, and hold registers.
1.1Comparison of Three Protocol Variants

1.2Protocol Specification Constraints

II.Modbus Data Model and Address Rules
2.1 Four Types of Operational Objects

2.2 Address Representation (Critical!)
Different manufacturers employ varying encoding schemes for Modbus addresses, which is the most common pitfall in practical implementations:

Address prefix meaning (industry-standard representation):
- Starting with0: Protocol address (hexadecimal), e.g., 0x0000
- 4 xxxx: Maintain registers; for example, 40001 indicates the protocol address of the maintain register 0.
- 3xxxx: Input register; e.g., 30001 indicates the protocol address of input register 0. ·
- 0xxxx: Coil; e.g., 00001 indicates the protocol address of coil 0. ·
- 1xxxx: Discrete input; e.g., 10001 indicates the protocol address of discrete input 0.
2.3 Big Data Processing Rules
1.1. Maximum number of registers read per operation: 125 (function codes 0x03/0x04)
2.2. Maximum number of coils/discrete inputs read per operation: 2000 (function codes 0x01/0x02)
3.3. Maximum number of registers written to per operation: 123 (function code 0x10)
4.4. Maximum number of coils written to per operation: 1968 (function code 0x0F)
5.5. Exceeding these limits requires block-based reading/writing; ensure staggered polling intervals to avoid bus conflicts
III. Detailed Analysis of Modbus RTU Messages
The RTU (Remote Terminal Unit) is the most widely used Modbus transmission protocol in industrial applications, employing binary encoding for high transmission efficiency and compatibility with RS485/RS232 serial interfaces. A minimum silent interval of 3.5 character time between frames is required to distinguish frame boundaries.
3.1 Complete Frame Structure of RTU
A complete instruction data frame in an RTU consists of the following four parts:

3.2 RTU Message Example – Read Holding Registers (Function Code 0x03)
[Request Message] The master reads the holding registers of Slave 1 (starting address 0x0001, reading 3 registers):
01 03 00 01 00 03 54 0B
Byte-by-byte analysis of the request message:

[Response Message] Slave Station 1 returns data from three registers: 01,03,06,02,2B,00,00,00,64, D5, 87
Byte-by-byte parsing of the response message:

3.3 Detailed Explanation of Standard Function Codes

3.4 Detailed Description of Each Function Code Message Format
(1) Read Coil / Read Discrete Input (Function Codes 0x01/0x02)
[Request Frame Format] (8 bytes in total)

[Response Frame Format]

[Example of position analysis] The response data consists of 3 bytes (coils 20–38):

(2)Read Holding Register/Read Input Register (Function Code 0x03/0x04)
[Request Frame Format] (8 bytes in total)

[Response Frame Format]

(3)Write a single hold register (function code 0x06)
[Request frame format] (total 8 bytes)

[Response Frame] Identical to the request frame, it confirms successful writing.
(4)Write multiple maintain registers (function code 0x10)
[Request Frame Format]

[Response Frame Format] (8 bytes in total): [Address][0x10][Start Address (2 bytes)][Number (2 bytes)][CRC (2 bytes)]
(5)Write a single coil (Function Code: 0x05)
[Request Frame Format] (8 bytes in total): Only two valid values are supported for writing:
- 0xFF00 – Coil set to ON (closed)
- 0x0000 – Coil set to OFF (open)

[Response Frame] Identical to the request frame, it confirms successful writing.
IV. Detailed Analysis of Modbus TCP Messages
Modbus TCP operates over the Ethernet TCP/IP protocol, using port 502 as the default. Unlike the RTU mode, Modbus TCP employs an MBAP (Modbus Application Protocol) header to define message boundaries and does not require CRC verification (instead relying on the TCP protocol's built-in error-checking mechanism).
4.1 MBAP header structure (7 bytes, included in all TCP packets)

4.2 Modbus TCP Message Example
[Request Message] Reading the hold register of Slave 1 (starting address 0x0000, reading 3 values):
00 01 00 00 00 06 01 03 00 00 00 03
Field-by-field analysis of the request message:

[Response Message] The slave returns data from three registers:
00 01 00 00 00 09 01 03 06 02 2B 00 00 00 64
Field-by-field parsing of the response message:

4.3 Core Differences Between RTU and TCP Packet Structures

V.Detailed Explanation of the CRC-16 Verification
Algorithm CRC (Cyclic Redundancy Check) is the frame verification mechanism of Modbus RTUs, designed to detect errors during message transmission. The CRC-16 generating polynomial is x¹⁶ + x¹⁵ + x² + 1 (equivalent to 0x8005); in practice, the inverted polynomial 0xA001 is used.
5.1 CRC-16 Calculation Steps
6.1. Initialize the 16-bit CRC register to 0xFFFF
7.2. Perform an XOR operation between the first byte and the low 8 bits of the CRC register; store the result back in the low 8 bits of the CRC register
8.3. Shift the CRC register right by 1 bit, filling the high bits with 0; determine the shifted-out bit (LSB):
9.4. If the shifted-out bit is 0: Continue shifting right by 1 bit only
10.5. If the shifted-out bit is 1: Perform an XOR operation between the CRC register and 0xA001
11.6. Repeat step 3 until processing of all 8 bits of the current byte is complete
12.7. Take the next byte and repeat steps 2–6 until all bytes have been processed 13.8. After completing all byte processing, swap the high and low bytes of the CRC register to obtain the final checksum
14.9. Append the final checksum to the end of the message, with the low byte first and the high byte last
5.2 CRC-16 Calculation Example (Python Code)
The following Python function can be directly used to compute the CRC-16 checksum for a Modbus RTU:
def modbus_crc16(data: bytes) -> bytes:
"""count Modbus RTU CRC-16 check code(Return the first two bytes with the least significant bit first)"""
crc = 0xFFFF
for byte in data:
crc ^= byte
for _ in range(8):
if crc & 0x0001:
crc = (crc >> 1) ^ 0xA001
else:
crc >>= 1
# Return the low byte first, followed by the high byte
return bytes([crc & 0xFF, (crc >> 8) & 0xFF])
# give typical examples:count 01 03 00 01 00 03 CRC
frame = bytes([0x01, 0x03, 0x00, 0x01, 0x00, 0x03])
crc = modbus_crc16(frame)
print(crc.hex()) # export:540b( 0x54 0x0B)
5.3 Location of CRC Check in the Message
Using request message 01 03 00 01 00 03 54 0B as an example:

The receiver recalculates the CRC value of the first 6 bytes and compares it with the last 2 bytes of the packet. If they differ, a transmission error is detected and no response is returned.
Ⅵ.Exception Handling and Error Code Details
When a slave station cannot execute a request from the master station (e.g., due to unsupported function codes or address out-of-range), it returns an exception response frame. The exception response function code is = original function code + 0x80 (i.e., the highest bit is set to 1).
6.1 Abnormal Response Format (RTU)
[Slave Address][Function Code | 0x80][Abnormal Code][CRC-16]

6.2 Meaning of Standard Exception Codes

6.3 Example of Abnormal Response
[Scenario] The master station requests to read a register at an invalid address from slave station 1, and the slave station returns an exception:
Request: 01 03 00 20 00 01 XX XX (reading register at address 0x0020);
Exception response: 01 83 02 C0 F1;
Byte-by-byte breakdown of the exception response:

VI.Introduction to Modbus ASCII Messages
The Modbus ASCII mode encodes all data bytes as two ASCII characters (e.g., 0x0F is encoded as "0F"). While highly readable, it offers low transmission efficiency and is primarily used for debugging purposes.
7.1ASCII Frame Structure
Complete ASCII frame format:
<Address (2 characters)> <Function Code (2 characters)> <Data (N × 2 characters)> CR LF

7.2LRC Verification Algorithm
Steps for calculating LRC (Longitudinal Redundancy Check):
15.1. Perform binary summation on all bytes of the address field, function code, and data field (excluding start and end markers).
16.2.Ignore carry bits (if the result exceeds 255, automatically wrap around and retain only the lower 8 bits).
17.3. Compute the complement of the summation result modulo 1 (i.e., 0xFF − sum).
18.4. Increment the complement by 1 to obtain the final LRC check code (1 byte).
19.5. Encode the LRC check code as two ASCII characters and append them to the end of the message.
VIII. Practical Tips and Common Issues
8.1 Byte Order (Big-Endian/Micro-endian) Issues
The Modbus standard uses big-endian order (with the high-order byte first) for transmitting 16-bit or longer data. Some devices (e.g., certain ARM-based devices) use micro-endian order; byte order conversion is required to avoid numerical parsing errors.

8.2 Registration Unit Size Limit (PDU ≤ 253 bytes)

8.3 Communication Timeout and Retry Configuration Recommendations
Recommended configurations:
- Polling interval: Set to 2–3 times the slave's maximum response time (typically 100–500 ms) • Timeout duration: Recommended range of 1000–3000 ms, adjustable based on slave response speed
- Retry attempts: Recommended limit of 3; trigger an alarm if failed
- Multi-slave polling: Schedule staggered polling intervals among slaves to prevent bus conflicts • Byte timeout: In RTU mode, a character interval exceeding 1.5 character time is considered a frame error
8.4 Recommended De bugging Tools
Appendix: Common Message Quick Reference Table
Read coil (Function Code 0x01)
[Request]: Read from slave station 1 at starting address 0 (coil 1), obtain 8 coil status messages: 01 01 00 00 00 08 3D CC
[Response]: Eight coil statuses: 0x55 (01010101B);
Message: 01 01 01 55 94 36
Write to a single coil (Function Code 0x05)
[Request] Set coil at address 0 to ON (write value 0xFF00):
Message: 01 05 00 00 FF 00 8C 3A
[Request] Set coil at address 0 to OFF (write value 0x0000):
Message: 01 05 00 00 00 00 CD CA
[Response] Identical to the request frame
Write to a single register (Function Code 0x06):
[Request] Write value 0x1234 to register at address 0.
Message: 01 06 00 00 12 34 XX XX (XX is CRC, to be calculated).
[Response] Identical to the request frame.
Write to multiple registers (Function Code 0x10)
[Request]: Write to two registers starting from address 0 with values 0x1234 and 0xABCD respectively.
Message: 01 10 00 00 00 02 04 12 34 AB CD XX XX
[Response]: Return the starting address and number of registers written.
Message: 01 10 00 00 00 02 41 C8
Read input registers (function code 0x04)
[Request]: Read from slave 1 at starting address 0, reading two input register
messages: 01 04 00 00 00 02 71 CB
[Response]: Return two input register values
messages: 01 04 04 00 64 01 90 XX XX (Return values: 100 and 400)










