基于欧姆龙PLC#FinsTcp协议上位机通讯(二)-C#通讯模块开发

  上一篇我们介绍了如何配置连接PLC(注意网线记得插到PLC以太网口!!!还有一个好像是伺服的网口不要插错了),接下来将介绍欧姆FinsTcp协议及使用C#实现过程。

  1. FinsTcp协议报文格式

基于欧姆龙PLC#FinsTcp协议上位机通讯(二)-C#通讯模块开发

 

 

 获取PLC节点地址基于欧姆龙PLC#FinsTcp协议上位机通讯(二)-C#通讯模块开发

 

 

 FINS command

基于欧姆龙PLC#FinsTcp协议上位机通讯(二)-C#通讯模块开发

 

 

 IO存储器地址标识基于欧姆龙PLC#FinsTcp协议上位机通讯(二)-C#通讯模块开发

 

 

 

  2.实现过程

以上为FinsTCP协议主要核心内容,代码原理很简单就是通过SOCKET /TCP IP,发送连接、读取、写入报文数据,接收解析返回数据;

  • 基于TcpClient的发送与接收Byte[]方法

发送BYTE

基于欧姆龙PLC#FinsTcp协议上位机通讯(二)-C#通讯模块开发
 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

基于欧姆龙PLC#FinsTcp协议上位机通讯(二)-C#通讯模块开发
 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

基于欧姆龙PLC#FinsTcp协议上位机通讯(二)-C#通讯模块开发
 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

基于欧姆龙PLC#FinsTcp协议上位机通讯(二)-C#通讯模块开发
 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

这里由于当初写时的想法不同,有的在外层写了连接状态判断有的写在发送接收方法里面;

  • 网络判断
基于欧姆龙PLC#FinsTcp协议上位机通讯(二)-C#通讯模块开发
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的连接与初始化

协议

基于欧姆龙PLC#FinsTcp协议上位机通讯(二)-C#通讯模块开发
 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 基于欧姆龙PLC#FinsTcp协议上位机通讯(二)-C#通讯模块开发
 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

这边连接初始化时需要获取网络节点号

基于欧姆龙PLC#FinsTcp协议上位机通讯(二)-C#通讯模块开发
 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

读取方法

基于欧姆龙PLC#FinsTcp协议上位机通讯(二)-C#通讯模块开发
 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

写入方法

基于欧姆龙PLC#FinsTcp协议上位机通讯(二)-C#通讯模块开发
 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的交互

测试结果

基于欧姆龙PLC#FinsTcp协议上位机通讯(二)-C#通讯模块开发

 

 

完毕!

 

上一篇:Android 学习笔记--android——Activity学习


下一篇:1、开发一个BLOG项目全程记录——项目初始化-03