基于三菱Q系列PLC#MC协议上位机通讯(四)-C#通讯模块开发

背景

  在做工控领域系统集成时,由于项目需要跟三菱PLC对接。主要是实现数据的下发及设备状态数据的读取。三菱自带的MC协议具有读写速度块,可集成性好,对电气编程角度非常友好电气工程师只需要分配D区地址块长度就可以与上位机通过该地址块进行交互。下面开始讲诉我的三菱通讯模块实现过程:

  • 1.MC通讯协议

写入数据协议

 读取数据协议

 读取数据反馈

写入数据反馈

   2.代码实现

  • 协议

数据读取

 1 private byte[] GetReadCommand(int startIndex, int len)
 2         {
 3             byte[] command = new byte[21];
 4             //发起指令
 5             command[0] = 0x50;
 6             command[1] = 0x00;
 7             //网路编号
 8             command[2] = 0x00;
 9             //PLC编号
10             command[3] = 0xFF;
11             //请求目标模块IO编号
12             command[4] = 0xFF;
13             command[5] = 0x03;
14             //请求目标模块站编号
15             command[6] = 0x00;
16             //应答数据物理长度
17             command[7] = 0x0C;
18             command[8] = 0x00;
19             //cpu监视定时器
20             command[9] = 0x11;
21             command[10] = 0x00;
22             //命令 
23             command[11] = 0x01;
24             command[12] = 0x04;
25             //子命令
26             command[13] = 0x00;
27             command[14] = 0x00;
28             //首地址,字节序反转
29             command[17] = (byte)(startIndex / 2 / 256 / 256 % 256);
30             command[16] = (byte)(startIndex / 2 / 256 % 256);
31             command[15] = (byte)(startIndex / 2 % 256);
32             //软元件
33             command[18] = 0xA8;
34             //读取长度,字节序反转
35             command[20] = (byte)(len / 2 / 256 % 256);
36             command[19] = (byte)(len / 2 % 256);
37 
38             return command;
39         }
View Code

数据写入

 1 private byte[] GetWriteCommand(int startIndex,byte[] bytes)
 2         {
 3             byte[] command = new byte[21];
 4 
 5             //发起指令
 6             command[0] = 0x50;
 7             command[1] = 0x00;
 8             //网路编号
 9             command[2] = 0x00;
10             //PLC编号
11             command[3] = 0xFF;
12             //请求目标模块IO编号
13             command[4] = 0xFF;
14             command[5] = 0x03;
15             //请求目标模块站编号
16             command[6] = 0x00;
17             //应答数据物理长度,字节序反转
18             command[8] = (byte)((bytes.Length + 12) / 256 % 256);
19             command[7] = (byte)((bytes.Length + 12) % 256);
20             //cpu监视定时器
21             command[9] = 0x11;
22             command[10] = 0x00;
23             //命令 
24             command[11] = 0x01;
25             command[12] = 0x14;
26             //子命令
27             command[13] = 0x00;
28             command[14] = 0x00;
29             //首地址,字节序反转
30             command[17] = (byte)(startIndex/2 / 256 / 256 % 256);
31             command[16] = (byte)(startIndex/2 / 256 % 256);
32             command[15] = (byte)(startIndex/2 % 256);
33             //软元件
34             command[18] = 0xA8;
35             //写入长度,字节序反转
36             command[20] = (byte)(bytes.Length / 2 / 256 % 256);
37             command[19] = (byte)(bytes.Length / 2 % 256);
38             //data
39             return command.Concat(bytes).ToArray();
40         }
View Code

