Modbus協(xié)議
1.概述
概念
Modbus是一種串行通信協(xié)議,是Modicon公司(現(xiàn)在的施耐德電氣 Schneider Electric)于1979年為使用可編程邏輯控制器(PLC)通信而發(fā)表。Modbus已經(jīng)成為工業(yè)領(lǐng)域通信協(xié)議的業(yè)界標(biāo)準(zhǔn)(De facto),并且現(xiàn)在是工業(yè)電子設(shè)備之間常用的連接方式。
優(yōu)勢(shì)
-
Modbus協(xié)議標(biāo)準(zhǔn)開(kāi)放、公開(kāi)發(fā)表且無(wú)版權(quán)要求
-
Modbus協(xié)議支持多種電氣接口,包括RS232、RS485、TCP/IP等,還可以在各種介質(zhì)上傳輸,如雙絞線、光纖、紅外、無(wú)線等
-
Modbus協(xié)議消息幀格式簡(jiǎn)單、緊湊、通俗易懂。用戶理解和使用簡(jiǎn)單,廠商容易開(kāi)發(fā)和集成,方便形成工業(yè)控制網(wǎng)絡(luò)
通訊方式
1、ASCII模式
當(dāng)控制器設(shè)為在Modbus網(wǎng)絡(luò)上以ASCII模式通信,在消息中的每個(gè)8Bit字節(jié)都作為兩個(gè)ASCII字符發(fā)送。這種方式的主要優(yōu)點(diǎn)是字符發(fā)送的時(shí)間間隔可達(dá)到1秒而不產(chǎn)生錯(cuò)誤。
2、RTU模式
當(dāng)控制器設(shè)為Modbus網(wǎng)絡(luò)上以RTU(遠(yuǎn)程終端單元)模式通信,在消息中的每個(gè)8Bit字節(jié)包含兩個(gè)4Bit的十六進(jìn)制字符。這種方式的主要優(yōu)點(diǎn)是:在同樣的波特率下,可比ASCII方式傳送更多的數(shù)據(jù)。
3、Modbus TCP
在Modbus TCP/IP協(xié)議中,串行鏈路中的主/從設(shè)備分別演變?yōu)榭蛻舳?服務(wù)器端設(shè)備。即客戶端相當(dāng)于主站設(shè)備,服務(wù)器端相當(dāng)于從站設(shè)備。基于TCP/IP網(wǎng)絡(luò)的傳輸特性,串行鏈路上一主多從的構(gòu)造也演變?yōu)槎嗫蛻舳?多服務(wù)器端的構(gòu)造模型。Modbus TCP/IP服務(wù)器端通常使用端口502作為接收?qǐng)?bào)文的端口, IANA(Internet Assigned Numbers Authority,互聯(lián)網(wǎng)編號(hào)分配管理機(jī)構(gòu))給Modbus協(xié)議賦予TCP端口號(hào)為502,這是目前在儀表與自動(dòng)化行業(yè)中唯一分配到的端口號(hào)。
2.組成
物理模型
線圈寄存器:實(shí)際上就可以類比為開(kāi)關(guān)量(繼電器狀態(tài)),每一個(gè)bit對(duì)應(yīng)一個(gè)信號(hào)的開(kāi)關(guān)狀態(tài)。所以一個(gè)byte就可以同時(shí)控制8路的信號(hào)。比如控制外部8路io的高低。 線圈寄存器支持讀也支持寫,寫在功能碼里面又分為寫單個(gè)線圈寄存器和寫多個(gè)線圈寄存器。對(duì)應(yīng)上面的功能碼也就是:0x01 0x05 0x0f
離散輸入寄存器:如果線圈寄存器理解了這個(gè)自然也明白了。離散輸入寄存器就相當(dāng)于線圈寄存器的只讀模式,他也是每個(gè)bit表示一個(gè)開(kāi)關(guān)量,而他的開(kāi)關(guān)量只能讀取輸入的開(kāi)關(guān)信號(hào),是不能夠?qū)懙?。比如我讀取外部按鍵的按下還是松開(kāi)。所以功能碼也簡(jiǎn)單就一個(gè)讀的 0x02
保持寄存器:這個(gè)寄存器的單位不再是bit而是兩個(gè)byte,也就是可以存放具體的數(shù)據(jù)量的,并且是可讀寫的。一般對(duì)應(yīng)參數(shù)設(shè)置,比如我我設(shè)置時(shí)間年月日,不但可以寫也可以讀出來(lái)現(xiàn)在的時(shí)間。寫也分為單個(gè)寫和多個(gè)寫,所以功能碼有對(duì)應(yīng)的三個(gè):0x03 0x06 0x10
輸入寄存器:這個(gè)和保持寄存器類似,但是也是只支持讀而不能寫,一般是讀取各種實(shí)時(shí)數(shù)據(jù)。一個(gè)寄存器也是占據(jù)兩個(gè)byte的空間。類比我我通過(guò)讀取輸入寄存器獲取現(xiàn)在的AD采集值。對(duì)應(yīng)的功能碼也就一個(gè) 0x0
ModbusTCP數(shù)據(jù)幀: MBAP + PDU
MBAP(報(bào)文頭)
內(nèi)容 | 大小 | 描述 |
---|---|---|
00 00 | 2字節(jié) | 報(bào)文的序列號(hào),一般通信之后要加1來(lái)區(qū)分不同的通信數(shù)據(jù)報(bào)文。 |
00 00 | 2字節(jié) | 00 00標(biāo)識(shí)Modus TCP協(xié)議 |
00 06 | 2字節(jié) | 數(shù)據(jù)的長(zhǎng)度,單位字節(jié) |
01 | 1字節(jié) | 設(shè)備地址 |
PDU(數(shù)據(jù)體)
功能碼(1字節(jié))+數(shù)據(jù)(不確定)
例如
? 0x03 : 讀保持寄存器
? 請(qǐng)求:MBAP(7字節(jié)) 功能碼(1字節(jié)) 起始地址(2字節(jié)) 寄存器數(shù)量(2字節(jié))
? 響應(yīng):MBAP(7字節(jié)) 功能碼(1字節(jié)) 數(shù)據(jù)長(zhǎng)度(1字節(jié)) 數(shù)據(jù)n個(gè)(2n字節(jié))
? 請(qǐng)求
? 00 01 00 00 00 06 01 03 00 00 00 03
? 00 01 :代表該連接的第1個(gè)請(qǐng)求
? 00 00 :代表Modbus TCP協(xié)議
? 00 06 :后面數(shù)據(jù)長(zhǎng)度,單位字節(jié)
? 01 :設(shè)備地址
? 03 :功能碼
? 00 00 :起始地址
? 00 03 :寄存器數(shù)量
? 響應(yīng)
? 00 01 00 00 00 09 01 03 06 00 21 00 00 00 00
? 06 :后面數(shù)據(jù)長(zhǎng)度
? 00 21 00 00 00 00:標(biāo)識(shí)三個(gè)寄存器中的值(00 21代表第一個(gè),00 00 第二 , 00 00 第三個(gè) )
功能碼
0x01: 讀線圈寄存器
0x02: 讀離散輸入寄存器
0x03: 讀保持寄存器
0x04: 讀輸入寄存器
0x05: 寫單個(gè)線圈寄存器
0x06: 寫單個(gè)保持寄存器
0x0f: 寫多個(gè)線圈寄存器
0x10: 寫多個(gè)保持寄存器
3.虛擬機(jī)
下載地址 : Modbus Slave Sim V3.2 x 64
1.進(jìn)入頁(yè)面
2.連接
3.配置選擇不同的寄存器/線圈
4.Java實(shí)現(xiàn)
關(guān)于Modbus相關(guān)的jar包
- Jamod:Java Modbus實(shí)現(xiàn):Java Modbus庫(kù)。該庫(kù)由Dieter Wimberger實(shí)施。
- ModbusPal:ModbusPal是一個(gè)正在進(jìn)行的Java項(xiàng)目,用于創(chuàng)建逼真的Modbus從站模擬器。由于預(yù)定義的數(shù)學(xué)函數(shù)和/或Python腳本,寄存器值是動(dòng)態(tài)生成的。ModbusPal依賴于RxTx進(jìn)行串行通信,而Jython則依賴于腳本支持
- Modbus4J:Serotonin Software用Java編寫的Modbus協(xié)議的高性能且易于使用的實(shí)現(xiàn)。支持ASCII,RTU,TCP和UDP傳輸作為從站或主站,自動(dòng)請(qǐng)求分區(qū),響應(yīng)數(shù)據(jù)類型解析和節(jié)點(diǎn)掃描
- JLibModbus:JLibModbus是java語(yǔ)言中Modbus協(xié)議的一種實(shí)現(xiàn)。jSSC和RXTX用于通過(guò)串行端口進(jìn)行通信。該庫(kù)是一個(gè)經(jīng)過(guò)積極測(cè)試和改進(jìn)的項(xiàng)目。
代碼使用 Modbus4J 來(lái)演示
引入Modbus4Jjar包
<!-- Modbus -->
<dependencies>
<dependency>
<groupId>com.infiniteautomation</groupId>
<artifactId>modbus4j</artifactId>
<version>3.0.4</version>
</dependency>
</dependencies>
<!-- 若想引用modbus4j需要引入下列repository id:ias-snapshots id:ias-releases 兩個(gè) ,使用默認(rèn)倉(cāng)庫(kù)下載,不要使用阿里云倉(cāng)庫(kù)-->
<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>
代碼實(shí)現(xiàn)讀取和寫入
import com.serotonin.modbus4j.ModbusFactory;
import com.serotonin.modbus4j.ModbusMaster;
import com.serotonin.modbus4j.code.DataType;
import com.serotonin.modbus4j.exception.ErrorResponseException;
import com.serotonin.modbus4j.exception.ModbusInitException;
import com.serotonin.modbus4j.exception.ModbusTransportException;
import com.serotonin.modbus4j.ip.IpParameters;
import com.serotonin.modbus4j.locator.BaseLocator;
import com.serotonin.modbus4j.msg.*;
/**
* Modbus 工具類
*/
public class ModbusUtils {
/**
* 工廠
*/
static ModbusFactory modbusFactory;
static ModbusMaster modbusMaster;
static {
if(modbusFactory == null){
modbusFactory = new ModbusFactory();
}
}
/**
* 獲取master
* @return
*/
public static ModbusMaster getMaster() throws ModbusInitException {
if(modbusMaster == null){
IpParameters ipParameters = new IpParameters();
ipParameters.setHost("127.0.0.1");
ipParameters.setPort(502);
modbusMaster = modbusFactory.createTcpMaster(ipParameters, true);
modbusMaster.init();
return modbusMaster;
}
return modbusMaster;
}
/**
* 讀取線圈開(kāi)關(guān)狀態(tài)數(shù)據(jù) 0x01
* @param slaveId
* @param offset
* @return
* @throws ModbusInitException
* @throws ModbusTransportException
* @throws ErrorResponseException
*/
public static Boolean readCoilStatus(int slaveId,int offset) throws ModbusInitException, ModbusTransportException, ErrorResponseException {
BaseLocator<Boolean> coilStatus = BaseLocator.coilStatus(slaveId, offset);
Boolean res = getMaster().getValue(coilStatus);
return res;
}
/**
* 讀離散輸入寄存器狀態(tài)數(shù)據(jù) 0x02
* @param slaveId
* @param offset
* @return
* @throws ModbusInitException
* @throws ModbusTransportException
* @throws ErrorResponseException
*/
public static Boolean inputStatus(int slaveId,int offset) throws ModbusInitException, ModbusTransportException, ErrorResponseException {
BaseLocator<Boolean> inputStatus = BaseLocator.inputStatus(slaveId, offset);
Boolean res = getMaster().getValue(inputStatus);
return res;
}
/**
* 讀保持寄存器數(shù)據(jù) 0x03
* @param slaveId
* @param offset
* @param dataType
* @return
* @throws ModbusInitException
* @throws ModbusTransportException
* @throws ErrorResponseException
*/
public static Number holdingRegister(int slaveId, int offset, int dataType) throws ModbusInitException, ModbusTransportException, ErrorResponseException {
BaseLocator<Number> holdingRegister = BaseLocator.holdingRegister(slaveId, offset, dataType);
Number value = getMaster().getValue(holdingRegister);
return value;
}
/**
* 讀輸入寄存器數(shù)據(jù) 0x04
* @param slaveId
* @param offset
* @param dataType
* @return
* @throws ModbusInitException
* @throws ModbusTransportException
* @throws ErrorResponseException
*/
public static Number inputRegister(int slaveId, int offset, int dataType) throws ModbusInitException, ModbusTransportException, ErrorResponseException {
BaseLocator<Number> inputRegister = BaseLocator.inputRegister(slaveId, offset, dataType);
Number value = getMaster().getValue(inputRegister);
return value;
}
/**
* 寫線圈開(kāi)關(guān)狀態(tài)數(shù)據(jù) 0x05
* @param slaveId
* @param offset
* @param status
* @return
* @throws ModbusTransportException
* @throws ModbusInitException
*/
public static Boolean writeCoilStatus(int slaveId,int offset,boolean status) throws ModbusTransportException, ModbusInitException {
boolean coilValue = status;
WriteCoilRequest coilRequest = new WriteCoilRequest(slaveId, offset, coilValue);
WriteCoilResponse coilResponse = (WriteCoilResponse) getMaster().send(coilRequest);
return !coilResponse.isException();
}
/**
* 寫單個(gè)保持寄存器數(shù)據(jù) 0x06
* @param slaveId
* @param offset
* @param vlaue
* @return
* @throws ModbusTransportException
* @throws ModbusInitException
*/
public static Boolean writeRegister(int slaveId,int offset,int vlaue) throws ModbusTransportException, ModbusInitException {
WriteRegisterRequest registerRequest = new WriteRegisterRequest(slaveId,offset,vlaue);
WriteRegisterResponse registerResponse = (WriteRegisterResponse) getMaster().send(registerRequest);
return !registerResponse.isException();
}
/**
* 寫線圈開(kāi)關(guān)狀態(tài)數(shù)據(jù)【多】 0x0f
* @param slaveId
* @param offset
* @param booleans
* @return
* @throws ModbusTransportException
* @throws ModbusInitException
*/
public static Boolean writeCoils(int slaveId,int offset,boolean[] booleans) throws ModbusTransportException, ModbusInitException {
WriteCoilsRequest writeCoils = new WriteCoilsRequest(slaveId, offset, booleans);
WriteCoilsResponse coilsResponse = (WriteCoilsResponse) getMaster().send(writeCoils);
return !coilsResponse.isException();
}
/**
* 寫保存寄存器數(shù)據(jù)【多】 0x10
* @param slaveId
* @param offset
* @param nums
* @return
* @throws ModbusTransportException
* @throws ModbusInitException
*/
public static Boolean writeRegisters(int slaveId,int offset,short[] nums) throws ModbusTransportException, ModbusInitException {
WriteRegistersRequest writeRegisters = new WriteRegistersRequest(slaveId, offset, nums);
WriteRegistersResponse registersResponse = (WriteRegistersResponse) getMaster().send(writeRegisters);
return !registersResponse.isException();
}
public static void main(String[] args) throws ModbusInitException, ModbusTransportException, ErrorResponseException {
// 01測(cè)試
// Boolean v0001 = readCoilStatus(1, 0);
// Boolean v0002 = readCoilStatus(1, 1);
// Boolean v0008 = readCoilStatus(1, 7);
// System.out.println("get v0001 :" + v0001);
// System.out.println("get v0002 :" + v0002);
// System.out.println("get v0008 :" + v0008);
// 03測(cè)試
// Number v0001 = holdingRegister(136, 3, DataType.TWO_BYTE_INT_SIGNED);
// Number v0003 = holdingRegister(1, 2, DataType.TWO_BYTE_INT_SIGNED);
// Number v0009 = holdingRegister(1, 8, DataType.TWO_BYTE_INT_SIGNED);
// System.out.println("get v0001 result:" + v0001);
// System.out.println("get v0003 result:" + v0003);
// System.out.println("get v0009 result:" + v0009);
// 04測(cè)試
// Number v0001 = inputRegister(136, 0, DataType.TWO_BYTE_INT_SIGNED);
// Number v0003 = inputRegister(136, 2, DataType.TWO_BYTE_INT_SIGNED);
// Number v0009 = inputRegister(136, 8, DataType.TWO_BYTE_INT_SIGNED);
// System.out.println("get v0001 result:" + v0001);
// System.out.println("get v0003 result:" + v0003);
// System.out.println("get v0009 result:" + v0009);
// 05測(cè)試
// Boolean v0001 = writeCoilStatus(1, 0, true);
// Boolean v0002 = writeCoilStatus(1, 1, false);
// Boolean v0007 = writeCoilStatus(1, 6, true);
// System.out.println("update v0001 status result:" + v0001);
// System.out.println("update v0002 status result:" + v0002);
// System.out.println("update v0007 status result:" + v0007);
// 06測(cè)試
// Boolean v0001 = writeRegister(136, 0, 98);
// Boolean v0002 = writeRegister(136, 1, 0);
// Boolean v0007 = writeRegister(136, 6, 100);
// System.out.println("update v0001 status result:" + v0001);
// System.out.println("update v0002 status result:" + v0002);
// System.out.println("update v0007 status result:" + v0007);
// 0f測(cè)試
//Boolean res1 = writeCoils(1, 1, new boolean[]{true, true, false, true});
// 10測(cè)試
//Boolean res2 = writeRegisters(136, 7, new short[]{1, 2, 3});
//Boolean res3 = writeRegisters(136, 7, new short[]{991, 778, 25, 0});
}
}
測(cè)試結(jié)果
? 01測(cè)試 (讀取線圈寄存器狀態(tài))
[main] DEBUG com.serotonin.modbus4j.ip.tcp.TcpMaster - Encap Request: 00 01 00 00 00 06 01 01 00 00 00 01
[main] DEBUG com.serotonin.modbus4j.ip.tcp.TcpMaster - Sending on port: 502
[main] DEBUG com.serotonin.modbus4j.ip.tcp.TcpMaster - Response: 00 01 00 00 00 04 01 01 01 01
[main] DEBUG com.serotonin.modbus4j.ip.tcp.TcpMaster - Encap Request: 00 02 00 00 00 06 01 01 00 01 00 01
[main] DEBUG com.serotonin.modbus4j.ip.tcp.TcpMaster - Sending on port: 502
[main] DEBUG com.serotonin.modbus4j.ip.tcp.TcpMaster - Response: 00 02 00 00 00 04 01 01 01 00
[main] DEBUG com.serotonin.modbus4j.ip.tcp.TcpMaster - Encap Request: 00 03 00 00 00 06 01 01 00 07 00 01
[main] DEBUG com.serotonin.modbus4j.ip.tcp.TcpMaster - Sending on port: 502
[main] DEBUG com.serotonin.modbus4j.ip.tcp.TcpMaster - Response: 00 03 00 00 00 04 01 01 01 01
get v0001 :true
get v0002 :false
get v0008 :true
讀取對(duì)應(yīng)的寄存器/線圈 系統(tǒng)中能獲取到模擬器中的數(shù)據(jù)。文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-541191.html
寫入對(duì)應(yīng)的寄存器/線圈 模擬器中的數(shù)據(jù)也會(huì)發(fā)生變化。文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-541191.html
到了這里,關(guān)于Java 整合 Modbus TCP的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!