Modbus协议
1.概述
概念
Modbus是一种串行通信协议,是Modicon公司(现在的施耐德电气 Schneider Electric)于1979年为使用可编程逻辑控制器(PLC)通信而发表。Modbus已经成为工业领域通信协议的业界标准(De facto),并且现在是工业电子设备之间常用的连接方式。
优势
- Modbus协议标准开放、公开发表且无版权要求
- Modbus协议支持多种电气接口,包括RS232、RS485、TCP/IP等,还可以在各种介质上传输,如双绞线、光纤、红外、无线等
- Modbus协议消息帧格式简单、紧凑、通俗易懂。用户理解和使用简单,厂商容易开发和集成,方便形成工业控制网络
通讯方式
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,这是目前在仪表与自动化行业中唯一分配到的端口号。
2.组成
物理模型
线圈寄存器:实际上就可以类比为开关量(继电器状态),每一个bit对应一个信号的开关状态。所以一个byte就可以同时控制8路的信号。比如控制外部8路io的高低。 线圈寄存器支持读也支持写,写在功能码里面又分为写单个线圈寄存器和写多个线圈寄存器。对应上面的功能码也就是:0x01 0x05 0x0f
离散输入寄存器:如果线圈寄存器理解了这个自然也明白了。离散输入寄存器就相当于线圈寄存器的只读模式,他也是每个bit表示一个开关量,而他的开关量只能读取输入的开关信号,是不能够写的。比如我读取外部按键的按下还是松开。所以功能码也简单就一个读的 0x02
保持寄存器:这个寄存器的单位不再是bit而是两个byte,也就是可以存放具体的数据量的,并且是可读写的。一般对应参数设置,比如我我设置时间年月日,不但可以写也可以读出来现在的时间。写也分为单个写和多个写,所以功能码有对应的三个:0x03 0x06 0x10
输入寄存器:这个和保持寄存器类似,但是也是只支持读而不能写,一般是读取各种实时数据。一个寄存器也是占据两个byte的空间。类比我我通过读取输入寄存器获取现在的AD采集值。对应的功能码也就一个 0x0
ModbusTCP数据帧: MBAP + PDU
MBAP(报文头)
内容大小描述00 002字节报文的序列号,一般通信之后要加1来区分不同的通信数据报文。00 002字节00 00标识Modus TCP协议00 062字节数据的长度,单位字节011字节设备地址
PDU(数据体)
功能码(1字节)+数据(不确定)
例如
0x03 : 读保持寄存器
请求:MBAP(7字节) 功能码(1字节) 起始地址(2字节) 寄存器数量(2字节)
响应:MBAP(7字节) 功能码(1字节) 数据长度(1字节) 数据n个(2n字节)
请求
00 01 00 00 00 06010300 00 00 03
00 01 :代表该连接的第1个请求
00 00 :代表Modbus TCP协议
00 06 :后面数据长度,单位字节
01 :设备地址
03 :功能码
00 00 :起始地址
00 03 :寄存器数量
响应
00 01 00 00 00 090103 06 00 21 00 00 00 00
06 :后面数据长度
00 21 00 00 00 00:标识三个寄存器中的值(00 21代表第一个,00 00 第二 , 00 00 第三个 )
功能码
0x01: 读线圈寄存器
0x02: 读离散输入寄存器
0x03: 读保持寄存器
0x04: 读输入寄存器
0x05: 写单个线圈寄存器
0x06: 写单个保持寄存器
0x0f: 写多个线圈寄存器
0x10: 写多个保持寄存器
3.虚拟机
下载地址 : Modbus Slave Sim V3.2 x 64
1.进入页面
2.连接
3.配置选择不同的寄存器/线圈
4.Java实现
关于Modbus相关的jar包
- 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用于通过串行端口进行通信。该库是一个经过积极测试和改进的项目。
代码使用 Modbus4J 来演示
引入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 两个 ,使用默认仓库下载,不要使用阿里云仓库--><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>
代码实现读取和写入
importcom.serotonin.modbus4j.ModbusFactory;importcom.serotonin.modbus4j.ModbusMaster;importcom.serotonin.modbus4j.code.DataType;importcom.serotonin.modbus4j.exception.ErrorResponseException;importcom.serotonin.modbus4j.exception.ModbusInitException;importcom.serotonin.modbus4j.exception.ModbusTransportException;importcom.serotonin.modbus4j.ip.IpParameters;importcom.serotonin.modbus4j.locator.BaseLocator;importcom.serotonin.modbus4j.msg.*;/**
* Modbus 工具类
*/publicclassModbusUtils{/**
* 工厂
*/staticModbusFactory modbusFactory;staticModbusMaster modbusMaster;static{if(modbusFactory ==null){
modbusFactory =newModbusFactory();}}/**
* 获取master
* @return
*/publicstaticModbusMastergetMaster()throwsModbusInitException{if(modbusMaster ==null){IpParameters ipParameters =newIpParameters();
ipParameters.setHost("127.0.0.1");
ipParameters.setPort(502);
modbusMaster = modbusFactory.createTcpMaster(ipParameters,true);
modbusMaster.init();return modbusMaster;}return modbusMaster;}/**
* 读取线圈开关状态数据 0x01
* @param slaveId
* @param offset
* @return
* @throws ModbusInitException
* @throws ModbusTransportException
* @throws ErrorResponseException
*/publicstaticBooleanreadCoilStatus(int slaveId,int offset)throwsModbusInitException,ModbusTransportException,ErrorResponseException{BaseLocator<Boolean> coilStatus =BaseLocator.coilStatus(slaveId, offset);Boolean res =getMaster().getValue(coilStatus);return res;}/**
* 读离散输入寄存器状态数据 0x02
* @param slaveId
* @param offset
* @return
* @throws ModbusInitException
* @throws ModbusTransportException
* @throws ErrorResponseException
*/publicstaticBooleaninputStatus(int slaveId,int offset)throwsModbusInitException,ModbusTransportException,ErrorResponseException{BaseLocator<Boolean> inputStatus =BaseLocator.inputStatus(slaveId, offset);Boolean res =getMaster().getValue(inputStatus);return res;}/**
* 读保持寄存器数据 0x03
* @param slaveId
* @param offset
* @param dataType
* @return
* @throws ModbusInitException
* @throws ModbusTransportException
* @throws ErrorResponseException
*/publicstaticNumberholdingRegister(int slaveId,int offset,int dataType)throwsModbusInitException,ModbusTransportException,ErrorResponseException{BaseLocator<Number> holdingRegister =BaseLocator.holdingRegister(slaveId, offset, dataType);Number value =getMaster().getValue(holdingRegister);return value;}/**
* 读输入寄存器数据 0x04
* @param slaveId
* @param offset
* @param dataType
* @return
* @throws ModbusInitException
* @throws ModbusTransportException
* @throws ErrorResponseException
*/publicstaticNumberinputRegister(int slaveId,int offset,int dataType)throwsModbusInitException,ModbusTransportException,ErrorResponseException{BaseLocator<Number> inputRegister =BaseLocator.inputRegister(slaveId, offset, dataType);Number value =getMaster().getValue(inputRegister);return value;}/**
* 写线圈开关状态数据 0x05
* @param slaveId
* @param offset
* @param status
* @return
* @throws ModbusTransportException
* @throws ModbusInitException
*/publicstaticBooleanwriteCoilStatus(int slaveId,int offset,boolean status)throwsModbusTransportException,ModbusInitException{boolean coilValue = status;WriteCoilRequest coilRequest =newWriteCoilRequest(slaveId, offset, coilValue);WriteCoilResponse coilResponse =(WriteCoilResponse)getMaster().send(coilRequest);return!coilResponse.isException();}/**
* 写单个保持寄存器数据 0x06
* @param slaveId
* @param offset
* @param vlaue
* @return
* @throws ModbusTransportException
* @throws ModbusInitException
*/publicstaticBooleanwriteRegister(int slaveId,int offset,int vlaue)throwsModbusTransportException,ModbusInitException{WriteRegisterRequest registerRequest =newWriteRegisterRequest(slaveId,offset,vlaue);WriteRegisterResponse registerResponse =(WriteRegisterResponse)getMaster().send(registerRequest);return!registerResponse.isException();}/**
* 写线圈开关状态数据【多】 0x0f
* @param slaveId
* @param offset
* @param booleans
* @return
* @throws ModbusTransportException
* @throws ModbusInitException
*/publicstaticBooleanwriteCoils(int slaveId,int offset,boolean[] booleans)throwsModbusTransportException,ModbusInitException{WriteCoilsRequest writeCoils =newWriteCoilsRequest(slaveId, offset, booleans);WriteCoilsResponse coilsResponse =(WriteCoilsResponse)getMaster().send(writeCoils);return!coilsResponse.isException();}/**
* 写保存寄存器数据【多】 0x10
* @param slaveId
* @param offset
* @param nums
* @return
* @throws ModbusTransportException
* @throws ModbusInitException
*/publicstaticBooleanwriteRegisters(int slaveId,int offset,short[] nums)throwsModbusTransportException,ModbusInitException{WriteRegistersRequest writeRegisters =newWriteRegistersRequest(slaveId, offset, nums);WriteRegistersResponse registersResponse =(WriteRegistersResponse)getMaster().send(writeRegisters);return!registersResponse.isException();}publicstaticvoidmain(String[] args)throwsModbusInitException,ModbusTransportException,ErrorResponseException{// 01测试// 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测试// 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测试// 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测试// 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测试// 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测试//Boolean res1 = writeCoils(1, 1, new boolean[]{true, true, false, true});// 10测试//Boolean res2 = writeRegisters(136, 7, new short[]{1, 2, 3});//Boolean res3 = writeRegisters(136, 7, new short[]{991, 778, 25, 0});}}
测试结果
01测试 (读取线圈寄存器状态)
[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
读取对应的寄存器/线圈 系统中能获取到模拟器中的数据。
写入对应的寄存器/线圈 模拟器中的数据也会发生变化。
版权归原作者 中断请求 所有, 如有侵权,请联系我们删除。