通讯协议之ModBus

Modbus是一种串行通信协议,是Modicon公司(现在的施耐德电气 Schneider Electric)于1979年为使用可编程逻辑控制器(PLC)通信而发表。Modbus已经成为工业领域通信协议的业界标准(De facto),并且现在是工业电子设备之间常用的连接方式。

  • 什么是ModBus
  • 相关工具
    • ModBus poll
    • ModBUs slave
  • 传输方式
    • TCP
    • RTU
    • ASCII
  • 消息帧

什么是ModBus

Modbus 协议是应用于电子控制器上的一种通用语言。通过此协议,控制器相互之间、控制器经由网络(例如以太网)和其它设备之间可以通信。Modbus 协议定义了一个控制器能认识使用的消息结构,而不管它们是经过何种网络进行通信的。它描述了一控制器请求访问其它设备的过程,如果回应来自其它设备的请求,以及怎样侦测错误并记录。它制定了消息域格局和内容的公共格式。

Modbus比其他通信协议使用的更广泛的主要原因有:

  1. 公开发表并且无著作权要求
  2. 易于部署和维护
  3. 对供应商来说,修改移动本地的比特或字节没有很多限制

ModBus能够在点对点和多点网络上运行,ModBus设备采用主从技术进行通信,其中只有一个设备可以发送请求。其他设备通过向主站提供所请求的数据来响应,或者通过采取查询中请求的操作。从机可以是任何外围设备,比如I/O传感器、阀门、网络驱动器、或者其他测量类型的设备。从站处理信息和使用ModBus将其响应消息发送给主站。

相关工具

传输方式

ModBus协议分为三种通信方式,分别是:

  • 异步串行: ModBus RTUModBus ASCII, 传输介质包括有线RS-232/422/485, 光纤, 无线
  • 以太网: ModBus TCP/IP
  • 高速令牌传递网络: ModBus PLUS

下面主要介绍的是 ModBus TCP/IP, ModBus RTUModBus ASCII

消息帧

ADU: 应用数据单元

PDU: 协议​数据​单元​

ModBus TCP

Modbus TCP的数据帧可分为两部分:ADU=MBAP+PDU = MBAP + 功能码 + 数据域,MBAP有7byte,功能码有1byte,数据域不确定,由具体功能决定。

MBAP为报文头,长度为7字节,组成如下:

  1. 事务处理标识 2个字节
  2. 协议标识 2个字节
  3. 长度 2个字节
  4. 单元标识符 1个字节

PDU格式如下:

  1. 功能码 1个字节
  2. 数据 (不同功能码,内部结构也不同)

ModBus RTU

ADU整体的结构如下:

  • 从站地址 1个字节
  • 功能码 1个字节
  • 数据 0-253个字节
  • 校验段 2个字节
    • CRC1 1个字节
    • CRC2 1个字节

Modbus RTU的报文格式没有定义帧的起始与结束字符, 因此对于帧识别有时间上的要求: 帧与帧之间的时间间隔要大于3.5个字符(字节)的时间, 否则认定为错误情况; 而帧内部的字符之间的间隔不能大于1.5个字符的时间, 否则, 不认为是同一个数据帧; 如下图所示.

ModBus ASCII

ASCII与RTU的区别是, 它的数据帧有开始和结束的标志位.

在ASCII(AmericanStandard Code for Information Interchange)传输模式下,消息帧以英文冒号(“:”,ASCII3A Hex)开始,以回车和换号(CRLF,ASCII 0D and 0A Hex)符号结束,允许的传输的字符集为十六进制的09和AF;网络中的从设备监视传输通路上是否有英文冒号(“:”),如果有的话,就对消息帧进行解码,查看消息中的地址是否与自己的地址相同,如果相同的话,就接收其中的数据;如果不同的话,则不予理会。

在ASCII模式下,每个8位的字节被拆分成两个ASCII字符进行发送,比如十六进制数0xAF ,会被分解成ASCII字符“A”和“F”进行发送,发送的字符量比RTU增加一倍。ASCII模式的好处是允许两个字符之间间隔的时间长达1s而不引发通信故障,该模式采用纵向冗余校验(Longitudinal Redundancy Check ,LRC)) 的方法来检验错误。

