栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

Modbus TCP通讯协议

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

Modbus TCP通讯协议

一、简介

Modbus由MODICON公司于1979年开发,是一种工业现场总线协议标准。1996年施耐德公司推出基于以太网TCP/IP的Modbus协议:ModbusTCP。

Modbus协议是一项应用层报文传输协议,包括ASCII、RTU、TCP三种报文类型。

标准的Modbus协议物理层接口有RS232、RS422、RS485和以太网接口,采用master/slave方式通信。

二、ModbusTCP数据帧

ModbusTCP的数据帧可分为两部分:MBAP+PDU。

2.1 报文头MBAP

MBAP为报文头,长度为7字节:

事务处理标识

协议标识

长度单元标识符
2字节2字节2字节1字节
内容解释
事务处理标识可以理解为报文的序列号,一般每次通信之后就要加1以区别不同的通信数据报文。
协议标识符00 00表示ModbusTCP协议。
长度表示接下来的数据长度,单位为字节。
单元标识符可以理解为设备地址。

2.2 帧结构PDU

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

2.2.1 功能码

Modbus的操作对象有四种:线圈、离散输入、保持寄存器、输入寄存器。

对象含义
线圈PLC的输出位,开关量,在Modbus中可读可写
离散量PLC的输入位,开关量,在Modbus中只读
输入寄存器PLC中只能从模拟量输入端改变的寄存器,在Modbus中只读
保持寄存器PLC中用于输出模拟量信号的寄存器,在Modbus中可读可写

根据对象的不同,Modbus的功能码有:

功能码含义
0x01读线圈
0x05写单个线圈
0x0F写多个线圈
0x02读离散量输入
0x04读输入寄存器
0x03读保持寄存器
0x06写单个保持寄存器
0x10写多个保持寄存器

三、ModbusTCP通信

3.1 通信方式

Modbus设备可分为主站(poll)和从站(slave)。主站只有一个,从站有多个,主站向各从站发送请求帧,从站给予响应。在使用TCP通信时,主站为client端,主动建立连接;从站为server端,等待连接。

  • 主站请求:功能码+数据
  • 从站正常响应:请求功能码+响应数据
  • 从站异常响应:异常功能码+异常码,其中异常功能码即将请求功能码的最高有效位置1,异常码指示差错类型
  • 注意:需要超时管理机制,避免无期限的等待可能不出现的应答

IANA(Internet Assigned Numbers Authority,互联网编号分配管理机构)给Modbus协议赋予TCP端口号为502,这是目前在仪表与自动化行业中唯一分配到的端口号。

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

3.3 仿真软件
  • 我是用的仿真软件是Modbus slave,可以实现Modbus RTU、TCP、串口仿真等。
  • Modbus slave 作为服务器端处理请求,客户端用java代替。
  • 使用软件时,需要指定功能码,在Modbus slave的Setup->slave definition中指定。

– slave ID:从站编号(事务标识符)
– function:功能码,0x01对应线圈操作,0x02对应离散量操作,0x03对应保持寄存器操作,0x04对应输入寄存器操作
– address:开始地址
– quantity:寄存器/线圈/离散量 的数量

参考:

https://www.cnblogs.com/ioufev/p/10831289.html  Java实现modbustcp通信

https://blog.csdn.net/zwxuse251/article/details/24154951 数据报文结构详细

https://blog.csdn.net/lakerszhy/article/details/68927178?locationNum=4&fps=1  Modbus功能码

3.4 概念

原文链接:https://wenku.baidu.com/view/55595d0690c69ec3d5bb75ed.html

1.开关量:

一般指的是触点的“开”与“关”的状态,一般在计算机设备中也会用“0”或“1”来表示开关量的状态。开关量分为有源开关量信号和无源开关量信号,有源开关量信号指的是“开”与“关”的状态是带电源的信号,专业叫法为跃阶信号,可以理解为脉冲量,一般的都有220VAC, 110VAC,24VDC,12VDC等信号,无源开关量信号指的是“开”和“关”的状态时不带电源的信号,一般又称之为干接点。电阻测试法为电阻0或无穷大。

2.数字量:

很多人会将数字量与开关量混淆,也将其与模拟量混淆。数字量在时间和数量上都是离散的物理量,其表示的信号则为数字信号。数字量是由0和1组成的信号,经过编码形成有规律的信号,量化后的模拟量就是数字量。

