FINS通讯概述

FINS(factory interface network service)通信协议是欧姆龙公司开发的用于工业自动化控制网络的指令/响应系统。运用 FINS指令可实现各种网络间的无缝通信,通过编程发送FINS指令,上位机或PLC就能够读写另一个PLC数据区的内容,甚至控制其运行状态,从而简化了用户程序。FINS协议支持工业以太网,这就为OMRON PLC与上位机以太网通信的实现提供了途径。

FINS-TCP需要握手命令成功后方可真正连接上PLC。

以下命令均为十六进制 字节数据流:

1. 握手指令

1.1. 发送

46494E53 0000000C 00000000 00000000 00000018

46494E53:ASCII编码:FINS;

0000000C:指后面跟的字节长度;12个字节

00000000:固定命令;

00000000:错误代码;

00000018:PC节点IP,当设置为0时,会自动获取节点IP。

1.2. 反馈

46494E53 00000010 00000001 00000000 00000018 00000017

46494E53:ASCII编码:FINS;

00000010:指后面跟的字节长度;16个字节

00000001:固定命令;

00000000:错误代码;

00000018:本机电脑节点IP;

00000017:PLC节点IP。

2. 读取指令

读D100开始的2个地址,注:一次最多读1000个地址【以字为单位,也就是2000个字节】。

2.1. 发送

46494E53 0000001A 00000002 00000000 80 00 02 001700 001800 FF 0101 82 006400 0002

46494E53:ASCII编码:FINS;

0000001A:指后面跟的字节长度;26个字节

00000002:固定命令;

00000000:错误代码;

80:ICF;

00:RSV;

02:GCT;

00:PLC网络地址;

17:PLC节点地址;

00:PLC单元地址;

00:PC网络地址;

18:PC节点地址;

00:PC单元地址;

FF:SID;

0101:读指令;

82:读地址区(D位:02,D字:82,W位:31,C位:30,W字:B1,C字:B0);

006400:起始地址;

0002:读个数。读取字【WORD】个数,也就是【WORD*2】个字节

2.2. 反馈

46494E53 0000001A 00000002 00000000 C0 00 02 001800 001700 FF 0101 0000 AABB CCDD

46494E53:ASCII编码:FINS;

0000001A:指后面跟的字节长度;

00000002:固定命令;

00000000:错误代码;

C0:ICF;

00:RSV;

02:GCT;

00:PC网络地址;

18:PC节点地址;

00:PC单元地址;

00:PLC网络地址;

17:PLC节点地址;

00:PLC单元地址;

FF:SID;

0101:读指令;

0000:读取成功标识;

AABB CCDD:读到的数据。

3. 写入指令

往W10,W11写入AABB,CCDD

3.1. 发送

46494E53 0000001E 00000002 00000000 80 00 02 001700 001800 FF 0102 B1 000A00 0002 AABBCCDD

46494E53:ASCII编码:FINS;

0000001E:指后面跟的字节长度;

00000002:固定命令;

00000000:错误代码;

80:ICF;

00:RSV;

02:GCT;

00:PLC网络地址;

17:PLC节点地址;

00:PLC单元地址;

00:PC网络地址;

18:PC节点地址;

00:PC单元地址;

FF:SID;

0102:写指令;

B1:写地址区(D位:02,D字:82,W位:31,C位:30,W字:B1,C字:B0);

000A00:起始地址;【2的24次方-1】,十进制范围【0~16777215】

0002:写个数;写入的字【WORD】个数,也就是【WORD*2】个字节

AABBCCDD:写入数据。

3.2. 反馈

46494E53 00000016 00000002 00000000 C0 00 02 001800 001700 FF 0102 0000

46494E53:ASCII编码:FINS;

00000016:指后面跟的字节长度;

00000002:固定命令;

00000000:错误代码;

C0:ICF;

00:RSV;

02:GCT;

00:PC网络地址;

18:PC节点地址;

00:PC单元地址;

00:PLC网络地址;

17:PLC节点地址;

00:PLC单元地址;

FF:SID;

0102:写指令;

0000:写入成功标识。

【原封装代码有误,这次重新更新 斯内科 20211116】

下面C#封装程序【部分类FinsTcpUtil.cs】,代码如下:

PLC寄存器区域类型枚举类【OmronAddressType.cs】,源程序如下:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace OmronFinsDemo
{/// <summary>/// Omron PLC地址类型【存储区域类别】/// </summary>public enum OmronAddressType{/// <summary>/// C区:I/O继电器区 【Input Output Area】 /// 输入区Input 1600 bits=100 word 范围【CIO0~CIO99】/// 输出区Output 1600 bits=100 word 范围【CIO100~CIO199】/// CPU Bus Unit Area 6400 bits=400Word 范围【CIO1500~CIO1899】/// </summary>CIO = 0,/// <summary>/// W区:工作继电器区 【Work Relay Area】/// 8192 bits = 512 words 范围【W0~W511】/// </summary>WR = 1,/// <summary>/// D区:动态数据存储区,仅可由字(16位Word)进行存取【Data Memory Area】/// 32768Words 范围【D0~D32767】/// </summary>DM = 2,/// <summary>/// 保持继电器区 【Hold Relay】/// 8192 bits = 512 words 范围【H0~H511】/// </summary>HR = 3,/// <summary>/// 定时器区 【Timer】/// PVs 4096Words  范围【T0~T4095】/// CompletionFlag 4096bits 范围【T0~T4095】/// </summary>TIM = 4,/// <summary>/// 特殊辅助继电器区 【Auxiliary Relay Area】/// ReadOnly 7168 bits=448Words 范围【A0~A447】/// Read-Write 8192 bits=512Words 范围【A448~A959】/// </summary>AR = 5,/// <summary>/// 计数器区 【Counter】/// PVs 4096Words  范围【C0~C4095】/// CompletionFlag 4096bits 范围【C0~C4095】/// </summary>CNT = 6}
}

