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协议
至于协议文档,大家可以去官方或者百度查询了解,该协议由以下特点:
- 数据传输采用ASCII的形式。
- 应答式协议,首先由工控机发送指令,然后PLC会自动对指令进行响应。也就是说,不需要编写任何PLC程序,只要PLC和工控机连接正常,工控机给PLC发送指令,都能得到PLC的响应回复。
4、上位机发送数据帧格式
指令是以帧为单位进行,工控机向PLC发送命令帧,然后PLC作出响应,向工控机发送响应帧。
格式内容如下:
其中:
- %为起始码,这是固定不变的。
- AD(H)和 AD(L)是目标站号的高位和低位。一般如果只有一个PLC的话,那么就填写01,高位是0,低位是1。
- #也是固定不变的。
- 指令代码。每个指令会有不同的指令代码,下面会讲。
文本代码。指令的内容,不同的指令,内容也不同。 - BCC(H)和BCC(L),是帧的数据校验的高低位,数据校验范围是BCC前面的所有字符;下面我们会展开介绍说明。
- 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);
}
#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
}
11、项目案例
以下是PLC定义的值表内容
来看看我的项目截图
12、总结
只要我们明白NewTocol协议的机制和内容,其他的逻辑部分的处理对于我们程序猿来说就是洒洒水的事的,祝大家看完我的博客可以有所收获,也欢迎大家踊跃来交流交流技术。
ps:部分截图和文字内容来自网上,程序是自己写的,如有侵权,请告知删除,写写。