上一篇我們介紹了如何配置連接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 }
接收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 }
- 基於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 }
接收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 }
這里由於當初寫時的想法不同,有的在外層寫了連接狀態判斷有的寫在發送接收方法里面;
- 網絡判斷

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 }
歐姆龍PLC的連接與初始化
協議

1 private byte[] HandShake() 2 { 3 byte[] array = new byte[20]; 4 array[0] = 0x46; 5 array[1] = 0x49; 6 array[2] = 0x4E; 7 array[3] = 0x53; 8 9 array[4] = 0; 10 array[5] = 0; 11 array[6] = 0; 12 array[7] = 0x0C; 13 14 array[8] = 0; 15 array[9] = 0; 16 array[10] = 0; 17 array[11] = 0; 18 19 array[12] = 0; 20 array[13] = 0; 21 array[14] = 0; 22 array[15] = 0; 23 24 array[16] = 0; 25 array[17] = 0; 26 array[18] = 0; 27 array[19] = 0; 28 29 return array; 30 }

1 private byte[] FinsCommand(RorW rw, PlcMemory mr, MemoryType mt, short ch, short offset, short cnt) 2 { 3 byte[] array = new byte[34]; 4 //TCP FINS header 5 array[0] = 0x46;//F 6 array[1] = 0x49;//I 7 array[2] = 0x4E;//N 8 array[3] = 0x53;//S 9 10 array[4] = 0;//cmd length 11 array[5] = 0; 12 13 if (rw == RorW.Read) 14 { 15 array[6] = 0; 16 array[7] = 0x1A;//26 17 } 18 else 19 { 20 //寫數據的時候一個字占兩個字節,而一個位只占一個字節 21 if (mt == MemoryType.Word) 22 { 23 array[6] = (byte)((cnt * 2 + 26) / 256); 24 array[7] = (byte)((cnt * 2 + 26) % 256); 25 } 26 else 27 { 28 array[6] = 0; 29 array[7] = 0x1B; 30 } 31 } 32 33 array[8] = 0;//frame command 34 array[9] = 0; 35 array[10] = 0; 36 array[11] = 0x02; 37 38 array[12] = 0; 39 array[13] = 0; 40 array[14] = 0; 41 array[15] = 0; 42 //command frame header 43 array[16] = 0x80;//ICF 44 array[17] = 0x00;//RSV 45 array[18] = 0x02;//GCT, less than 8 network layers 46 array[19] = 0x00;//DNA, local network 47 48 array[20] = PLCNode;//DA1 49 array[21] = 0x00;//DA2, CPU unit 50 array[22] = 0x00;//SNA, local network 51 array[23] = PCNode;//SA1 52 53 array[24] = 0x00;//SA2, CPU unit 54 array[25] = 0xFF; 55 56 //指令碼 57 if (rw == RorW.Read) 58 { 59 array[26] = 0x01;//cmdCode--0101 60 array[27] = 0x01; 61 } 62 else 63 { 64 array[26] = 0x01;//write---0102 65 array[27] = 0x02; 66 } 67 //地址 68 //array[28] = (byte)mr; 69 array[28] = GetMemoryCode(mr, mt); 70 array[29] = (byte)(ch / 256); 71 array[30] = (byte)(ch % 256); 72 array[31] = (byte)offset; 73 74 array[32] = (byte)(cnt / 256); 75 array[33] = (byte)(cnt % 256); 76 77 return array; 78 }
這邊連接初始化時需要獲取網絡節點號

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 }
讀取方法

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 }
寫入方法

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 }
通過讀取與寫入方法就完成了對歐姆龍PLC的交互
測試結果
完畢!