C# OMRON PLC FINS TCP协议简单测试

       FINS(factory interface network service)通信协议是欧姆龙公司开发的用于工业自动化控制网络的指令/响应系统。运用 FINS指令可实现各种网络间的无缝通信,包括用于信息网络的 Etherne(以太网),用于控制网络的Controller Link和SYSMAC LINK。

      ORMON PLC的FINS协议看起来非常简单,但其中数据内容涉及高低位转换、16进制整数、字符串,有时需要自己写代码来进行通讯处理。

1、PLC的数据类型

数据类型

说明

布尔型

单个位

短整型

有符号 16 位值
位 0 是低位
位 14 是高位
位 15 是符号位

无符号 16 位值
位 0 是低位
位 15 是高位

长整型

有符号 32 位值
位 0 是低位
位 30 是高位
位 31 是符号位

双字型

无符号 32 位值
位 0 是低位
位 31 是高位

浮点型

32 位实数

BCD

两个字节封装的 BCD
值的范围是 0 至 9999。对于超出此范围的值,未定义行为。

LBCD

四个字节封装的 BCD
值的范围是 0 至 99999999。对于超出此范围的值,未定义行为。

字符串

空终止 ASCII 字符串。
支持多达 512 个字符的字符串长度,并支持择由高到低的字节排序、由低到高的字节排序、仅高位字节和仅低位字节。

短整型、长整型、双字等也可以是BCD码,需要根据PLC的程序设定进行解析。

1、FINS帧定义

FINS/UDP运用的是一种嵌套格式数据包,即Ethernet报头、IP报头、 UDP报头和FINS帧。一个UDP数据段(FINS 帧)超过1472字节将被分成若干个数据包来传送。分开的UDP数据将在UDP/IP协议层自动组合。通常不须要关注运用 层的数据分段,但是在一个多层 IP网络中1427字节的UDP包可能无法 发送。在这种系统中就须要运用 FINS/TCP方式。

C# OMRON PLC FINS TCP协议简单测试_第1张图片

ICF为信息控制域,用于标明指令和响应;

RSV为系统保留;

GCT为网关允许数目;

DNA为目的网络号;

DA1为目的节点号;

DA2为目的单元号;

SNA为源网络号;

SA1为源节点号;

SA2为源单元号;

SID为服务和响应的标识号,可任意配置,指令和响应对应相同;

MRC和SRC分别为 FINS指令的主指令和从指令;

参数/数据域,用于标明所操作的数据地址、范围等,在响应帧中前两个字节MRES和SRES构成响应码,用来诊断不正确信息

填充示例:

C# OMRON PLC FINS TCP协议简单测试_第2张图片

2、PLC连接、数据读取和解析示例

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Net.Http;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;


namespace PascalMing
{
    public class Tcp_FINS_PascalMingTest
    {
        TcpClient _tcpClient = new TcpClient();
        int _port;
        string _host;
        byte plcAddr = 0;
        byte pcAddr = 0;
        int headDm = 30;
        const int readDMStart = 5000;
        const int readDMCount = 60;
        EventWaitHandle ewh = new EventWaitHandle(false, EventResetMode.AutoReset);
        CancellationTokenSource cts = new CancellationTokenSource();

        //返回:46 49 4E 53 00 00 00 10 00 00 00 01 00 00 00 00 00 00 00 F0 00 00 00 9B //PC,PLC IP最后一位(4个字节一组)
        //不同网络配置返回值有区别,需要根据实际进行替换
        byte[] cmdConnect = { 0x46, 0x49, 0x4E, 0x53, 0x00, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };//最后一位:PC端IP最后一位

