Modbus协议——ModbusRTU、ModbusASCII
Modbus 是一项应用层报文传输协议,用于在通过不同类型的总线或网络连接的设备之间的客户机/服务器通信。常用于串口、以太网上的数据传输。

- ADU:应用数据单元。最大不超过256个字节。一次Modbus通讯报文,可以被成为1个ADU。
- PDU:协议数据单元。指代Modbus通讯报文中的 功能码 + 数据
串行通讯上的实现
Modbus 串行链路协议是一个主/从协议。
常用物理接口为RS485、RS232、RS422
Modbus串行链路上所有设备的 串口参数 和 传输模式 必须相同。
串口参数
| 参数名称 | 常见选项 |
|---|---|
| 波特率 | 9600、19200、38400、115200 (单位:bps) |
| 数据长度 | 8 、7 (单位:bit) |
| 校验方式 | 偶校验(Even)、奇校验(Odd)、无校验(None) |
| 停止位 | 1、2 (单位:bit) |
传输模式
该协议在串口上2种传输模式:Modbus-RTU 和 Modbus-ASCII。
两种传输模式的不同点
| Modbus-RTU | Modbus-ASCII | |
|---|---|---|
| 编码格式 | 二进制码 | ASCII码 |
| 起始符 | 无 | “冒号”(:)(ASCII码:0x3A) |
| 结束符 | 无 | “回车 + 换行”(CR LF)(ASCII码:0x0D、0x0A) |
| 报文起停判断 | 时间间隔 | 起始符+结束符 |
| 校验方式 | CRC | LRC |
Modbus-RTU
数据帧格式
| 从站地址(站号) | 功能码 | 数据 | CRC校验 |
|---|---|---|---|
| 1 byte | 1 byte | 0~252 byte | 2 byte |
从站地址(站号)
| 广播地址 | 从站地址 | 保留 |
|---|---|---|
| 0 | 1~247 | 248~255 |
- 地址0 为广播地址。所有的子节点必须识别广播地址。(标准而已,并不是所有从站都完全遵照标准设计。在实际应用中较为少见)
- Modbus主站没有节点地址,Modbus从站则必须拥有一个在串行链路上唯一的节点地址。
功能码
| 功能描述 | 功能码(十进制) | 功能码(十六进制) | 备注 |
|---|---|---|---|
| 读线圈 | 01 | 0x01 | 常用 |
| 写单个线圈 | 05 | 0x05 | |
| 写多个线圈 | 15 | 0x0F | |
| 读输入寄存器 | 04 | 0x04 | |
| 读保持寄存器 | 03 | 0x03 | 常用 |
| 写单个保持寄存器 | 06 | 0x06 | |
| 写多个保持寄存器 | 16 | 0x10 | 常用 |
| 读/写多个保持寄存器 | 23 | 0x17 |
功能码 0x03 命令格式解析
请求命令格式
| 大小 | 取值范围 | 备注 | |
|---|---|---|---|
| 从站地址 | 1 byte | 0x01 至 0xFF | |
| 功能码 | 1 byte | 0x03 | |
| 起始地址 | 2 byte | 0x0000 至 0xFFFF | |
| 寄存器数量(N) | 2 byte | 0x0000 至 0x007D | |
| CRC校验 | 2 byte | 0x0000 至 0xFFFF | 所有数据的CRC校验 |
响应命令格式
| 大小 | 取值范围 | 备注 | |
|---|---|---|---|
| 从站地址 | 1 byte | 0x01 至 0xFF | |
| 功能码 | 1 byte | 0x03 | |
| 字节数 | 1 byte | 2 * N | 寄存器值所包括的字节数 |
| 寄存器值 | 2 * N byte | 数据值 | 1个寄存器占用2个字节 |
| CRC校验 | 2 byte | 0x0000 至 0xFFFF | 所有数据的CRC校验 |
注:N = 寄存器数量
错误响应格式
| 大小 | 取值范围 | |
|---|---|---|
| 从站地址 | 1 byte | 0x01 至 0xFF |
| 错误码 | 1 byte | 0x83 (0x80 + 0x03) |
| 异常码 | 1 byte | 0x01:该功能码不支持 0x02:起始地址不支持 或者 起始地址+寄存器数量不支持 0x03:读取寄存器数量不支持 0x04:读取寄存器失败 |
| CRC校验 | 2 byte | 0x0000 至 0xFFFF |
eg.使用 功能码0x03 读取 站号01 地址0x0000~0x0009 的数据
请求命令:SEND HEX>
(站号)01 (功能码)03 (起始地址)00 00 (读取寄存器数量)00 0A (CRC校验)C5 CD
响应命令:RECV HEX>
(站号)01 (功能码)03 (字节数)14 (读取的各个寄存器值)00 01 00 02 00 03 00 04 00 05 00 06 00 07 00 08 00 09 00 0A (CRC校验)8F 16

