C#上位机和松下PLC通讯
/// <summary>
/// 松下PLC Newtocol协议
/// </summary>
public class Panasonic_Newtocol : IDisposable
{
/// <summary>
/// 232串口对象
/// </summary>
public SerialPort serialPort { get; set; }
/// <summary>
/// 起始码%固定不变
/// </summary>
private const string headStr = "%";
/// <summary>
/// 目标站号 高位和低位
/// </summary>
private static string stationCode { get; set; }
/// <summary>
/// 分隔符#固定不变
/// </summary>
public const string fixCode = "#";
/// <summary>
/// 结束符\r 固定不变
/// </summary>
public const string endStr = "\r";
/// <summary>
/// 正确响应时的字符串内容
/// </summary>
private string successResponseHead = "";
/// <summary>
/// 失败响应时的字符串内容
/// </summary>
private string failResponseHead = "";
public double[] DTValue = null;
public bool[] arrXYMValue = null;
/// <summary>
/// 保存日志的委托
/// </summary>
/// <param name="logStr"></param>
public delegate void SaveLogForRecord(string logStr);
/// <summary>
/// 记录日志事件实现
/// </summary>
public event SaveLogForRecord SaveLog;
/// <summary>
/// 写入单触点标志位
/// </summary>
bool writeCoilSingleResult = false;
/// <summary>
/// 写入寄存器数据结果
/// </summary>
bool writeDataResult = false;
/// <summary>
/// 读取寄存器数据结果
/// </summary>
bool readDataResult = false;
/// <summary>
/// 读取多个/单个触点结果
/// </summary>
bool readCoilMany_SingleResult = false;
public Panasonic_Newtocol(PanasonicParaModel _serialPort)
{
stationCode = _serialPort.PlcStationNo.ToString("X2");
serialPort = new SerialPort()
{
PortName = _serialPort.PlcComNo.ToString(),
BaudRate = _serialPort.PlcBoardRate,
Parity = _serialPort.PlcParity,
DataBits = _serialPort.PlcDataLen,
StopBits = _serialPort.PlcStopBit,
ReceivedBytesThreshold = 8
};
successResponseHead = headStr + stationCode + "$";
failResponseHead = headStr + stationCode + "!";
SaveLog += Panasonic_Newtocol_SaveLog;
}
private void SerialPort_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
Thread.Sleep(50);//让缓存区数据接收完成
int n = serialPort.BytesToRead;
byte[] buf = new byte[n];
serialPort.Read(buf, 0, n);
string readText = Encoding.ASCII.GetString(buf).Replace("\r", "").Replace("\n", "");
if (readText.StartsWith("%") && readText.Length >= serialPort.ReceivedBytesThreshold)
{
#region 字节流数据正确
int len = readText.Length;
string sHead = readText.Substring(0, 4);
string sComm = readText.Substring(4, 2);
string sComm1 = readText.Substring(4, 3);
string sValues = "";
string tValue = "";
if (sHead == successResponseHead)
{
switch (sComm)
{
case "RD":
{
//%01$RD XXXX_XXXX_XXXX_XXXX_XXXX_XXXX 文本代码 BCC高低位 结束符
if (!readDataResult)
{
#region 读取D,转换为十进制
sValues = readText.Substring(6, len - 8);
DTValue = new double[sValues.Length / 4];
for (int i = 0; i < (sValues.Length / 4); i++)
{
tValue = sValues.Substring(i * 4, 4);
DTValue[i] = (double)Convert.ToInt32(tValue.Substring(2, 2) + tValue.Substring(0, 2), 16);
}
#endregion
readDataResult = true;
}
break;
}
case "RC":
{
if (!readCoilMany_SingleResult)
{
#region 读多个/单个触点
sValues = readText.Substring(6, len - 8);
arrXYMValue = new bool[sValues.Length];
for (int i = 0; i < arrXYMValue.Length; i++)
{
tValue = sValues.Substring(i, 1);
arrXYMValue[i] = tValue == "1";
}
#endregion
readCoilMany_SingleResult = true;
}
break;
}
case "WD":
{
//写入D成功,没返回值
if (!writeDataResult)
{
writeDataResult = true;
}
break;
}
case "WC":
{
//写入触点成功,没返回值
if (!writeCoilSingleResult)
{
writeCoilSingleResult = true;
}
break;
}
default:
break;
}
}
#endregion
}
}
#region 记录日志
/// <summary>
/// 记录日志
/// </summary>
/// <param name="logStr"></param>
private void Panasonic_Newtocol_SaveLog(string logStr)
{
System.Diagnostics.Debug.WriteLine(logStr);
}
#endregion
#region 打开串口连接
/// <summary>
/// 打开串口连接
/// </summary>
/// <returns></returns>
public bool OpenLinkSerial()
{
try
{
if (serialPort != null)
{
CloseLinkSerial();
Thread.Sleep(10);
serialPort.DataReceived += SerialPort_DataReceived;
serialPort.Open();
Panasonic_Newtocol_SaveLog("串口打开成功");
return true;
}
else
{
return false;
}
}
catch (Exception ex)
{
Panasonic_Newtocol_SaveLog(ex.StackTrace.ToString());
return false;
}
}
#endregion
#region 关闭串口连接
/// <summary>
/// 关闭串口连接
/// </summary>
public void CloseLinkSerial()
{
if (serialPort != null && serialPort.IsOpen)
{
serialPort.DiscardInBuffer();
serialPort.DiscardOutBuffer();
serialPort.Close();
}
}
#endregion
#region 写入单触点值
/// <summary>
/// 写入单触点值
/// </summary>
/// <param name="XYMAddr">例如R12 X触点 Y触点 M状态寄存器</param>
/// <param name="value">0=off 1=on</param>
/// <returns></returns>
public bool WriteCoilSingle(string XYMAddr, bool value)
{
writeCoilSingleResult = false;
string writeStr = WriteCoilSingle_CommStr(XYMAddr, value);
if (serialPort != null && serialPort.IsOpen)
{
serialPort.Write(writeStr);
int numPro = 0;
while (!writeCoilSingleResult && numPro < 100)
{
Thread.Sleep(1);
numPro++;
}
if (numPro < 100)
{
return true;
}
return false;
}
else
{
return false;
}
}
#endregion
#region 写入单触点值发送的字符串内容 WCS
/// <summary>
/// 写入单触点值发送的字符串内容 WCS X Y M
/// </summary>
/// <param name="XYMAddr">例如R12 X触点 Y触点 M状态寄存器</param>
/// <param name="value">0=off 1=on</param>
public string WriteCoilSingle_CommStr(string XYMAddr, bool value)
{
string commmandCode = "WCS";
//发送
string outStr = "";
string sReg = XYMAddr.Substring(0, 1);
string sAddr = XYMAddr.Substring(1).PadLeft(4, '0');
outStr = headStr + stationCode + fixCode + commmandCode + sReg + sAddr + string.Format(value ? "1" : "0");
outStr = outStr + Bcc(outStr) + endStr;
return outStr;
}
#endregion
#region 读单触点状态
/// <summary>
/// 读单触点状态
/// </summary>
/// <param name="XYMAddr">X Y M地址 比如M1</param>
/// <param name="value">读取的结果</param>
/// <returns>TRUE表示读取成功 false表示读取失败</returns>
public bool ReadCoilSingle(string XYMAddr, out bool value)
{
readCoilMany_SingleResult = false;
string writeStr = ReadCoilSingle_CommStr(XYMAddr);
if (serialPort != null && serialPort.IsOpen)
{
serialPort.Write(writeStr);
int numPro = 0;
while (!readCoilMany_SingleResult && numPro < 100)
{
Thread.Sleep(1);
numPro++;
}
if (numPro < 100)
{
value = arrXYMValue[0];
return true;
}
value = false;
return false;
}
else
{
value = false;
return false;
}
}
#endregion
#region 读单触点状态发送的字符串内容 RCS
/// <summary>
/// 读单触点状态发送的字符串内容 RCS
/// </summary>
/// <param name="XYMAddr">例如R12 X触点 Y触点 M状态寄存器</param>
/// <returns></returns>
public string ReadCoilSingle_CommStr(string XYMAddr)
{
string commmandCode = "RCS";
//发送
string outStr = "";
string sReg = XYMAddr.Substring(0, 1);
string sAddr = XYMAddr.Substring(1).PadLeft(4, '0');
outStr = headStr + stationCode + fixCode + commmandCode + sReg + sAddr;
outStr = outStr + Bcc(outStr) + endStr;
return outStr;
}
#endregion
#region 读多个单触点状态结果 RCP
/// <summary>
/// 读多个单触点状态结果 RCP
/// </summary>
/// <param name="startAddr"></param>
/// <param name="endAddr"></param>
/// <param name="value"></param>
/// <returns></returns>
public bool ReadManyCoilData(List<string> xymAddr, out bool[] value)
{
readCoilMany_SingleResult = false;
string writeStr = ReadCoilMany_CommStr(xymAddr);
value = new bool[xymAddr.Count];
for (int i = 0; i < value.Length; i++)
{
value[i] = false;
}
if (serialPort != null && serialPort.IsOpen)
{
serialPort.Write(writeStr);
int numPro = 0;
while (!readCoilMany_SingleResult && numPro < 100)
{
Thread.Sleep(1);
numPro++;
}
if (numPro < 100)
{
value = arrXYMValue;
Thread.Sleep(1);
return true;
}
return false;
}
else
{
return false;
}
}
#endregion
#region 读多个单触点状态发送的字符串内容 RCP
/// <summary>
/// 读多个单触点状态发送的字符串内容 RCP
/// </summary>
/// <param name="XYMAddr">长度为1-8的触点集合</param>
/// <returns></returns>
public string ReadCoilMany_CommStr(List<string> XYMAddr)
{
string commmandCode = "RCP";
//发送
string outStr = "";
outStr = headStr + stationCode + fixCode + commmandCode + XYMAddr.Count.ToString();
for (int i = 0; i < XYMAddr.Count; i++)
{
string sReg1 = XYMAddr[i].Substring(0, 1);
string sAddr1 = XYMAddr[i].Substring(1).PadLeft(4, '0');
outStr += sReg1 + sAddr1;
}
outStr = outStr + Bcc(outStr) + endStr;
return outStr;
}
#endregion
#region 读取数据寄存器值
/// <summary>
/// 读取数据寄存器值
/// </summary>
/// <param name="startAddr"></param>
/// <param name="endAddr"></param>
/// <param name="value"></param>
/// <returns></returns>
public bool ReadDT_Data(string startAddr, string endAddr, out double[] value)
{
readDataResult = false;
string writeStr = ReadData_CommStr(startAddr, endAddr);
string sAddr1 = startAddr.Substring(1).PadLeft(5, '0');
string sAddr2 = endAddr.Substring(1).PadLeft(5, '0');
int readLength = int.Parse(sAddr2) - int.Parse(sAddr1) + 1;
value = new double[readLength];
for (int i = 0; i < value.Length; i++)
{
value[i] = 0;
}
if (serialPort != null && serialPort.IsOpen)
{
serialPort.Write(writeStr);
int numPro = 0;
while (!readDataResult && numPro < 100)
{
Thread.Sleep(1);
numPro++;
}
if (numPro < 100)
{
value = DTValue;
return true;
}
return false;
}
else
{
return false;
}
}
#endregion
#region 读取数据寄存器值发送的字符串内容 RD
/// <summary>
/// 读取数据寄存器值发送的字符串内容 RD
/// </summary>
/// <param name="startAddr">起始地址D/L/F XXXX</param>
/// <param name="endAddr">结束地址D/L/F XXXX</param>
/// <returns></returns>
public string ReadData_CommStr(string startAddr, string endAddr)
{
string commmandCode = "RD";
//发送
string outStr = "";
string sReg = startAddr.Substring(0, 1);
string sAddr1 = startAddr.Substring(1).PadLeft(5, '0');
string sAddr2 = endAddr.Substring(1).PadLeft(5, '0');
outStr = headStr + stationCode + fixCode + commmandCode + sReg + sAddr1 + sAddr2;
outStr = outStr + Bcc(outStr) + endStr;
return outStr;
}
#endregion
#region 写入数据寄存器值
/// <summary>
/// 写入数据寄存器值
/// </summary>
/// <param name="startAddr">D/L/F XXXX</param>
/// <param name="endAddr">D/L/F XXXX</param>
/// <param name="writeContent">写入的数据内容</param>
/// <returns></returns>
public bool WriteData(string startAddr, string endAddr, int[] writeContent)
{
writeDataResult = false;
string writeStr = WriteData_CommStr(startAddr, endAddr, writeContent);
if (serialPort != null && serialPort.IsOpen)
{
serialPort.Write(writeStr);
int numPro = 0;
while (!writeDataResult && numPro < 100)
{
Thread.Sleep(1);
numPro++;
//等待正确响应 或者超时
}
if (numPro < 100)
{
return true;
}
return false;
}
else
{
return false;
}
}
#endregion
#region 写入数据寄存器值发送的字符串内容 WD
/// <summary>
/// 写入数据寄存器值发送的字符串内容 WD
/// 写入的数值是按字写入,也就是说,每个值占2个字节
/// 低位在前,高位在后。
/// </summary>
/// <param name="startAddr">D/L/F XXXX</param>
/// <param name="endAddr">D/L/F XXXX</param>
/// <param name="writeContent">写入的数据内容</param>
/// <returns></returns>
public string WriteData_CommStr(string startAddr, string endAddr, int[] writeContent)
{
string commmandCode = "WD";
//发送
string outStr = "";
string sReg = startAddr.Substring(0, 1);
string sAddr1 = startAddr.Substring(1).PadLeft(5, '0');
string sAddr2 = endAddr.Substring(1).PadLeft(5, '0');
outStr = headStr + stationCode + fixCode + commmandCode + sReg + sAddr1 + sAddr2;
int writeLength = int.Parse(sAddr2) - int.Parse(sAddr1) + 1;
if (writeLength == 1)
{
string strTemp = ConvertShortToPlcFormat(writeContent[0]);
outStr += strTemp + strTemp;
}
else
{
for (int i = 0; i < writeLength; i++)
{
string strTemp = ConvertShortToPlcFormat(writeContent[i]);
outStr += strTemp;
}
}
outStr = outStr + Bcc(outStr) + endStr;
return outStr;
}
#endregion
#region 写入字单位的触点的状态信息发送的字符串内容 WCC
/// <summary>
/// 写入字单位的触点的状态信息发送的字符串内容 WCC
/// </summary>
/// <param name="startAddr">起始地址</param>
/// <param name="endAddr">结束地址</param>
/// <param name="writeValues">写入的值数组 1=on 0=off</param>
/// <returns></returns>
public string WriteCoilCoils_CommStr(string startAddr, string endAddr, bool[] writeValues)
{
string commmandCode = "WCC";
//发送
string outStr = "";
string sReg = startAddr.Substring(0, 1);//Y R L
string sAddr1 = startAddr.Substring(1).PadLeft(4, '0');
string sAddr2 = endAddr.Substring(1).PadLeft(4, '0');
outStr = headStr + stationCode + fixCode + commmandCode + sReg + sAddr1 + sAddr2;
int readLength = int.Parse(sAddr2) - int.Parse(sAddr1) + 1;
for (int i = 0; i < readLength; i++)
{
string strTemp = ConvertShortToPlcFormat(writeValues[i] ? 1 : 0);
outStr += strTemp;
}
outStr = outStr + Bcc(outStr) + endStr;
return outStr;
}
#endregion
#region 读取字单位的触点的状态信息发送的字符串内容 RCC
/// <summary>
/// 读取字单位的触点的状态信息发送的字符串内容 RCC
/// </summary>
/// <param name="startAddr">M1</param>
/// <param name="endAddr">M4</param>
/// <returns></returns>
public string ReadCoilCoils_CommStr(string startAddr, string endAddr)
{
string commmandCode = "RCC";
//发送
string outStr = "";
string sReg = startAddr.Substring(0, 1);
string sAddr1 = startAddr.Substring(1).PadLeft(4, '0');
string sAddr2 = endAddr.Substring(1).PadLeft(4, '0');
outStr = headStr + stationCode + fixCode + commmandCode + sReg + sAddr1 + sAddr2;
outStr = outStr + Bcc(outStr) + endStr;
return outStr;
}
#endregion
#region 读多个触点发送的字符串内容
/// <summary>
/// 读多个触点发送的字符串内容
/// </summary>
/// <param name="XYMAddr"></param>
/// <returns></returns>
public string ReadCoilPlural_CommStr(string XYMAddr)
{
string commmandCode = "RCP";
//发送
string outStr = "";
string sReg = XYMAddr.Substring(0, 1);
string sAddr = XYMAddr.Substring(1).PadLeft(4, '0');
outStr = headStr + stationCode + fixCode + commmandCode + sReg + sAddr;
outStr = outStr + Bcc(outStr) + endStr;
return outStr;
}
#endregion
#region 获得BCC校验码
/// <summary>
/// BCC校验码
/// 计算方式是将指令中的各个ASCII字符的16进制(00~FF)进行异或求和后生成的.
/// 该校验码也以两个ASCII码字符表示(高位在前,低位在后)
/// </summary>
/// <param name="cmd"></param>
/// <returns></returns>
public static string Bcc(string cmd)
{
cmd = cmd.Trim();
byte bcc = 0;
byte[] cmdArr = System.Text.Encoding.ASCII.GetBytes(cmd);
for (int i = 0; i < cmdArr.Length; i++)
{
bcc = (byte)(bcc ^ cmdArr[i]);
}
return bcc.ToString("X2");
}
#endregion
#region ASC
/// <summary>
/// ASC
/// </summary>
/// <param name="cmd"></param>
/// <returns></returns>
public static int Asc(string cmd)
{
if (cmd.Length == 1)
{
ASCIIEncoding ascii = new ASCIIEncoding();
int intAscii = (int)ascii.GetBytes(cmd)[0];
return intAscii;
}
else
{
return -1;
}
}
#endregion
#region 将整形转换为16进制 然后低位在前 高位在后
/// <summary>
/// 将整形转换为16进制 然后低位在前 高位在后
/// </summary>
/// <param name="value"></param>
/// <returns></returns>
private string ConvertShortToPlcFormat(int value)
{
string temp = value.ToString("X4");
return temp.Substring(2, 2) + temp.Substring(0, 2);
}
#endregion
#region 释放
public void Dispose()
{
if (serialPort != null)
{
if (serialPort.IsOpen)
{
serialPort.Close();
Thread.Sleep(10);
serialPort.Dispose();
}
}
}
#endregion
#region 获得所有串口名数组
/// <summary>
/// 所有串口名数组
/// </summary>
/// <returns></returns>
public static List<string> GetPortsName()
{
return SerialPort.GetPortNames().ToList<string>();
}
#endregion
}