Reference Materials#
Each blog site is missing chapters and sentences, with chaotic formatting and occasional errors, so it is not recommended to refer to them.
Here is the information from the Modbus Chinese website
MODBUS Protocol Chinese/English Version Preview and Download | Modbus IoT Cloud Platform
More authoritative is the official documentation
MODBUS Application Protocol 1 1 b
And other official documents can be found here
Of course, I am not very satisfied with the official documents either. To maintain their ancient design architecture, the introduction of various frame segments is not intuitive, and there are also a few typos.
Below is the effective information I have rewritten.
Additionally, there is a library I implemented myself
stbanana/modbusX: modbus protocol support (github.com)
Protocol Features#
Overall Frame Structure#
Message Header | Address Field | Function Code | Data Field | Check Field | |
---|---|---|---|---|---|
RTU | 1 byte (Slave ID) | 1 byte | n bytes variable | 2 bytes (CRC-MB16 for all other content) | |
TCP | 6 bytes (2 bytes Transaction ID + 2 bytes Protocol Identifier - all 0 + 2 bytes Total Length of Remaining Bytes - including Slave ID) | 1 byte (Slave ID) | 1 byte | n bytes variable |
Regarding the message header, it is classified into the address field in the overall model of the official document, but the actual total length of bytes includes the Slave ID. This is probably because when there was only a serial protocol in the early days, the overall model was already established, and the newly added Modbus TCP could only be messed up for compatibility.
I have directly reclassified it here, which is not standard, but I am happy with it.
Register Attributes#
RW Attribute | Bit Count | |
---|---|---|
Coils (0x01) | Read/Write | 1 bit |
Discrete Input Registers (0x02) | Read Only | 1 bit |
Holding Registers (0x03) | Read/Write | 16 bits |
Input Registers (0x04) | Read Only | 16 bits |
Overall Model#
Use software to abstract a memory structure, with different meaningful data stored at each address, similar to FPGA simulating SRAM. However, not every register address is 16 bits; it can also be 1 bit. The attributes are RO/RW.
RTU Series#
(0x01) Read Coils#
Master Request#
Address Field | Function Code | Starting Address | Coil Count | Check Field |
---|---|---|---|---|
1 byte | 1 byte | 2 bytes | 2 bytes | 2 bytes (CRC-MB16) |
Slave Response#
Address Field | Function Code | Byte Count | Coil Status | Check Field |
---|---|---|---|---|
1 byte | 1 byte | 1 byte (calculating Coil Status byte count) | n bytes | 2 bytes (CRC-MB16) |
Example#
Request to read coil data from address 20 to 38, the response address is from low to high, and the final byte is padded with 0 in the high position.
[!NOTE]
The final output status for 38-36 response bytes is padded with five remaining bits (up to the high position).
Request | Response | ||
---|---|---|---|
Address Field (Slave ID) | 0x01 | Address Field (Slave ID) | 0x01 |
Function Code | 0x01 | Function Code | 0x01 |
Starting Address High 8 bits | 0x00 | Byte Count | 0x03 |
Starting Address Low 8 bits | 0x13 | Output Status 27-20 | 0xCD |
Output Count High 8 bits | 0x00 | Output Status 35-28 | 0x6B |
Output Count Low 8 bits | 0x13 | Output Status 38-36 | 0x05 |
Check CRC Low 8 bits | 0xA9 | Check CRC Low 8 bits | 0x42 |
Check CRC High 8 bits | 0xC8 | Check CRC High 8 bits | 0x82 |
(0x02) Read Discrete Input Registers#
Master Request#
Address Field | Function Code | Starting Address | Discrete Input Count | Check Field |
---|---|---|---|---|
1 byte | 1 byte | 2 bytes | 2 bytes | 2 bytes (CRC-MB16) |
Slave Response#
Address Field | Function Code | Byte Count | Discrete Input Status | Check Field |
---|---|---|---|---|
1 byte | 1 byte | 1 byte (calculating Discrete Input Status byte count) | n bytes | 2 bytes (CRC-MB16) |
Example#
Request to read discrete input register data from address 197 to 218, the response address is from low to high, and the final byte is padded with 0 in the high position.
[!NOTE]
The final input status for 218-213 response bytes is padded with two remaining bits (up to the high position).
Request | Response | ||
---|---|---|---|
Address Field (Slave ID) | 0x01 | Address Field (Slave ID) | 0x01 |
Function Code | 0x02 | Function Code | 0x02 |
Starting Address High 8 bits | 0x00 | Byte Count | 0x03 |
Starting Address Low 8 bits | 0xC4 | Input Status 204-197 | 0xAC |
Output Count High 8 bits | 0x00 | Input Status 212-205 | 0xDB |
Output Count Low 8 bits | 0x16 | Input Status 218-213 | 0x35 |
Check CRC Low 8 bits | 0xB8 | Check CRC Low 8 bits | 0x22 |
Check CRC High 8 bits | 0x39 | Check CRC High 8 bits | 0x88 |
(0x03) Read Holding Registers#
Master Request#
Address Field | Function Code | Starting Address | Holding Register Count | Check Field |
---|---|---|---|---|
1 byte | 1 byte | 2 bytes | 2 bytes | 2 bytes (CRC-MB16) |
Slave Response#
Address Field | Function Code | Byte Count | Holding Register Status | Check Field |
---|---|---|---|---|
1 byte | 1 byte | 1 byte (calculating Holding Register Status byte count) | n bytes | 2 bytes (CRC-MB16) |
Example#
Request to read holding register data from address 108 to 110
Request | Response | ||
---|---|---|---|
Address Field (Slave ID) | 0x01 | Address Field (Slave ID) | 0x01 |
Function Code | 0x03 | Function Code | 0x03 |
Starting Address High 8 bits | 0x00 | Byte Count | 0x06 |
Starting Address Low 8 bits | 0x6B | Register Value High Byte (108) | 0x02 |
Register Count High 8 bits | 0x00 | Register Value Low Byte (108) | 0x2B |
Register Count Low 8 bits | 0x03 | Register Value High Byte (109) | 0x00 |
Check CRC Low 8 bits | 0x74 | Register Value Low Byte (109) | 0x00 |
Check CRC High 8 bits | 0x17 | Register Value High Byte (110) | 0x00 |
Register Value Low Byte (110) | 0x64 | ||
Check CRC Low 8 bits | 0x05 | ||
Check CRC High 8 bits | 0x7A |
(0x04) Read Input Registers#
Master Request#
Address Field | Function Code | Starting Address | Input Register Count | Check Field |
---|---|---|---|---|
1 byte | 1 byte | 2 bytes | 2 bytes | 2 bytes (CRC-MB16) |
Slave Response#
Address Field | Function Code | Byte Count | Input Register Status | Check Field |
---|---|---|---|---|
1 byte | 1 byte | 1 byte (calculating Input Register Status byte count) | n bytes | 2 bytes (CRC-MB16) |
Example#
Request to read holding register data from address 9 to 10
Request | Response | ||
---|---|---|---|
Address Field (Slave ID) | 0x01 | Address Field (Slave ID) | 0x01 |
Function Code | 0x04 | Function Code | 0x04 |
Starting Address High 8 bits | 0x00 | Byte Count | 0x06 |
Starting Address Low 8 bits | 0x6B | Register Value High Byte (9) | 0x02 |
Register Address High 8 bits | 0x00 | Register Value Low Byte (9) | 0x2B |
Register Address Low 8 bits | 0x03 | Register Value High Byte (10) | 0x00 |
Check CRC Low 8 bits | 0xC1 | Register Value Low Byte (10) | 0x00 |
Check CRC High 8 bits | 0xD7 | Check CRC Low 8 bits | 0xF3 |
Check CRC High 8 bits | 0xF4 |
(0x05) Write Single Coil#
[!NOTE]
When writing a single coil, the output value part only allows FF 00 to represent ON, and 00 00 to represent OFF; other values are invalid.
Master Request#
Address Field | Function Code | Output Address | Output Value | Check Field |
---|---|---|---|---|
1 byte | 1 byte | 2 bytes | 2 bytes | 2 bytes (CRC-MB16) |
Slave Response#
Address Field | Function Code | Address | Output Value | Check Field |
---|---|---|---|---|
1 byte | 1 byte | 2 bytes | 2 bytes | 2 bytes (CRC-MB16) |
Example#
Request to write the coil data at address 173 to ON
Request | Response | ||
---|---|---|---|
Address Field (Slave ID) | 0x01 | Address Field (Slave ID) | 0x01 |
Function Code | 0x05 | Function Code | 0x05 |
Register Address High 8 bits | 0x00 | Register Address High 8 bits | 0x00 |
Register Address Low 8 bits | 0xAC | Register Address Low 8 bits | 0xAC |
Register Value High 8 bits | 0xFF | Register Value High 8 bits | 0xFF |
Register Value Low 8 bits | 0x00 | Register Value Low 8 bits | 0x00 |
Check CRC Low 8 bits | 0x4C | Check CRC Low 8 bits | 0x4C |
Check CRC High 8 bits | 0x1B | Check CRC High 8 bits | 0x1B |
(0x06) Write Single Holding Register#
Master Request#
Address Field | Function Code | Holding Register Address | Register Value | Check Field |
---|---|---|---|---|
1 byte | 1 byte | 2 bytes | 2 bytes | 2 bytes (CRC-MB16) |
Slave Response#
Address Field | Function Code | Holding Register Address | Register Value | Check Field |
---|---|---|---|---|
1 byte | 1 byte | 2 bytes | 2 bytes | 2 bytes (CRC-MB16) |
Example#
Request to write the holding register data at address 2 to 0x0003
Request | Response | ||
---|---|---|---|
Address Field (Slave ID) | 0x01 | Address Field (Slave ID) | 0x01 |
Function Code | 0x06 | Function Code | 0x06 |
Register Address High 8 bits | 0x00 | Register Address High 8 bits | 0x00 |
Register Address Low 8 bits | 0x02 | Register Address Low 8 bits | 0x02 |
Register Value High 8 bits | 0x00 | Register Value High 8 bits | 0x00 |
Register Value Low 8 bits | 0x03 | Register Value Low 8 bits | 0x03 |
Check CRC Low 8 bits | 0x2C | Check CRC Low 8 bits | 0x2C |
Check CRC High 8 bits | 0x0B | Check CRC High 8 bits | 0x0B |
(0x0F) Write Multiple Coils#
Master Request#
Address Field | Function Code | Starting Address | Quantity to Set | Byte Count | Set Values | Check Field |
---|---|---|---|---|---|---|
1 byte | 1 byte | 2 bytes | 2 bytes | 1 byte (calculating Set Values byte count) | n bytes | 2 bytes (CRC-MB16) |
Slave Response#
Address Field | Function Code | Starting Address | Quantity Set | Check Field |
---|---|---|---|---|
1 byte | 1 byte | 2 bytes | 2 bytes | 2 bytes (CRC-MB16) |
Example#
Request to write 10 coils starting from address 20
[!NOTE]
A total of 2 bytes (16 bits) need to be written, with 6 remaining bits padded with 0 (up to the high position).
Coil Address | 27 | 26 | 25 | 24 | 23 | 22 | 21 | 20 | — | — | — | — | — | — | 29 | 28 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
Corresponding Value | 1 | 1 | 0 | 0 | 1 | 1 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 |
After filling according to the specification, the actual Set Values segment should be 0xCD 0x01
Request | Response | ||
---|---|---|---|
Address Field (Slave ID) | 0x01 | Address Field (Slave ID) | 0x01 |
Function Code | 0x0F | Function Code | 0x0F |
Starting Address High 8 bits | 0x00 | Starting Address High 8 bits | 0x00 |
Starting Address Low 8 bits | 0x13 | Starting Address Low 8 bits | 0x13 |
Quantity High 8 bits | 0x00 | Quantity High 8 bits | 0x00 |
Quantity Low 8 bits | 0x0A | Quantity Low 8 bits | 0x0A |
Byte Count | 0x02 | Check CRC Low 8 bits | 0x24 |
Set Values (27~20 Address) | 0xCD | Check CRC High 8 bits | 0x09 |
Set Values (29~28 Address) | 0x01 | ||
Check CRC Low 8 bits | 0x72 | ||
Check CRC High 8 bits | 0xCB |
(0x10) Write Multiple Holding Registers#
Master Request#
Address Field | Function Code | Starting Address | Quantity to Set | Byte Count | Set Values | Check Field |
---|---|---|---|---|---|---|
1 byte | 1 byte | 2 bytes | 2 bytes | 1 byte (calculating Set Values byte count) | n bytes | 2 bytes (CRC-MB16) |
Slave Response#
Address Field | Function Code | Starting Address | Quantity Set | Check Field |
---|---|---|---|---|
1 byte | 1 byte | 2 bytes | 2 bytes | 2 bytes (CRC-MB16) |
Example#
Request to write 4 holding registers starting from address 34
Request | Response | ||
---|---|---|---|
Address Field (Slave ID) | 0x01 | Address Field (Slave ID) | 0x01 |
Function Code | 0x10 | Function Code | 0x10 |
Starting Address High 8 bits | 0x00 | Starting Address High 8 bits | 0x00 |
Starting Address Low 8 bits | 0x22 | Starting Address Low 8 bits | 0x22 |
Quantity High 8 bits | 0x00 | Quantity High 8 bits | 0x00 |
Quantity Low 8 bits | 0x04 | Quantity Low 8 bits | 0x04 |
Byte Count | 0x08 | Check CRC Low 8 bits | 0x61 |
Set Value High 8 bits (34 Address) | 0x00 | Check CRC High 8 bits | 0xC0 |
Set Value Low 8 bits (34 Address) | 0x40 | ||
Set Value High 8 bits (35 Address) | 0x00 | ||
Set Value Low 8 bits (35 Address) | 0x24 | ||
Set Value High 8 bits (36 Address) | 0x00 | ||
Set Value Low 8 bits (36 Address) | 0x01 | ||
Set Value High 8 bits (37 Address) | 0xBF | ||
Set Value Low 8 bits (37 Address) | 0x52 | ||
Check CRC Low 8 bits | 0x5F | ||
Check CRC High 8 bits | 0xCC |
Exception Response Frame#
Address Field | Function Code | Exception Code | Check Field |
---|---|---|---|
1 byte | 1 byte (Function Code + 0x80) | 1 byte | 2 bytes (CRC-MB16) |
Exception Code | Name | Meaning |
---|---|---|
0x01 | Illegal Function Code | The function code of the received request is not allowed. It may be that the function code is not supported or that it is in an error state while processing the request. |
0x02 | Illegal Data Address | The data address received in the inquiry is not allowed. In particular, the combination of starting address and transmission length is invalid. For a controller with 100 registers, a request with a starting address of 96 and a length of 4 will succeed, while a request with a starting address of 96 and a length of 5 will generate exception code 0x02. |
0x03 | Illegal Data Value | This actually means that the data segment is illegal. For example, an illegal data segment length, or the number of registers written or read does not match the data segment. Note that this does not mean that the register was written with a value outside the expected range; actual write failures (this situation is 0x04). |
0x04 | Slave Device Failure | An error occurred when the server (or slave) performed the requested operation on the register, such as writing a value outside the expected range. |
0x05 | Acknowledge | This is not an error, but indicates that a long-running command has been received and processing has begun. |
0x06 | Slave Device Busy | The slave is busy processing a long-running command. (The request that caused this error should be resent when the slave is idle.) |
0x08 | Memory Parity Error | An attempt was made to read a record file, but a parity error was found in memory. |
0x0A | Gateway Path Unavailable | Used with gateways, indicating that the gateway cannot allocate an internal communication path from input port to output port for processing the request. This usually means that the gateway is misconfigured or overloaded. |
0x0B | Gateway Target Device Failed to Respond | Used with gateways, indicating that no response was received from the target device. This usually means that the device is not on the network. |
TCP Series#
In short, Modbus TCP is just a wrapper for the previous message header compared to RTU, while removing the CRC check 👍. == Because the TCP management layer's link transmission is already very robust, while serial transmission is not robust ==
The transaction number in the message header will continuously increment when the host (TCP client) initiates a request, and the slave (TCP server) will respond with the same transaction number to indicate which request is being processed. Therefore, Modbus TCP inherently supports multiple frame transmissions, and the host will not misunderstand the response 👍.
Personally, I prefer this smooth communication protocol. || Let the physical link layer handle the accuracy of the information; it’s all foggy, but checks are still necessary ||
(0x01) Read Coils#
Master Request#
Transaction ID | Protocol Identifier | Total Length | Address Field | Function Code | Starting Address | Coil Count |
---|---|---|---|---|---|---|
2 bytes | 2 bytes (all 0) | 2 bytes (total length of remaining bytes) | 1 byte | 1 byte | 2 bytes | 2 bytes |
Slave Response#
Transaction ID | Protocol Identifier | Total Length | Address Field | Function Code | Byte Count | Coil Status |
---|---|---|---|---|---|---|
2 bytes | 2 bytes (all 0) | 2 bytes (total length of remaining bytes) | 1 byte | 1 byte | 1 byte (calculating Coil Status byte count) | n bytes |
Example#
Request to read coil data from address 20 to 38, the response address is from low to high, and the final byte is padded with 0 in the high position.
[!NOTE]
The final output status for 38-36 response bytes is padded with five remaining bits (up to the high position).
Request | Response | ||
---|---|---|---|
Transaction ID High 8 bits | 0x00 | Transaction ID High 8 bits | 0x00 |
Transaction ID Low 8 bits | 0x01 | Transaction ID Low 8 bits | 0x01 |
Protocol Identifier 16 bits (all 0) | 0x00 0x00 | Protocol Identifier 16 bits (all 0) | 0x00 0x00 |
Total Length High 8 bits | 0x00 | Total Length High 8 bits | 0x00 |
Total Length Low 8 bits | 0x06 | Total Length Low 8 bits | 0x06 |
Address Field (Slave ID) | 0x01 | Address Field (Slave ID) | 0x01 |
Function Code | 0x01 | Function Code | 0x01 |
Starting Address High 8 bits | 0x00 | Byte Count | 0x03 |
Starting Address Low 8 bits | 0x13 | Output Status 27-20 | 0xCD |
Output Count High 8 bits | 0x00 | Output Status 35-28 | 0x6B |
Output Count Low 8 bits | 0x13 | Output Status 38-36 | 0x05 |
Other protocols will not be elaborated on
Other protocols will not be elaborated on
In short, Modbus TCP is just a wrapper for the previous message header compared to RTU, while removing the CRC check 👍.
This article is synchronized and updated to xLog by Mix Space. The original link is https://www.yono233.cn/posts/shoot/24_7_26_modbus