欧姆龙NJ/NX系列PLC 基于以太网的CIP通讯(EtherNet/IP)

前言

CIP (Common Industrial Protocol, 通用工业协议) 是由 ODVA组织提出并维护的具有增强服务的自动化通讯协议。是一种使用生产者-消费者通信模型的与媒体无关的协议,并且是上层的严格面向对象的协议。每个CIP对象都有属性(数据)、服务(命令)、连接和行为(属性值和服务之间的关系)。CIP包括一个广泛的对象库,用于支持通用网络通信、文件传输等网络服务以及模拟和数字输入/输出设备、HMI、运动控制和位置反馈等典型自动化功能。
EtherNet/IP是基于以太网的通讯协议,为用户提供了为工业自动化应用部署标准以太网技术(IEEE 802.3与TCP/IP套件相结合)的网络工具,同时实现了互联网和企业连接,从而随时随地产生数据。EtherNet/IP提供各种拓扑选项,包括具有标准以太网基础设施设备的传统星形,或启用EtherNet/IP设备的设备级环(DLR)。QuickConnectTM功能允许在网络运行时通过使用简短的启动程序快速重新连接设备。

一、 EtherNet/IP协议的两种消息传递模式

1.消息种类

1)显式消息

显式消息连接是点对点的关系,旨在方便两个节点之间的请求-响应事务。这些连接是通用性质的,通常用于两个节点之间的频繁请求。它们可用于访问设备内的任何网络可访问项。显式消息连接利用TCP/IP服务在以太网上传递消息。

2)隐式消息

隐式(I/O数据)连接是为了在规律的时间间隔内移动特定应用程序的I/O数据而建立的。这些连接可以设置为一对一的关系或一对多的关系,以充分利用生产者-消费者组播模型。隐式消息使用UDP/IP资源使以太网上的组播数据传输成为现实。

2. 已连接消息传递(CMM)

用于传递EtherNet/IP上每个节点内预先专用于特定目的的资源,例如频繁的显式消息交易或实时I/O数据。连接资源是使用通过UCMM可用的通信服务保留和配置的。

3. 未连接消息传递(UCMM)

在连接建立过程中用于传递不频繁、低优先级的显式消息。设备中的未连接资源称为未连接消息管理器或UCMM。EtherNet/IP上的未连接消息利用TCP/IP资源在以太网上传递消息。

二、未连接消息模式下欧姆龙NJ/NX系列PLC通讯数据报文格式解析C#代码实现

1. 报文格式分析

1)头部数据帧解析
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;

namespace OmronCIP
{
    internal class EncapsulationHeader
    {
        /// <summary>
        /// Encapsulation Command
        /// </summary>
        public ushort Command { get; set; }
        /// <summary>
        /// Length of the data portion in bytes
        /// </summary>
        public ushort Length { get; set; }
        /// <summary>
        /// Session handle
        /// </summary>
        public uint SessionHandle { get; set; }
        /// <summary>
        /// Status Code
        /// </summary>
        public uint Status { get; set; }

        private byte[] _senderContext = new byte[8];
        /// <summary>
        /// Sender Context
        /// </summary>
        /// <remarks>Information only pertinent to the sender of the encaps command. Must be 8 bytes.</remarks>
        public byte[] SenderContext
        {
            get { return _senderContext; }
            set
            {
                if (value == null)
                    _senderContext = new byte[8];

                if (value.Length >= 8)
                {
                    _senderContext = new byte[8];
                    Array.Copy(value, _senderContext, 8);
                }

                if (value.Length < 8)
                {
                    _senderContext = new byte[8];
                    Array.Copy(value, _senderContext, value.Length);
                }
            }
        }
        /// <summary>
        /// Options
        /// </summary>
        public uint Options { get; set; }
        /// <summary>
        /// Protocol Version
        /// </summary>
        public ushort ProtocolVersion { get; set; }
        /// <summary>
        /// Options Flags
        /// </summary>
        public ushort OptionsFlag { get; set; }

