MODBUS 协议初步

MODBUS 协议初步

MODBUS 协议是工业领域中常见的通信协议,用于采集与控制各种设备的数据。最初基于串口通信,后支持 TCP 模式。

MODBUS 可工作在以下标准中:

MODBUS 通信栈
MODBUS 通信栈

一个网络架构的示例如下:

MODBUS 网络架构
MODBUS 网络架构

通过网关,不同类型的总线或网络可以使用 MODBUS 协议进行通信。

字节序

尽管 MODBUS 协议规定使用高尾端字节序,但在实际应用中,随厂商与设备型号的不同,数据存储的字节序也不同。

十六进制数字 0xaabbccdd 在不同字节序下内存的排列如下:

字节序 内存低地址 -> 内存高地址 备注
big endian aa bb cc dd 高尾端,高字节在低地址
big endian byte swap bb aa dd cc 每 2 字节使用高尾端,2 字节内部使用低尾端
little endian dd cc bb aa 低尾端,低字节在低地址
little endian byte swap cc dd aa bb 每 2 字节使用低尾端,2 字节内部使用高尾端

MODBUS 应用层协议

缩写 全称 含义
MB MODBUS Protocol MODBUS 协议
ADU Application Data Unit 应用数据单元
PDU Protocol Data Unit 协议数据单元
MBAP MODBUS Application Protocol MODBUS 应用协议

协议描述

1
2
3
|<-------------------------ADU------------------------>|
Addition address || Function code || Data || Error check
|<--------PDU---------->|

一个 MODBUS ADU 由发起请求的客户端构建,功能码(function code) 指明了服务端要执行的操作,功能码占 1 字节,有效范围为 [1, 255],其中 [128, 255] 是为错误响应保留的值。

如果 client 发送的请求能被 server 正常处理,响应中的功能码与请求的功能码一致,data 字段包含了请求的数据,如果发生了错误,响应中的功能码等于请求功能码最高位值 1,即+128,data 字段指明了出错的原因。

由于首次基于串行线路实现的 MODBUS 协议的 ADU 最长为 256 字节,因此 PDU 最长为 253 字节(ADU 最大长度减去 1 字节 slave 地址和 2 字节校验字段)。

TCP ADU 最长 = 253 字节 + ADU 首部 7 字节 = 260 字节

MODBUS 协议定义了 3 种类型的 PDU

Request PDU

字段 大小(字节) 描述
function code 1 功能码
request data n 具体意义与功能码相关

Response PDU

字段 大小(字节) 描述
function code 1 功能码
response data n 具体意义与功能码相关

Exception Response PDU

