Modbus由MODICON公司于1979年开发,是一种工业现场总线协议标准。1996年施耐德公司推出基于以太网TCP/IP的Modbus协议,ModbusTCP。
Modbus通信的设备分为主站(mater)和从站(slave),主站为主动方,从站为被动方。

通信过程

通信的过程为:

  1. 主站设备主动向从站设备发送请求
  2. 从站设备处理主站的请求后,向主站返回结果。
  3. 如果从站设备处理请求出现异常,则向主站设备返回异常功能码。

数据传输方式

modbus的数据传输被定义为对以下4个存储块的读写:

  1. 线圈(coils) 操作单位为1位字的开关量,PLC的输出位,在Modbus中可读可写
  2. 离散量(discreteinputs) 操作单位为1位字的开关量,PLC的输入位,在Modbus中只读
  3. 输入寄存器(inputregisters) 操作单位为16位字(两个字节)数据,PLC中只能从模拟量输入端改变的寄存器,在Modbus中只读
  4. 保持寄存器(holdingregisters) 操作单位为16位字(两个字节)数据,PLC中用于输出模拟量信号的寄存器,在Modbus中可读可写

MODBUS-TCP报文结构

modbus-tcp报文结构:

modbus tcp从站断开重连python modbus tcp主从站_java


如上图所示:modbus-tcp的报文由MBAP+PDU组成。

MBAP报文头

其中MBAP报文头的组成为:


长度

描述

事务元标识符

2 个字节

MODBUS 请求/响应事务处理的识别码,主要用于在主站设备在接收到响应时能知道是哪个请求的响应

协议标识符

2 个字节

对于MODBUS 协议来说,这里恒为0

长度

2 个字节

以下字节的数量,也就是完整报文的字节数减去6

单元标识符

1 个字节

串行链路或其它总线上连接的远程从站的识别码,也就是要访问的从站的标识号,因为只有一个字节,所以一个主站最多只能访问256个从站设备

由上表可知,报文头为 7 个字节长。

PDU报文体

PDU的组成为功能码(一个字节)和数据(n个字节)

其中功能码为一个字节,modbus定义的功能码有:

  • 01 读线圈(coils)状态,读取单个或多个
  • 02 读离散输入(discreteinputs)状态,读取单个或多个
  • 03 读保持寄存器(holdingregisters),读取单个或多个
  • 04 读输入寄存器(inputregisters),读取单个或多个
  • 05 写单个线圈(coils)状态,单个写入
  • 06 写单个保持寄存器(holdingregisters),单个写入
  • 15 写多个线圈(coils),多个写入
  • 16 写多个保持寄存器(holdingregisters),多个写入

另外,当响应报文的功能码最高位为1时(即:(function & 0x80) != 0),表示为异常响应,这时数据为一个字节的异常码,具体的异常码定义有:

  • 01 功能码不能被从机识别
  • 02 从机的单元标识符不正确
  • 03 值不被从机接受
  • 04 当从机试图执行请求的操作时,发生了不可恢复的错误。
  • 05 从机已接受请求并正在处理,但需要很长时间。返回此响应是为了防止在主机中发生超时错误。主站可以在下一个轮询程序中发出一个完整的消息,以确定处理是否完成。
  • 06 从站正在处理长时间命令。Master应该稍后重试。
  • 07 从站不能执行程序功能。主站应该向从站请求诊断或错误信息。
  • 08 从站在内存中检测到奇偶校验错误。主设备可以重试请求,但从设备上可能需要服务。
  • 10 专门用于Modbus网关。表示配置错误的网关。
  • 11 专用于Modbus网关的响应。当从站无法响应时发送。

PDU报文详情

1、读线圈

请求报文:

功能码:01,一个字节

偏移量offset(读取数据的开始位置),两个字节

读取数量,两个字节

正常响应报文:

功能码:01,一个字节

数据长度(字节数),一个字节

线圈状态数据,n个字节(由于网络传输的数据都是以整字节为单位的,所以收到的数据可能比请求中要读的位数要多,这时按位将数据转换为开关量,只需解析请求中读取数量字段设定的位数就可以了

异常响应报文:

功能码:129(0x81),一个字节

异常码,一个字节

2、读离散输入

请求报文:

功能码:02,一个字节

偏移量offset(读取数据的开始位置),两个字节

读取数量,两个字节

正常响应报文:

功能码:02,一个字节

数据长度(字节数),一个字节

离散输入状态数据,n个字节(由于网络传输的数据都是以整字节为单位的,所以收到的数据可能比请求中要读的位数要多,这时按位将数据转换为开关量,只需解析请求中读取数量字段设定的位数就可以了

异常响应报文:

功能码:130(0x82),一个字节

异常码,一个字节

3、读保持寄存器

请求报文:

功能码:03,一个字节

偏移量offset(读取数据的开始位置),两个字节

读取数量,两个字节

正常响应报文:

功能码:03,一个字节

数据长度(字节数,这里应该是请求报文中的读取数量的2倍),一个字节

保持寄存器数据,n个字节(数据的字节数应该是请求报文中的读取数量的2倍

异常响应报文:

功能码:131(0x83),一个字节

异常码,一个字节

4、读输入寄存器

请求报文:

功能码:04,一个字节

偏移量offset(读取数据的开始位置),两个字节

读取数量,两个字节

正常响应报文:

功能码:04,一个字节

数据长度(字节数,这里应该是请求报文中的读取数量的2倍),一个字节

输入寄存器数据,n个字节(数据的字节数应该是请求报文中的读取数量的2倍

异常响应报文:

功能码:132(0x84),一个字节

异常码,一个字节

5、写单个线圈

请求报文:

功能码:05,一个字节

偏移量offset(写入数据的开始位置),两个字节

要写入的线圈状态值(只关注0和非0),两个字节

正常响应报文:

功能码:05,一个字节

偏移量offset(写入数据的开始位置),两个字节

要写入的线圈状态值(只关注0和非0),两个字节

异常响应报文:

功能码:133(0x85),一个字节

异常码,一个字节

6、写单个保持寄存器

请求报文:

功能码:06,一个字节

偏移量offset(写入数据的开始位置),两个字节

要写入的保持寄存器数据,两个字节

正常响应报文:

功能码:06,一个字节

偏移量offset(写入数据的开始位置),两个字节

要写入的保持寄存器数据,两个字节

异常响应报文:

功能码:134(0x86),一个字节

异常码,一个字节

7、写多个线圈

请求报文:

功能码:15,一个字节

偏移量offset(写入数据的开始位置),两个字节

要写入的数量,两个字节

数据长度(字节数),一个字节

线圈状态数据,n个字节

正常响应报文:

功能码:15,一个字节

偏移量offset(写入数据的开始位置),两个字节

要写入的数量,两个字节

异常响应报文:

功能码:143(0x8f),一个字节

异常码,一个字节

8、写多个保持寄存器

请求报文:

功能码:16,一个字节

偏移量offset(写入数据的开始位置),两个字节

要写入的数量,两个字节

数据长度(字节数),一个字节

保持寄存器数据,n个字节

正常响应报文:

功能码:16,一个字节

偏移量offset(写入数据的开始位置),两个字节

要写入的数量,两个字节

异常响应报文:

功能码:144(0x90),一个字节

异常码,一个字节