PLC通讯实现-C#实现欧姆龙串口通讯HostLink(一)
程序员文章站
2024-03-17 12:17:46
...
PLC通讯实现-C#实现欧姆龙串口通讯HostLink(一)
背景
本人近十年的工作都与工业软件相关、其中工控系统开发过程中有一个必要环节就是跟各大厂商的PLC进行通讯,而对于从互联网行业跨入工业互联网行业的从业人员来说要实现各型号PLC通讯还是需要一个过程的,本人在此对主流型号PLC通讯实现进行总结以便大家参考。
抽象设计
首先我们要进行一下抽象设计,先设计一个抽象类(接口也可以,此处因为还有其他业务使用了抽象类)BaseEquip,对PLC的常规操作进行定义,即Open、Read、Write、Close,业务代码调用BaseEquip进行PLC的读写,然后在实现各型号的Equip类,对Open、Read、Write、Close进行实现,根据配置在业务代码中对BaseEquip进行实例化,这样后期更改PLC型号后,只需修改配置即可,不用修改业务代码。
欧姆龙串口通讯实现HostLink
实现语言C#
抽象基类BaseEquip
public class BaseEquip
{
/// <summary>
/// 打开设备
/// </summary>
/// <returns>成功返回true,失败返回false</returns>
public abstract bool Open();
/// <summary>
/// 读取信息
/// </summary>
/// <param name="block">数据块</param>
/// <param name="start">起始地址</param>
/// <param name="len">长度</param>
/// <param name="buff">读取返回信息</param>
/// <returns>成功返回true,失败返回false</returns>
public abstract bool Read(string block, int start, int len, out object[] buff);
/// <summary>
/// 写入信息
/// </summary>
/// <param name="block">数据块</param>
/// <param name="start">起始地址</param>
/// <param name="buff">要写入的数据</param>
/// <returns>成功返回true,失败返回false</returns>
public abstract bool Write(int block, int start, object[] buff);
/// <summary>
/// 关闭设备
/// </summary>
public abstract void Close();
}
设备实现类Equip实现
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Mesnac.Equips;
namespace Mesnac.Equip.OMRON.HostLink.COM
{
/// <summary>
/// 三菱通讯封装设备类
/// </summary>
public class Equip : BaseEquip
{
private SerialPortFactory _serialPortFactory = null; //SerialPortFactory.GetInstance("00", "COM3", 9600, "Even", 7, "2", 1024);
private bool _isOpen = false; //是否打开连接
/// <summary>
/// 获取串口工厂
/// </summary>
/// <returns></returns>
private SerialPortFactory GetSerialPortFactory()
{
Mesnac.Equips.Connection.COM.ConnType connType = (Mesnac.Equips.Connection.COM.ConnType)this.Main.ConnType;
string stationNum = connType.StationNum;
string portName = connType.PortName;
int baudRate = connType.BaudRate;
string parity = connType.Parity;
int dataBits = connType.DataBits;
string stopBits = connType.StopBits;
int buffSize = connType.BuffSize;
this._serialPortFactory = SerialPortFactory.GetInstance(stationNum, portName, baudRate, parity, dataBits, stopBits, buffSize);
return this._serialPortFactory;
}
/// <summary>
/// 打开PLC设备
/// </summary>
/// <returns></returns>
public override bool Open()
{
lock (this)
{
try
{
if (this._isOpen == true || (_serialPortFactory != null && _serialPortFactory.MainSerialPort.IsOpen))
{
return true;
}
this.State = false;
this._serialPortFactory = this.GetSerialPortFactory();
this._serialPortFactory.MainSerialPort.Open();
if (!_serialPortFactory.MainSerialPort.IsOpen)
{
ICSharpCode.Core.LoggingService.Warn("PLC连接失败:串口未打开失败!");
this.State = false;
return this.State;
}
else
{
this.State = true;
this._isOpen = true;
//Console.WriteLine("连接成功!");
return this.State;
}
}
catch (Exception ex)
{
this.State = false;
this._isOpen = false;
Console.WriteLine(ex.Message);
return this.State;
}
}
}
/// <summary>
/// PLC数据读取方法
/// </summary>
/// <param name="block">要读取的块号</param>
/// <param name="start">要读取的起始字</param>
/// <param name="len">要读取的长度,最大255,超过255则不读取</param>
/// <param name="buff"></param>
/// <returns></returns>
public override bool Read(string block, int start, int len, out object[] buff)
{
lock (this)
{
buff = new object[len];
try
{
if (len > 256)
{
for (int i = 0; i < len; i++)
{
buff[i] = 0;
}
base.State = false;
return false;
}
int maxOneLen = 20; //单次允许读取的最大长度,欧姆龙限制为20个字
int count = len / maxOneLen; //要读取的次数
int mod = len % maxOneLen; //剩余的长度
bool flag = true; //保存读取标志
for (int i = 0; i < count; i++)
{
object[] _buff = new object[maxOneLen];
flag = this.ReadByLen(block, start + i * maxOneLen, maxOneLen, out _buff);
if (flag == false)
{
base.State = flag;
return false;
}
for (int k = i * maxOneLen; k < (i + 1) * maxOneLen; k++)
{
buff[k] = _buff[k - i * maxOneLen];
}
}
if (mod > 0)
{
object[] _buff = new object[mod];
flag = this.ReadByLen(block, start + count * maxOneLen, mod, out _buff);
if (flag == false)
{
base.State = flag;
return false;
}
for (int k = count * maxOneLen; k < count * maxOneLen + mod; k++)
{
buff[k] = _buff[k - count * maxOneLen];
}
}
base.State = flag;
return flag;
}
catch (Exception ex)
{
ICSharpCode.Core.LoggingService.Error(String.Format("读取PLC(OMRON)设备失败-({0})!", ex.Message));
base.State = false;
return false;
}
}
}
/// <summary>
/// 单次读取最长20个字的方法
/// </summary>
/// <param name="block">块号</param>
/// <param name="start">起始字</param>
/// <param name="len">长度,最长不超过100</param>
/// <param name="buff">数据缓冲区,存放读取的数据</param>
/// <returns>读取成功返回true,读取失败返回false</returns>
private bool ReadByLen(string block, int start, int len, out object[] buff)
{
lock (this)
{
buff = new object[len];
if (!this.Open())
{
return false;
}
int state = len;
int[] _buff = new int[len];
int iblock = Convert.ToInt32(block);
//iblock = iblock + start;
string result = this._serialPortFactory.Read(iblock, start, len, out _buff);
if (result != "00")
{
ICSharpCode.Core.LoggingService.Warn("PLC读取失败:" + this.GetErrInfo(result));
this.State = false;
return false;
}
else
{
this.State = true;
}
int iReadLen = len;
if (iReadLen > state)
{
iReadLen = state;
}
for (int i = 0; i < iReadLen; i++)
{
int value = _buff[i];
if (value > ushort.MaxValue)
{
value = ushort.MaxValue - value;
}
buff[i] = value;
}
return true;
}
}
private ushort ToValue(object obj, ushort defaultValue)
{
ushort result = 0;
if (obj != null
&& obj != DBNull.Value
&& ushort.TryParse(obj.ToString(), out result))
{
return result;
}
return defaultValue;
}
/// <summary>
/// PLC数据写入方法
/// </summary>
/// <param name="block">要写入的块号</param>
/// <param name="start">起始字</param>
/// <param name="buff">要写入PLC的数据</param>
/// <returns>写入成功返回true,失败返回false</returns>
public override bool Write(int block, int start, object[] buff)
{
lock (this)
{
try
{
if (buff.Length > 256)
{
return false;
}
int len = buff.Length;
int maxOneLen = 25; //单次允许读取的最大长度,OMRON限制为25个字
int count = len / maxOneLen; //要读取的次数
int mod = len % maxOneLen; //剩余的长度
bool flag = true; //保存写入标志
for (int i = 0; i < count; i++)
{
object[] _buff = new object[maxOneLen];
for (int k = i * maxOneLen; k < (i + 1) * maxOneLen; k++)
{
_buff[k - i * maxOneLen] = buff[k];
}
flag = this.WriteMax25(block, start + i * maxOneLen, _buff);
if (flag == false)
{
return false;
}
}
if (mod > 0)
{
object[] _buff = new object[mod];
for (int k = count * maxOneLen; k < count * maxOneLen + mod; k++)
{
_buff[k - count * maxOneLen] = buff[k];
}
flag = this.WriteMax25(block, start + count * maxOneLen, _buff);
}
return flag;
}
catch (Exception ex)
{
ICSharpCode.Core.LoggingService.Error(String.Format("写入PLC(OMRON)设备失败-({0})!", ex.Message));
//Console.WriteLine(ex.Message);
return false;
}
}
}
/// <summary>
/// 单次写入最多25个字至PLC
/// </summary>
/// <param name="block">要写入的块号</param>
/// <param name="start">要写入的起始字</param>
/// <param name="buff">要写入的数据</param>
/// <returns>写入成功返回true,失败返回false</returns>
private bool WriteMax25(int block, int start, object[] buff)
{
lock (this)
{
if (!this.Open())
{
return false;
}
int state = buff.Length;
int[] _buff = new int[buff.Length];
for (int i = 0; i < buff.Length; i++)
{
int value = 0;
int.TryParse(buff[i].ToString(), out value);
_buff[i] = value;
}
string result = this._serialPortFactory.Write(block, start, _buff);
if (result != "00")
{
ICSharpCode.Core.LoggingService.Error("PLC【欧姆龙】写入失败:" + this.GetErrInfo(result));
return false;
}
return true;
}
}
/// <summary>
/// 关闭PLC设备
/// </summary>
public override void Close()
{
lock (this)
{
try
{
this._serialPortFactory.MainSerialPort.Close();
if (this._serialPortFactory.MainSerialPort.IsOpen)
{
ICSharpCode.Core.LoggingService.Warn("PLC【欧姆龙】关闭失败:端口处于打开状态");
}
else
{
this.State = false;
this._isOpen = false;
//Console.WriteLine("关闭成功!");
}
}
catch (Exception ex)
{
ICSharpCode.Core.LoggingService.Error("PLC【欧姆龙】关闭失败:" + ex.Message);
}
}
}
#region 根据错误代码返回错误信息
/// <summary>根据错误代码返回错误信息
/// </summary>
/// <param name="errCode">错误码</param>
/// <returns>错误信息</returns>
public string GetErrInfo(string errCode)
{
string result = "未知错误";
switch (errCode)
{
case "00":
result = "正常完成!";
break;
case "01":
result = "在RUN模式下不可执行!";
break;
case "02":
result = "在MONITOR模式下不可执行";
break;
case "04":
result = "地址越界";
break;
case "0B":
result = "在PROGRAM模式下不可执行";
break;
case "13":
result = "FCS错误";
break;
case "14":
result = "格式错误";
break;
case "15":
result = "入口号数据错误";
break;
case "16":
result = "命令不支持";
break;
case "18":
result = "帧长度错误";
break;
case "19":
result = "不可执行";
break;
case "23":
result = "用户存储区写保护";
break;
case "A3":
result = "由于数据传送中FCS错误而终止";
break;
case "A4":
result = "由于数据传送中格式错误而终止";
break;
case "A5":
result = "由于数据传送中入口号数据错误而终止";
break;
case "A8":
result = "由于数据传送中帧长错误而终止";
break;
default:
result = "未知错误";
break;
}
return result;
}
#endregion
}
}
串口通讯工厂类SerialPortFactory实现
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO.Ports;
namespace Mesnac.Equip.OMRON.HostLink
{
/// <summary>
/// 串口工厂类
/// </summary>
public class SerialPortFactory
{
#region 单例实现
private static SerialPortFactory _instance = null; //串口工厂实例
private SerialPort _mainSerialPort = null; //主串口对象
private string _recivedResultMsg = String.Empty; //串口接收结果字符串
private string _lastRecivedMsg = String.Empty; //保存最后一次接收的完整信息
private string _stationNum = "00"; //站号
private string _portName = "COM1"; //端口号
private int _baudRate = 9600; //波特率
private Parity _parity = Parity.Even; //校验位
private int _dataBits = 7; //数据位
private StopBits _stopBits = StopBits.Two; //停止位
private int _buffSize = 1024; //缓冲区大小
private SerialPortFactory(string stationNum, string portName, int baudRate, string parity, int dataBits, string stopBits, int buffSize)
{
this._stationNum = stationNum;
this._portName = portName;
this._baudRate = baudRate;
this._parity = ConvertToParity(parity);
this._dataBits = dataBits;
this._stopBits = ConvertToStopBits(stopBits);
this._buffSize = buffSize;
this._mainSerialPort = new SerialPort();
this._mainSerialPort.PortName = this._portName;
this._mainSerialPort.BaudRate = this._baudRate;
this._mainSerialPort.Parity = this._parity;
this._mainSerialPort.DataBits = this._dataBits;
this._mainSerialPort.StopBits = this._stopBits;
this._mainSerialPort.ReadBufferSize = this._buffSize;
this._mainSerialPort.WriteBufferSize = this._buffSize;
this._mainSerialPort.ReceivedBytesThreshold = 1;
this._mainSerialPort.Handshake = Handshake.None;
//this._mainSerialPort.DataReceived += new SerialDataReceivedEventHandler(_mainSerialPort_DataReceived);
}
/// <summary>
/// 获取串口工厂实例
/// </summary>
/// <param name="stationNum">站号</param>
/// <param name="portName">端口号</param>
/// <param name="baudRate">波特率</param>
/// <param name="parity">校验位</param>
/// <param name="dataBits">数据位</param>
/// <param name="stopBits">停止位</param>
/// <param name="buffSize">缓冲区大小</param>
/// <returns>返回串口工厂实例</returns>
public static SerialPortFactory GetInstance(string stationNum, string portName, int baudRate, string parity, int dataBits, string stopBits, int buffSize)
{
if (_instance == null)
{
_instance = new SerialPortFactory(stationNum, portName, baudRate, parity, dataBits, stopBits, buffSize);
}
return _instance;
}
/// <summary>
/// 主串口对象
/// </summary>
public SerialPort MainSerialPort
{
get
{
return this._mainSerialPort;
}
}
/// <summary>
/// 最后一次接收的完整信息
/// </summary>
public string RecivedResultMsg
{
get { return _recivedResultMsg; }
}
#endregion
#region 从串口读取数据
/// <summary>
/// 从串口读取数据
/// </summary>
/// <param name="block">块号</param>
/// <param name="start">起始地址</param>
/// <param name="len">长度</param>
/// <param name="buff">数据缓冲区</param>
/// <returns>返回结果吗</returns>
public string Read(int block, int start, int len, out int[] buff)
{
lock (this)
{
if (this._mainSerialPort.IsOpen)
{
this._mainSerialPort.DiscardOutBuffer();
start = block + start;
string WriteBit = String.Format("@{0}RD{1}", this._stationNum, this.FillZero(start.ToString(), 4));
WriteBit += this.FillZero(len.ToString(), 4);
this._mainSerialPort.Write(FCS(WriteBit));
System.Threading.Thread.Sleep(2);
string temp1 = this._mainSerialPort.ReadExisting();
this._mainSerialPort.DiscardOutBuffer();
this._recivedResultMsg += temp1;
while (String.IsNullOrEmpty(temp1) || temp1.Substring(temp1.Length - 1, 1) != "\r")
{
temp1 = this._mainSerialPort.ReadExisting();
this._recivedResultMsg += temp1;
this._mainSerialPort.DiscardOutBuffer();
System.Threading.Thread.Sleep(2);
}
this._lastRecivedMsg = this._recivedResultMsg;
this._recivedResultMsg = String.Empty;
if (String.IsNullOrEmpty(this._recivedResultMsg) && !String.IsNullOrEmpty(this._lastRecivedMsg))
{
buff = new int[len];
for (int i = 0; i < len; i++)
{
if (this._lastRecivedMsg.Length > 7 + i * 4)
{
string temp = ToDEC(this._lastRecivedMsg.Substring(7 + i * 4, 4));//this.HEX2DEC(
buff[i] = 0;
if ((block >= 3016 && block <= 3023) || block==3005)
{
int.TryParse(this.HEX2DEC(temp),out buff[i]);
}
else
int.TryParse(temp, out buff[i]);
}
else
{
break;
}
}
return GetReturnCode(this._lastRecivedMsg);
}
buff = new int[] { 0 };
return String.Empty;
}
else
{
buff = new int[] { 0 };
return "COM is Not Open!";
}
}
}
#endregion
#region 向串口写数据
/// <summary>
/// 向串口写数据
/// </summary>
/// <param name="block">块号</param>
/// <param name="start">起始地址</param>
/// <param name="buff">要写入的数</param>
/// <returns>返回结果码</returns>
public string Write(int block, int start, int[] buff)
{
lock (this)
{
if (this._mainSerialPort.IsOpen)
{
this._mainSerialPort.DiscardOutBuffer();
//string WriteBit = null;
//WriteBit = "@00WD0000" + DEC2HEX(TextBox_DEC.Text);
start = block + start;
string WriteBit = String.Format("@{0}WD{1}", this._stationNum, this.FillZero(start.ToString(), 4));
for (int i = 0; i < buff.Length; i++)
{
WriteBit += DectoBCD(buff[i].ToString());//DEC2HEX(
}
this._mainSerialPort.Write(FCS(WriteBit));
System.Threading.Thread.Sleep(2);
string temp1 = this._mainSerialPort.ReadExisting();
this._mainSerialPort.DiscardOutBuffer();
this._recivedResultMsg += temp1;
while (String.IsNullOrEmpty(temp1) || temp1.Substring(temp1.Length - 1, 1) != "\r")
{
temp1 = this._mainSerialPort.ReadExisting();
this._recivedResultMsg += temp1;
this._mainSerialPort.DiscardOutBuffer();
System.Threading.Thread.Sleep(1);
}
this._lastRecivedMsg = this._recivedResultMsg;
this._recivedResultMsg = String.Empty;
return GetReturnCode(this._lastRecivedMsg);
}
else
{
return "COM is Not Open!";
}
}
}
#endregion
#region 辅助方法
#region 获取响应码
/// <summary>
/// 获取响应码
/// </summary>
/// <param name="responseStr">响应字符串</param>
/// <returns>获取响应码</returns>
public string GetReturnCode(string responseStr)
{
if (!String.IsNullOrEmpty(responseStr) && responseStr.Length >= 7)
{
return responseStr.Substring(5, 2);
}
return String.Empty;
}
#endregion
#region "按字符串位数补0"
/// <summary>
/// 按字符串位数补0
/// </summary>
/// <param name="CharTxt">字符串</param>
/// <param name="CharLen">字符长度</param>
/// <returns></returns>
public string FillZero(string CharTxt, int CharLen)
{
if (CharTxt.Length < CharLen)
{
StringBuilder sb = new StringBuilder();
for (int i = 0; i < CharLen - CharTxt.Length; i++)
{
sb.Append("0");
}
sb.Append(CharTxt);
return sb.ToString();
}
else
{
return CharTxt;
}
}
#endregion
#region 对指令及数据的字符串进行最终FCS运算,并添加“*CR”
/// <summary>
/// 对指令及数据的字符串进行最终FCS运算,并添加“*CR”
/// </summary>
/// <param name="Str">要进行FCS运算的字符串</param>
/// <returns>返回经过FCS运算后的结果</returns>
public string FCS(string Str) //对指令及数据的字符串进行最终FCS运算,并添加“*CR”
{
char[] chararraytemp = Str.ToCharArray();
int fcs = 0;
foreach (char chartemp in chararraytemp)
{
fcs = fcs ^ Convert.ToInt16(chartemp); //对字符进行ASC转换,并进行异或运算
}
string fcstohex = String.Format("{0:X2}", (uint)System.Convert.ToUInt32(fcs));
string result = Str + fcstohex + "*" + Convert.ToChar(13);
return result;
}
#endregion
#region 十进制转十六进制
/// <summary>
/// 十进制转十六进制
/// </summary>
/// <param name="ox">十进制字符串</param>
/// <returns>返回对应的十六进制字符串</returns>
public string DEC2HEX(string ox)
{
uint ux = 0;
if (uint.TryParse(ox, out ux))
{
string strHex = String.Format("{0:X4}", ux); //转换为16禁止,{0:X4}大写4位;{0:x4}小写4位
return strHex;
}
else
{
return ox;
}
}
#endregion
#region 十六进制转十进制
/// <summary>
/// 十六进制转十进制
/// </summary>
/// <param name="ox">十六进制字符串</param>
/// <returns>返回对应的十进制字符串</returns>
public string HEX2DEC(string ox)
{
uint test;
test = System.Convert.ToUInt32(ox, 16); //转换为10禁止
return test.ToString();
}
#endregion
#region 类型转换
/// <summary>
/// 校验位类型转换
/// </summary>
/// <param name="strParity"></param>
/// <returns></returns>
private Parity ConvertToParity(string strParity)
{
Parity result = Parity.None;
switch (strParity)
{
case "None":
result = Parity.None;
break;
case "Odd":
result = Parity.Odd;
break;
case "Even":
result = Parity.Even;
break;
case "Mark":
result = Parity.Mark;
break;
case "Space":
result = Parity.Space;
break;
default:
result = Parity.None;
break;
}
return result;
}
/// <summary>
/// 停止位类型转换
/// </summary>
/// <param name="strStopBits"></param>
/// <returns></returns>
private StopBits ConvertToStopBits(string strStopBits)
{
StopBits result = StopBits.None;
switch (strStopBits)
{
case "None":
result = StopBits.None;
break;
case "1":
result = StopBits.One;
break;
case "1.5":
result = StopBits.OnePointFive;
break;
case "2":
result = StopBits.Two;
break;
default:
result = StopBits.None;
break;
}
return result;
}
string DectoBCD(string Dec)
{
//if (Dec.Length > 4)
//{
// return DEC2HEX(Dec);
//}
//else
//{
// string retValue = string.Empty;
// for (int j = 0; j < Dec.Length; j++)
// {
// string bv = string.Empty;
// int[] bitValue = ParseBinaryValue(Convert.ToInt32(Dec.Substring(Dec.Length - 1 - j, 1)), 4);
// for (int i = 0; i < 4; i++)
// {
// bv += bitValue[i];
// }
// retValue = GetValues(bv)[0] + retValue;
// }
// return retValue.PadLeft(4, '0');
//}
return Dec.PadLeft(4, '0');
}
/// <summary>
/// 解析二进制位
/// </summary>
/// <param name="x">要解析的变量</param>
/// <param name="len">解析的长度</param>
/// <returns>返回解析的二进制值数组</returns>
public int[] ParseBinaryValue(int x, int len)
{
int[] result = new int[len];
int b = 1;
for (int i = len - 1; i >= 0; i--)
{
result[i] = (x & b) == 0 ? 0 : 1;
b = b << 1;
}
return result;
}
/// <summary>
/// 二进制转化为十进制
/// </summary>
/// <param name="valvestates"></param>
/// <param name="toBase"></param>
/// <returns></returns>
public object[] GetValues(string valvestates)
{
object[] returnvalue = { Convert.ToInt32(valvestates, 2) };
return returnvalue;
}
public string ToDEC(string ox)
{
//int dec = Convert.ToInt32(ox, 16);
//if (dec.ToString().Length > 4)
// return dec.ToString();
//else
return ox;
}
#endregion
#endregion
}
}
上一篇: 使用RXTX实现串口通讯准备工作
下一篇: JS如何获取元素的不透明属性值?