字段 大小(字节) 描述
exception-function code 1 相应功能码+0x80(对于一个异常响应,server 返回的代码等于原始请求 PDU 中的功能码的最高有效位置为 1
exception code 1 协议定义的异常码

数据模型

Primary tables 对象类型 能否读写
Discretes Input 1 bit 只读
Coils 1 bit 读写
Input Registers 16 bit 只读
Holding Registers 16 bit 读写

在 MODBUS PDU 中,每个数据寻址范围是 0 到 65535。 在 MODBUS 数据模型中,数据块的每个元素从 1 到 65536 编号。 MODBUS 数据模型到设备应用程序的映射取决于供应商设备的规范。

编号为 x 的 MODBUS 数据通过 MODBUS PDU x - 1 寻址。

常见功能码

0x01 Read Coils

读取远程设备中线圈的 1 到 2000 个连续状态,请求 PDU 指定起始地址与线圈数量。PDU 线圈地址从 0 开始。

Request
字段 长度
Function code 1 byte 0x01
Starting Address 2 bytes [0, 0xffff]
Quantity of coils 2 bytes [1, 0x7d0], 2000=0x7d0
Response
字段 长度
Function code 1 byte 0x01
Byte count 1 byte N = ceil(线圈数/8)
Coil Status n bytes n = N
Error
字段 长度
Error code 1 byte 0x81
Exception code 1 byte 01 or 02 or 03 or 04
example
请求字段 请求值 响应字段 响应值
Function 01 Function 01
起始地址高字节 00 字节数 03
起始地址低字节 13 输出状态 27-20 CD,二进制 1100 1101,最高位为线圈 27 的状态,最低位为 20 的状态
输出数量高字节 00 输出状态 35-28 6B
输出数量低字节 13 输出状态 38-36 05

0x02 Read Discrete Inputs

Request
字段 长度
Function code 1 byte 0x02
Starting Address 2 bytes [0, 0xffff]
Quantity of Inputs 2 bytes [1, 0x7d0]
Response
字段 长度
Function code 1 byte 0x02
Byte count 1 byte N = ceil(Inputs 数/8)
Input Status n bytes n = N
Error
字段 长度
Error code 1 byte 0x82
Exception code 1 byte 01 or 02 or 03 or 04

0x03 Read Holding Registers

Request
字段 长度
Function code 1 byte 0x03
Starting Address 2 bytes [0, 0xffff]
Quantity of Registers 2 bytes [1, 0x7d], 125=0x7d
Response
字段 长度
Function code 1 byte 0x03
Byte count 1 byte N = 2 * 寄存器数
Register Value n bytes n = N
Error
字段 长度
Error code 1 byte 0x83
Exception code 1 byte 01 or 02 or 03 or 04

0x04 Read Input Registers

Request
字段 长度
Function code 1 byte 0x04
Starting Address 2 bytes [0, 0xffff]
Quantity of Input Registers 2 bytes [1, 0x7d]
Response
字段 长度
Function code 1 byte 0x04
Byte count 1 byte N = 2 * 寄存器数
Input Registers n bytes n = N
Error
字段 长度
Error code 1 byte 0x84
Exception code 1 byte 01 or 02 or 03 or 04

MODBUS 串行线路协议

主从模式协议,有且仅有一个 master 连接到总线,一个或多个(最多 247)slave 节点连接到该总线,一次 MODBUS 通信总由 master 发起,slave 仅在收到 master 请求的情况下传输数据,slave 节点之间不会相互通信,master 节点在同一时间只会发起一次 MODBUS 事务。master, slave 分别对应于应用层协议的 client, server。

MODBUS 请求分为单播模式与广播模式。在单播模式中,master 向一个特定的 slave 发起请求,该 slave 接受并处理请求,返回一个响应消息。地址为 0 代表广播模式,在广播模式中,master 不会收到任何响应,广播模式请求必须是写入命令,所有的设备必须接受该广播。

地址规则

master 没有具体的地址,但 salve 节点必须有在该串行总线中唯一的地址。0 用于广播,1-247 用于分配 slave 的地址,248-255 保留。

帧描述

偏移 长度(字节) 描述
0 1 slave 地址
1 1 功能码
2 [0, 252] 数据长度 n
n + 2 2 CRC or LRC

其中,帧尾的错误校验算法使用 CRC 或 LRC 取决于传输模式使用 RTU 还是 ASCII。

状态图

master_state
master_state
slave_state
slave_state

串行传输模式

串行传输模式分为 RTU 与 ASCII 两种,MODBUS 设备必须支持 RTU 模式,ACSII 可选。用户可以决定设备使用的传输模式,但默认必须为 RTU。同一串行总线上的所有设备,其传输模式与串口参数必须保持一致。

RTU(Remote Terminal Unit) 传输模式

每个 8-bit 字节包含 2 个 4-bit 十六进制字符,优点是在相同波特下比 ASCII 模式拥有更大数据吞吐量。每个消息必须通过连续的字符流传输。

RTU 模式中每个字节的格式为 11 bit,包括

  • 编码系统 8-bit 二进制
  • 字节中的每个 bit 1 开始 bit 8 数据 bit,先发送最低有效位 1 bit 用于奇偶校验 1 停止 bit

奇偶校验默认使用偶校验,也可选择奇校验、无校验,无校验要求使用 2 个停止位。

实际中常使用 9600 波特,8 数据位,无校验,1 停止位,读写数据时只考虑数据位,其他位(开始、停止、校验)由串口 API 负责。

波特:每秒传输符号的个数。在串口通信中,1 个符号表示 1 bit。

CRC 校验

使用 CRC16 进行对整条消息进行校验,最终将校验值添加到帧尾,规定校验值的低字节放在高字节之前。

ASCII 传输模式

当物理通信链路或设备不满足 RTU 计时器管理的要求时,使用 ASCII 模式,在该模式下,每个字节被编码为两个 ASCII 字符,如 0x12 编码为 0x31(=‘1’), 0x32(=‘2’)。该模式不常用,不再赘述。

MODBUS on TCP

1
2
3
|<-------MODBUS TCP/IP ADU---------->|
| MBAP Header | Function code | Data |
|<--------PDU--------->|

MBAP Header 大小为 7 字节

Fields Length Description Client Server
Transaction Identifier 2 Bytes 用于标识一个特定的 MODBUS 请求/响应 由 client 初始化 server 直接从收到的请求中复制过来
Protocol Identifier 2 Bytes 0 = MODBUS 协议 由 client 初始化 server 直接从收到的请求中复制过来
Length 2 Bytes 后续字节的数量,包括 Unit Identifier 和数据部分 请求中由 client 初始化 响应中由 server 初始化
Unit Identifier 1 Byte 标识一个特定的连接在串行线路或其他总线上的远程 slave, 由 client 初始化 server 直接从收到的请求中复制过来

默认端口为 502,使用高尾端编码。

MODBUS 采集实例

在诸多厂商的 MODBUS 实现中,字节序、地址、数据类型的格式千奇百怪,甚至没有给出说明,需要根据特定文档以及运行时调试来决定 MODBUS 采集指令和解析方式。

此外,采集数据时,某些测点为模拟量,比如温度,而 MODBUS 设备可能用一个 int16 的数据来表示,这种情况下就需要乘以一个缩放因子得到真实值,比如设备返回数据为 275,而实际温度为 275 * 0.1 = 27.5 摄氏度。

以某电表的协议文档为例,地址未说明进制,测点的数据类型不统一:

pm
pm
pm
pm

寄存器地址为 4xxxx 格式,实际的寄存器为 10 进制的 xxxx - 1,不同寄存器又出现了 高尾端 与 交换的低尾端 两种字节序。

一个寄存器存放多个状态量 & 一个寄存器存放一个状态量:

ats
ats

一次读多个寄存器与一次读单个寄存器,测点的地址不同:

inv
inv

十进制寄存器地址,未指明字节序:

temphum
temphum

两个测点间隔一个寄存器,32bit 的有功电能分布在 0x10 ~ 0x12 三个寄存器:

dcpdu
dcpdu

某些设备甚至没有协议文档,可以通过监听 master 发出的指令,手动发送指令观察其返回格式来分析协议。比如监听到 master 向某一串温湿度设备发送的指令为

1
01 04 00 00 00 04 F1 C9

可以推测该温湿度串使用 MODBUS 通信,master 请求了地址为 1 的 slave 从 0 开始的 4 个寄存器,断开 master 与温湿度串的连接,手动向温湿度设备发送该指令,收到的响应为:

1
01 04 08 00 00 00 01 00 F2 03 E0 B9 46

其中一个温湿度设备显示当前的温度为 26.3 摄氏度,湿度为 99.9%,可以推测温湿度分别位于 2, 3 号寄存器,使用高尾端字节序(0x00F2 = 242, 0x03E0=992),缩放因子为 0.1。由于 master 在采集多个温湿度数据,所以 slave 地址为 1 返回的温湿度与显示温度 26.3 的温湿度设备不一致是可以正常的。

串行线路的 MODBUS 通信还与该串口的波特相关,设备的协议文档不一定会指明使用波特或者使用的波特已被修改过。假如 master 波特与 slave 设置的波特不一致,收发的数据也会“乱码”,某个 slave 使用 9600 波特,master 使用 4800 波特收到的响应:

1
2
3
4
5
6
7
8
9
2020-06-30 09:41:46 recv: E5
2020-06-30 09:41:47 recv: E5 04 04 E4 E4
2020-06-30 09:41:48 recv: 26 26 A6 A6
2020-06-30 09:41:49 recv: E7 E7
2020-06-30 09:41:51 recv:
2020-06-30 09:41:52 recv: 25
2020-06-30 09:41:53 recv: 25 E5 E5 04
2020-06-30 09:41:54 recv: 04 E4 E4 26 26
2020-06-30 09:41:55 recv: A6 A6

使用 19200 波特:

1
2
3
4
5
6
7
2020-06-30 09:43:40 recv: FE FE 9E
2020-06-30 09:43:41 recv: FE FE 9E FE FE 9E
2020-06-30 09:43:42 recv: 98 80
2020-06-30 09:43:43 recv: E0 98 FE FE 98 80 E0 98 FE FE F8 86 9E F8 86 9E 80 86 FE
2020-06-30 09:43:44 recv: 80 86 FE E6 98 E6 98 E0 9E FE E0 9E FE 98 E0 FE
2020-06-30 09:43:45 recv: 98 E0 FE FE 98 80 FE 98 80 98 FE 98 9E F8 98 FE 98 9E F8 F8 98 F8 F8 98 F8
2020-06-30 09:43:46 recv: F8 9E F8 98 FE F8 9E F8 98 FE 80 FE E6 80 FE E6

使用 9600 波特:

1
2
3
4
5
2020-06-30 09:42:35 recv: 0A 04 00 00 00 11 31 7D
2020-06-30 09:42:36 recv: 0A 04 00 00 00 11 31 7D 14 04 00 00 00 11 32 C3 14 04 00 00 00 11 32 C3 1E 04 00 00 00 11 32 69 1E 04 00 00 00 11 32 69 28 04 00 00 00 11 37 FF
2020-06-30 09:42:37 recv: 28 04 00 00 00 11 37 FF 32 04 00 00 00 11 35 C5 32 04 00 00 00 11 35 C5 3C 04 00 00 00 11 34 EB 3C 04 00 00 00 11 34 EB 46 04 00 00 00 11 3F 71 46 04 00 00 00 11 3F 71
2020-06-30 09:42:38 recv: 50 04 00 00 00 11 3D 87 50 04 00 00 00 11 3D 87 5A 04 00 00 00 11 3D 2D 5A 04 00 00 00 11 3D 2D 64 04 00 00 00 11 39 F3 64 04 00 00 00 11 39 F3
2020-06-30 09:42:39 recv: 6E 04 00 00 00 11 39 59 6E 04 00 00 00 11 39 59 78 04 00 00 00 11 3B AF 78 04 00 00 00 11 3B AF

常用的波特为 9600,其次是 115200,在 master 发送指令后接受不到 slave 的响应时,可以考虑波特是否与 slave 一致。

参考资料

  • MODBUS APPLICATION PROTOCOL SPECIFICATION V1.1b3
  • MODBUS over Serial Line Specification and Implementation Guide V1.02
  • MODBUS MESSAGING ON TCP/IP IMPLEMENTATION GUIDE V1.0b
  • 各种厂商协议文档
------ 本文结束 ------

版权声明

Memory is licensed under a Creative Commons BY-NC-SA 4.0 International License.
博客采用知识共享署署名(BY)-非商业性(NC)-相同方式共享(SA)
本文首发于Memory,转载请保留出处。