C#上位机和松下PLC通讯

时间:2025-04-18 08:00:40
/// <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 }