C#上位机和松下PLC通讯

1、前言
也好久没回来看博客了,看大家对于其他的PLC的需求都是有的,说明搞我们工业控制这一行的人也是很多的。刚好最近基于项目需要,一些新的东西,需要和松下PLC的FP系列的CPU进行通讯然后使用松下的NewTocol协议进行通讯,对对应的寄存器/线圈进行读写操作。于是,就随笔记录下相应的程序代码。
松下FP系列PLC与工控机之间的通信方式可以采用串口通信,与工控机连接的RS232电缆(长度有限485的长度会比较长很多)必须按照松下的产品手册所给的连线图进行制作,否则通信将无法实现。电缆连线图如下:

至于怎么接线,需要的可以去了解下,不过一般在项目过程中,PLC的编程人员会把线接好,直接接入到工控机就可以使用了。
既然是串口通讯,我们就需要了解下串口通讯的一些概念。我们往下走。
2、概念
2.1 波特率
在电子通信领域,波特(Baud)即调制速率,指的是有效数据讯号调制载波的速率,即单位时间内载波调制状态变化的次数。通俗一点,就是一个单位时间内数据传递的符号的个数
2.2 奇偶校验位
奇偶校验位 (Parity)通常用在数据通信中来保证数据的有效性。分为偶校验、奇校验、或非校验。如果偶校验在使用,校验位将这些位置为偶数;如果奇校验在使用,校验位将这些位置为奇数。
2.3 数据位
数据位一般为8位一个字节的数据(也有6位、7位的情况),低位(LSB)在前,高位(MSB)在后。
2.4 停止位
用于表示单个包的最后一位。典型的值为1,1.5和2位。
2.5 SerialPort类
需要引入System.IO.Port命名空间。

3、NewTocol协议
至于协议文档,大家可以去官方或者百度查询了解,该协议由以下特点:

  1. 数据传输采用ASCII的形式。
  2. 应答式协议,首先由工控机发送指令,然后PLC会自动对指令进行响应。也就是说,不需要编写任何PLC程序,只要PLC和工控机连接正常,工控机给PLC发送指令,都能得到PLC的响应回复。

4、上位机发送数据帧格式
指令是以帧为单位进行,工控机向PLC发送命令帧,然后PLC作出响应,向工控机发送响应帧。
格式内容如下:
其中:

  1. %为起始码,这是固定不变的。
  2. AD(H)和 AD(L)是目标站号的高位和低位。一般如果只有一个PLC的话,那么就填写01,高位是0,低位是1。
  3. #也是固定不变的。
  4. 指令代码。每个指令会有不同的指令代码,下面会讲。
    文本代码。指令的内容,不同的指令,内容也不同。
  5. BCC(H)和BCC(L),是帧的数据校验的高低位,数据校验范围是BCC前面的所有字符;下面我们会展开介绍说明。
  6. CR,回车键,ASCII为0x0D,不可见字符。

5、PLC响应帧格式
响应帧有两种,一种是正确响应,一种是错误响应。也就是说,如果工控机给PLC发送的指令是正确的,那么PLC就会返回正确的响应帧,否则就返回错误的响应帧。
6、工作站的配置
以下是电气工程师提供的PLC配置图为例,我们可以看到PLC内部的设备预设站号是1,那么对于上位机而言目标站号就是1,此配置可以找电气工程师告知即可。
7、指令代码
工控机可以给PLC发送的指令一共有20多种,不过我们常用的指令一般有9种。如下图所示:

其中常用的:

8、BCC的计算方式
BCC校验码的计算方式是将指令中的各个ASCII字符的16进制(00~FF)进行异或求和后生成的. 该校验码也以两个ASCII码字符表示(高位在前,低位在后)。
例如这条指令:
%01#RCSX00001DCR
注意:CR不是两个字符,是一个字符,回车键,但是是不可显示字符,所以这里用CR来表示。
计算方式:
在此贴上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");}

9、常用指令举例
例1:写入单触点状态(指令代码:WCS)
例如我们往触点R12写入1,则命令帧为:
%01#WCSR0012120CR
拆开成各个部分:% 01 # WCS R 0012 1 20 CR
正常通信情况下,PLC会返回正确的响应帧:%01$WC14CR

例2:读取单触点状态(指令代码:RCS)


例如我们读取触点R12的值,则命令帧为:
%01#RCSR001214CR
拆分成各个部分:% 01 # RCS R 0012 14 CR
正常通信情况下,假如R12触点的值为1,那么PLC返回的响应帧为
%01$RC120**CR
例3:写入数据寄存器值(指令代码:WD)