3.模拟量:

模拟量的概念与数字量相对应,但是经过量化之后又可以转化为数字量。模拟量是在时间和数量上都是连续的物理量,其表示的信号则为模拟信号。模拟量在连续的变化过程中任何一个取值都是一个具体有意义的物理量,如温度,电压,电流等。

4.离散量:

离散量是将模拟量离散化之后得到的物理量。即任何仪器设备对于模拟量都不可能有个完全精确的表示,因为他们都有一个采样周期,在该采样周期内,其物理量的数值都是不变的,而实际上的模拟量则是变化的。这样就将模拟量离散化,成为了离散量。

5.脉冲量:

脉冲量就是瞬间电压或电流由某一值跃变到另一值的信号量。在量化后,其变化持续有规律就是数字量,如果其由0变成某一固定值并保持不变,其就是开关量。

四、仿真软件的使用

多处参考取自:https://www.cnblogs.com/ioufev/p/10831289.html

验证4个常用功能码,仿真软件上面有F=01,F=02,F=03、F=04来显示

  • 0x01:读线圈
  • 0x02:读离散量输入
  • 0x03:读保持寄存器
  • 0x04:读输入寄存器

代码参数的理解

  • saveid:看资料"从站在modbus总线上可以有多个",仿真软件就能模拟一个从站,就是ID=1,当然可以修改成ID=2
  • 功能码:4个功能码,对应写4个方法,,仿真软件上的F=1,或者F=2,3,4
  • addr:一开始看代码4个方法addr都是从0开始,是否重复?答案是:4个功能码表示4个区域或者设备,addr表示各自区域的地址编号。

仿真软件激活后选择Connection --> Connection Setup 选择TCP模式,端口是固定的502

4.1 地址类型

 操作:新建四个不同功能码的窗口,然后运行代码,修改仿真软件上的值。

4.2 数据类型 

功能码01:false/true  0/1

 功能码02:false/true  0/1

功能码03:多种类型

signed:有符号
unsigned:无符号
hex:十六进制
binary:二进制

big-endian:大端,将高序字节存储在起始地址(高位编址)
little-endian:小端,将低序字节存储在起始地址(低位编址)

swap:交换

 注意:双击第一个地址输入数据,会提示输入数据的类型,32位数据占2个地址,所以下一个地址是--

功能码04:多种类型 

注意: 

数据类型 WORD:无符号双字节整型(字,16位)     取值:0~65535(非负)

五、案例 5.1 引入依赖

Modbus4j开源库:Serotonin Software用Java编写的Modbus协议的高性能且易于使用的实现。支持ASCII,RTU,TCP和UDP传输作为从站或主站,自动请求分区,响应数据类型解析和节点扫描。

Modbus的github地址

公共 Maven 存储库现已可用,最新构建将此添加到您的 pom 中.xml


    
        
            false
        
        
            true
        
        ias-snapshots
        Infinite Automation Snapshot Repository
        https://maven.mangoautomation.net/repository/ias-snapshot/
    
    
        
            true
        
        
            false
        
        ias-releases
        Infinite Automation Release Repository
        https://maven.mangoautomation.net/repository/ias-release/
    

    com.infiniteautomation
    modbus4j
    3.0.3

5.2 编写Modbus4j工具类

Modbus4jUtils.java

package com.ioufev;

import com.serotonin.modbus4j.BatchRead;
import com.serotonin.modbus4j.BatchResults;
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;


public class Modbus4jUtils {
    