ADU整体的结构如下:

  • 起始 1个字节
  • 地址 2个字节
  • 功能码 2字节
  • 数据 0-252*2字节
  • LRC 2个字节
  • 截止 2个字节

ModBuss数据模型

Modbus中,数据可以分为两大类,分别为位变量(Coil)和整形变量(Register),每一种数据,根据读写方式的不同,又可细分为两种(只读,读写)。

  1. Discretes Input 位变量 只读
  2. Coils 位变量 读写
  3. Input Registers 16-bit整型 只读
  4. Holding Registers 16-bit整型 读写

地址范围:

设备地址 ModBus地址 描述 功能 R/W
1~10000 address-1 Coils(Output) 0 R/W
10001~20000 address-10001 Discrete Inputs 01 R
30001~40000 address-30001 Input Registers 04 R
40001~50000 address-40001 Holding Registers 03 R/W

常见功能码:

功能码 名称 功能 对应的地址类型
01 读线圈状态 读位(读N个bit)---读从机线圈寄存器,位操作 0x
02 读输入离散量 读位(读N个bit)---读离散输入寄存器,位操作 1x
03 读多个寄存器 读整型、字符型、状态字、浮点型(读N个words)---读保持寄存器,字节操作 4X
04 读输入寄存器 读整型、状态字、浮点型(读N个words)---读输入寄存器,字节操作 3x
05 写单个线圈 写位(写一个bit)---写线圈寄存器,位操作 0x
06 写单个保持寄存器 写整型、字符型、状态字、浮点型(写一个word)---写保持寄存器,字节操作 4x
0F 写多个线圈 写位(写n个bit)---强置一串连续逻辑线圈的通断 0x

举例说明

使用ModBusTCP作为例子, RTU和ASCII格式参考不同

读取数据