例如我们写入字数值到PLC的 DT1到DT3,其中:DT1=05H,DT2=1507H,DT3=900H,则命令帧为:
这里需要注意的是:
写入的数值是按字写入,也就是说,每个值占2个字节。
低位在前,高位在后。
写入的字符是16进制的。
所以,需要代码进行处理。例如此处的DT2=1507H,1507H的10进制值是5383,则处理步骤为:
先将5383转换为16进制的字符1507
将字符转换顺序,低位在前,高位在后,也就是转换成:0715
我们写一个函数来进行处理:

private string ConvertShortToPlcFormat(short value){string temp = value.ToString("X4");return temp.Substring(2, 2) + temp.Substring(0, 2);}

正常通信情况下,PLC的响应帧为:
10、代码实现
1、先定义松下通讯配置用的数据模型
以下是配置文件内容和模型代码:

 public class PanasonicParaModel{/// <summary>/// 名称/// </summary>public string PlcName { get; set; }/// <summary>/// 站号/// </summary>public int PlcStationNo { get; set; }/// <summary>/// COM口号 比如COM1/// </summary>public string PlcComNo { get; set; }/// <summary>/// 波特率/// </summary>public int PlcBoardRate { get; set; }/// <summary>/// 奇偶校验1奇校验 2偶校验 0不校验 /// </summary>public Parity PlcParity { get; set; }/// <summary>/// 数据位 正常是8/// </summary>public int PlcDataLen { get; set; }/// <summary>/// 停止位1 2 1.5 默认是1/// </summary>public StopBits PlcStopBit { get; set; }/// <summary>/// 连接标志位/// </summary>public bool LinkRes { get; set; }}

2、定义一个串口帮助方法类

 public class SerialPortHelper{#region 单例实现private static SerialPortHelper instance = null;private Dictionary<int, SerialPort> _dicSerialPort = null;  //保存串口对象的集合private SerialPortHelper(){if (this._dicSerialPort == null){this._dicSerialPort = new Dictionary<int, SerialPort>();}}public static SerialPortHelper Instance{get{if (instance == null){instance = new SerialPortHelper();}return instance;}}#endregion#region 获得奇偶校验/// <summary>/// 获得奇偶校验/// </summary>/// <param name="num"></param>/// <returns></returns>public static Parity GetParityByConfigNum(int num){switch(num){case 1:return Parity.Odd;case 2:return Parity.Even;case 0:default:return Parity.None;}}#endregion#region 获得停止位/// <summary>/// 获得停止位/// </summary>/// <param name="num"></param>/// <returns></returns>public static StopBits GetStopBitByConfigNum(double num){switch (num){case 1:return StopBits.One;case 2:return StopBits.Two;case 1.5:return StopBits.OnePointFive;default:return StopBits.One;}}#endregion}

3、定义连接对象类
话不多说了,直接上代码

     /// <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);}#endregionreadDataResult = 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";}#endregionreadCoilMany_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 Lstring 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   }

11、项目案例
以下是PLC定义的值表内容
来看看我的项目截图


12、总结
只要我们明白NewTocol协议的机制和内容,其他的逻辑部分的处理对于我们程序猿来说就是洒洒水的事的,祝大家看完我的博客可以有所收获,也欢迎大家踊跃来交流交流技术。

ps:部分截图和文字内容来自网上,程序是自己写的,如有侵权,请告知删除,写写。

C#上位机和松下PLC通讯相关推荐

  1. wincc上位机与1200组态步骤_组态上位机WINCC与PLC通讯连接

    WINCC 与 PLC 通讯连接 1 . STEP 7 硬件组态 STEP7 设置 MPI 通讯, 2 .安装 CP5611 通讯板卡 3 .添加驱动程序和系统参数设置 打开 WINCC 工程在 Ta ...

  2. 上位机与PLC 通讯源码 上位机与三菱PLC,西门子PLC通讯 同时一起通讯,单独控制,三菱采用官方MX 通讯,支持三菱FX系列,

    上位机与PLC 通讯源码 上位机与三菱PLC,西门子PLC通讯 同时一起通讯,单独控制,三菱采用官方MX 通讯,支持三菱FX系列,A系列,Q系列,L系列,R系列,全系系列,各种串口和各种网口通讯,, ...

  3. 上位机与PLC 通讯源码 上位机与三菱PLC,西门子PLC通讯

    上位机与PLC 通讯源码 上位机与三菱PLC,西门子PLC通讯 同时一起通讯,单独控制,三菱采用官方MX 通讯,支持三菱FX系列,A系列,Q系列,L系列,R系列,全系系列,各种串口和各种网口通讯,, ...

  4. 上位机与PLC 通讯源码DEMO 上位机与三菱PLC,西门子PLC通讯 同时一起通讯,单独控制

    上位机与PLC 通讯源码DEMO 上位机与三菱PLC,西门子PLC通讯 同时一起通讯,单独控制,三菱采用官方MX 通讯,支持三菱FX系列,A系列,Q系列,L系列,R系列,全系系列,各种串口和各种网口通 ...

  5. 【无标题】C#上位机与三菱PLC FX2NC通讯方法

    *** C#上位机与三菱PLC FX2NC通讯方法*** 第一步从官网中下载MX COMPONENT并安装好; 第二步从安装好的MX COMPONENT路径中找到"Samples" ...

  6. S7300与G120变频器控制上位机 C#与S7300通讯上位机

    S7300与G120变频器控制上位机 C#与S7300通讯上位机 提供给需要学习的同学,C#读取,经过xml配置文件读取进行定义,G120使用说明,写入S7300控制西门子G120源代码,手动控制正反 ...

  7. VB.NET上位机和西门子PLC通信模块程序

    VB.NET上位机和西门子PLC通信模块程序 新项目可直接调用模块! 可创建与PLC对应的变量表! 项目直接访问变量,省去中间转化步骤! 目前支持常用的布尔.字符.整数.浮点数变量! 模块中带有参数设 ...

  8. 先学习上位机编程还是PLC编程

    当我们们谈到学习自动化编程时,可能会面临一个选择:学习上位机编程还是PLC编程?针对这个问题,我想说,这两种编程方式之间没有直接的联系.PLC编程是基于梯形图的编程方式,而上位机编程则是基于代码的编程 ...

  9. 上位机与fx3u通讯串口_c#上位机与三菱PLC(FX3U)串口通讯

    项目中会经常用到上位机与PLC之间的串口通信,本文介绍一下C#如何编写上位机代码 与三菱FX3U进行通讯 1. 第一种方法是自己写代码实现,主要代码如下: //对PLC的Y7进行置1 byte[] Y ...

最新文章

  1. ScrollView 嵌套 ListView 只显示第一行的源码分析
  2. Spring Boot 整合 Elasticsearch,实现 function score query 权重分查询
  3. 利用BeanUtils在对象间复制属性
  4. OSS 下载延迟或超时
  5. 数据中心的未来:一体化无人值守
  6. 为防止员工带薪拉屎,快手公司推出“计时厕所”?官方称只是为了测试!
  7. 1.2.5 计算机系统的多级层次结构(硬联逻辑级、微程序级、传统机器级、操作系统级、汇编语言级、高级语言级、应用语言级)
  8. Jira Concept- Issues
  9. GDOI2020游记
  10. linux umount 时出现device is busy 的处理方法--fuser
  11. o oia ospf 路由优先_乾颐堂军哥HCIE课程4-OSPF的3、4、5类LSA以及虚连接和转发地址的理论与实践...
  12. 通过代码学 Sutton 强化学习:SARSA、Q-Learning 时序差分算法训练 CartPole
  13. JSR-303 数据校验学习
  14. svn 把本地的项目,上传到服务器端
  15. 关于微信隐藏分享按钮的心得
  16. java文件下载文件损坏_java上传并下载以及解压zip文件有时会报文件被损坏错误分析以及解决...
  17. vue中如何设置鼠标经过切换样式
  18. html图片缩放全部显示不全,100% width CSS 在缩小/放大窗口时候内容被截断或显示不全...
  19. 全国院线总票房破50亿!影院复工后,哪些电影最受欢迎?可视化案例
  20. ReadFile WriteFile DO_DIRECT_IO 中的IRP操作

热门文章

  1. Navicat链接数据库奇葩错误
  2. MySql的数据找回
  3. Virtualbox虚拟机网络配置(NAT + Host-only - Bridged) 实现主机->虚拟机,虚拟机->虚拟机
  4. 关于等号数量判断的问题
  5. 常见的几种最优化方法Matlab原理和深度分析
  6. 【引用】稳压二极管扩流电路
  7. 实现微信跳转外部浏览器从而生成微信跳转链接
  8. 网站架构之缓存应用(3)实现篇
  9. oracle sys dba 01031,SYS用户登录Oracle报错ORA-01031: insufficient privileges
  10. 英语语音语调练习-American Accent Training(PDF+CD1-5下载)