    static ModbusFactory modbusFactory;
    static {
        if (modbusFactory == null) {
            modbusFactory = new ModbusFactory();
        }
    }

    
    public static ModbusMaster getMaster() throws ModbusInitException {
        IpParameters params = new IpParameters();
        params.setHost("127.0.0.1");
        params.setPort(502);
        //
        // modbusFactory.createRtuMaster(wapper); //RTU 协议
        // modbusFactory.createUdpMaster(params);//UDP 协议
        // modbusFactory.createAsciiMaster(wrapper);//ASCII 协议
        ModbusMaster master = modbusFactory.createTcpMaster(params, false);// TCP 协议
        master.init();

        return master;
    }

    
    public static Boolean readCoilStatus(int slaveId, int offset)
            throws ModbusTransportException, ErrorResponseException, ModbusInitException {
        // 01 Coil Status
        baseLocator loc = baseLocator.coilStatus(slaveId, offset);
        Boolean value = getMaster().getValue(loc);
        return value;
    }

    
    public static Boolean readInputStatus(int slaveId, int offset)
            throws ModbusTransportException, ErrorResponseException, ModbusInitException {
        // 02 Input Status
        baseLocator loc = baseLocator.inputStatus(slaveId, offset);
        Boolean value = getMaster().getValue(loc);
        return value;
    }

    
    public static Number readHoldingRegister(int slaveId, int offset, int dataType)
            throws ModbusTransportException, ErrorResponseException, ModbusInitException {
        // 03 Holding Register类型数据读取
        baseLocator loc = baseLocator.holdingRegister(slaveId, offset, dataType);
        Number value = getMaster().getValue(loc);
        return value;
    }

    
    public static Number readInputRegisters(int slaveId, int offset, int dataType)
            throws ModbusTransportException, ErrorResponseException, ModbusInitException {
        // 04 Input Registers类型数据读取
        baseLocator loc = baseLocator.inputRegister(slaveId, offset, dataType);
        Number value = getMaster().getValue(loc);
        return value;
    }
}

5.3 测试 

测试代码:

public static void main(String[] args) {
        try {
//            // 01测试
            Boolean v011 = readCoilStatus(1, 0);
            Boolean v012 = readCoilStatus(1, 1);
            Boolean v013 = readCoilStatus(1, 6);
            System.out.println("v011:" + v011);
            System.out.println("v012:" + v012);
            System.out.println("v013:" + v013);
            // 02测试
            Boolean v021 = readInputStatus(1, 0);
            Boolean v022 = readInputStatus(1, 1);
            Boolean v023 = readInputStatus(1, 2);
            System.out.println("v021:" + v021);
            System.out.println("v022:" + v022);
            System.out.println("v023:" + v023);
//
            // 03测试
            Number v031 = readHoldingRegister(1, 0, DataType.TWO_BYTE_INT_SIGNED);// 注意,float
            Number v032 = readHoldingRegister(1, 1, DataType.TWO_BYTE_INT_SIGNED);// 同上
            System.out.println("v031:" + v031);
            System.out.println("v032:" + v032);

            // 04测试
            Number v041 = readInputRegisters(1, 0, DataType.FOUR_BYTE_FLOAT);//
            Number v042 = readInputRegisters(1, 2, DataType.FOUR_BYTE_FLOAT);//
            System.out.println("v041:" + v041);
            System.out.println("v042:" + v042);

        } catch (Exception e) {
            e.printStackTrace();
        }
}

5.4 代码解释

5.5 仿真软件Modbus slave信息

 5.6 运行结果

补充:

 举例:

功能码监测项规约地址数据类型
02直流输入过高停机1080HBOOL
功能码描述PLC地址寄存器地址位/字操作操作数量
01H读线圈寄存器00001-099990000H-FFFFH位操作单个或多个
02H读离散输入寄存器10001-199990000H-FFFFH位操作单个或多个
03H读保持寄存器40001-499990000H-FFFFH字操作单个或多个
04H读输入寄存器30001-399990000H-FFFFH字操作单个或多个
05H写单个线圈寄存器00001-099990000H-FFFFH位操作单个
06H写单个保持寄存器40001-499990000H-FFFFH字操作单个
0FH写多个线圈寄存器00001-099990000H-FFFFH位操作多个
10H写多个保持寄存器40001-499990000H-FFFFH字操作多个

直流输入过高停机监测项的规约地址为1080H

                转10进制:1080H  ————>   4224

                02功能码的PLC起始地址为:10001 ~ 19999

                读离散输入寄存器值的地址为:PLC起始地址+规约地址  10001 + 4224 = 14225

代码读离散输入寄存器的值:

public static void main(String[] args) {
        try {
            // 读离散输入寄存器
            //参数1:slaveID  参数2:起始地址
            Boolean v021 = readInputStatus(1, 14225);
     
            System.out.println("v021:" + v021);
        } catch (Exception e) {
            e.printStackTrace();
        }
}

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/572931.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号