Modbus三种通信模式
1、RTU RTU帧详解
2、ASCII ASCII帧详解
3、TCP(本文使用)
串行链路上一个主站多个从站的模式演变为多个客户机和多个服务器的模式,给Modbus协议赋予TCP端口号为502,ModbusTCP/IP服务器端通常该端口作为接收报文的端口, 这是目前在仪表与自动化行业中唯一分配到的端口号。
目前最主流的方法是Modbus4j,它提供了各种寄存器的读写操作,集成的非常方便
(1)引入(也可以自己下载modbus4j-3.0.5版本下载)
(1.1)Maven
<repositories>
<repository>
<releases>
<enabled>false</enabled>
</releases>
<snapshots>
<enabled>true</enabled>
</snapshots>
<id>ias-snapshots</id>
<name>Infinite Automation Snapshot Repository</name>
<url>https://maven.mangoautomation.net/repository/ias-snapshot/</url>
</repository>
<repository>
<releases>
<enabled>true</enabled>
</releases>
<snapshots>
<enabled>false</enabled>
</snapshots>
<id>ias-releases</id>
<name>Infinite Automation Release Repository</name>
<url>https://maven.mangoautomation.net/repository/ias-release/</url>
</repository>
</repositories>
<!-- modbus4j -->
<dependency>
<groupId>com.infiniteautomation</groupId>
<artifactId>modbus4j</artifactId>
<version>3.0.3</version>
</dependency>
(1.2)Kotlin(Gradle)
repositories {
maven {
name = "Infinite Automation Snapshot Repository"
url = uri("https://maven.mangoautomation.net/repository/ias-snapshot/")
mavenContent {
releases {
include = false
}
snapshots {
include = true
}
}
}
maven {
name = "Infinite Automation Release Repository"
url = uri("https://maven.mangoautomation.net/repository/ias-release/")
mavenContent {
releases {
include = true
}
snapshots {
include = false
}
}
}
}
dependencies {
implementation("com.infiniteautomation:modbus4j:3.0.3")
}
(2)读寄存器
可以看到我们首先需要进行ip及端口号设置
IpParameters params = new IpParameters();
params.setHost("192.168.6.6");
params.setPort(502);
其次进行读寄存器
modbusMaster = Modbus4Android.getInstance().getModbusMaster(params);
ReadHoldingRegistersRequest request = new ReadHoldingRegistersRequest(1, 10100, 100);
ReadHoldingRegistersResponse response = (ReadHoldingRegistersResponse) modbusMaster.send(request);
打开ReadHoldingRegistersRequest源码可以看到
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//
package com.serotonin.modbus4j.msg;
import com.serotonin.modbus4j.ProcessImage;
import com.serotonin.modbus4j.exception.ModbusTransportException;
public class ReadHoldingRegistersRequest extends ReadNumericRequest {
public ReadHoldingRegistersRequest(int slaveId, int startOffset, int numberOfRegisters) throws ModbusTransportException {
super(slaveId, startOffset, numberOfRegisters);
}
ReadHoldingRegistersRequest(int slaveId) throws ModbusTransportException {
super(slaveId);
}
public byte getFunctionCode() {
return 3;
}
ModbusResponse handleImpl(ProcessImage processImage) throws ModbusTransportException {
return new ReadHoldingRegistersResponse(this.slaveId, this.getData(processImage));
}
protected short getNumeric(ProcessImage processImage, int index) throws ModbusTransportException {
return processImage.getHoldingRegister(index);
}
ModbusResponse getResponseInstance(int slaveId) throws ModbusTransportException {
return new ReadHoldingRegistersResponse(slaveId);
}
public String toString() {
return "ReadHoldingRegistersRequest [slaveId=" + this.slaveId + ", getFunctionCode()=" + this.getFunctionCode() + ", toString()=" + super.toString() + "]";
}
}
第一个参数是从机序号(由于Mobus是主从架构,一个主可以配置多个从节点PLC)
第二个参数是寄存器起始地址
注意:这里有坑!这里的寄存器起始地址号是指modbus地址,与厂家自定义的起始地址并不一样。可以根据出厂设置文件找到
第三个参数是读取寄存器的位数
注意:根据Modbus协议规范知道,一个 Modbus 请求最多只能读取 125 个寄存器(每个寄存器 2 字节),也就是最多读取 250字节的数据。如果参数3过大,因为 Modbus4j 底层会抛出 ModbusTransportException
或 ErrorResponseException
,如果你没有加 try-catch
捕获异常,Android 应用就会直接崩溃。
解决办法:
方法1:分批读取
// 举例读取 600 个寄存器,分成 6 次读取,每次读取 100 个
for (int i = 0; i < 6; i++) {
int start = 10100 + i * 100;
int len = 100;
if (start + len > 10699) len = 10699 - start + 1;
ReadHoldingRegistersRequest request = new ReadHoldingRegistersRequest(1, start, len);
try {
ReadHoldingRegistersResponse response = (ReadHoldingRegistersResponse) modbusMaster.send(request);
if (response.isException()) {
Log.e("Modbus", "Exception response: " + response.getExceptionMessage());
} else {
short[] data = response.getShortData();
// 处理 data
}
} catch (Exception e) {
Log.e("Modbus", "Error reading registers: " + e.getMessage());
}
}
方法2:封装一个安全的读取函数
public short[] safeReadHoldingRegisters(ModbusMaster master, int slaveId, int start, int count) throws Exception {
int maxPerRequest = 125;
List<Short> allData = new ArrayList<>();
for (int i = 0; i < count; i += maxPerRequest) {
int len = Math.min(maxPerRequest, count - i);
ReadHoldingRegistersRequest request = new ReadHoldingRegistersRequest(slaveId, start + i, len);
ReadHoldingRegistersResponse response = (ReadHoldingRegistersResponse) master.send(request);
if (response.isException()) {
throw new Exception("Modbus Exception: " + response.getExceptionMessage());
}
for (short val : response.getShortData()) {
allData.add(val);
}
}
short[] result = new short[allData.size()];
for (int i = 0; i < allData.size(); i++) result[i] = allData.get(i);
return result;
}
\\分割线--------------------------------------------------------------
\\调用方式
short[] allRegisters = safeReadHoldingRegisters(modbusMaster, 1, 10100, 600);