功能码 0x10 命令格式解析
请求命令格式
| 大小 | 取值范围 | 备注 | |
|---|---|---|---|
| 从站地址 | 1 byte | 0x01 至 0xFF | |
| 功能码 | 1 byte | 0x10 | |
| 起始地址 | 2 byte | 0x0000 至 0xFFFF | |
| 寄存器数量(N) | 2 byte | 0x0000 至 0x007D | |
| 字节数 | 1 byte | 2 * N | 寄存器值所包括的字节数 |
| 寄存器值 | 2 * N byte | 数据值 | 1个寄存器占用2个字节 |
| CRC校验 | 2 byte | 0x0000 至 0xFFFF | 所有数据的CRC校验 |
注:N = 寄存器数量
响应命令格式
| 大小 | 取值范围 | 备注 | |
|---|---|---|---|
| 从站地址 | 1 byte | 0x01 至 0xFF | |
| 功能码 | 1 byte | 0x03 | |
| 起始地址 | 2 byte | 0x0000 至 0xFFFF | |
| 寄存器数量(N) | 2 byte | 0x0000 至 0x007D | |
| CRC校验 | 2 byte | 0x0000 至 0xFFFF | 所有数据的CRC校验 |
错误响应格式
| 大小 | 取值范围 | |
|---|---|---|
| 从站地址 | 1 byte | 0x01 至 0xFF |
| 错误码 | 1 byte | 0x90 (0x80 + 0x10) |
| 异常码 | 1 byte | 0x01:该功能码不支持 0x02:起始地址不支持 或者 起始地址+寄存器数量不支持 0x03:读取寄存器数量不支持 0x04:读取寄存器失败 |
| CRC校验 | 2 byte | 0x0000 至 0xFFFF |
eg.使用 功能码0x10 写入 站号01 地址0x0000~0x0009 的数据
请求命令:SEND HEX >
(站号)01 (功能码)10 (起始地址)00 00 (写入寄存器数量)00 0A (字节数)14 (各个寄存器的写入值)00 0A 00 14 00 1E 00 28 00 32 00 3C 00 46 00 50 00 5A 00 64 (CRC校验)69 8A
响应命令:RECV HEX >
(站号)01 (功能码)10 (起始地址)00 00 (寄存器数量)00 0A (CRC校验)40 0E

CRC校验
除了CRC校验码外的所有Modbus-RTU帧的数据,均需要进行CRC校验计算。
移位法计算步骤:
步骤一:加载一个内容为 0xFFFF 的 16位缓存器,称之为『CRC』缓存器。
步骤二:将需要计算的数据的第一个字节与 CRC 缓存器的低字节进行 异或运算(XOR),并将结果存回 CRC 缓存器。
步骤三:检查 CRC 缓存器的最低位(LSB),若此位为 0,则右移一位;若此位为 1,则 CRC 缓存器值右移一位后,再与 0xA001 进行 异或运算(XOR)。
步骤四:回到步骤三,直到步骤三已被执行过 8 次(即当前字节已经处理完),才进到步骤五。
步骤五:对数据的下一个字节重复步骤二到步骤四,直到所有字节皆完全处理过,此时 CRC 缓存器的内容即是 CRC校验码。
半查表法
鉴于移位法、全查表法网上的资料已经太多了,这里介绍另外一种计算CRC校验码的方式。
1 | |
Modbus-ASCII
数据帧格式
| 起始字符 | 从站地址(站号) | 功能码 | 数据 | LRC校验 | 结束字符 |
|---|---|---|---|---|---|
| 1 byte 冒号(:) |
2 byte | 2 byte | 0~504(252*2) byte | 2 byte | 2 byte 回车换行(CR LF) |
** 注:**
- ASCII数据项最大长度为252 * 2 个字节。是RTU的两倍
- 除了起始字符、结束字符,以及校验方式。其他项的数据是将RTU的报文直接翻译为十六进制数字对应的ASCII码,即0x01 翻译为 ‘01’(ASCII码:0x30、0x31)
eg.使用 功能码0x03 读取 站号01 地址0x0000~0x0009 的数据
请求命令:SEND ASCII >
(冒号):(站号)01(功能码)03(起始地址)0000(读取的寄存器数量)000A(LRC校验)F2(回车换行)CRLF
(对比)请求命令:SEND Hex >
(冒号)0x3A(站号)0x30 0x31(功能码)0x30 0x33(起始地址)0x30 0x30 0x30 0x30(读取的寄存器数量)0x30 0x30 0x30 0x3A(LRC校验)0x41 0x32(回车换行)0x0D 0x0A
LRC校验
除了起始字符、结束字符,以及LRC校验外的所有数据,均需要进行LRC校验计算。
LRC计算步骤:
步骤一:对需要校验的数据(2n个字符)两两组成一个16进制的数值求和。
步骤二:将求和结果与256求模。
步骤三:用256减去所得模值得到校验结果。(另一种方法:将模值按位取反然后加1)
1 | |
使用命令字符对应的数字进行LRC计算。
以请求命令(:01030000000AF2 CRLF)为例子说明:
需要进行LRC校验计算的数据为 01030000000A
另外一种LRC计算步骤如下:
步骤一:(累加)0+1+0+3+0+0+0+0+0+0+0+10 = 0xE (不是 30 + 31 … + 40 = 0x176)
步骤二:(FF减去步骤一累加值)0xFF - 0xE = 0xF1
步骤三:(步骤二值加1)0xF1 + 1 = 0xF2