写点什么

详解物联网 Modbus 通讯协议

发布于: 刚刚

​​摘要:Modbus 是当前非常流行的一种通讯协议。

 

本文分享自华为云社区《一文搞懂物联网Modbus通讯协议丨【拜托了,物联网!】》,作者: jackwangcumt。

1、概述


随着 IT 技术的快速发展,当前已经步入了智能化时代,其中的物联网技术将在未来占据越来越重要的地位。根据百度百科的定义,物联网(Internet of things,简称 IOT )即“万物相连的互联网”,是互联网基础上的延伸和扩展的网络,物联网将各种信息有机的结合起来,实现任何时间、任何地点,人、机、物的互联互通。物联网从技术上来说,很重要的核心是通讯协议,即如何按约定的通讯协议,把机、物和人与互联网相连接,进行信息通信,以实现对人、机和物的智能化识别、定位、跟踪、监控和管理的一种网络。


一般来说,常见的物联网通讯协议众多,如蓝牙、Zigbee、WiFi、ModBus、PROFINET、EtherCAT、蜂窝等。而在众多的物联网通讯协议中,Modbus 是当前非常流行的一种通讯协议。它一种串行通信协议,是 Modicon 公司于 1979 年为使用可编程逻辑控制器(PLC)通信而制定的,可以说,它已经成为工业领域通信协议的业界标准。其优势如下:

  • 免费无版税限制

  • 容易部署

  • 灵活限制少

2、ModBus 协议概述


Modbus 通讯协议使用请求-应答机制在主(Master)(客户端 Client)和从(Slave)(服务器 Server)之间交换信息。Client-Server 原理是通信协议的模型,其中一个主设备控制多个从设备。这里需要注意的是:Modbus 通讯协议当中的 Master 对应 Client,而 Slave 对应 Server。Modbus 通讯协议的官网为 www.modbus.org。目前官网组织已经建议将 Master-Slave 替换为 Client-Server。从协议类型上可以分为:Modbus-RTU(ASCII)、Modbus-TCP 和 Modbus-Plus。本文主要介绍 Modbus-RTU(ASCII)的通讯协议原理。标准的 Modbus 协议物理层接口有 RS232、RS422、RS485 以太网接口。


通讯示意图如下:



一般来说,Modbus 通信协议原理具备如下的特征:

  • 一次只有一个主机(Master)连接到网络

  • 只有主设备(Master)可以启动通信并向从设备(Slave)发送请求

  • 主设备(Master)可以使用其特定地址单独寻址每个从设备(Slave),也可以使用地址 0(广播)同时寻址所有从设备(Slave)

  • 从设备(Slave)只能向主设备(Master)发送回复

  • 从设备(Slave)无法启动与主设备(Master)或其他从设备(Slave)的通信


Modbus 协议可使用 2 种通信模式交换信息:

  • 单播模式

  • 广播模式


不管是请求报文还是答复报文,数据结构如下:



即报文(帧数据)由 4 部分构成:地址(Slave Number)+功能码(Function Codes)+数据(Data)+校验(Check) 。其中的地址代表从设备的 ID 地址,作为寻址的信息。功能码表示当前的请求执行具体什么操作,比如读还是写。数据代表需要通讯的业务数据,可以根据实际情况来确定。最后一个校验则是验证数据是否有误。其中的功能码说明如下:



比如功能码为 03 代表读取当前寄存器内一个或多个二进制值,而 06 代表将二进制值写入单一寄存器。为了模拟 Modbus 通讯协议过程,这里可以借助模拟软件:

  • Modbus Poll(Master)

  • Modbus Slave


具体的安装过程这里不再赘述。首先这里需要模拟一个物联网传感器设备,这里用 Modbus Slave 来定义,首先打开此软件,并定义一个 ID 为 1 的设备:



此功能码为 03。另外,设置连接参数,示例界面如下:



下面再用 Modbus Poll 软件来模拟主机,来获取从设备的数据。首先定义一个读写报文。



然后再定义一个连接信息:



注意:两个 COM 口要使用不同的名称。

成功建立通讯后,通信的报文格式如下:



Tx 代表请求报文,而 Rx 代表答复报文。

3、ModBusJava 实现


下面介绍一下如何用 Java 来实现一个 Modbus TCP 通信。这里 Java 框架采用 Spring Boot,首先需要引入 Modbus 库。Maven 依赖库的 pom.xml 定义如下:


<?xml version="1.0" encoding="UTF-8"?><project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">	<modelVersion>4.0.0</modelVersion>	<parent>		<groupId>org.springframework.boot</groupId>		<artifactId>spring-boot-starter-parent</artifactId>		<version>2.5.5</version>		<relativePath/> <!-- lookup parent from repository -->	</parent>	<groupId>com.example</groupId>	<artifactId>demo</artifactId>	<version>0.0.1-SNAPSHOT</version>	<name>demo</name>	<description>Demo project for Spring Boot</description>	<properties>		<java.version>1.8</java.version>	</properties>	<dependencies>		<dependency>			<groupId>org.springframework.boot</groupId>			<artifactId>spring-boot-starter-web</artifactId>		</dependency>
<dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency>
<!--Modbus Master --> <dependency> <groupId>com.digitalpetri.modbus</groupId> <artifactId>modbus-master-tcp</artifactId> <version>1.2.0</version> </dependency> <!--Modbus Slave --> <dependency> <groupId>com.digitalpetri.modbus</groupId> <artifactId>modbus-slave-tcp</artifactId> <version>1.2.0</version> </dependency>
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies>
<build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build>
</project>
复制代码