线圈读取
1
2
请求:
0003 0000 0006 01 01 00 00 00 01
  • 0003 序号
  • 0000 协议标识符
  • 0006 长度(01 01 00 00 00 01
  • 01 单元标识符(设备地址)
  • 01 功能码(线圈)
  • 00000000开始读
  • 0001 读取1
1
2
响应:
0003 0000 0004 0101 0101
  • 0003 序号
  • 0000 协议标识符
  • 0004 长度 (01 01 01 01
  • 01 单元标识符(设备地址)
  • 01 功能码(线圈)
  • 01 长度为1
  • 01 值为01
离散值读取
1
2
请求:
0001 0000 0006 01 02 0000 000a
  • 0001 序号
  • 0000 协议标识符
  • 0006 长度(01 02 00 00 00 0a)
  • 01 单元标识符(设备地址)
  • 02 功能码
  • 00000000开始读
  • 000a 读取0x0a (十进制为10)个位
1
2
响应:
0001 0000 0005 01 02 02 95 02
  • 0001 序号
  • 0000 协议标识符
  • 0005 长度为5(01 02 02 95 02)
  • 01 单元标识符(设备地址)
  • 02 功能码
  • 02 值的长度(95 02)
  • 95 第一个字节的值 二进制为: 10010101
  • 02 第二个字节的值 二进制为: 10

第一个字节:1 0 1 0 1 0 0 1

第二个字节:0 1

保持寄存器读取
1
2
请求:
0001 0000 0006 01 03 00 00 00 0a
  • 0001 序号
  • 0000 协议标识符
  • 0006 长度(01 03 00 00 00 0a)
  • 01 单元标识符(设备地址)
  • 03 功能码
  • 0000 从0000开始读
  • 000a 读取0x0a (十进制为10)个字符
1
2
响应:
0003 0000 0017 01 03 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  • 0001 序号
  • 0000 协议标识符
  • 0017 长度(01 03 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00)
  • 01 单元标识符(设备地址)
  • 03 功能码
  • 14 长度(十进制为20) (00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00)
  • 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 具体的数据

10条数据都为 0

如果10条数据为1-10递增, 则表示为 0001 0002 0003 0004 0005 0006 0007 0008 0009 000a

输入寄存器读取
1
2
请求:
0001 0000 0006 01 04 0000 000a
  • 0001 序号
  • 0000 协议标识符
  • 0006 长度(01 04 00 00 00 0a)
  • 01 单元标识符(设备地址)
  • 04 功能码
  • 00000000开始读
  • 000a 读取0x0a (十进制为10)个字符
1
2
正确的响应
0003 0000 0017 01 04 14 0001 0002 0003 0004 0005 0006 0007 0008 0009 7fff
  • 0001 序号
  • 0000 协议标识符
  • 0017 长度(01 03 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00)
  • 01 单元标识符(设备地址)
  • 04 功能码
  • 14 长度(十进制为20) (00 01 00 02 00 03 00 04 00 05 00 06 00 07 00 08 00 09 7f ff)
  • 0001 0002 0003 0004 0005 0006 0007 0008 0009 7fff 具体的数据

前9个为 1 到 9 递增, 最后一个为 32767(十进制), 范围是(-3276832767)

写数据

单写
写线圈
1
2
请求
0008 0000 0006 01 05 0000 ff00
  • 0008 序号
  • 0000 协议标识符
  • 0006 长度(01 05 00 00 ff 00)
  • 01 单元标识符(设备地址)
  • 05 功能码
  • 0000 写入0000地址
  • ff00 写入开 (0000 写入关)
1
2
成功的响应
0023 0000 0006 01 05 0001 0000
  • 0023 序号
  • 0000 协议标识符
  • 0006 长度(01 05 00 00 00 00)
  • 01 单元标识符(设备地址)
  • 05 功能码
  • 0000 写入0000地址
  • ff00 写入开 (0000 写入关)
写保持寄存器
1
2
请求:
0001 0000 0006 01 06 0000 0001
  • 0001 序号
  • 0000 协议标识符
  • 0006 长度(01 06 0000 0001)
  • 01 单元标识符(设备地址)
  • 06 功能码
  • 0000 写入0000地址
  • 0001 写入的值
1
2
成功的响应
0069 0000 0006 01 06 0000 0068
  • 0069 序号
  • 0000 协议标识符
  • 0006 长度(01 06 00 00 00 68)
  • 01 单元标识符(设备地址)
  • 06 功能码
  • 0000 写入0000地址
  • 0068 写入值68(十进制:104)
批量写
写线圈
1
2
请求
000b 0000 0009 01 0f 00 00 00 09 02 7a 01
  • 000b 序号
  • 0000 协议标识符
  • 0009 长度(01 0f 0000 00 09 02 7a 01)
  • 01 单元标识符
  • 0f 功能码 15
  • 0000 从地址0000开始
  • 0009 写入数量
  • 02 长度(7a 01)
  • 7a 01 输入这些值 (7a 二进制为 0111 1010, 01 二进制为 0000 0001)

输入的值为 0 1 0 1 1 1 1 0 1

1
2
正确响应
000b 0000 0006 01 0f 0000 0009
  • 000b 序号
  • 0000 协议标识符
  • 0006 长度(01 0f 0000 0009)
  • 01 单元标识符
  • 0f 功能码 15
  • 0000 从地址0000开始
  • 0009 更新值的个数
写保持寄存器
1
2
请求
0006 0000 001b 01 10 0000 000a 14 0006 0007 0008 0009 000a 000b 000c 000d 000e 8004
  • 0006 序号
  • 0000 协议标识符
  • 001b 长度(十进制为27)
  • 01 单元标识符
  • 10 功能码(十进制为:16)
  • 0000 从地址0000开始更改
  • 000a 改的个数为a(十进制为:10)
  • 14 字节长度(十进制为20)
  • 0006 0007 0008 0009 000a 000b 000c 000d 000e 8004 具体的数据
1
2
正确的响应
0006 0000 0006 01 10 0000 000a
  • 0006 序号
  • 0000 协议标识符
  • 0006 长度(01 10 00 00 00 0a)
  • 01 单元标识符
  • 10 功能码(十进制为:16)
  • 0000 从地址0000开始更改
  • 000a 改的个数为a(十进制为:10)