        public void Expand(byte[] DataArray, int Offset)
        {
            if (DataArray.Length < Offset + 24)
                throw new IndexOutOfRangeException("Not enough data in the DataArray for the encapsulated packet");
            Command = BitConverter.ToUInt16(DataArray, Offset);
            Length = BitConverter.ToUInt16(DataArray, Offset + 2);
            SessionHandle = BitConverter.ToUInt32(DataArray, Offset + 4);
            Status = BitConverter.ToUInt32(DataArray, Offset + 8);
            _senderContext = new byte[8];
            Buffer.BlockCopy(DataArray, Offset + 12, _senderContext, 0, 8);
            Options = BitConverter.ToUInt32(DataArray, Offset + 20);
            if (Command == (ushort)EncapsCommand.RegisterSession)
            {
                ProtocolVersion = BitConverter.ToUInt16(DataArray, Offset + 24);
                OptionsFlag = BitConverter.ToUInt16(DataArray, Offset + 26);
            }
        }
        public byte[] ReplyPack()
        {
            byte[] data = null;
            if (Command == (ushort)EncapsCommand.RegisterSession)
            {
                data = new byte[28];
                Buffer.BlockCopy(BitConverter.GetBytes(Command), 0, data, 0, 2);
                Buffer.BlockCopy(BitConverter.GetBytes(Length), 0, data, 2, 2);
                Buffer.BlockCopy(BitConverter.GetBytes(SessionHandle), 0, data, 4, 4);
                Buffer.BlockCopy(BitConverter.GetBytes(Status), 0, data, 8, 4);
                Buffer.BlockCopy(_senderContext, 0, data, 12, 8);
                Buffer.BlockCopy(BitConverter.GetBytes(Options), 0, data, 20, 4);
                Buffer.BlockCopy(BitConverter.GetBytes(ProtocolVersion), 0, data, 24, 2);
                Buffer.BlockCopy(BitConverter.GetBytes(OptionsFlag), 0, data, 26, 2);
            }
            if (Command == (ushort)EncapsCommand.UnRegisterSession || Command == (ushort)EncapsCommand.SendRRData)
            {
                data = new byte[24];
                Buffer.BlockCopy(BitConverter.GetBytes(Command), 0, data, 0, 2);
                Buffer.BlockCopy(BitConverter.GetBytes(Length), 0, data, 2, 2);
                Buffer.BlockCopy(BitConverter.GetBytes(SessionHandle), 0, data, 4, 4);
                Buffer.BlockCopy(BitConverter.GetBytes(Status), 0, data, 8, 4);
                Buffer.BlockCopy(_senderContext, 0, data, 12, 8);
                Buffer.BlockCopy(BitConverter.GetBytes(Options), 0, data, 20, 4);
            }
            return data;
        }
    }
}

2)服务命令与数据内容解析(CommandSpecificData)
// 这里的解析部分代码省略(代码太长)
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace OmronCIP
{
    public class CommandSpecificData
    {
        /// <summary>
        /// Interface handle
        /// </summary>
        public uint InterfaceHandle { get; set; }
        /// <summary>
        /// Connect timeout
        /// </summary>
        public ushort ConnectTimeout { get; set; }
        /// <summary>
        /// Service timeout
        /// </summary>
        public ushort ServiceTimeout { get; set; }
        /// <summary>
        /// Message request size
        /// </summary>
        public ushort MessageRequestSize { get; set; }
        /// <summary>
        /// Multiple service packet
        /// </summary>
        public byte MultipleServicePacket { get; set; }
        /// <summary>
        /// Multiple tag request path size
        /// </summary>
        public byte MultipleTagRequestPathSize { get; set; }
        /// <summary>
        /// Message router request path
        /// </summary>
        public byte[] MessageRouterRequestPath { get; set; }
        /// <summary>
        /// Number of Services
        /// </summary>
        public ushort ServiceNumber { get; set; }
        /// <summary>
        /// Offset list
        /// </summary>
        public ushort[] OffsetList { get; set; }
        /// <summary>
        /// Item count
        /// </summary>
        public ushort ItemCount { get; set; }
        /// <summary>
        /// Service type
        /// </summary>
        public byte ServiceType { get; set; }
        /// <summary>
        /// Request service code
        /// </summary>
        public byte RequestService { get; set; }
        /// <summary>
        /// Address item
        /// </summary>
        public ushort AddressItem { get; set; }
        /// <summary>
        /// Address item length
        /// </summary>
        public ushort AddressItemLen { get; set; }
        /// <summary>
        /// Unconnected data item
        /// </summary>
        public ushort DataItem { get; set; }
        /// <summary>
        /// Unconnected data item length(byte)
        /// </summary>
        public ushort DataItemLen { get; set; }
        /// <summary>
        /// Request path length
        /// </summary>
        public byte RequestPathLen { get; set; }

        /// <summary>
        /// Request path
        /// </summary>
        public byte[] RequestPath { get; set; }
        /// <summary>
        /// CIP message length (word)
        /// </summary>
        public byte[] CipMsgLens { get; set; }
        /// <summary>
        /// Tag name byte array
        /// </summary>
        public string[] TagNames { get; set; }
        /// <summary>
        /// PLC slot serial number
        /// </summary>
        public uint Slot { get; set; }
        /// <summary>
        /// Read / Write data type
        /// </summary>
        public ushort DataType { get; set; }
        /// <summary>
        /// Return error code
        /// </summary>
        public ushort StateCode { get; set; }
        /// <summary>
        /// Write data type
        /// </summary>
        public DataTypeCode WriteDataType { get; set; }
        /// <summary>
        /// Write data quantity
        /// </summary>
        public ushort WriteDataQuantity { get; set; }
        /// <summary>
        /// Write value
        /// </summary>
        public object WriteDataValue { get; set; }
        /// <summary>
        /// Array length
        /// </summary>
        public int ArrayLength { get; set; } = 0;     
    }
}
2)服务命令与数据内容封装(EncapsulationPacket)
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;