【部分类FinsTcpUtil.cs】

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;namespace OmronFinsDemo
{/// <summary>/// FINS(factory interface network service)通信协议是欧姆龙公司开发的用于工业自动化控制网络的指令-响应系统。/// 我们只使用欧姆龙PLC的FINS-TCP协议,FINS协议可以读写位(Bit)、字(WORD)操作/// 斯内科/// </summary>public partial class FinsTcpUtil{/// <summary>/// 是否已连接/// </summary>private bool connected;/// <summary>/// PLC的IP地址/// </summary>private string serverIP;/// <summary>/// PLC的端口,默认9600/// </summary>private int serverPort;/// <summary>/// 连接PLC的客户端对象/// </summary>TcpClient msender;/// <summary>/// 发送命令 和 接收反馈的套接字对象/// </summary>Socket msock;/// <summary>/// 工控机【上位机PC】的IP地址的尾节点/// </summary>private byte clientIpTail;/// <summary>/// 服务端【欧姆龙PLC】的IP地址的尾节点/// </summary>private byte serverIpTail;/// <summary>/// 记录FINS的发送和接收数据包事件/// 第一个参数是发送的数据包,第二个参数是响应的数据包,第三个参数的获取到响应数据包所花费的时间(ms)/// </summary>public event Action<byte[], byte[], double> RecordDataEvent;/// <summary>/// 是否已连接上欧姆龙PLC【握手成功后才连接成功】/// </summary>public bool IsConnected{get{return connected;}}/// <summary>/// 断开连接/// </summary>public void Disconnect(){if (connected){msender.Close();connected = false;}}/// <summary>/// 初始化TCP,并发送握手协议命令,尝试连接欧姆龙PLC/// </summary>/// <param name="ip">PLC的IP地址</param>/// <param name="port">端口号,默认为9600</param>/// <param name="frame"></param>/// <returns></returns>public bool ConnectPlc(string ip, int port = 9600, int frame = 0){serverIP = ip;serverPort = port;if (!connected){msender = new TcpClient(ip, port);msender.ReceiveTimeout = 3000;msock = msender.Client;//握手协议 共20个字节【标识头4个字节,长度4个字节,命令码4个字节,错误代码4个字节,客户端节点地址4个字节】byte[] handshakeProtocol = new byte[20];//标识头命令:固定FINShandshakeProtocol[0] = 0x46;//FhandshakeProtocol[1] = 0x49;//IhandshakeProtocol[2] = 0x4E;//NhandshakeProtocol[3] = 0x53;//S//数据长度:4个字节handshakeProtocol[4] = 0;handshakeProtocol[5] = 0;handshakeProtocol[6] = 0;handshakeProtocol[7] = 0x0C;//Length长度:后面跟的字节长度:12个字节//00000000:固定命令;【索引 8~11】//00000000:错误代码;【索引 12~15】handshakeProtocol[16] = 0;handshakeProtocol[17] = 0;handshakeProtocol[18] = 0;handshakeProtocol[19] = (byte)frame;//FINS Frame (工控机IP节点的最后一个字节,如 192.168.1.139 就填入139),frame为0时服务端为客户端自动分配IP尾号msock.Send(handshakeProtocol);//【握手的】反馈结果 共24个字节【标识头4个字节,长度4个字节,命令码4个字节,错误代码4个字节,客户端节点地址4个字节,服务端节点地址4个字节】/** 46494E53:ASCII编码:FINS;* 00000010:指后面跟的字节长度;16个字节* 00000001:固定命令;* 00000000:错误代码;* 00000018:本机电脑节点IP;* 00000017:PLC节点IP。*/byte[] feedbackBuffer = new byte[24];//反馈结果msock.Receive(feedbackBuffer, SocketFlags.None);if (handshakeProtocol[0] == feedbackBuffer[0] && handshakeProtocol[1] == feedbackBuffer[1] && handshakeProtocol[2] == feedbackBuffer[2] && handshakeProtocol[3] == feedbackBuffer[3]&& feedbackBuffer[7] == 0x10  && feedbackBuffer[15] == 0x00 && (handshakeProtocol[19] == 0 || feedbackBuffer[19] == handshakeProtocol[19])){//&& feedbackBuffer[11] == 0x01//反馈结果 需满足 按照FINS开头,字节长度一定是16,错误代码为0//客户端IP尾号frame为0时服务端将为客户端自动分配IP尾号clientIpTail = feedbackBuffer[19];serverIpTail = feedbackBuffer[23];connected = true;}}return connected;}/// <summary>/// 获取欧姆龙地址类型对应的 FINS通信标识/// </summary>/// <param name="omronAddressType">欧姆龙地址类型枚举</param>/// <param name="isBitProcess">是否位处理。true:位处理,false:字处理</param>/// <returns></returns>private byte GetAreaTypeFlag(OmronAddressType omronAddressType, bool isBitProcess){if (isBitProcess){//位处理switch (omronAddressType){case OmronAddressType.CIO:return 0x30;case OmronAddressType.WR:return 0x31;case OmronAddressType.DM:return 0x02;case OmronAddressType.HR:return 0x32;case OmronAddressType.TIM:return 0x09;case OmronAddressType.AR:return 0x33;case OmronAddressType.CNT:return 0x09;default:return 0x00;}}else{//字处理switch (omronAddressType){case OmronAddressType.CIO:return 0xB0;case OmronAddressType.WR:return 0xB1;case OmronAddressType.DM:return 0x82;case OmronAddressType.HR:return 0xB2;case OmronAddressType.TIM:return 0x89;case OmronAddressType.AR:return 0xB3;case OmronAddressType.CNT:return 0x89;default:return 0x00;}}}/// <summary>/// 用于排他锁,确保在多线程调用该接口时,不会同时调用。确保在处理当前命令时,其他命令请等待/// </summary>static int lockedValue = 0;/// <summary>/// 发送命令并解析反馈【关键方法】,需要进行加锁操作,防止同时操作一个地址时出现脏读等异常情况/// 当位操作时,只读取或写入一位/// </summary>/// <param name="omronAddressType">欧姆龙PLC存储区域类别枚举</param>/// <param name="startAddress">起始地址</param>/// <param name="bitIndexOrWordLength">要读写的位索引 或者 字长度。读字时,一次最多读1000个字【也就是2000个字节】.如果是写入Word时,该参数无意义,直接按(writeData.Length+1)/2处理</param>/// <param name="isBitProcess">是否位处理。true:位处理,false:字【WORD】处理</param>/// <param name="isRead">读内存区域还是写内存区域 true:读,false:写</param>/// <param name="receiveData">读取时反馈的数据流,写入操作时该参数无意义</param>/// <param name="errMsg">处理时的异常错误信息,默认为空</param>/// <param name="writeData">需要写入的连续字节流,读取操作时该参数无意义,写入操作【isRead为false】时该参数不能为空</param>/// <returns>返回的错误号,0代表操作成功</returns>private int SendCommandAndParseFeedback(OmronAddressType omronAddressType, int startAddress, int bitIndexOrWordLength, bool isBitProcess, bool isRead, ref byte[] receiveData, out string errMsg, byte[] writeData = null){errMsg = string.Empty;if (!IsConnected){errMsg = $"【未建立连接】尚未连接PLC成功或者握手协议失败【{serverIP}:{serverPort}】,请检查配置和网络,当前起始地址【{startAddress}】";return 1000;}if (!isRead && (writeData == null || writeData.Length == 0 || writeData.Length > 2000)){errMsg = $"【参数非法】写入操作时,需要写入的数据为空 或者 写入数据流的字节长度不在【1~2000】之间,当前起始地址【{startAddress}】";return 1001;}if (startAddress < 0 || startAddress > 65535){errMsg = $"【参数非法】欧姆龙PLC的FINS协议的起始地址必须在0~65535之间,当前起始地址【{startAddress}】";return 1002;}//读保持寄存器0x03读取的寄存器数量的范围为 1~1000。因一个寄存器【一个Word】存放两个字节,因此 字节数组的长度范围 为 1~2000if (isRead && bitIndexOrWordLength > 1000){errMsg = $"【参数非法】读取的位索引 或者 字长度不能超过1000,当前起始地址【{startAddress}】,读取长度【{bitIndexOrWordLength}】";return 1003;}//写入WORD时,直接使用 写入的字节长度除以2【因 一个字相当于两个字节 1WORD=2Byte】if (!isRead && !isBitProcess){bitIndexOrWordLength = (writeData.Length + 1) / 2;}byte serviceId = 0x15;//SID:SID用于标识数据发送的过程。【服务标识】,可以任意指定,返回数据包的SID和发送包的SID一致 int commandLength = 34;//发送命令的长度【读取命令 固定34位】if (!isRead){commandLength = 34 + writeData.Length;}byte[] sendBytes = new byte[commandLength];//标识头命令:固定FINSsendBytes[0] = 0x46;//FsendBytes[1] = 0x49;//IsendBytes[2] = 0x4E;//NsendBytes[3] = 0x53;//S//数据长度:4个字节sendBytes[4] = 0;sendBytes[5] = 0;sendBytes[6] = 0;sendBytes[7] = 0x1A;//Length长度:后面跟的字节长度:26个字节if (!isRead){sendBytes[7] = (byte)(26 + writeData.Length);}//命令码 固定 00 00 00 02sendBytes[8] = 0;sendBytes[9] = 0;sendBytes[10] = 0;sendBytes[11] = 0x02;//frame command //【索引12~15】错误码 00 00 00 00sendBytes[16] = 0x80;//ICFsendBytes[17] = 0x00;//RSVsendBytes[18] = 0x02;//GCT, less than 8 network layerssendBytes[19] = 0x00;//DNA, local networksendBytes[20] = serverIpTail;//DA1 PLC的IP节点尾号sendBytes[21] = 0x00;//DA2, CPU unitsendBytes[22] = 0x00;//SNA, local networksendBytes[23] = clientIpTail;//SA1 工控机IP节点尾号sendBytes[24] = 0x00;//SA2, CPU unitsendBytes[25] = serviceId;//SID:SID用于标识数据发送的过程。 【服务标识】//SID能够设置为00到FF十六进制的任何数字。SID用于检测响应请求是否正确,当发送节点与响应节点的SID值相同,表明响应的数据是请求的数据,不相同,表明响应的数据非请求数据。if (isRead){//读命令 01 01sendBytes[26] = 0x01;sendBytes[27] = 0x01;}else{//写命令 01 02sendBytes[26] = 0x01;sendBytes[27] = 0x02;}//地址区域标识sendBytes[28] = GetAreaTypeFlag(omronAddressType, isBitProcess);byte[] addressParts = BitConverter.GetBytes((ushort)startAddress);//起始地址【索引 29~30】sendBytes[29] = addressParts[1];sendBytes[30] = addressParts[0];//读取的字【Word】的长度byte[] lengthParts = BitConverter.GetBytes((ushort)bitIndexOrWordLength);if (isBitProcess){//位处理时,表示读取的位的索引,只读取 或者 写入 一位sendBytes[31] = lengthParts[0];sendBytes[32] = 0x00;sendBytes[33] = 0x01;//每次只读取一位/** 读取位操作【从索引26开始】0101+1字节存储区代码+3字节开始地址+2字节数量                * 示例 0101 31  010A 01  000A * 0101 代表读操作  0x31代表WR区的位操作  010A代表起始地址266 01代表起始位  000A代表读取位的个数:10个* 写入位操作【从索引26开始】 0102+1字节存储区代码+3字节开始地址+2字节数量+1字节第1位值+1字节第2位值+…* 示例 0102 31 00D4 01 0002 01 00* 31是W位代码,0x00D401=212.01, 00D4代表起始地址,01代表位索引 0002写入2个位,0100 第一个位W212.01写入1,第二个位W212.02写入0*                 */}else{//字处理时,按零处理sendBytes[31] = 0x00;//字处理时,起始地址的位索引,按零处理如 D1234.0sendBytes[32] = lengthParts[1];sendBytes[33] = lengthParts[0];}//要写入的字节流数据,写入位时writeData只考虑第一个if (!isRead){Array.Copy(writeData, 0, sendBytes, 34, writeData.Length);}//添加锁while (Interlocked.Exchange(ref lockedValue, 1) != 0) { }System.Diagnostics.Stopwatch stopwatch = System.Diagnostics.Stopwatch.StartNew();try{msock.Send(sendBytes, SocketFlags.None);}catch (SocketException ex){connected = false;errMsg = $"发送数据时出现套接字异常,【{omronAddressType}.{startAddress}.{bitIndexOrWordLength}】【{ex.SocketErrorCode}】,{ex.Message}";Interlocked.Exchange(ref lockedValue, 0);//将current重置为0return 1004;}catch (Exception ex){connected = false;errMsg = $"发送数据时出现异常,【{omronAddressType}.{startAddress}.{bitIndexOrWordLength}】【{ex.Message}】";Interlocked.Exchange(ref lockedValue, 0);//将current重置为0return 1005;}byte[] buffer = new byte[2048];byte[] receiveByts = null;//接收的有效字节流int rcvCount = 0;//收到的字节数try{rcvCount = msock.Receive(buffer);receiveByts = new ArraySegment<byte>(buffer, 0, rcvCount).ToArray();stopwatch.Stop();RecordDataEvent?.Invoke(sendBytes, receiveByts, stopwatch.Elapsed.TotalMilliseconds);/** 反馈数据流* 46494E53 0000001A       00000002 00000000 C0   00   02   001800   001700   FF   0101    0000       AABB CCDD XXXX XXXX * 标识头 后面跟的字节长度  固定    错误代码 ICF  RSV  GCT  工控机IP PLC的IP  SID  读命令  成功标识   实际读取到的数据,索引30开始*/}catch (SocketException ex){connected = false;errMsg = $"【网络异常】接收数据失败,【{omronAddressType}.{startAddress}.{bitIndexOrWordLength}】,套接字错误【{ex.SocketErrorCode}】,【{ex.Message}】";//释放锁Interlocked.Exchange(ref lockedValue, 0);//将current重置为0return 1006;}catch (Exception ex){connected = false;errMsg = $"【处理异常】接收数据失败,【{omronAddressType}.{startAddress}.{bitIndexOrWordLength}】【{ex.Message}】";//释放锁Interlocked.Exchange(ref lockedValue, 0);//将current重置为0return 1007;}if (receiveByts == null || receiveByts.Length < 30){errMsg = $"接收数据为空,或者低于30个字节,接收数据为【{(receiveByts == null ? "" : string.Join(",", receiveByts))}】";Interlocked.Exchange(ref lockedValue, 0);//将current重置为0return 1008;}if (receiveByts[0] != sendBytes[0] || receiveByts[1] != sendBytes[1] || receiveByts[2] != sendBytes[2] || receiveByts[3] != sendBytes[3]){errMsg = $"接收数据头非法,不是从【FINS】开始,接收数据为【{string.Join(",", receiveByts)}】";Interlocked.Exchange(ref lockedValue, 0);//将current重置为0return 1009;}if (receiveByts[15] != 0)//if (receiveByts[11] != 2 || receiveByts[15] != 0){errMsg = $"数据非法,解析时出现错误,错误号【{receiveByts[15]}】,接收数据为【{string.Join(",", receiveByts)}】";Interlocked.Exchange(ref lockedValue, 0);//将current重置为0return 1010;}//if (receiveByts[25] != serviceId)//{//    errMsg = $"服务标识非法,发送的SID【{serviceId}】与接收的SID【{receiveByts[25]}】不一致,接收数据为【{string.Join(",", receiveByts)}】";//    Interlocked.Exchange(ref lockedValue, 0);//将current重置为0//    return 1011;//}if (receiveByts[26] != sendBytes[26] || receiveByts[27] != sendBytes[27]){errMsg = $"读写命令不匹配,发送命令【{sendBytes[26].ToString("X2")}{sendBytes[27].ToString("X2")}】,反馈命令【{receiveByts[26].ToString("X2")}{receiveByts[27].ToString("X2")}】";Interlocked.Exchange(ref lockedValue, 0);//将current重置为0return 1012;}if (receiveByts[28] != 0 || receiveByts[29] != 0){errMsg = GetErrorMessage(receiveByts[28], receiveByts[29]);Interlocked.Exchange(ref lockedValue, 0);//将current重置为0return 1013;}//抓取读取到的实际字节流 【一个字WORD 相当于 两个字节】if (isRead){//读取位时,只读取一位receiveData = new byte[isBitProcess ? 1 : (bitIndexOrWordLength * 2)];                Array.Copy(receiveByts, 30, receiveData, 0, receiveData.Length);}//释放锁Interlocked.Exchange(ref lockedValue, 0);//将current重置为0return 0;}/// <summary>/// 高低字节转化,也就是相邻的两个字节交换位置。【索引0与索引1的值交换位置,索引2与索引3的值交换位置,索引4与索引5的值交换位置...】/// 注意奇数个数组长度时,强行补零按偶数处理【一个字 相当于 两个字节】/// </summary>/// <param name="sourceData"></param>/// <returns></returns>private byte[] HightLowChange(byte[] sourceData){if (sourceData.Length % 2 == 1){byte[] dres = new byte[sourceData.Length];for (int i = 0; i < sourceData.Length - 1; i += 2){dres[i] = sourceData[i + 1];dres[i + 1] = sourceData[i];}dres[sourceData.Length - 1] = sourceData[sourceData.Length - 1];return dres;}int len = (sourceData.Length % 2 == 1 ? sourceData.Length + 1 : sourceData.Length);byte[] res = new byte[len];for (int i = 0; i < sourceData.Length; i += 2){res[i] = sourceData[i + 1];res[i + 1] = sourceData[i];}return res;}/// <summary>/// 获得反馈指定错误消息描述/// </summary>/// <param name="errCode1"></param>/// <param name="errCode2"></param>/// <returns></returns>private string GetErrorMessage(byte errCode1, byte errCode2){string msg = $"错误代码【{errCode1.ToString("X2")} {errCode2.ToString("X2")}】";switch (errCode1){case 0x00:if (errCode2 == 0x01)msg += "service canceled";break;case 0x01:switch (errCode2){case 0x01: msg += "Ip配置错误:local node not in network"; break; case 0x02: msg += "权限出错:token timeout"; break;case 0x03: msg += "retries failed"; break;case 0x04: msg += "too many send frames"; break; case 0x05: msg += "node address range error"; break;case 0x06: msg += "node address duplication"; break; }break;case 0x02:switch (errCode2){case 0x01: msg += "destination node not in network"; break; case 0x02: msg += "unit missing"; break; case 0x03: msg += "third node missing"; break;case 0x04: msg += "destination node busy"; break; case 0x05: msg += "response timeout"; break; }break;case 0x03:switch (errCode2){case 0x01: msg += "communications controller error"; break; case 0x02: msg += "CPU unit error"; break; case 0x03: msg += "控制器错误:controller error"; break; case 0x04: msg += "unit number error"; break; }break;case 0x04:switch (errCode2){case 0x01: msg += "未定义的指令:undefined command"; break;case 0x02: msg += "不支持的模式:not supported by model/version"; break; }break;case 0x05:switch (errCode2){case 0x01: msg += "destination address setting error"; break; case 0x02: msg += "no routing tables"; break; case 0x03: msg += "routing table error"; break; case 0x04: msg += "too many relays"; break; }break;case 0x10:switch (errCode2){case 0x01: msg += "指令太长:command too long"; break;case 0x02: msg += "command too short"; break; case 0x03: msg += "数据与长度不匹配:elements/data don't match"; break; case 0x04: msg += "command format error"; break; case 0x05: msg += "header error"; break; }break;case 0x11:switch (errCode2){case 0x01: msg += "area classification missing"; break; case 0x02: msg += "access size error"; break; case 0x03: msg += "address range error"; break; case 0x04: msg += "address range exceeded"; break; case 0x06: msg += "program missing"; break; case 0x09: msg += "relational error"; break; case 0x0a: msg += "duplicate data access"; break; case 0x0b: msg += "response too long"; break; case 0x0c: msg += "parameter error"; break; }break;case 0x20:switch (errCode2){case 0x02: msg += "protected"; break; case 0x03: msg += "table missing"; break; case 0x04: msg += "data missing"; break; case 0x05: msg += "program missing"; break; case 0x06: msg += "file missing"; break; case 0x07: msg += "data mismatch"; break; }break;case 0x21:switch (errCode2){case 0x01: msg += "read-only"; break; case 0x02: msg += "protected,cannot write data link table"; break; case 0x03: msg += "cannot register"; break; case 0x05: msg += "program missing"; break; case 0x06: msg += "file missing"; break; case 0x07: msg += "file name already exists"; break; case 0x08: msg += "cannot change"; break; }break;case 0x22:switch (errCode2){case 0x01: msg += "not possible during execution"; break; case 0x02: msg += "not possible while running"; break; case 0x03: msg += "wrong PLC mode"; break; case 0x04: msg += "wrong PLC mode"; break; case 0x05: msg += "wrong PLC mode"; break; case 0x06: msg += "wrong PLC mode"; break; case 0x07: msg += "specified node not polling node"; break; case 0x08: msg += "step cannot be executed"; break; }break;case 0x23:switch (errCode2){case 0x01: msg += "file device missing"; break; case 0x02: msg += "memory missing"; break; case 0x03: msg += "clock missing"; break; }break;case 0x24:if (errCode2 == 0x01) msg += "table missing"; break;case 0x25:switch (errCode2){case 0x02: msg += "memory error"; break; case 0x03: msg += "I/O setting error"; break; case 0x04: msg += "too many I/O points"; break; case 0x05: msg += "CPU bus error"; break; case 0x06: msg += "I/O duplication"; break; case 0x07: msg += "CPU bus error"; break; case 0x09: msg += "SYSMAC BUS/2 error"; break; case 0x0a: msg += "CPU bus unit error"; break; case 0x0d: msg += "SYSMAC BUS No. duplication"; break; case 0x0f: msg += "memory error"; break; case 0x10: msg += "SYSMAC BUS terminator missing"; break; }break;case 0x26:switch (errCode2){case 0x01: msg += "no protection"; break; case 0x02: msg += "incorrect password"; break; case 0x04: msg += "protected"; break; case 0x05: msg += "service already executing"; break; case 0x06: msg += "service stopped"; break; case 0x07: msg += "no execution right"; break; case 0x08: msg += "settings required before execution"; break; case 0x09: msg += "necessary items not set"; break; case 0x0a: msg += "number already defined"; break; case 0x0b: msg += "error will not clear"; break; }break;case 0x30:if (errCode2 == 0x01)  msg += "无访问权限:no access right";break;case 0x40:if (errCode2 == 0x01) msg += "服务未开启:service aborted";break;}return msg;}}
}

