设备信息



测试结果
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;
}
}
}

![“OSError: [WinError 1455]页面文件太小,无法完成操作。”解决方案](https://img-blog.csdnimg.cn/6862885665e44cb385ac97262d9d0434.png)







![【LeetCode】数据结构题解(13)[设计循环链表]](https://img-blog.csdnimg.cn/ff2fb1c912194d748ab01f8ad1bdf91d.png)