        //读D5000,连续100个
        byte[] cmdReadD5000 = { 0x46, 0x49, 0x4E, 0x53, 0x00, 0x00, 0x00, 0x1A, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x02, 0x00, 0x9B, 0x00, 0x00, 0xF0, 0x00, 0x00, 0x01, 0x01, 0x82, (byte)(readDMStart / 0x100), (byte)(readDMStart % 0x100), 0x00, (byte)(readDMCount/0x100), (byte)(readDMCount%0x100) };
        public bool ConnectDevice(string ip, int port)
        {
            _host = ip;
            _port = port;


            try
            {
                _tcpClient = new TcpClient();
                _tcpClient.Connect(_host, _port);
                //_tcpClient.ReceiveTimeout = 5_000;

                _tcpClient.Client.Send(cmdConnect);
                byte[] rx = new byte[100];
                for (int k = 0; k < 100; k++)
                {
                    if (_tcpClient.Available >= 24)
                        break;
                    Thread.Sleep(50);
                }
                int ret = _tcpClient.Client.Receive(rx);
                Console.WriteLine($"connect recv Len:{ret},{rx[0]},{rx[1]},{rx[2]},{rx[3]},{rx[7]}");
                if (ret == 24 && rx[0] == 0x46 && rx[1] == 0x49 && rx[2] == 0x4E && rx[3] == 0x53)
                {
                    pcAddr = rx[19];
                    plcAddr = rx[23];
                    cmdReadD5000[20] = plcAddr;
                    cmdReadD5000[23] = pcAddr;
                    Console.WriteLine("connect ok");
                    Task.Run(() =>
                    {
                        myTaskExecute(cts.Token);
                    });                
                    return true;
                }
                Console.WriteLine("connect fail,check fail");
                return false;
            }
            catch (Exception ex)
            {
                Console.WriteLine("connect fail,ex:"+ex.Message);
                return false;
            }
        }
        int count = 0;
        async Task myTaskExecute(CancellationToken token)
        {
            Stopwatch sw = Stopwatch.StartNew();
            int timeOutMs = 30_000;
            List cmdIndex = new List();
            ushort txCount = 0;

            try
            {
                while (!token.IsCancellationRequested)
                {
                    int ret = -1;
                    byte[] rx = new byte[4096];
                    Console.WriteLine($"{DateTime.Now} send read");
                    _tcpClient.Client.Send(cmdReadD5000);
                    for(int k = 0; k < 200; k ++)
                    {
                        if (_tcpClient.Available >= headDm+ readDMCount*2)
                                break;
                        await Task.Delay(20, token);
                    }
                   
                    Console.WriteLine($"{DateTime.Now} send Available:{_tcpClient.Available}");
                    if (_tcpClient.Available > 0)
                    {
                        sw.Restart();
                        ret = _tcpClient.Client.Receive(rx);
                        try
                        {
                            Console.WriteLine($"{DateTime.Now} DataReceived len:{ret},data:{rx[7]},{rx[19]},{rx[23]}");
                            if (ret >= 16 && rx[0] == 0x46 && rx[1] == 0x49 && rx[2] == 0x4E && rx[3] == 0x53)
                            {
							    //数据解析根据PLC定义进行,包括数据值。此处使用比较简单的测试方案
								Console.WriteLine($"D{5000}:{GetInt16(rx,0)}");
								Console.WriteLine($"D{5001}:{GetInt16(rx,1)}");
								Console.WriteLine($"D{5010}:{GetInt32_2(rx, 10)}");
								Console.WriteLine($"D{5012}:{GetInt32_2(rx, 12)}");
								Console.WriteLine($"D{5014}:{GetInt16(rx, 14)}");
								Console.WriteLine($"D{5015}:{GetString(rx, 15,40)}");

								Console.WriteLine();                            
							}

                        }
                        catch (Exception ex)
                        {
                            Console.WriteLine("DataReceived parse err:" + ex.Message);
                        }
                    }
                    await Task.Delay(2000, token);
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine("DataReceived ex:" + ex.Message);
            }
            Console.WriteLine("DataReceived leave");
        }
        public int GetInt16(byte[]data,int offset)
        {
            string sV = $"{data[headDm+ offset*2]:X2}{data[headDm+offset*2+1]:X2}";
            int iV = int.Parse(sV);
            return iV;
        }
        public int GetInt32(byte[] data, int offset)
        {
            string sV = $"{data[headDm + offset*2]:X2}{data[headDm + offset*2 + 1]:X2}{data[headDm + offset*2 + 2]:X2}{data[headDm + offset*2 + 3]:X2}";
            int iV = int.Parse(sV);
            return iV;
        }
        public int GetInt32_2(byte[] data, int offset)
        {
            string sV = $"{data[headDm + offset * 2+2]:X2}{data[headDm + offset * 2 + 3]:X2}{data[headDm + offset * 2 + 0]:X2}{data[headDm + offset * 2 + 1]:X2}";
            int iV = int.Parse(sV);
            return iV;
        }
        public string GetString(byte[] data, int offset,int len)
        {
            int headDm = 30;
            StringBuilder sb = new StringBuilder();
            for(int k = 0; k < len; k ++)
            {
                if (data[headDm + offset*2 + k] == 0)
                    break;
                sb.Append($"{(char)data[headDm+offset*2+k]}");
            }
            return sb.ToString();
        }
        public void DisconnectDevice()
        {
            _tcpClient?.Close();
        }
    }
}

验证:

Tcp_FINS_PascalMingTest tcp_fins = new Tcp_FINS_PascalMingTest();
void Do_Tcp_FINS()
{
     tcp_FINS_Yinlun.ConnectDevice("192.168.0.1", 9600);
}

3、参考资料

FinsTCP协议报文详细分析 - 知乎
基于FINS协议的OMRON PLC与上位机以太网通信的实现_omron nx fins上位机配置-CSDN博客
 

你可能感兴趣的:(编程,IoT,PLC,FINS,OMRON,C#)