FINS(factory interface network service)通信协议是欧姆龙公司开发的用于工业自动化控制网络的指令/响应系统。运用 FINS指令可实现各种网络间的无缝通信,包括用于信息网络的 Etherne(以太网),用于控制网络的Controller Link和SYSMAC LINK。
ORMON PLC的FINS协议看起来非常简单,但其中数据内容涉及高低位转换、16进制整数、字符串,有时需要自己写代码来进行通讯处理。
1、PLC的数据类型
数据类型 | 说明 |
布尔型 | 单个位 |
有符号 16 位值 | |
字 | 无符号 16 位值 |
有符号 32 位值 | |
双字型 | 无符号 32 位值 |
32 位实数 | |
BCD | 两个字节封装的 BCD |
四个字节封装的 BCD | |
空终止 ASCII 字符串。 |
短整型、长整型、双字等也可以是BCD码,需要根据PLC的程序设定进行解析。
1、FINS帧定义
FINS/UDP运用的是一种嵌套格式数据包,即Ethernet报头、IP报头、 UDP报头和FINS帧。一个UDP数据段(FINS 帧)超过1472字节将被分成若干个数据包来传送。分开的UDP数据将在UDP/IP协议层自动组合。通常不须要关注运用 层的数据分段,但是在一个多层 IP网络中1427字节的UDP包可能无法 发送。在这种系统中就须要运用 FINS/TCP方式。
ICF为信息控制域,用于标明指令和响应;
RSV为系统保留;
GCT为网关允许数目;
DNA为目的网络号;
DA1为目的节点号;
DA2为目的单元号;
SNA为源网络号;
SA1为源节点号;
SA2为源单元号;
SID为服务和响应的标识号,可任意配置,指令和响应对应相同;
MRC和SRC分别为 FINS指令的主指令和从指令;
参数/数据域,用于标明所操作的数据地址、范围等,在响应帧中前两个字节MRES和SRES构成响应码,用来诊断不正确信息
填充示例:
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<byte[]> cmdIndex = new List<byte[]>();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);
}