zoukankan      html  css  js  c++  java
  • java android 读写三菱PLC 使用TCP/IP 协议

    本文将使用一个Github开源的组件库技术来读写三菱PLC和西门子plc数据,使用的是基于以太网的TCP/IP实现,不需要额外的组件,读取操作只要放到后台线程就不会卡死线程,本组件支持超级方便的高性能读写操作

    本项目目前支持C#语言和java语言,C#语言的功能比较齐全,java版本的库还在开发及完善中。

    C# 版本nuget地址:https://www.nuget.org/packages/HslCommunication/       nuget     下载

    C# 版本github地址:https://github.com/dathlin/HslCommunication      fork      star                     如果喜欢可以star或是fork,还可以打赏支持。

    联系作者及加群方式:http://www.hslcommunication.cn/Cooperation

    Java版本的Demo代码 https://github.com/dathlin/HslCommunicationJavaDemo

    在Maven里搜索 HslCommunication 即可。

    com.github.dathlin:HslCommunication
    

      

    本文将展示如何配置网络参数及怎样使用代码来访问PLC数据,希望给有需要的人解决一些实际问题。主要对三菱Q系列PLC的X,Y,M,L,B,V,F,S,D,W,R区域的数据读写,对西门子PLC的M,Q,I,DB块的数据读写,亲测有效。

    此处使用了网线直接的方式,如果PLC接进了局域网,就可以进行远程读写了^_^

    此处使用到了2个命名空间:

    import HslCommunication.Core.Types.OperateResultExOne;
    import HslCommunication.Profinet.Melsec.MelsecMcNet;

    访问测试项目


    下载JAVA版本的Demo源代码,然后运行即可,当然你也可以使用C#版本的来测试。

    https://github.com/dathlin/HslCommunicationJavaDemo

    随便聊聊


    当我们一个上位机需要读取100台西门子PLC设备(此处只是举个例子,凡是都是使用Modbus tcp的都是一样的)的时候,你采用服务器主动去请求100台设备的机制对性能来说是个极大的考验,如果开100个线程去轮询100台设备,那么性能损失将是非常大的,更不用说再增加设备,如果搭建Modbus tcp服务器,就可以完美的解决性能问题,因为连接的压力将会平均分摊给每一台PLC,服务器端只要新增一个时间戳就可以知道客户端有没有连接上。

    我们在100台PLC里都增加发送Modbus tcp方法,将数据发送到服务器的ip和端口上去,服务器根据站号来区分设备。这样就可以搭建一个高性能总站。 本组件支持快速搭建一个高性能的Modbus tcp总站。

    http://www.cnblogs.com/dathlin/p/7782315.html

    关于两种模式


    在PLC端,包括三菱,西门子,欧姆龙以及Modbus Tcp客户端的访问器上,都支持两种模式,短连接模式和长连接模式,现在就来解释下什么原理。

    短连接:每次读写都是一个单独的请求,请求完毕也就关闭了,如果服务器的端口仅仅支持单连接,那么关闭后这个端口可以被其他连接复用,但是在频繁的网络请求下,容易发生异常,会有其他的请求不成功,尤其是多线程的情况下。

    长连接:创建一个公用的连接通道,所有的读写请求都利用这个通道来完成,这样的话,读写性能更快速,即时多线程调用也不会影响,内部有同步机制。如果服务器的端口仅仅支持单连接,那么这个端口就被占用了,比如三菱的端口机制,西门子的Modbus tcp端口机制也是这样的。以下代码默认使用长连接,性能更高,还支持多线程同步。

    在短连接的模式下,每次请求都是单独的访问,所以没有重连的困扰,在长连接的模式下,如果本次请求失败了,在下次请求的时候,会自动重新连接服务器,直到请求成功为止。另外,尽量所有的读写都对结果的成功进行判断。

    关于日志记录


    暂时不支持

    PLC的配置


    环境1:此处以GX Works3为示例,fx5u的配置如下:(感谢 山楂 提供的图片)

    环境2:此处以GX Works2为示例,测试PLC为L02CPU,内置了以太网协议


    环境3:此处以GX Works2为示例,添加以太网模块,型号为QJ71E71-100,组态里添加完成后进行以太网的参数配置,此处需要注意的是:参数的配置对接下来的代码中配置参数要一一对应




    注意:在PLC的以太网模块的配置中,无法设置网络号为0,也无法设置站号为0, 所以此处均设置为1,在C#程序中也使用上述的配置,在代码中均配置为0,如果您自定义设置为网络2, 站号8,那么在代码中就要写对应的数据。如果仍然通信失败,重新测试0,0。

    打开设置:在上图中的打开设置选项,进行其他参数的配置,下图只是举了一个例子,开通了4个端口来支持读写操作:



    端口号设置规则:

    • 为了不与原先存在的系统发生冲突,您在添加自己的端口时尽量使用您自己的端口。
    • 如果读写都需要,尽可能的将读取端口和写入端口区分开来,这样做比较高性能。
    • 如果您的网络状态不是特别稳定,读取端口使用2个,一个受阻切换另一个读取可以提升系统的稳定性。


    本文档仅作组件的测试,所以只用了一个端口作为读写。如果你的程序也使用了一个端口,那么你在读取数据时候, 刚好也在写入(异步操作可能发生这样的情况),那么写入会失败!)(在长连接模式下没有这个问题)

    三菱PLC的数据主要由两类数据组成,位数据和字数据,在位数据中,例如X,Y,M,L都是位数据,字数据例如D,W。 两类的数据在读取解码上存在一点小差别。(事实上也可以先将16个M先赋值给一个D,读取D数据再进行解析, 在读取M的数量比较多的时候,这样操作效率更高)

    初始化访问PLC对象

    如果想使用本组件的数据读取功能,必须先初始化数据访问对象,根据实际情况进行数据的填入。 下面仅仅是测试中的数据:

    MelsecMcNet melsec_net = new MelsecMcNet("192.168.1.192",6001);
    

      

    然后你可以指定一些参数,网络号,网络站号之类的,通常的情况都是不需要指定的

            melsec_net.setNetworkNumber((byte) 0x00);
            melsec_net.setNetworkStationNumber((byte) 0x00);
            melsec_net.setConnectTimeOut(1000);
    

      

      

    打开连接

    melsec_net.ConnectServer();

    如果想知道有没有连接上去

            OperateResult connectResult = melsec_net.ConnectServer();
            if(connectResult.IsSuccess){
                System.out.print("连接成功");
            }
            else {
                System.out.print("连接失败:"+connectResult.Message);
            }
    

      

      

    关于地址的表示方式

    使用字符串表示,这个组件里所有的读写操作提供字符串表示的重载方法,所有的支持访问的类型对应如下,字符串的表示方式存在十进制和十六进制的区别:

    • 输入继电器:"X100","X1A0"            // 字符串为十六进制机制
    • 输出继电器:"Y100" ,"Y1A0"           // 字符串为十六进制机制
    • 内部继电器:"M100","M200"           // 字符串为十进制
    • 锁存继电器:"L100"  ,"L200"           // 字符串为十进制
    • 报警器:       "F100", "F200"            // 字符串为十进制
    • 边沿继电器:"V100" , "V200"          // 字符串为十进制
    • 链接继电器:"B100" , "B1A0"          // 字符串为十六进制
    • 步进继电器:"S100" , "S200"          // 字符串为十进制
    • 数据寄存器:"D100", "D200"           // 字符串为十进制
    • 链接寄存器:"W100" ,"W1A0"         // 字符串为十六进制
    • 文件寄存器:"R100","R200"            // 字符串为十进制

    关于数据分类

    以上地址的数据是分为位数据和字数据的,位数据只能调用ReadBool,字数据用Read及其扩展的方法

    简单读写的示例

            boolean[] M100 = melsec_net.ReadBool("M100",(short) 1).Content;            // 读取M100是否通,十进制地址
            boolean[] X1A0 = melsec_net.ReadBool("X1A0",(short) 1).Content;            // 读取X1A0是否通,十六进制地址
            boolean[] Y1A0 = melsec_net.ReadBool("Y1A0",(short) 1).Content;            // 读取Y1A0是否通,十六进制地址
            boolean[] B1A0 = melsec_net.ReadBool("B1A0",(short) 1).Content;            // 读取B1A0是否通,十六进制地址
            short short_D1000 = melsec_net.ReadInt16("D1000").Content;                 // 读取D1000的short值  ,W3C0,R3C0 效果是一样的
            int int_D1000 = melsec_net.ReadInt32("D1000").Content;                     // 读取D1000-D1001组成的int数据
            float float_D1000 = melsec_net.ReadFloat("D1000").Content;                 // 读取D1000-D1001组成的float数据
            long long_D1000 = melsec_net.ReadInt64("D1000").Content;                   // 读取D1000-D1003组成的long数据
            double double_D1000 = melsec_net.ReadDouble("D1000").Content;              // 读取D1000-D1003组成的double数据
            String str_D1000 = melsec_net.ReadString("D1000", (short) 10).Content;     // 读取D1000-D1009组成的条码数据
    
    
    
            melsec_net.Write("M100", new boolean[] { true} );                          // 写入M100为通
            melsec_net.Write( "Y1A0", new boolean[] { true } );                        // 写入Y1A0为通
            melsec_net.Write( "X1A0", new boolean[] { true } );                        // 写入X1A0为通
            melsec_net.Write( "B1A0", new boolean[] { true } );                        // 写入B1A0为通
            melsec_net.Write( "D1000", (short)1234);                                   // 写入D1000  short值  ,W3C0,R3C0 效果是一样的
            melsec_net.Write( "D1000", 1234566);                                // 写入D1000  int值
            melsec_net.Write( "D1000", 123.456f);                               // 写入D1000  float值
            melsec_net.Write( "D1000", 123.456d);                               // 写入D1000  double值
            melsec_net.Write( "D1000", 123456661235123534L);                    // 写入D1000  long值
            melsec_net.Write( "D1000", "K123456789");                           // 写入D1000  string值
    

      

    X,Y,M,L,F,V,B,S位数据的读写说明

    • X 输入继电器
    • Y 输出继电器
    • M 内部继电器
    • L 锁存继电器
    • F 报警器
    • V 边沿继电器
    • B 链接继电器
    • S 步进继电器

    本小节将展示八种位数据的读取,虽然更多的时候只是读取D数据即可,或者是将位数据批量挪到D数据中, 但是在此处仍然进行介绍单独的读取X,Y,M,L,F,V,B,S,由于这八种读取手法一致,故针对M数据进行介绍,其他的您可以自己测试。

    如下方法演示读取了M200-M209这10个M的值,注意:读取长度必须为偶数,即时写了奇数,也会补齐至偶数,读取和写入的最大长度为7168,否则报错。如需实际需求确实大于7168的,请分批次读取。
    返回值解析:如果读取正常则共返回10个字节的数据,以下示例数据进行批量化的读取

     OperateResultExOne<boolean[]> read = melsec_net.ReadBool("M100",(short)10);
            if(read.IsSuccess){
                boolean m100 = read.Content[0];
                boolean m101 = read.Content[1];
                boolean m102 = read.Content[2];
                boolean m103 = read.Content[3];
                boolean m104 = read.Content[4];
                boolean m105 = read.Content[5];
                boolean m106 = read.Content[6];
                boolean m107 = read.Content[7];
                boolean m108 = read.Content[8];
                boolean m109 = read.Content[9];
            }
            else {
                System.out.print("读取失败:"+read.Message);
            }
    

      

            // 写入测试,M100-M104 写入测试 此处写入后M100:通 M101:断 M102:断 M103:通 M104:通
            boolean[] values = new boolean[]{true,false,false,true,true};
            OperateResult write = melsec_net.Write("M100",values);
            if(write.IsSuccess){
                System.out.print("写入成功");
            }
            else {
                System.out.print("写入失败:"+write.Message);
            }
    

      

    D,W,R字数据的读写操作

    此处读取针对中间存在整数数据的情况,因为两者读取方式相同,故而只演示一种数据读取, 使用该组件读取数据,一次最多读取或写入960个字,超出则失败。 如果读取的长度确实超过限制,请考虑分批读取。

            OperateResultExOne<byte[]> read1 = melsec_net.Read("D100",(short)5);
            if(read1.IsSuccess){
                short D100 = melsec_net.getByteTransform().TransByte(read1.Content,0);
                short D101 = melsec_net.getByteTransform().TransByte(read1.Content,2);
                short D102 = melsec_net.getByteTransform().TransByte(read1.Content,4);
                short D103 = melsec_net.getByteTransform().TransByte(read1.Content,6);
                short D104 = melsec_net.getByteTransform().TransByte(read1.Content,8);
            }
            else {
                System.out.print("读取失败:"+read1.Message);
            }
    

      

            // D100为1234,D101为8765,D102为1234,D103为4567,D104为-2563
            short[] values2 = new short[]{1335, 8765, 1234, 4567, -2563 };
            OperateResult write = melsec_net.Write("M100",values2);
            if(write.IsSuccess){
                System.out.print("写入成功");
            }
            else {
                System.out.print("写入失败:"+write.Message);
            }
    

     

    一个实际中复杂的例子演示

    实际中可能碰到的情况会很复杂,一台设备中需要上传的数据包含了温度,压力,产量,规格等等信息,在一串数据中 会包含各种各样的不同的数据,上述的读取D,读取M,读取条码的方式不太好用,所以此处做一个完整示例的演示,假设我们需要读取 D4000-D4009的数据,假设D4000存放了温度数据,55.1℃在D中为551,D4001存放了压力数据,1.23MPa在D中存放为123,D4002存放了 设备状态,0为停止,1为运行,D4003存放了产量,1000就是指1000个,D4004备用,D4005-D4009存放了规格,以下代码演示如何去解析数据:

            //解析复杂数据
            OperateResultExOne<byte[]> read3 = melsec_net.Read("D4000", (short) 10);
            if (read3.IsSuccess)
            {
                double 温度 = melsec_net.getByteTransform().TransInt16(read3.Content, 0) / 10d;//索引很重要
                double 压力 = melsec_net.getByteTransform().TransInt16(read3.Content, 2) / 100d;
                boolean IsRun = melsec_net.getByteTransform().TransInt16(read3.Content, 4) == 1;
                int 产量 =melsec_net.getByteTransform().TransInt32(read3.Content, 6);
                String 规格 = melsec_net.getByteTransform().TransString(read3.Content, 10, 10,"ascii");
            }
            else
            {
                System.out.print("读取失败:"+read3.Message);
            }
    

      

    更详细的信息,可以参照源代码里面的测试项目。


  • 相关阅读:
    Cannot read property 'createElement' of undefined
    Another git process seems to be running in this repository
    React第一次渲染为何容易出现TypeError: Cannot read property 'XX' of undefined
    哟,来看看JS里面变量声明方式
    工欲善其事,必先利其器(一)
    ==与===
    day day up
    代码不规范,同事皮锤现(下)
    代码不规范,同事皮锤现(中)
    医学影像调窗技术
  • 原文地址:https://www.cnblogs.com/dathlin/p/9176069.html
Copyright © 2011-2022 走看看