namespace OmronCIP
{
    public class EncapsulationPacket
    {
        private EncapsulationHeader encapsulationHeader = new EncapsulationHeader();
        public CommandSpecificData CommandSpecificData = new CommandSpecificData();
        /// <summary>
        /// Encapsulation Command
        /// </summary>
        public ushort Command { get; set; }
        public void Expand(byte[] data)
        {
            ushort flag = BitConverter.ToUInt16(data, 0);
            Command = flag;
            if (flag == (ushort)EncapsCommand.RegisterSession)
            {
                encapsulationHeader.Expand(data, 0);
                CommandSpecificData.loadTags();//读写数组时需要加载全局变量列表
                encapsulationHeader.SessionHandle = GenerateSessionHandle();
            }
            if (flag == (ushort)EncapsCommand.UnRegisterSession)
            {
                encapsulationHeader.Expand(data, 0);
                encapsulationHeader.SessionHandle = 0;
            }
            if (flag == (ushort)EncapsCommand.SendRRData)
            {
                encapsulationHeader.Expand(data, 0);
                CommandSpecificData.Expand(data, 24);
            }
        }
        public byte[] ReplyPack()
        {
            byte[] packet = null;
            byte[] headerPacket = null;
            switch (Command)
            {
                case (ushort)EncapsCommand.RegisterSession://0x65
                    {
                        headerPacket = encapsulationHeader.ReplyPack();
                        packet = new byte[headerPacket.Length];
                        Buffer.BlockCopy(headerPacket, 0, packet, 0, headerPacket.Length);
                        break;
                    }
                case (ushort)EncapsCommand.UnRegisterSession://0x66
                    {
                        headerPacket = encapsulationHeader.ReplyPack();
                        packet = new byte[headerPacket.Length];
                        Buffer.BlockCopy(headerPacket, 0, packet, 0, headerPacket.Length);
                        break;
                    }
                case (ushort)EncapsCommand.SendRRData://0x6F
                    {
                        if (ServiceType.Read == (ServiceType)CommandSpecificData.ServiceType)
                        {
                            headerPacket = encapsulationHeader.ReplyPack();
                            byte[] tempPacket = CommandSpecificData.ReplyPack();
                            packet = new byte[headerPacket.Length + tempPacket.Length];
                            encapsulationHeader.Length = BitConverter.ToUInt16(BitConverter.GetBytes(tempPacket.Length), 0);
                            Buffer.BlockCopy(encapsulationHeader.ReplyPack(), 0, packet, 0, headerPacket.Length);
                            Buffer.BlockCopy(tempPacket, 0, packet, headerPacket.Length, tempPacket.Length);
                        }
                        else if (ServiceType.Write == (ServiceType)CommandSpecificData.ServiceType)
                        {
                            headerPacket = encapsulationHeader.ReplyPack();
                            byte[] tempPacket = CommandSpecificData.ReplyPack();
                            packet = new byte[headerPacket.Length + tempPacket.Length];
                            encapsulationHeader.Length = BitConverter.ToUInt16(BitConverter.GetBytes(tempPacket.Length), 0);
                            Buffer.BlockCopy(encapsulationHeader.ReplyPack(), 0, packet, 0, headerPacket.Length);
                            Buffer.BlockCopy(tempPacket, 0, packet, headerPacket.Length, tempPacket.Length);
                        }
                        break;
                    }
            }
            return packet;
        }
        private uint GenerateSessionHandle()
        {
            byte[] sessionHandle = new byte[4];
            MD5 md5 = MD5.Create();
            byte[] temp = md5.ComputeHash(Encoding.UTF8.GetBytes(Guid.NewGuid().ToString()));
            for (int i = 0; i < 4; i++)
                sessionHandle[i] = temp[i];
            return BitConverter.ToUInt32(sessionHandle, 0);
        }
    }
}

2. 欧姆龙EtherNet/IP协议的解析C#实现用途说明

