基于三菱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 }
数据写入
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 }
数据反馈
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 }
- 连接
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 }
- 断开连接
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 }
- 读取操作
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 }
- 写入操作
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 }
测试结果:
完毕!
本文来自博客园,作者:豆腐柠檬,转载请注明原文链接:https://www.cnblogs.com/ToufuLemon/p/15762037.html