其中关于 Modbus 库的依赖项为 com.digitalpetri.modbus,它分 modbus-master-tcp modbus-slave-tcp 。此示例用 Java 项目模拟了一个 Modbus Master 端,用 Modbus Slave 软件模拟了 Slave 端,通信连接方式选择 Modbus TCP/IP 方式,IP 地址和端口限定了 Slave 设备。示意图如下:



由于此处连接方式采用 Modbus TCP 方式,因此在 Modbus Slave 的连接配置的地方,需要调整连接方式,示意截图如下:



Java 核心代码如下:


package com.example.demo.modbus;import java.util.List;import java.util.Random;import java.util.concurrent.CompletableFuture;import java.util.concurrent.CopyOnWriteArrayList;import java.util.concurrent.Executors;import java.util.concurrent.ScheduledExecutorService;import java.util.concurrent.TimeUnit;import com.digitalpetri.modbus.codec.Modbus;import com.digitalpetri.modbus.master.ModbusTcpMaster;import com.digitalpetri.modbus.master.ModbusTcpMasterConfig;import com.digitalpetri.modbus.requests.ReadHoldingRegistersRequest;import com.digitalpetri.modbus.responses.ReadHoldingRegistersResponse;import io.netty.buffer.ByteBufUtil;import io.netty.util.ReferenceCountUtil;import org.slf4j.Logger;import org.slf4j.LoggerFactory;
public class MBMaster {
private final Logger logger = LoggerFactory.getLogger(getClass());
private final ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();
private final List<ModbusTcpMaster> masters = new CopyOnWriteArrayList<>(); private volatile boolean started = false;
private final int nMasters ; private final int nRequests ;
public MBMaster(int nMasters, int nRequests) { if (nMasters < 1){ nMasters = 1; } if (nRequests < 1){ nMasters = 1; } this.nMasters = nMasters; this.nRequests = nRequests; } //启动 public void start() { started = true;
ModbusTcpMasterConfig config = new ModbusTcpMasterConfig.Builder("127.0.0.1") .setPort(50201) .setInstanceId("S-001") .build();
new Thread(() -> { while (started) { try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); }
double mean = 0.0; int mcounter = 0;
for (ModbusTcpMaster master : masters) { mean += master.getResponseTimer().getMeanRate(); mcounter += master.getResponseTimer().getCount(); }
logger.info("Mean Rate={}, counter={}", mean, mcounter); } }).start();
for (int i = 0; i < nMasters; i++) { ModbusTcpMaster master = new ModbusTcpMaster(config); master.connect(); masters.add(master); for (int j = 0; j < nRequests; j++) { sendAndReceive(master); } } } //发送请求 private void sendAndReceive(ModbusTcpMaster master) { if (!started) return;
//10个寄存器 CompletableFuture<ReadHoldingRegistersResponse> future = master.sendRequest(new ReadHoldingRegistersRequest(0, 10), 0);
//响应处理 future.whenCompleteAsync((response, ex) -> { if (response != null) { //System.out.println("Response: " + ByteBufUtil.hexDump(response.getRegisters())); System.out.println("Response: " + ByteBufUtil.prettyHexDump(response.getRegisters())); //[00 31 00 46 00 00 00 b3 00 00 00 00 00 00 00 00] byte[] bytes = ByteBufUtil.getBytes(response.getRegisters()); System.out.println("Response Value = " + bytes[3]);//根据业务情况获取寄存器数值 ReferenceCountUtil.release(response); } else { logger.error("Error Msg ={}", ex.getMessage(), ex); } scheduler.schedule(() -> sendAndReceive(master), 1, TimeUnit.SECONDS); }, Modbus.sharedExecutor()); }
public void stop() { started = false; masters.forEach(ModbusTcpMaster::disconnect); masters.clear(); }
public static void main(String[] args) { //启动Client进行数据交互 new MBMaster(1, 1).start(); }}
复制代码


首先,需要用 ModbusTcpMasterConfig 来初始化一个 Modbus Tcp Master 主机的配置信息,比如 IP 地址(127.0.0.1)和端口号(50201),此需要和 Slave 一致。其次,将配置信息 config 作为参数传递到 ModbusTcpMaster 对象中,构建一个 master 实例。最后,用 master.sendRequest(newReadHoldingRegistersRequest(0, 10), 0)对象来查询数据,此功能码为 03,寄存器数据为 10。在 Modbus Slave 开启连接后,设置界面如下所示:



运行 Java 程序。控制台输出示例如下所示:



由此,可以知晓,返回的报文中在 0 到 f 这 15 个位置中,有需要的业务数据,具体获取哪个位置,取决于 Slave 设备的设置。


点击关注,第一时间了解华为云新鲜技术~

发布于: 刚刚阅读数: 2
用户头像

提供全面深入的云计算技术干货 2020.07.14 加入

华为云开发者社区,提供全面深入的云计算前景分析、丰富的技术干货、程序样例,分享华为云前沿资讯动态,方便开发者快速成长与发展,欢迎提问、互动,多方位了解云计算! 传送门:https://bbs.huaweicloud.com/

评论

发布
暂无评论
详解物联网Modbus通讯协议