MODBUS 协议初步
MODBUS 协议是工业领域中常见的通信协议,用于采集与控制各种设备的数据。最初基于串口通信,后支持 TCP 模式。
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 | |<-------------------------ADU------------------------>| |
一个 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。
状态图
串行传输模式
串行传输模式分为 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 | |<-------MODBUS TCP/IP ADU---------->| |
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 摄氏度。
以某电表的协议文档为例,地址未说明进制,测点的数据类型不统一:
寄存器地址为 4xxxx 格式,实际的寄存器为 10 进制的 xxxx - 1,不同寄存器又出现了 高尾端 与 交换的低尾端 两种字节序。
一个寄存器存放多个状态量 & 一个寄存器存放一个状态量:
一次读多个寄存器与一次读单个寄存器,测点的地址不同:
十进制寄存器地址,未指明字节序:
两个测点间隔一个寄存器,32bit 的有功电能分布在 0x10 ~ 0x12 三个寄存器:
某些设备甚至没有协议文档,可以通过监听 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 | 2020-06-30 09:41:46 recv: E5 |
使用 19200 波特:
1 | 2020-06-30 09:43:40 recv: FE FE 9E |
使用 9600 波特:
1 | 2020-06-30 09:42:35 recv: 0A 04 00 00 00 11 31 7D |
常用的波特为 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
- 各种厂商协议文档