设备信息
测试结果
D值测试
Y值写入后读取测试
协议解析
三菱FX 3U系列PLC的通信协议
1. 每次给PLC发送指令后,必须等待PLC的应答完成才能发送下一条指令;
2. 报文都是十六进制ASCII码的形式
3. 相关指令
指令 命令码(ASCII码) 操作原件
读 0(30H) X,Y,M,S,T,C,D
写 1(31H) X,Y,M,S,T,C,D
置位 7(37H) X,Y,M,S,T,C
复位 8(38H) X,Y,M,S,T,C
地址换算:D123这个地址写入数据,那么地址为: address = 123*2 + 4096 = 4342 = 10F6
==================================================================================
读指令
上位机请求:STX(1) + CMD(1) + Address(4) + Length(2) + ETX(1) + SUM(2,从cmd到etx)
PLC响应:STX(1) + 值(n字节) + ETX(1) + SUM(2)
**********************************************************************************
例子:
X,Y,D通过相应的地址换算成新的地址
读取Y005 / Y006 bool
02 30 30 30 41 30 30 31 03 36 35 :范围(0-7 地址) 地址: A0 160
02 30 30 30 41 31 30 31 03 36 36 :范围(10-17 地址) 地址: A1 161
02 30 30 30 41 32 30 31 03 36 37 :范围(20-27 地址) 地址: A2 162
02 30 30 30 41 30 30 31 03 36 35
02 32 30 03 36 35:5亮 32 30 -> 20H -> 转二进制 0010 0000
02 36 30 03 36 39:5,6亮 36 30 -> 60H -> 转二进制 0110 0000
D123读取2个字节,short类型
新版:指令45
老版:
02 30 31 30 46 36 30 32 03 37 32
02 30 30 30 30 03 43 33 -- 值为0
02 30 31 30 30 03 43 34 -- 值为1
==================================================================================
写指令
上位机请求:STX(1) + CMD(1) + Address(4) + Length(2) + Data(4*n)+ ETX(1) + SUM(2,从cmd到etx)
PLC响应:STX(1) + 值(1字节 正确:06H;错误:15H) + ETX(1) + SUM(2)
例子:
Y006设置true
02 37 30 36 30 35 03 30 35
06
D123写入2个字节,short类型,值1
新版
0245313034304636303230313030034143
06
老版:写入值:1
02 31 31 30 46 36 30 34 30 31 30 30 30 30 30 30 03 46 36
06
================================================
核心代码
using MelsecFxOverTcp; using System; using System.Net.Sockets; using System.Text; namespace MelsecFxSerialOverTcp.Util { class MelsecFx { string ip = string.Empty; int port = 0; int SendTimeout = 2000; int ReceiveTimeout = 2000; public MelsecFx(string ip, int port) { this.ip = ip; this.port = port; } static string NotSupportedDataType => "输入的类型不支持,请重新输入"; /// <summary> /// 地址解析 /// </summary> static void FxAnalysisAddress(string address, ref MelsecMcDataType Content1, ref ushort Content2) { switch (address[0]) { case 'M': case 'm': Content1 = MelsecMcDataType.M; Content2 = Convert.ToUInt16(address.Substring(1), MelsecMcDataType.M.FromBase); break; case 'X': case 'x': Content1 = MelsecMcDataType.X; Content2 = Convert.ToUInt16(address.Substring(1), 8); break; case 'Y': case 'y': Content1 = MelsecMcDataType.Y; Content2 = Convert.ToUInt16(address.Substring(1), 8); break; case 'D': case 'd': Content1 = MelsecMcDataType.D; Content2 = Convert.ToUInt16(address.Substring(1), MelsecMcDataType.D.FromBase); break; case 'S': case 's': Content1 = MelsecMcDataType.S; Content2 = Convert.ToUInt16(address.Substring(1), MelsecMcDataType.S.FromBase); break; case 'T': case 't': if (address[1] == 'N' || address[1] == 'n') { Content1 = MelsecMcDataType.TN; Content2 = Convert.ToUInt16(address.Substring(2), MelsecMcDataType.TN.FromBase); break; } if (address[1] == 'S' || address[1] == 's') { Content1 = MelsecMcDataType.TS; Content2 = Convert.ToUInt16(address.Substring(2), MelsecMcDataType.TS.FromBase); break; } if (address[1] == 'C' || address[1] == 'c') { Content1 = MelsecMcDataType.TC; Content2 = Convert.ToUInt16(address.Substring(2), MelsecMcDataType.TC.FromBase); break; } throw new Exception(NotSupportedDataType); case 'C': case 'c': if (address[1] == 'N' || address[1] == 'n') { Content1 = MelsecMcDataType.CN; Content2 = Convert.ToUInt16(address.Substring(2), MelsecMcDataType.CN.FromBase); break; } if (address[1] == 'S' || address[1] == 's') { Content1 = MelsecMcDataType.CS; Content2 = Convert.ToUInt16(address.Substring(2), MelsecMcDataType.CS.FromBase); break; } if (address[1] == 'C' || address[1] == 'c') { Content1 = MelsecMcDataType.CC; Content2 = Convert.ToUInt16(address.Substring(2), MelsecMcDataType.CC.FromBase); break; } throw new Exception(NotSupportedDataType); default: throw new Exception(NotSupportedDataType); } } public bool ConnectServer() { bool ret = false; TcpClient client = null; try { using (client = new TcpClient(ip, port)) { ret = client.Connected; client.Close(); } } catch (Exception ex) { } finally { if (null != client) client.Close(); } return ret; } /// <summary> /// // 串口或者网口发送数据 /// </summary> /// <exception cref="Exception"></exception> byte[] SendWaitResponse(byte[] data) { var requestStr = DataHelper.ToHexString(data, data.Length, true); DataMgr.MainUI.AddMessage("C -> S: " + requestStr); byte[] ret = null; using (var client = new TcpClient(ip, port)) { client.SendTimeout = SendTimeout; client.ReceiveTimeout = ReceiveTimeout; var netstream = client.GetStream(); // netstream.Write(data, 0, data.Length); // byte[] temp = new byte[2048]; int recvnum = netstream.Read(temp, 0, temp.Length); if (recvnum == 0) { throw new Exception("数据接收超时"); } ret = new byte[recvnum]; Array.Copy(temp, 0, ret, 0, recvnum); } var responseStr = DataHelper.ToHexString(ret, ret.Length, true); DataMgr.MainUI.AddMessage("S -> C: " + responseStr); return ret; } public bool[] ReadBool(string address, int length) { Console.WriteLine($"ReadBool,address={address},length={length}"); MelsecMcDataType Content1 = MelsecMcDataType.M; ushort Content2 = 0; FxAnalysisAddress(address, ref Content1, ref Content2); // 地址转换 ushort content = Content2; if (Content1 == MelsecMcDataType.M) { content = ((content < 8000) ? ((ushort)((int)content / 8 + 256)) : ((ushort)((content - 8000) / 8 + 480))); } else if (Content1 == MelsecMcDataType.X) { content = (ushort)((int)content / 8 + 128); } else if (Content1 == MelsecMcDataType.Y) { content = (ushort)((int)content / 8 + 160); } else if (Content1 == MelsecMcDataType.S) { content = (ushort)((int)content / 8); } else if (Content1 == MelsecMcDataType.CS) { content = (ushort)((int)content / 8 + 448); } else if (Content1 == MelsecMcDataType.CC) { content = (ushort)((int)content / 8 + 960); } else if (Content1 == MelsecMcDataType.TS) { content = (ushort)((int)content / 8 + 192); } else { if (Content1 != MelsecMcDataType.TC) { throw new Exception("当前的类型不支持位读写"); } content = (ushort)((int)content / 8 + 704); } var Content3 = (ushort)((int)Content2 % 8); ushort num = (ushort)((Content2 + length - 1) / 8 - (int)Content2 / 8 + 1); byte[] array = new byte[11] { 2, 48, SoftBasic.BuildAsciiBytesFrom(content)[0], SoftBasic.BuildAsciiBytesFrom(content)[1], SoftBasic.BuildAsciiBytesFrom(content)[2], SoftBasic.BuildAsciiBytesFrom(content)[3], SoftBasic.BuildAsciiBytesFrom((byte)num)[0], SoftBasic.BuildAsciiBytesFrom((byte)num)[1], 3, 0, 0 }; DataHelper.FxCalculateSum(array).CopyTo(array, 9); byte[] response = SendWaitResponse(array); // ********************** // Y005 or Y006 读取测试 //string responseStr = "02 36 30 03 36 39";// 5亮:02 32 30 03 36 35 // 5和6亮:02 36 30 03 36 39 //var response = DataHelper.ToHexByte(responseStr); var results = ExtractActualBoolData(response, Content3, length); return results; } public byte[] ReadWord(string address, ushort length, bool isNewVersion = false) { Console.WriteLine($"ReadWord,address={address},length={length}"); MelsecMcDataType Content1 = MelsecMcDataType.M; ushort Content2 = 0; FxAnalysisAddress(address, ref Content1, ref Content2); ushort content = Content2; if (Content1 == MelsecMcDataType.D) { content = ((content < 8000) ? (isNewVersion ? ((ushort)(content * 2 + 16384)) : ((ushort)(content * 2 + 4096))) : ((ushort)((content - 8000) * 2 + 3584))); } else if (Content1 == MelsecMcDataType.CN) { content = ((content < 200) ? ((ushort)(content * 2 + 2560)) : ((ushort)((content - 200) * 4 + 3072))); } else { if (Content1 != MelsecMcDataType.TN) { throw new Exception("当前的类型不支持字读写"); } content = (ushort)(content * 2 + 2048); } length = (ushort)(length * 2); byte[] array; if (isNewVersion) { array = new byte[13] { 2, 69, 48, 48, SoftBasic.BuildAsciiBytesFrom(content)[0], SoftBasic.BuildAsciiBytesFrom(content)[1], SoftBasic.BuildAsciiBytesFrom(content)[2], SoftBasic.BuildAsciiBytesFrom(content)[3], SoftBasic.BuildAsciiBytesFrom((byte)length)[0], SoftBasic.BuildAsciiBytesFrom((byte)length)[1], 3, 0, 0 }; DataHelper.FxCalculateSum(array).CopyTo(array, 11); } else { array = new byte[11] { 2, 48, SoftBasic.BuildAsciiBytesFrom(content)[0], SoftBasic.BuildAsciiBytesFrom(content)[1], SoftBasic.BuildAsciiBytesFrom(content)[2], SoftBasic.BuildAsciiBytesFrom(content)[3], SoftBasic.BuildAsciiBytesFrom((byte)length)[0], SoftBasic.BuildAsciiBytesFrom((byte)length)[1], 3, 0, 0 }; DataHelper.FxCalculateSum(array).CopyTo(array, 9); } //var request = DataHelper.ToHexString(array, array.Length, true); //DataMgr.MainUI.AddMessage("request:" + request); // 串口或者网口发送数据 // ..... // // ********************** // D123 读取测试 //string responseStr = "02 30 31 30 30 03 43 34";// 值为0:02 30 30 30 30 03 43 33 // 值为1:02 30 31 30 30 03 43 34 //var response = DataHelper.ToHexByte(responseStr); //DataMgr.MainUI.AddMessage(responseStr); var response = SendWaitResponse(array); var results = ExtractActualData(response); return results; } public void WriteBool(string address, bool value) { Console.WriteLine($"WriteBool,address={address},value={value}"); MelsecMcDataType Content1 = MelsecMcDataType.M; ushort Content2 = 0; FxAnalysisAddress(address, ref Content1, ref Content2); ushort content = Content2; if (Content1 == MelsecMcDataType.M) { content = ((content < 8000) ? ((ushort)(content + 2048)) : ((ushort)(content - 8000 + 3840))); } else if (Content1 == MelsecMcDataType.S) { content = content; } else if (Content1 == MelsecMcDataType.X) { content = (ushort)(content + 1024); } else if (Content1 == MelsecMcDataType.Y) { content = (ushort)(content + 1280); } else if (Content1 == MelsecMcDataType.CS) { content = (ushort)(content + 448); } else if (Content1 == MelsecMcDataType.CC) { content = (ushort)(content + 960); } else if (Content1 == MelsecMcDataType.CN) { content = (ushort)(content + 3584); } else if (Content1 == MelsecMcDataType.TS) { content = (ushort)(content + 192); } else if (Content1 == MelsecMcDataType.TC) { content = (ushort)(content + 704); } else { if (Content1 != MelsecMcDataType.TN) { // "当前的类型不支持位读写" return ; } content = (ushort)(content + 1536); } byte[] array = new byte[9] { 2, (byte)(value ? 55 : 56), SoftBasic.BuildAsciiBytesFrom(content)[2], SoftBasic.BuildAsciiBytesFrom(content)[3], SoftBasic.BuildAsciiBytesFrom(content)[0], SoftBasic.BuildAsciiBytesFrom(content)[1], 3, 0, 0 }; DataHelper.FxCalculateSum(array).CopyTo(array, 7); SendWaitResponse(array); } //public static void Write(string address, int value) //{ // Write(address, new int[1] { value }); //} //public static void Write(string address, int[] values) //{ // Write(address, ByteTransformBase.TransByte(values)); //} public void WriteWord(string address, byte[] value, bool isNewVersion = false) { Console.WriteLine($"WriteBytes,address={address},value={value}"); MelsecMcDataType Content1 = MelsecMcDataType.M; ushort Content2 = 0; FxAnalysisAddress(address, ref Content1, ref Content2); ushort content = Content2; if (Content1 == MelsecMcDataType.D) { content = ((content < 8000) ? (isNewVersion ? ((ushort)(content * 2 + 16384)) : ((ushort)(content * 2 + 4096))) : ((ushort)((content - 8000) * 2 + 3584))); } else if (Content1 == MelsecMcDataType.CN) { content = ((content < 200) ? ((ushort)(content * 2 + 2560)) : ((ushort)((content - 200) * 4 + 3072))); } else { if (Content1 != MelsecMcDataType.TN) { return;// 当前的类型不支持字读写 } content = (ushort)(content * 2 + 2048); } if (value != null) { value = SoftBasic.BuildAsciiBytesFrom(value); } byte[] array = null; if (isNewVersion) { array = new byte[13 + value.Length]; array[0] = 2; array[1] = 69; array[2] = 49; array[3] = 48; array[4] = SoftBasic.BuildAsciiBytesFrom(content)[0]; array[5] = SoftBasic.BuildAsciiBytesFrom(content)[1]; array[6] = SoftBasic.BuildAsciiBytesFrom(content)[2]; array[7] = SoftBasic.BuildAsciiBytesFrom(content)[3]; array[8] = SoftBasic.BuildAsciiBytesFrom((byte)(value.Length / 2))[0]; array[9] = SoftBasic.BuildAsciiBytesFrom((byte)(value.Length / 2))[1]; Array.Copy(value, 0, array, 10, value.Length); array[array.Length - 3] = 3; } else { array = new byte[11 + value.Length]; array[0] = 2; array[1] = 49; array[2] = SoftBasic.BuildAsciiBytesFrom(content)[0]; array[3] = SoftBasic.BuildAsciiBytesFrom(content)[1]; array[4] = SoftBasic.BuildAsciiBytesFrom(content)[2]; array[5] = SoftBasic.BuildAsciiBytesFrom(content)[3]; array[6] = SoftBasic.BuildAsciiBytesFrom((byte)(value.Length / 2))[0]; array[7] = SoftBasic.BuildAsciiBytesFrom((byte)(value.Length / 2))[1]; Array.Copy(value, 0, array, 8, value.Length); array[array.Length - 3] = 3; } DataHelper.FxCalculateSum(array).CopyTo(array, array.Length - 2); SendWaitResponse(array); } public static string CheckPlcReadResponse(byte[] ack) { if (ack.Length == 0) { return "接收的数据长度为0"; } if (ack[0] == 21) { return "PLC反馈的数据无效,Actual: " + SoftBasic.ByteToHexString(ack, ' '); } if (ack[0] != 2) { return "PLC反馈信号错误:" + ack[0] + " Actual: " + SoftBasic.ByteToHexString(ack, ' '); } if (!DataHelper.CheckSum(ack)) { return "PLC反馈报文的和校验失败!"; } return string.Empty; } public static byte[] ExtractActualData(byte[] response) { byte[] array = new byte[(response.Length - 4) / 2]; for (int i = 0; i < array.Length; i++) { byte[] bytes = new byte[2] { response[i * 2 + 1], response[i * 2 + 2] }; array[i] = Convert.ToByte(Encoding.ASCII.GetString(bytes), 16); } return array; } public static bool[] ExtractActualBoolData(byte[] response, int start, int length) { // 02 32 30 03 36 35 Data:20H -> 十进制32 -> 0010 0000 // 02 36 30 03 36 39 Data:60H -> 十进制96 -> 0110 0000 byte[] Content = ExtractActualData(response); bool[] arraybool = new bool[length]; bool[] array2 = SoftBasic.ByteToBoolArray(Content, Content.Length * 8);// false false false false true false false for (int i = 0; i < length; i++) { arraybool[i] = array2[i + start]; } return arraybool; } } }