以上代码都是小编在做项目的过程中所写,当然,有些代码是基于EEIP这个项目写的,小编在这里感谢EEIP项目的所有贡献者 ,本项目用于创建欧姆龙EtherNet/IP协议的虚拟服务端,HslCommunication这个通讯库可以直接访问,基于此可以访问欧姆龙编程软件Sysmac Studio的模拟器,并在没有实体PLC的情况下完全模拟欧姆龙PLC测试PLC程序的逻辑问题,也就是可以实现EIP协议的解析与协议报文封装。最后,已连接模式下的消息传递报文解析与封装将在下一片文章分享,届时将可以实现与威纶通HMI模拟器通讯。

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: nj/nx系列CPU单元内置Ethernet/IP端口,是指欧姆龙公司所生产的nj/nx系列CPU单元的硬件设计中,已经预先集成了Ethernet/IP通信协议的功能。Ethernet/IP是一种应用于工业自动化领域的通信协议,可以实现不同设备之间的数据交换和通信。 这个内置的Ethernet/IP端口对于用户来说,提供了方便快捷的网络连接方式。用户可以通过这个端口与其他支持Ethernet/IP协议的设备进行数据的传输和通信。同时,这个端口也支持其他一些常见的工业通信协议,如Modbus TCP/IP等。 在nj/nx系列CPU单元的用户手册中,会详细介绍如何配置和使用这个Ethernet/IP端口。用户可以通过手册中提供的步骤和指南,实现与其他设备的连接和通信。手册中可能包含以下内容: 1. Ethernet/IP端口的物理接口描述:用户可以了解到这个端口的外部接口类型、连接针脚定义等相关信息。 2. 网络连接设置:手册会介绍如何为这个Ethernet/IP端口配置网络连接参数,包括IP地址、子网掩码、网关等。 3. 通信配置:手册会说明如何设置与其他设备之间的通信参数,比如设备的IP地址、端口号、通信方式等。 4. 数据传输与访问方式:手册会介绍如何在nj/nx系列CPU单元和其他设备之间进行数据的传输和共享,并提供相关的编程指导和示例代码。 通过仔细阅读nj/nx系列CPU单元的用户手册,用户可以快速了解并掌握如何利用内置的Ethernet/IP端口与其他设备进行通信。这将为工业自动化系统的搭建和运维提供便利,促进设备之间的数据交换与互联。 ### 回答2: nj/nx系列CPU单元内置Ethernet/IP端口的用户手册详细介绍了这一特性的使用方法和注意事项。 首先,用户手册会对Ethernet/IP端口的特点进行介绍。它说明了该端口是单元内置的,可以用于实现设备之间的通信和数据传输。同时,还提到了这个端口支持Ethernet/IP协议,这意味着用户可以通过该端口与其他兼容的设备进行通信。 在用户手册中,会详细介绍如何配置和设置Ethernet/IP端口。首先,需设置IP地址,子网掩码和网关地址,以便连接到网络。手册将会详细说明如何在控制器的设置界面中进行这些配置,并提供简单易懂的操作步骤。 接下来,手册会介绍如何使用通信指令来实现数据的发送和接收。用户可以根据自己的需要,选择不同的指令来实现不同的通信功能,比如发送数据、接收数据或者读/写寄存器等。用户手册会提供各种通信指令的详细说明和使用示例,以帮助用户更好地理解和使用。 此外,用户手册还会介绍如何使用其他附加功能,比如设定通信超时、设定连接参数等。通过这些功能,用户可以根据具体要求进行定制化的设置,以满足项目的需要。 在用户手册的最后,还会提供一些常见问题的解答和故障排除方法。这些内容将帮助用户在使用过程中遇到问题时能够快速解决,并保证设备的正常运行。 总之,nj/nx系列CPU单元内置Ethernet/IP端口用户手册提供了全面详尽的说明,帮助用户正确配置和使用此功能,以实现设备之间的网络通信。 ### 回答3: nj/nx系列CPU单元内置Ethernet/IP端口,用户可以通过它实现与其他网络设备的通讯。在使用之前,用户可以参考nj/nx系列CPU单元的用户手册来了解关于Ethernet/IP通讯的详细信息。 用户手册通常会包含以下内容: 1. 概述:用户手册会介绍nj/nx系列CPU单元内置Ethernet/IP端口的基本功能和特点,以及它如何与其他设备进行通讯。 2. 硬件配置:用户手册会提供nj/nx系列CPU单元内嵌的Ethernet/IP端口的硬件配置指南,包括连接器类型、电源要求以及适用的网络类型等。 3. 通讯参数设置:用户手册会详细介绍如何进行Ethernet/IP通讯参数设置,包括IP地址、子网掩码、网关等信息的配置。 4. 通讯功能说明:用户手册会列举nj/nx系列CPU单元内置Ethernet/IP端口所支持的通讯功能,比如数据读写、远程维护、远程监控等。 5. 配置示例:用户手册中通常会提供一些示例配置,展示如何将nj/nx系列CPU单元与其他设备进行连接和通讯。 6. 故障排除:用户手册通常会列出一些常见的故障排除方法,以帮助用户在遇到通讯问题时能够快速解决。 通过仔细阅读nj/nx系列CPU单元的用户手册,用户可以了解到如何正确配置和使用内置Ethernet/IP端口,实现与其他设备的高效通讯,提升自动化控制系统的功能与性能。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值