上一篇我们介绍了如何配置连接PLC(注意网线记得插到PLC以太网口!!!还有一个好像是伺服的网口不要插错了),接下来将介绍欧姆FinsTcp协议及使用C#实现过程。
- FinsTcp协议报文格式
获取PLC节点地址
FINS command
IO存储器地址标识
2.实现过程
以上为FinsTCP协议主要核心内容,代码原理很简单就是通过SOCKET /TCP IP,发送连接、读取、写入报文数据,接收解析返回数据;
- 基于TcpClient的发送与接收Byte[]方法
发送BYTE
1 public static bool SendData(out string msg,TcpClient tcpClient,byte[] sd) 2 { 3 msg = string.Empty; 4 try 5 { 6 tcpClient.GetStream().Write(sd, 0, sd.Length); 7 return true; 8 } 9 catch(Exception ex) 10 { 11 msg = ex.Message; 12 return false; 13 } 14 }View Code
接收BYTE
1 public static bool ReceiveData(out string msg, TcpClient tcpClient,byte[] rd) 2 { 3 msg = string.Empty; 4 try 5 { 6 int index = 0; 7 do 8 { 9 int len = tcpClient.GetStream().Read(rd, index, rd.Length - index); 10 if (len == 0) 11 return false;//这里控制读取不到数据时就跳出,网络异常断开,数据读取不完整。 12 else 13 index += len; 14 } while (index < rd.Length); 15 return true; 16 } 17 catch(Exception ex) 18 { 19 msg = ex.Message; 20 return false; 21 } 22 }View Code
- 基于Socket的发送与接收Byte[]方法
发送BYTE
1 public bool SendData(out string msg,byte[] sd) 2 { 3 msg = string.Empty; 4 try 5 { 6 if(!(IsConnected && _Socket != null && _Socket.Connected)) 7 { 8 if(!Connect(out msg)) 9 { 10 Thread.Sleep(40); 11 if (!Connect(out msg)) return false; 12 } 13 } 14 _Socket.Send(sd, sd.Length, 0); 15 return true; 16 } 17 catch (Exception ex) 18 { 19 msg = ex.Message; 20 Disconnect(out string _msg); 21 return false; 22 } 23 }View Code
接收BYTE
1 public bool ReceiveData(out string msg,byte[] rd) 2 { 3 msg = string.Empty; 4 try 5 { 6 if (!(IsConnected && _Socket != null && _Socket.Connected)) 7 { 8 if (!Connect(out msg)) 9 { 10 Thread.Sleep(40); 11 if (!Connect(out msg)) return false; 12 } 13 } 14 _Socket.Receive(rd, rd.Length, 0); 15 return true; 16 } 17 catch (Exception ex) 18 { 19 msg = ex.Message; 20 Disconnect(out string _msg); 21 return false; 22 } 23 }View Code
这里由于当初写时的想法不同,有的在外层写了连接状态判断有的写在发送接收方法里面;
- 网络判断
1 public static bool PingCheck(string ip, int connectTimeout = 10000) 2 { 3 Ping ping = new Ping(); 4 PingReply pr = ping.Send(ip, connectTimeout); 5 if (pr.Status == IPStatus.Success) 6 return true; 7 else 8 return false; 9 }View Code
欧姆龙PLC的连接与初始化
协议
1 private byte[] HandShake() 2 { 3 #region fins command 4 byte[] array = new byte[20]; 5 array[0] = 0x46; 6 array[1] = 0x49; 7 array[2] = 0x4E; 8 array[3] = 0x53; 9 10 array[4] = 0; 11 array[5] = 0; 12 array[6] = 0; 13 array[7] = 0x0C; 14 15 array[8] = 0; 16 array[9] = 0; 17 array[10] = 0; 18 array[11] = 0; 19 20 array[12] = 0; 21 array[13] = 0; 22 array[14] = 0; 23 array[15] = 0;//ERR? 24 25 array[16] = 0; 26 array[17] = 0; 27 array[18] = 0; 28 array[19] = 0;//TODO:ask for client and server node number, the client node will allocated automatically 29 //array[19] = this.GetIPNode(lIP);//本机IP地址的末位 30 #endregion fins command 31 return array; 32 }View Code
1 private byte[] FinsCmd(RorW rw, PlcMemory mr, MemoryType mt, short ch, short offset, short cnt) 2 { 3 //byte[] array; 4 //if (rw == RorW.Read) 5 // array = new byte[34]; 6 //else 7 // array = new byte[(int)(cnt * 2 + 33 + 1)];//长度是如何确定的在fins协议174页 8 byte[] array = new byte[34];//写指令还有后面的写入数组需要拼接在一起! 9 //TCP FINS header 10 array[0] = 0x46;//F 11 array[1] = 0x49;//I 12 array[2] = 0x4E;//N 13 array[3] = 0x53;//S 14 15 array[4] = 0;//cmd length 16 array[5] = 0; 17 //指令长度从下面字节开始计算array[8] 18 if (rw == RorW.Read) 19 { 20 array[6] = 0; 21 array[7] = 0x1A;//26 22 } 23 else 24 { 25 //写数据的时候一个字占两个字节,而一个位只占一个字节 26 if (mt == MemoryType.Word) 27 { 28 array[6] = (byte)((cnt * 2 + 26) / 256); 29 array[7] = (byte)((cnt * 2 + 26) % 256); 30 } 31 else 32 { 33 array[6] = 0; 34 array[7] = 0x1B; 35 } 36 } 37 38 array[8] = 0;//frame command 39 array[9] = 0; 40 array[10] = 0; 41 array[11] = 0x02; 42 43 array[12] = 0;//err 44 array[13] = 0; 45 array[14] = 0; 46 array[15] = 0; 47 //command frame header 48 array[16] = 0x80;//ICF 49 array[17] = 0x00;//RSV 50 array[18] = 0x02;//GCT, less than 8 network layers 51 array[19] = 0x00;//DNA, local network 52 53 array[20] = PLCNode;//DA1 54 array[21] = 0x00;//DA2, CPU unit 55 array[22] = 0x00;//SNA, local network 56 array[23] = PCNode;//SA1 57 58 array[24] = 0x00;//SA2, CPU unit 59 array[25] = 0xFF; 60 //TODO:array[25] = Convert.ToByte(21);//SID//?????----------------------------------00-FF任意值 61 62 //指令码 63 if (rw == RorW.Read) 64 { 65 array[26] = 0x01;//cmdCode--0101 66 array[27] = 0x01; 67 } 68 else 69 { 70 array[26] = 0x01;//write---0102 71 array[27] = 0x02; 72 } 73 //地址 74 //array[28] = (byte)mr; 75 array[28] = GetMemoryCode(mr, mt); 76 array[29] = (byte)(ch / 256); 77 array[30] = (byte)(ch % 256); 78 array[31] = (byte)offset; 79 80 array[32] = (byte)(cnt / 256); 81 array[33] = (byte)(cnt % 256); 82 83 return array; 84 }View Code
这边连接初始化时需要获取网络节点号
1 public bool Open(out string msg) 2 { 3 msg = string.Empty; 4 try 5 { 6 if (!SocketHelper.PingCheck(Ip, ConnectTimeout)) 7 { 8 msg = "网络故障!"; 9 return false; 10 } 11 System.Diagnostics.Stopwatch sp = new System.Diagnostics.Stopwatch(); 12 sp.Start(); 13 tcpClient = new TcpClient(); 14 tcpClient.ReceiveTimeout = ReceiveTimeout; 15 tcpClient.SendTimeout = SendTimeout; 16 tcpClient.Connect(Ip, Port); 17 Thread.Sleep(10); 18 if (!tcpClient.Connected) 19 { 20 throw new ApplicationException($"未连接到{Ip}"); 21 } 22 if (!SocketHelper.SendData(out msg, tcpClient, HandShake())) 23 { 24 msg = $"连接,数据写入失败:{msg}!"; 25 return false; 26 } 27 28 //开始读取返回信号 29 byte[] buffer = new byte[24]; 30 if (!SocketHelper.ReceiveData(out msg, tcpClient, buffer)) 31 { 32 msg = $"连接握手信号接收失败:{msg}!"; 33 return false; 34 } 35 36 if (buffer[15] != 0)//TODO:这里的15号是不是ERR信息暂时不能完全肯定 37 { 38 msg = $"超过最大连接数或内部连接错误"; 39 return false; 40 } 41 PCNode = buffer[19]; 42 PLCNode = buffer[23]; 43 msg = $"连接[{Ip}]成功,耗时{sp.Elapsed.TotalMilliseconds.ToString()}ms"; 44 return true; 45 46 } 47 catch (Exception ex) 48 { 49 Close(out string _msg);//连接断开,重试 50 msg = $"连接失败:{ex.Message}"; 51 return false; 52 } 53 }View Code
读取方法
1 public bool ReadWordsByte_B(out string msg, PlcMemory mr, int startIndex, int len, out byte[] reData) 2 { 3 msg = string.Empty; reData = new byte[0]; 4 try 5 { 6 System.Diagnostics.Stopwatch sp = new System.Diagnostics.Stopwatch(); 7 sp.Start(); 8 int i = 0; 9 for (int index = startIndex; index < startIndex + len; index += OmronConsts.MAXREADDATE) 10 { 11 int _newLen = len + startIndex- index; 12 if (_newLen > OmronConsts.MAXREADDATE) _newLen = OmronConsts.MAXREADDATE; 13 i++; 14 byte[] array = FinsCmd(RorW.Read, mr, MemoryType.Word, (short)(index/2), 00, (short)(_newLen/2)); 15 16 if (!SocketHelper.SendData(out msg, tcpClient, array)) 17 { 18 msg = $"读取,数据写入失败[{i}次]:{msg}!"; 19 return false; 20 } 21 byte[] buffer = new byte[30 + _newLen];//用于接收数据的缓存区大小 22 if (!SocketHelper.ReceiveData(out msg, tcpClient, buffer)) 23 { 24 msg = $"读取,数据接收失败[{i}次]:{msg}!"; 25 return false; 26 } 27 //命令返回成功,继续查询是否有错误码,然后在读取数据 28 if (buffer[11] == 3) 29 { 30 if (!ErrorCode.CheckHeadError(buffer[15], out msg)) 31 { 32 msg = $"读取数据失败[{i}次]:{msg}!"; 33 return false; 34 } 35 } 36 //endcode为fins指令的返回错误码 37 if (!ErrorCode.CheckEndCode(buffer[28], buffer[29], out msg)) 38 { 39 msg = $"读取数据失败[{i}次]:{msg}!"; 40 return false; 41 } 42 byte[] _bytes = new byte[_newLen]; 43 44 Array.Copy(buffer, 30, _bytes, 0, _newLen); 45 46 reData = reData.Concat(_bytes).ToArray(); 47 } 48 49 msg = $"读取({reData.Length})字节数据成功,耗时{sp.Elapsed.TotalMilliseconds.ToString()}ms,{i}次读取"; 50 return true; 51 } 52 catch (Exception ex) 53 { 54 msg = ex.Message; 55 return false; 56 } 57 }View Code
写入方法
1 public bool WriteWordsByte_B(out string msg, PlcMemory mr, short startIndex, byte[] inData) 2 { 3 msg = string.Empty; 4 try 5 { 6 if (inData == null || inData.Length < 1) 7 { 8 msg = "写入数据失败,写入数据为空!"; 9 return false; 10 } 11 //奇数补零,写入数据必须为一个字 12 if ((inData.Length % 2) > 0) 13 { 14 inData = inData.Concat(new byte[1] { 0 }).ToArray(); 15 } 16 //写入长度大于2000 17 System.Diagnostics.Stopwatch sp = new System.Diagnostics.Stopwatch(); 18 sp.Start(); 19 int i = 0;int len = inData.Length; 20 for (int index= startIndex; index < startIndex + len; index += OmronConsts.MAXRWRIDATE) 21 { 22 int _newLen = len + startIndex - index; 23 if (_newLen > OmronConsts.MAXRWRIDATE) _newLen = OmronConsts.MAXRWRIDATE; 24 i++; 25 byte[] nData = new byte[_newLen]; 26 27 Array.Copy(inData, index- startIndex, nData,0, _newLen); 28 29 byte[] dataHead = FinsCmd(RorW.Write, mr, MemoryType.Word, (short)(index/2), 00, (short)(_newLen /2)); 30 31 byte[] zData = new byte[_newLen+34]; 32 33 dataHead.CopyTo(zData,0); 34 35 nData.CopyTo(zData, 34); 36 37 if (!SocketHelper.SendData(out msg, tcpClient, zData)) 38 { 39 msg = $"写入,数据写入失败[{i}次]:{msg}!"; 40 return false; 41 } 42 byte[] rBuffer= new byte[30]; 43 if (!SocketHelper.ReceiveData(out msg, tcpClient, rBuffer)) 44 { 45 msg = $"写入,数据接收失败[{i}次]:{msg}!"; 46 return false; 47 } 48 if (rBuffer[11] == 3) 49 { 50 if (!ErrorCode.CheckHeadError(rBuffer[15], out msg)) 51 { 52 msg = $"写入数据失败[{i}次]:{msg}!"; 53 return false; 54 } 55 } 56 if (!ErrorCode.CheckEndCode(rBuffer[28], rBuffer[29], out msg)) 57 { 58 msg = $"写入数据失败[{i}次]:{msg}!"; 59 return false; 60 } 61 62 } 63 msg = $"写入({len})字节数据成功,耗时{sp.Elapsed.TotalMilliseconds.ToString()}ms,{i}次写入"; 64 return true; 65 } 66 catch (Exception ex) 67 { 68 msg = ex.Message; 69 return false; 70 } 71 }View Code
通过读取与写入方法就完成了对欧姆龙PLC的交互
测试结果
完毕!