一、什么是Modbus

Modbus是一种串行通信协议,是Modicon公司于1979年为使用可编程逻辑控制器(PLC)通信而发表。Modbus是工业领域通信协议的业界标准,是工业电子设备之间常用的连接方式Modbus就是一个总线通信协议,像IIC SPI这种,但是他不依赖于硬件总线

  • Modbus之所以使用广泛,是有他的优点的
  • Modbus协议标准开放、公开发表且无版权要求
  • Modbus协议支持多种电气接口,包括RS232、RS485、TCP/IP等,还可以在各种介质上传输,如双绞线、光纤、红外、无线等
  • Modbus协议消息帧格式简单、紧凑、通俗易懂。用户理解和使用简单,厂商容易开发和集成,方便形成工业控制网络

二、modbus是哪一层协议

Modbus是 OSI 模型第 7 层上的应用层报文传输协议,它在连接至不同类型总线或网络的设备之间提供客户机/服务器通信。包括了ASCII、RTU、TCP三种报文类型,协议本身并没有定义物理层,定义了控制器能够认识和使用的消息结构,不管它们是经过何种网络进行通信的。Modbus协议使用串口传输时可以选择RTU或者ASCII模式,并规定了消息、数据结构、命令和应答方式,且需要对数据进行校验。ASCII模式采用LRC校验,RTU模式采用16位CRC校验。通过以太网传输时使用TCP,这种模式下不使用校验,因为TCP协议是一个面向连接的可靠协议

Modbus在Java中使用总结_寄存器

Modbus在Java中使用总结_寄存器_02

三、Modbus 通讯方式

MODBUS 协议允许在各种网络体系结构内进行简单通信。每种设备(PLC、HMI、控制面板、驱动程序、动作控制、输入/输出设备)都能使用 MODBUS协议来启动远程操作。

Modbus有下列三种通信方式:

  • 以太网:对应的通信模式是Modbus TCP/IP
  • 异步串行传输(各种介质如有线RS-232-/422/485/;光纤、无线等), 对应的通信模式是Modbus RTU或Modbus ASCII
  • 高速令牌传递网络:对应的通信模式是Modbus PLUS

1、ASCII模式

当控制器设为在Modbus网络上以ASCII模式通信,在消息中的每个8Bit字节都作为两个ASCII字符发送。这种方式的主要优点是字符发送的时间间隔可达到1秒而不产生错误。

2、RTU模式

当控制器设为Modbus网络上以RTU(远程终端单元)模式通信,在消息中的每个8Bit字节包含两个4Bit的十六进制字符。这种方式的主要优点是:在同样的波特率下,可比ASCII方式传送更多的数据。

3、Modbus TCP

在Modbus TCP/IP协议中,串行链路中的主/从设备分别演变为客户端/服务器端设备。即客户端相当于主站设备,服务器端相当于从站设备。基于TCP/IP网络的传输特性,串行链路上一主多从的构造也演变为多客户端/多服务器端的构造模型。Modbus TCP/IP服务器端通常使用端口502作为接收报文的端口, IANA(Internet Assigned Numbers Authority,互联网编号分配管理机构)给Modbus协议赋予TCP端口号为502,这是目前在仪表与自动化行业中唯一分配到的端口号。

其通信过程:

  1. connect 建立TCP连接
  2. 准备Modbus报文
  3. 使用send命令发送报文
  4. 在同一连接下等待应答
  5. 使用recv命令读取报文,完成一次数据交换
  6. 通信任务结束时,关闭TCP连接

ModbusTCP数据帧:MBAP+PDU

MBAP:

内容

长度

解释

00 00

2字节

可以理解为报文的序列号,一般每次通信之后就要加1以区别不同的通信数据报文。

00 00

2字节

00 00表示ModbusTCP协议。

00 06

2字节

表示接下来的数据长度,单位为字节。

01

1字节

可以理解为设备地址。以上七个字节也被称为Modbus报文头

PDU帧结构:PDU由功能码+数据组成。功能码为1字节,数据长度不定,由具体功能决定。

PDU详细结构

0x01:读线圈

在从站中读1~2000个连续线圈状态,ON=1,OFF=0

请求:MBAP 功能码 起始地址H 起始地址L 数量H 数量L(共12字节)

响应:MBAP 功能码 数据长度 数据(一个地址的数据为1位)

如:在从站0x01中,读取开始地址为0x0002的线圈数据,读0x0008位

00 01 00 00 00 06 01 01 00 02 00 08

回:数据长度为0x01个字节,数据为0x01,第一个线圈为ON,其余为OFF

00 01 00 00 00 04 01 01 01 01

0x05:写单个线圈

将从站中的一个输出写成ON或OFF,0xFF00请求输出为ON,0x000请求输出为OFF

请求:MBAP 功能码 输出地址H 输出地址L 输出值H 输出值L(共12字节)