部分类FinsTcpUtil.Partial.cs,源程序如下:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace OmronFinsDemo
{public partial class FinsTcpUtil{/// <summary>/// 写基本数据类型到PLC,如bool,short,int,uint,float等/// </summary>/// <typeparam name="T"></typeparam>/// <param name="startAddress">起始寄存器地址,以字(WORD)为单位,形如D32000</param>/// <param name="value"></param>/// <param name="msg"></param>/// <returns></returns>public int WriteValue<T>(OmronAddressType omronAddressType, int startAddress, T value, out string msg) where T : struct{bool isBitProcess = false;//是否位操作,true:位操作,false:字操作【一个字 相当于 2个字节】byte[] datas = new byte[0];if (typeof(T) == typeof(bool)){//位操作 只考虑0位datas = BitConverter.GetBytes(Convert.ToBoolean(value));isBitProcess = true;}else if (typeof(T) == typeof(sbyte)){datas = new byte[1] { (byte)Convert.ToSByte(value) };}else if (typeof(T) == typeof(byte)){datas = new byte[1] { Convert.ToByte(value) };}else if (typeof(T) == typeof(short)){datas = BitConverter.GetBytes(Convert.ToInt16(value));}else if (typeof(T) == typeof(ushort)){datas = BitConverter.GetBytes(Convert.ToUInt16(value));}else if (typeof(T) == typeof(int)){datas = BitConverter.GetBytes(Convert.ToInt32(value));}else if (typeof(T) == typeof(uint)){datas = BitConverter.GetBytes(Convert.ToUInt32(value));}else if (typeof(T) == typeof(long)){datas = BitConverter.GetBytes(Convert.ToInt64(value));}else if (typeof(T) == typeof(ulong)){datas = BitConverter.GetBytes(Convert.ToUInt64(value));}else if (typeof(T) == typeof(float)){datas = BitConverter.GetBytes(Convert.ToSingle(value));}else if (typeof(T) == typeof(double)){datas = BitConverter.GetBytes(Convert.ToDouble(value));}else{//暂不考虑 char(就是ushort,两个字节),decimal(十六个字节) 等类型msg = $"写Fins数据暂不支持其他类型:{value.GetType()}";return -1;}byte[] rcvBuffer = new byte[0];int bitIndexOrWordLength = 0;//位操作 默认处理0位if (!isBitProcess){bitIndexOrWordLength = (datas.Length + 1) / 2;}datas = HightLowChange(datas);return SendCommandAndParseFeedback(omronAddressType, startAddress, bitIndexOrWordLength, isBitProcess, false, ref rcvBuffer, out msg, datas);}/// <summary>/// 写入连续的字节数组/// </summary>/// <param name="startAddress">起始寄存器地址,以字(WORD)为单位,形如MW32000</param>/// <param name="buffer"></param>/// <param name="msg"></param>/// <returns></returns>public int WriteValue(OmronAddressType omronAddressType, int startAddress, byte[] buffer, out string msg){byte[] rcvBuffer = new byte[0];buffer = HightLowChange(buffer);return SendCommandAndParseFeedback(omronAddressType, startAddress, (buffer.Length + 1) / 2, false, false, ref rcvBuffer, out msg, buffer);}/// <summary>/// 写指定长度的字符串(比如条码)到汇川PLC,最大240个字符/// 将字符串填充满length个,不足时用 空字符'\0'填充,相当于清空所有字符后重新填充/// </summary>/// <param name="startAddress"></param>/// <param name="length">字符串的最大长度【范围1~240】,超过240个字符将直接返回错误</param>/// <param name="barcode">实际字符串,长度可能小于最大长度length</param>/// <returns></returns>public int WriteString(OmronAddressType omronAddressType, int startAddress, int length, string barcode, out string msg){if (barcode == null){barcode = string.Empty;}//将字符串填充满length个,不足时用 空字符'\0'填充,相当于清空所有字符后填充//防止出现 上一次写 【ABCD】,本次写 【12】, 读取字符串 结果是【12CD】 的问题barcode = barcode.PadRight(length, '\0');return WriteValue(omronAddressType, startAddress, Encoding.ASCII.GetBytes(barcode), out msg);}/// <summary>/// 写长字符串/// 一个寄存器地址可以存放两个字节【两个字符】,设定每次最多写入200个字符,/// 分多次写入,每一次写入的起始寄存器地址偏移100/// </summary>/// <param name="startAddress">起始地址</param>/// <param name="longString">长字符串</param>/// <param name="msg"></param>/// <returns></returns>public int WriteLongString(OmronAddressType omronAddressType, int startAddress, string longString, out string msg){msg = string.Empty;if (longString == null){longString = string.Empty;}int cycleCount = 200;//每20个字符就进行分段int maxLength = longString.Length;int pageSize = (maxLength + cycleCount - 1) / cycleCount;int errorCode = -1;for (int i = 0; i < pageSize; i++){int writeLength = cycleCount;if (i == pageSize - 1){//最后一次writeLength = maxLength - i * cycleCount;}//分段字符串,每次最多200个string segment = longString.Substring(i * cycleCount, writeLength);//寄存器地址是字,所以需要 字节个数 除以2errorCode = WriteString(omronAddressType, startAddress + (i * cycleCount / 2), writeLength, segment, out msg);if (errorCode != 0){return errorCode;}}return errorCode;}/// <summary>/// 读取基本数据类型/// </summary>/// <typeparam name="T">基本的数据类型,如short,int,double等</typeparam>/// <param name="startAddress"></param>/// <param name="value"></param>/// <param name="msg"></param>/// <returns></returns>public int ReadValue<T>(OmronAddressType omronAddressType, int startAddress, out T value, out string msg) where T : struct{bool isBitProcess = false;//是否位操作,true:位操作,false:字操作【一个字 相当于 2个字节】value = default(T);int length = 0;//读取的连续字节个数if (typeof(T) == typeof(bool)){isBitProcess = true;//位操作时,只考虑0位}else if (typeof(T) == typeof(sbyte) || typeof(T) == typeof(byte)){length = 1;}else if (typeof(T) == typeof(short) || typeof(T) == typeof(ushort)){length = 2;}else if (typeof(T) == typeof(int) || typeof(T) == typeof(uint) || typeof(T) == typeof(float)){length = 4;}else if (typeof(T) == typeof(long) || typeof(T) == typeof(ulong) || typeof(T) == typeof(double)){length = 8;}else{//暂不考虑 char(就是ushort,两个字节),decimal(十六个字节) 等类型msg = $"读Fins数据暂不支持其他类型:{value.GetType()}";return -1;}byte[] rcvBuffer = new byte[0];int errorCode = SendCommandAndParseFeedback(omronAddressType, startAddress, (length + 1) / 2, isBitProcess, true, ref rcvBuffer, out msg);rcvBuffer = HightLowChange(rcvBuffer);if (errorCode != 0){return errorCode;}if (typeof(T) == typeof(bool)){value = (T)(object)Convert.ToBoolean(rcvBuffer[0]);}else if (typeof(T) == typeof(sbyte)){value = (T)(object)(sbyte)rcvBuffer[0];}else if (typeof(T) == typeof(byte)){value = (T)(object)rcvBuffer[0];}else if (typeof(T) == typeof(short)){value = (T)(object)BitConverter.ToInt16(rcvBuffer, 0);}else if (typeof(T) == typeof(ushort)){value = (T)(object)BitConverter.ToUInt16(rcvBuffer, 0);}else if (typeof(T) == typeof(int)){value = (T)(object)BitConverter.ToInt32(rcvBuffer, 0);}else if (typeof(T) == typeof(uint)){value = (T)(object)BitConverter.ToUInt32(rcvBuffer, 0);}else if (typeof(T) == typeof(long)){value = (T)(object)BitConverter.ToInt64(rcvBuffer, 0);}else if (typeof(T) == typeof(ulong)){value = (T)(object)BitConverter.ToUInt64(rcvBuffer, 0);}else if (typeof(T) == typeof(float)){value = (T)(object)BitConverter.ToSingle(rcvBuffer, 0);}else if (typeof(T) == typeof(double)){value = (T)(object)BitConverter.ToDouble(rcvBuffer, 0);}return 0;}/// <summary>/// 从起始地址开始,读取一段字节流/// </summary>/// <param name="startAddress">起始寄存器地址</param>/// <param name="length">读取的字节个数</param>/// <param name="values">返回的字节流数据</param>/// <returns>true:读取成功 false:读取失败</returns>public int ReadValue(OmronAddressType omronAddressType, int startAddress, int length, out byte[] values, out string msg){values = new byte[length];int errorCode = SendCommandAndParseFeedback(omronAddressType, startAddress, (length + 1) / 2, false, true, ref values, out msg);           if (errorCode != 0){return errorCode;}values = HightLowChange(values);return errorCode;}/// <summary>/// 读取(长)字符串,设定每次最多读取200个字符,/// 分多次读取,每一次读取的起始寄存器地址偏移100/// </summary>/// <param name="startAddress"></param>/// <param name="length"></param>/// <param name="longString"></param>/// <param name="msg"></param>/// <returns></returns>public int ReadLongString(OmronAddressType omronAddressType, int startAddress, int length, out string longString, out string msg){msg = string.Empty;longString = string.Empty;int cycleCount = 200;int pageSize = (length + cycleCount - 1) / cycleCount;int errorCode = -1;for (int i = 0; i < pageSize; i++){int readLength = cycleCount;if (i == pageSize - 1){//最后一次readLength = length - i * cycleCount;}byte[] values;errorCode = ReadValue(omronAddressType, startAddress + (i * cycleCount / 2), readLength, out values, out msg);if (errorCode != 0){return errorCode;}longString += Encoding.ASCII.GetString(values);}return errorCode;}}
}

C#使用欧姆龙PLC的Fins协议读写PLC地址(基本封装)相关推荐

  1. C#使用西门子S7 协议读写PLC DB块

    教大家使用C#如何方便快速的读写西门子DB块的值,实现上位机跟plc进行通讯的流程 使用的西门子PLC型号,S7 1200 1.Nuget 安装s7 驱动包 2.西门子plc定义一个db块,这个是我们 ...

  2. C#使用Modbus协议读写汇川PLC的M区寄存器(基本接口封装)

    C#使用Modbus-TCP协议读取汇川PLC,Modbus读写是按照MW地址来处理的 [寄存器单位是字WORD,占用两个字节,类似于C#中的ushort(UInt16)],实际测试发现字符串是按照字 ...

  3. ppi协议源码 c语言,S7-200 PLC的PPI协议及其开发实例

    通过硬件和软件侦听的方法,分析PLC内部固有的PPI通讯协议,然后上位机采用VB编程,遵循PPI通讯协议,读写PLC数据,实现人机操作任务.这种通讯方法,与一般的自由通讯协议相比,省略了PLC的通讯程 ...

  4. 如何使用HTTP协议,读写PLC点位状态(含欧姆龙、三菱、西门子、施耐德、松下等PLC)

    前言 现代工业产线,与以往的技术方向发生了很大变化,更多的互联网元素(REST,HTTP,H5,WEB),更多的编程语言(JAVASCRIPT,PYTHON,GO,Node.js)融入了工业生成的应用 ...

  5. PLC通讯实现-C#实现欧姆龙以太网通讯FINS(二)

    PLC通讯实现-C#实现欧姆龙以太网通讯FINS(二) 背景 抽象设计 欧姆龙以太网通讯实现FINS 背景 本人近十年的工作都与工业软件相关.其中工控系统开发过程中有一个必要环节就是跟各大厂商的PLC ...

  6. 晨控CK-GW208-EC与欧姆龙PLC工业EtherCAT协议通讯指南

    晨控CK-GW208-EC与欧姆龙PLC工业EtherCAT协议通讯指南 EtherCAT 是一种实时以太网络通讯协议,旨在解决传统现场总线系统在数据传输速率和复杂性方面的限制.相比传统现场总线,Et ...

  7. 2022-02-21 WPF上位机 121-三菱PLC协议读写代码的封装

    文章目录 一.三菱PLC协议读写代码的封装 1.base基类封装 2.Result返回结果 3.A1E封装 4.QNA3E的封装 5.使用 一.三菱PLC协议读写代码的封装 1.base基类封装 pu ...

  8. 2021-12-20 WPF上位机 121-三菱PLC协议读写代码的封装

    文章目录 一.三菱PLC协议读写代码的封装 1.base基类封装 2.Result返回结果 3.A1E封装 4.QNA3E的封装 5.使用 一.三菱PLC协议读写代码的封装 1.base基类封装 pu ...

  9. 西门子PLC协议转换CIP协议,实现与罗克韦尔/AB、欧姆龙PLC之间通讯

    IGT-DSER智能网关模块支持西门子.三菱.欧姆龙.AB等各种品牌的PLC之间通讯,同时也支持PLC与Modbus协议的工业机器人.智能仪表等设备通讯.网关有多个网口.串口,也可选择WIFI,4G无 ...

  10. 欧姆龙CJ2M 与海利普变频器通讯 协议宏方式可直接拿来实用了,欧姆龙CJ2M PLC与变频器协议宏通讯

    欧姆龙CJ2M 与海利普变频器通讯 协议宏方式可直接拿来实用了,欧姆龙CJ2M PLC与变频器协议宏通讯 采用器件:欧姆龙CJ2M的PLC,1个CJ1W SCU21 V1通讯模块,1台海利普HLP-B ...

最新文章

  1. 演化树,靠不住?挑战物种出现与灭绝速率的估算方法
  2. 用python画图代码比卡丘-Python选修课第二届Turtle绘图大赛 (皮卡丘)
  3. 1351. 密码锁【难度: 一般 / 知识点: 枚举 容斥原理】
  4. 基于ffmpeg的流媒体服务器
  5. 获取web.py上面的示例code
  6. 如何使用Visual Studio创建SQL Server数据库项目
  7. JSR-303校验-转载
  8. Python数据可视化库——Matplotlib
  9. Python tinypng 压缩脚本
  10. Maven下载安装及修改setting内容
  11. Qt Visual Studio生成moc文件
  12. 基于Kubernetes构建企业容器云
  13. Linux系统load average异常值处理的trick
  14. 小程序开发API之mDNS
  15. 张口就来!查看docker镜像地址
  16. DeepFool运行代码中间问题
  17. 云的新出路迷你云?轻松搭建私有云平台 转载7
  18. 智能算法---蚁群算法介绍
  19. 将自动化测试推向极限
  20. php7 三元运算 精简

热门文章

  1. 剖析 Chrome 的小恐龙游戏
  2. SSL安全证书:免费的SSL证书申请渠道有哪些?
  3. apple 关闭双重认证_这次Apple可以关闭工作吗?
  4. Eureka微服务之服务核心动作
  5. Python pickle 反序列化详解
  6. 51单片机DS18B20(单总线)温度读取
  7. 微信公众号迁移函、公证所需资料与流程
  8. 90后男屌丝如何让淘宝客日赚10万滴
  9. IOS calculater
  10. 微信小程序报错40163-“errmsg“解决方案