数据反馈

 1 private byte[] GetResponse()
 2         {
 3             byte[] response = new byte[7];
 4             //发起指令
 5             response[0] = 0xD0;
 6             response[1] = 0x00;
 7             //网路编号
 8             response[2] = 0x00;
 9             //PLC编号
10             response[3] = 0xFF;
11             //请求目标模块IO编号
12             response[4] = 0xFF;
13             response[5] = 0x03;
14             //请求目标模块站编号
15             response[6] = 0x00;
16             return response;
17         }
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                
23                 msg = $"连接[{Ip}]成功,耗时{sp.Elapsed.TotalMilliseconds.ToString()}ms";
24                 return true;
25 
26             }
27             catch (Exception ex)
28             {
29                 msg = $"连接失败:{ex.Message}";
30                 return false;
31             }
32         }
View Code
  • 断开连接
 1  public bool Close(out string msg)
 2         {
 3             msg = string.Empty;
 4             try
 5             {
 6                 tcpClient?.Close();
 7                 tcpClient = null;
 8                 return true;
 9             }
10             catch (Exception ex)
11             {
12                 msg = $"关闭失败:{ex.Message}";
13                 tcpClient = null;
14                 return false;
15             }
16         }
View Code
  • 读取操作
 1 public bool ReadDataBlock(out string msg, 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 
 9                 #region 连接状态
10                 if (tcpClient == null || !tcpClient.Connected)
11                 {
12                     if (!Open(out msg))
13                     {
14                         Thread.Sleep(40);
15                         if (!Open(out msg)) return false;
16                     }
17                 }
18                 #endregion
19 
20                 int i = 0;
21                 for (int index = startIndex; index < startIndex + len; index += MelsecConsts.MAXREADDATE)
22                 {
23                     int _newLen = len + startIndex - index;
24 
25                     if (_newLen > MelsecConsts.MAXREADDATE) _newLen = MelsecConsts.MAXREADDATE;
26                     i++;
27 
28                     byte[] command = GetReadCommand(index, _newLen);
29 
30                     if (!SocketHelper.SendData(out msg, tcpClient, command))
31                     {
32                         msg = $"发送读取指令失败:{msg}!";
33                         return false;
34                     }
35                     byte[] r = GetResponse();
36 
37                     byte[] head = new byte[r.Length+2+2];//head+Length+err->7+2+2
38 
39                     if (!SocketHelper.ReceiveData(out msg, tcpClient, head))
40                     {
41                         msg = $"读取数据接收失败:{msg}!";
42                         return false;
43                     }
44                     #region 头部校验
45 
46                     if (!head.EqualsBytes(r))
47                     {
48                         msg = $"反馈头部校验失败";
49                         return false;
50                     };
51                     #endregion
52 
53                     int le = (int)BitConverter.ToUInt16(head, 7);
54 
55                     //错误码
56                     if (!head.EqualsBytes(new byte[2],9))
57                     {
58                         byte[] e2 = new byte[le-2];//剩余错误码
59 
60                         if (!SocketHelper.ReceiveData(out msg, tcpClient, e2))
61                         {
62                             msg = $"二次接收错误码失败:{msg}!";
63                             return false;
64                         }
65                         byte[] e1 = new byte[2];
66                         Array.Copy(head, 9, e1, 0, 2);
67                         msg = $"{string.Join(" ", string.Join(" ", (e1.Concat(e2)).Select(x => x.ToString("x2"))))}";
68                         return false;
69                     };
70 
71                     if (_newLen != (le - 2))
72                     {
73                         msg = $"接收数据不正确";
74                         return false;
75                     }
76 
77                     byte[] nData = new byte[_newLen];
78 
79                     if (!SocketHelper.ReceiveData(out msg, tcpClient, nData))
80                     {
81                         msg = $"读取数据接收失败:{msg}!";
82                         return false;
83                     }
84 
85                     reData = reData.Concat(nData).ToArray();
86                 }
87 
88                 msg = $"读取({reData.Length})字节数据成功,耗时{sp.Elapsed.TotalMilliseconds.ToString()}ms,{i}次读取";
89                 return true;
90             }
91             catch (Exception ex)
92             {
93                 Close(out string _msg);
94                 msg = $"读取失败:{ex.Message},{_msg}";
95                 return false;
96             }
97         }
View Code
  • 写入操作
 1 public bool WriteDataBlock(out string msg, int startIndex, byte[] inData)
 2         {
 3             msg = string.Empty;
 4             try
 5             {
 6                 System.Diagnostics.Stopwatch sp = new System.Diagnostics.Stopwatch();
 7                 sp.Start();
 8 
 9                 #region 连接状态
10                 if (tcpClient == null || !tcpClient.Connected)
11                 {
12                     if (!Open(out msg))
13                     {
14                         Thread.Sleep(40);
15                         if (!Open(out msg)) return false;
16                     }
17                 }
18                 #endregion
19                 //奇数补0
20                 if (inData.Length % 2 > 0) inData = inData.Concat(new byte[1] { 0 }).ToArray();
21                 int i = 0; int len = inData.Length;
22                 for (int index = startIndex; index < startIndex + len; index += MelsecConsts.MAXWRIDATE)
23                 {
24                     int _newLen = len + startIndex - index;
25                     if (_newLen > MelsecConsts.MAXWRIDATE) _newLen = MelsecConsts.MAXWRIDATE;
26                     i++;
27                     byte[] nData = new byte[_newLen];
28 
29                     Array.Copy(inData, index - startIndex, nData, 0, _newLen);
30 
31                     byte[] command = GetWriteCommand(index, nData);
32 
33                     if (!SocketHelper.SendData(out msg, tcpClient, command))
34                     {
35                         msg = $"发送写入指令失败:{msg}!";
36                         return false;
37                     }
38                     byte[] r = GetResponse();
39 
40                     byte[] head = new byte[r.Length + 4];
41 
42                     if (!SocketHelper.ReceiveData(out msg, tcpClient, head))
43                     {
44                         msg = $"写入数据接收失败:{msg}!";
45                         return false;
46                     }
47                     #region 头部校验
48 
49                     if (!head.EqualsBytes(r))
50                     {
51                         msg = $"反馈头部校验失败";
52                         return false;
53                     };
54                     #endregion
55 
56                     #region 长度校验
57 
58                     int le = (int)BitConverter.ToUInt16(head, 7);
59 
60                     if (le != 2|| !head.EqualsBytes(new byte[2], 9))
61                     {
62                         byte[] e2 = new byte[le-2];
63                         if (!SocketHelper.ReceiveData(out msg, tcpClient, e2))
64                         {
65                             msg = $"写入数据失败,错误码数据接收失败:{msg}!";
66                             return false;
67                         }
68                         byte[] e1 = new byte[2];
69                         Array.Copy(head,9, e1,0,2);
70                         msg = $"写入数据失败,错误码:{string.Join(" ", (e1.Concat(e2)).Select(x => x.ToString("x2")))}";
71                         return false;
72                     }
73                     #endregion
74                 }
75                 msg = $"写入({len})字节数据成功,耗时{sp.Elapsed.TotalMilliseconds.ToString()}ms,{i}次写入";
76                 return true;
77             }
78             catch (Exception ex)
79             {
80                 Close(out string _msg);
81                 msg = $"写入失败:{ex.Message},{_msg}";
82                 return false;
83             }
84         }
View Code

测试结果:

 

 

 

 

完毕!

posted @ 2022-01-06 11:12  豆腐柠檬  阅读(5429)  评论(0编辑  收藏  举报