响应:MBAP 功能码 输出地址H 输出地址L 输出值H 输出值L(共12字节)

如:将地址为0x0003的线圈设为ON

00 01 00 00 00 06 01 05 00 03 FF 00

回:写入成功

00 01 00 00 00 06 01 05 00 03 FF 00

0x0F:写多个线圈

将一个从站中的一个线圈序列的每个线圈都强制为ON或OFF,数据域中置1的位请求相应输出位ON,置0的位请求响应输出为OFF

请求:MBAP 功能码 起始地址H 起始地址L 输出数量H 输出数量L 字节长度 输出值H 输出值L

响应:MBAP 功能码 起始地址H 起始地址L 输出数量H 输出数量L

如:将地址为0x02的线圈和0x03的线圈设置

00 00 00 00 00 08 01 0F 00 02 00 02 01 01

0x02:读离散量输入

从一个从站中读1~2000个连续的离散量输入状态

请求:MBAP 功能码 起始地址H 起始地址L 数量H 数量L(共12字节)

响应:MBAP 功能码 数据长度 数据(长度:9+ceil(数量/8))

如:从地址0x0000开始读0x0012个离散量输入

00 01 00 00 00 06 01 02 00 00 00 12

回:数据长度为0x03个字节,数据为0x01 04 00,表示第一个离散量输入和第11个离散量输入为ON,其余为OFF

00 01 00 00 00 06 01 02 03 01 04 00

0x04:读输入寄存器

从一个远程设备中读1~2000个连续输入寄存器


请求:MBAP 功能码 起始地址H 起始地址L 寄存器数量H 寄存器数量L(共12字节)

响应:MBAP 功能码 数据长度 寄存器数据(长度:9+寄存器数量×2)

如:读起始地址为0x0002,数量为0x0005的寄存器数据

00 01 00 00 00 06 01 04 00 02 00 05

回:数据长度为0x0A,第一个寄存器的数据为0x0c,其余为0x00

00 01 00 00 00 0D 01 04 0A 00 0C 00 00 00 00 00 00 00 00

0x03:读保持寄存器

从远程设备中读保持寄存器连续块的内容

请求:MBAP 功能码 起始地址H 起始地址L 寄存器数量H 寄存器数量L(共12字节)

响应:MBAP 功能码 数据长度 寄存器数据(长度:9+寄存器数量×2)

如:00 01 此次通信事务处理标识符,00 00 标识modbus TCP协议,00 06为数据长度,01 为设备地址,03  为功能码此时代表读取保持寄存器,00 00代表起始地址,00 03为寄存器数量

00 01 00 00 00 06 01 03 00 00 00 03

回:数据长度为0x06,第一个寄存器的数据为0x21,其余为0x00

00 01 00 00 00 09 01 03 06 00 21 00 00 00 00

0x06:写单个保持寄存器

在一个远程设备中写一个保持寄存器

请求:MBAP 功能码 寄存器地址H 寄存器地址L 寄存器值H 寄存器值L(共12字节)

响应:MBAP 功能码 寄存器地址H 寄存器地址L 寄存器值H 寄存器值L(共12字节)

如:向地址是0x0000的寄存器写入数据0x000A

00 01 00 00 00 06 01 06 00 00 00 0A

回:写入成功

00 01 00 00 00 06 01 06 00 00 00 0A

0x10:写多个保持寄存器

在一个远程设备中写连续寄存器块(1~123个寄存器)

请求:MBAP 功能码 起始地址H 起始地址L 寄存器数量H 寄存器数量L 字节长度 寄存器值(13+寄存器数量×2)

响应:MBAP 功能码 起始地址H 起始地址L 寄存器数量H 寄存器数量L(共12字节)

如:向起始地址为0x0000,数量为0x0001的寄存器写入数据,数据长度为0x02,数据为0x000F

00 01 00 00 00 09 01 10 00 00 00 01 02 00 0F

回:写入成功

00 01 00 00 00 06 01 10 00 00 00 01

这里需要注意的是一位16进制数(用二进制表示是xxxx)最多只表示到15(即对应16进制的F),要表示到255,就还需要第二位。

所以1个字节=2个16进制字符,一个16进制位=0.5个字节。

参考链接:https://blog.csdn.net/weixin_43652507/article/details/122522608

四、通信过程

Modbus是一主多从的通信协议,控制器相互之间、或控制器经由网络(如以太网)可以和其它设备之间进行通信。Modbus协议使用的是主从通讯技术,即由主设备主动查询和操作从设备。一般将主控设备方所使用的协议称为Modbus Master,从设备方使用的协议称为Modbus Slave。典型的主设备包括工控机和工业控制器等;典型的从设备如PLC可编程控制器等。

Modbus的工作方式是请求/应答,每次通讯都是主站先发送指令,可以是广播,或是向特定从站的单播;从站响应指令,并按要求应答,或者报告异常。当主站不发送请求时,从站不会自己发出数据,从站和从站之间不能直接通讯 。

Modbus通讯物理接口可以选择串口,也可以选择以太网。它的通信遵循以下过程:

  • 主设备向从设备发送请求
  • 从设备分析并处理主设备的请求,然后向主设备发送结果
  • 如果出现任何差错,从设备将返回一个异常功能码

五、在Java中使用

关于Java的开源库

  • Jamod:Java Modbus实现:Java Modbus库。该库由Dieter Wimberger实施。
  • ModbusPal:ModbusPal是一个正在进行的Java项目,用于创建逼真的Modbus从站模拟器。由于预定义的数学函数和/或Python脚本,寄存器值是动态生成的。ModbusPal依赖于RxTx进行串行通信,而Jython则依赖于脚本支持。
  • Modbus4J:Serotonin Software用Java编写的Modbus协议的高性能且易于使用的实现。支持ASCII,RTU,TCP和UDP传输作为从站或主站,自动请求分区,响应数据类型解析和节点扫描。
  • JLibModbus:JLibModbus是java语言中Modbus协议的一种实现。jSSC和RXTX用于通过串行端口进行通信。该库是一个经过积极测试和改进的项目。

Java中能用来做什么?

  • 作为服务端(slave端、从端)接收数据:指的是java作为报文的接收者,接收硬件设备的反馈消息
  • 作为客户端(master端、主动端)发送数据数据:指的是Java作为报文的发送者,来达到控制设备

参考资料:

https://blog.csdn.net/xixiyuguang/article/details/123353651【Java实现ModbusTCP通信】              

https://runlion.blog.csdn.net/article/details/108647301【jamod例子】

https://blog.csdn.net/Crazy_Cw/article/details/126613967【Netty 实现】

https://blog.csdn.net/xfx_1994/article/details/117687884【jlibmodbus例子 】

https://www.leftso.com/blog/83.html【modbus4j例子 】

https://zhuanlan.zhihu.com/p/529715356【串口调试工具——Modbus Poll】

六、仿真软件Modbus Poll的使用

modbus协议中的线圈、寄存器等的解释:

  • 0x01: 读线圈寄存器 
  • 0x02: 读离散输入寄存器
  • 0x03: 读保持寄存器
  • 0x04: 读输入寄存器 
  • 0x05: 写单个线圈寄存器
  • 0x06: 写单个保持寄存器 
  • 0x0f: 写多个线圈寄存器 
  • 0x10: 写多个保持寄存器 

如上所示一共8种功能码。这其中有涉及到线圈、离散输入、保持、输入四种寄存器。 

线圈寄存器:实际上就可以类比为开关量(继电器状态),每一个bit对应一个信号的开关状态。所以一个byte就可以

同时控 制8路的信号。比如控制外部8路io的高低。 线圈寄存器支持读也支持写,写在功能码里面又分为写单个线圈寄存器和写多个 线圈寄存器。对应上面的功能码也就是:0x01 0x05 0x0f 

离散输入寄存器:如果线圈寄存器理解了这个自然也明白了。离散输入寄存器就相当于线圈寄存器的只读模式,他也是每个 bit表示一个开关量,而他的开关量只能读取输入的开关信号,是不能够写的。比如我读取外部按键的按下还是松开。所以功 能码也简单就一个读的 0x02 

保持寄存器:这个寄存器的单位不再是bit而是两个byte,也就是可以存放具体的数据量的,并且是可读写的。一般对应参数 设置,比如我我设置时间年月日,不但可以写也可以读出来现在的时间。写也分为单个写和多个写,所以功能码有对应的三 个:0x03 0x06 0x10 

输入寄存器:这个和保持寄存器类似,但是也是只支持读而不能写,一般是读取各种实时数据。一个寄存器也是占据两个 byte的空间。类比我我通过读取输入寄存器获取现在的AD采集值。对应的功能码也就一个 0x04

七、后续

为什么要使用驱动库?我们知道了Modbus是一种总线协议,它可以基于串口或网口,以基于串口的Modbus-RTU为例,我们需要在Windows或Linux下实现一个上位机,上位机的功能是读写Modbus接口传感器设备的数据,或者是和单片机等从设备进行交互。

所以作为主机,写数据的流程是:

  • 构建一个Modbus-RTU数据帧
  • 等待从机响应的数据
  • 如果响应数据正确,说明写入成功,否则写入失败。

读数据也是同样的流程,我们可以基于串口发送、串口接收函数、定时器等,自己写一个Modbus驱动库,来实现对从设备的读写。当然,也可以直接使用别人写好的Modbus驱动库,比如libmodbus,本文将介绍如何使用libmodbus驱动库,实现Modbus主机和从机。

https://blog.csdn.net/ccf19881030/article/details/108695110【CentOS7下编译安装libmodbus库】