PLC通讯实现-C#实现西门子PLC串口通讯W95_S7(四)
程序员文章站
2024-03-17 12:38:04
...
PLC通讯实现-C#实现西门子PLC串口通讯W95_S7(四)
背景
本人近十年的工作都与工业软件相关、其中工控系统开发过程中有一个必要环节就是跟各大厂商的PLC进行通讯,而对于从互联网行业跨入工业互联网行业的从业人员来说要实现各型号PLC通讯还是需要一个过程的,本人在此对主流型号PLC通讯实现进行总结以便大家参考。
抽象设计
首先我们要进行一下抽象设计,先设计一个抽象类(接口也可以,此处因为还有其他业务使用了抽象类)BaseEquip,对PLC的常规操作进行定义,即Open、Read、Write、Close,业务代码调用BaseEquip进行PLC的读写,然后在实现各型号的Equip类,对Open、Read、Write、Close进行实现,根据配置在业务代码中对BaseEquip进行实例化,这样后期更改PLC型号后,只需修改配置即可,不用修改业务代码。
西门子串口通讯实现W95_S7
实现语言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 Mesnac.Equips;
using Mesnac.Equips.BaseInfo;
using System.Runtime.InteropServices;
namespace Mesnac.Equip.Siemens.S7.Default
{
/// <summary>
/// 西门子S7-300PLC串口通讯设备类
/// </summary>
public class Equip : BaseEquip
{
private bool _isOpen = false; //是否打开连接
private IntPtr equipHandle = new IntPtr(Marshal.SizeOf(typeof(int)));
private IntPtr structToIntPtr<T>(T s)
{
int nSizeOfT = Marshal.SizeOf(typeof(T));
int nSizeOfIntPtr = Marshal.SizeOf(typeof(IntPtr));
IntPtr Result = new IntPtr();
Result = Marshal.AllocHGlobal(nSizeOfT);
Marshal.StructureToPtr(s, (IntPtr)((UInt32)Result), true);
return Result;
}
private T IntPtrTostruct<T>(IntPtr p)
{
return (T)Marshal.PtrToStructure(p, typeof(T));
}
private int Swap(int a)
{
return (a >> 8 & 0xFF) + (a << 8 & 0xFF00);
}
public override bool Open()
{
lock (this)
{
if (this._isOpen == true)
{
return true;
}
this.State = false;
//W95_S7.PLCConnParam adressType = new W95_S7.PLCConnParam();
byte c1 = 1;
byte[,] p3 = { { 2, 0, 2, 0 }, { 3, 0, 2, 0 }, { 0, 0, 0, 0 } };
int Result = W95_S7.load_tool(c1, "S7ONLINE", p3);
if ((Result != 0) && (Result != 39)) // 39已经初始化
{
Console.WriteLine("PLC连接失败:" + this.GetErrInfo(Result));
this.State = false;
return this.State;
}
else
{
this.State = true;
this._isOpen = true;
}
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 = 100; //单次允许读取的最大长度,AB500限制为100个字
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(AB500)设备失败-({0})!", ex.Message));
base.State = false;
return false;
}
}
}
/// <summary>
/// 单次读取最长100个字的方法
/// </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 ("EQUIP")
{
buff = new object[len];
if (!this.Open())
{
return false;
}
int state = len;
ushort[] _buff = new ushort[len];
int iblock = Convert.ToInt32(block);
int iResult = W95_S7.db_read(iblock, start, ref state, _buff);
if (iResult != 0)
{
Console.WriteLine("PLC读取失败:" + this.GetErrInfo(iResult));
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] = Swap(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 = 100; //单次允许读取的最大长度,AB500限制为100个字
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.WriteMax100(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.WriteMax100(block, start + count * maxOneLen, _buff);
}
return flag;
}
catch (Exception ex)
{
//ICSharpCode.Core.LoggingService.Error(String.Format("写入PLC(AB500)设备失败-({0})!", ex.Message));
return false;
}
}
}
/// <summary>
/// 单次写入最多100个字至PLC
/// </summary>
/// <param name="block">要写入的块号</param>
/// <param name="start">要写入的起始字</param>
/// <param name="buff">要写入的数据</param>
/// <returns>写入成功返回true,失败返回false</returns>
private bool WriteMax100(int block, int start, object[] buff)
{
lock (this)
{
if (!this.Open())
{
return false;
}
int state = buff.Length;
ushort[] _buff = new ushort[buff.Length];
for (int i = 0; i < buff.Length; i++)
{
int value = 0;
int.TryParse(buff[i].ToString(), out value);
value = Swap(value);
_buff[i] = (ushort)value;
}
int iblock = Convert.ToInt32(block);
int iResult = W95_S7.db_write(iblock, start, ref state, _buff);
if (iResult != 0)
{
Console.WriteLine("PLC【西门子S7-300】写入失败:" + this.GetErrInfo(iResult));
return false;
}
return true;
}
}
public override void Close()
{
lock (this)
{
try
{
int result = W95_S7.unload_tool();
if (result != 0)
{
Console.WriteLine("PLC【西门子S7-300】关闭失败:" + this.GetErrInfo(result));
}
else
{
this.State = false;
this._isOpen = false;
}
}
catch (Exception ex)
{
Console.WriteLine("PLC【西门子S7-300】关闭失败:" + ex.Message);
}
}
}
/// <summary>根据错误代码返回错误信息
/// 例如int errCode=ActiveConn(1); sring errInfo = GetErrInfo(err);
/// </summary>
/// <param name="errCode">错误码</param>
/// <returns>错误信息</returns>
public string GetErrInfo(int errCode)
{
switch (errCode)
{
case -1: return "User-Defined Error!"; //自定义错误,主要是参数传递错误!
case 0x0000: return "Success";
case 0x0001: return "Load dll failed";
case 0x00E1: return "User max";
case 0x00E2: return "SCP entry";
case 0x00E7: return "SCP board open";
case 0x00E9: return "No Windows server";
case 0x00EA: return "Protect";
case 0x00CA: return "SCP no resources";
case 0x00CB: return "SCP configuration";
case 0x00CD: return "SCP illegal";
case 0x00CE: return "SCP incorrect parameter";
case 0x00CF: return "SCP open device";
case 0x00D0: return "SCP board";
case 0x00D1: return "SCP software";
case 0x00D2: return "SCP memory";
case 0x00D7: return "SCP no meas";
case 0x00D8: return "SCP user mem";
case 0x00DB: return "SCP timeout";
case 0x00F0: return "SCP db file does not exist";
case 0x00F1: return "SCP no global dos memory";
case 0x00F2: return "SCP send not successful";
case 0x00F3: return "SCP receive not successful";
case 0x00F4: return "SCP no device available";
case 0x00F5: return "SCP illegal subsystem";
case 0x00F6: return "SCP illegal opcode";
case 0x00F7: return "SCP buffer too short";
case 0x00F8: return "SCP buffer1 too short";
case 0x00F9: return "SCP illegal protocol sequence";
case 0x00FA: return "SCP illegal PDU arrived";
case 0x00FB: return "SCP request error";
case 0x00FC: return "SCP no license";
case 0x0101: return "Connection is not established / parameterized";
case 0x010a: return "Negative Acknowledgment received / timeout errors";
case 0x010c: return "Data not available or locked";
case 0x012A: return "No system memory left";
case 0x012E: return "Incorrect parameter";
case 0x0132: return "No storage space in the DPRAM";
case 0x0200: return "xx";
case 0x0201: return "Falsche Schnittstelle angegeben";
case 0x0202: return "Incorrect interface indicated";
case 0x0203: return "Toolbox already installed";
case 0x0204: return "Toolbox with other compounds already installed";
case 0x0205: return "Toolbox is not installed";
case 0x0206: return "Handle can not be set";
case 0x0207: return "Data segment can not be blocked";
case 0x0209: return "Erroneous data field";
case 0x0300: return "Timer init error";
case 0x0301: return "Com init error";
case 0x0302: return "Module is too small, DW does not exist";
case 0x0303: return "Block boundary erschritten, number correct";
case 0x0310: return "Could not find any hardware";
case 0x0311: return "Hardware defective";
case 0x0312: return "Incorrect configuration parameters";
case 0x0313: return "Incorrect baud rate/interrupt vector";
case 0x0314: return "HSA incorrectly parameterized";
case 0x0315: return "Address already assigned";
case 0x0316: return "Device already assigned";
case 0x0317: return "Interrupt not available";
case 0x0318: return "Interrupt occupied";
case 0x0319: return "SAP not occupied";
case 0x031A: return "Could not find any remote station";
case 0x031B: return "syni error";
case 0x031C: return "System error";
case 0x031D: return "Error in buffer size";
case 0x0320: return "DLL/VxD not found";
case 0x0321: return "DLL function error";
case 0x0330: return "Version conflict";
case 0x0331: return "Com config error";
case 0x0332: return "smc timeout";
case 0x0333: return "Com not configured";
case 0x0334: return "Com not available";
case 0x0335: return "Serial drive in use";
case 0x0336: return "No connection";
case 0x0337: return "Job rejected";
case 0x0380: return "Internal error";
case 0x0381: return "Device not in Registry";
case 0x0382: return "L2 driver not in Registry";
case 0x0384: return "L4 driver not in Registry";
case 0x03FF: return "System error";
case 0x4001: return "Connection is not known";
case 0x4002: return "Connection is not established";
case 0x4003: return "Connection is being established";
case 0x4004: return "Connection is collapsed";
case 0x0800: return "Toolbox occupied";
case 0x8001: return "in this mode is not allowed";
case 0x8101: return "Hardware error";
case 0x8103: return "Object Access not allowed";
case 0x8104: return "Context is not supported";
case 0x8105: return "ungtige Address";
case 0x8106: return "Type (data) is not supported";
case 0x8107: return "Type (data) is not consistent";
case 0x810A: return "Object does not exist";
case 0x8301: return "Memory on CPU is not sufficient";
case 0x8404: return "grave error";
case 0x8500: return "Incorrect PDU Size";
case 0x8702: return "Invalid address";
case 0xA0CE: return "User occupied";
case 0xA0CF: return "User does not pick up";
case 0xA0D4: return "Connection not available because modem prevents immediate redial (waiting time before repeat dial not kept to) ";
case 0xA0D5: return "No dial tone";
case 0xD201: return "Syntax error module name";
case 0xD202: return "Syntax error function parameter";
case 0xD203: return "Syntax error Bausteshortyp";
case 0xD204: return "no memory module in eingeketteter";
case 0xD205: return "Object already exists";
case 0xD206: return "Object already exists";
case 0xD207: return "Module available in the EPROM";
case 0xD209: return "Module does not exist";
case 0xD20E: return "no module present";
case 0xD210: return "Block number is too big";
case 0xD241: return "Protection level of function is not sufficient";
case 0xD406: return "Information not available";
case 0xEF01: return "Wrong ID2";
case 0xFFFE: return "unknown error FFFE hex";
case 0xFFFF: return "Timeout error. Interface KVD";
default: return "Unkonw error";
}
}
}
}
W95_S7类实现
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices;
namespace Mesnac.Equip.Siemens.S7.Default
{
/// <summary>
/// 西门子S7-300PLC串口通讯接口类
/// </summary>
public class W95_S7
{
/*
PAdressType = ^AdressType;
AdressType = packed record
Adress : Char; // stationsadresse default 2
SegmentId : Char; // segment id default 0
SlotNo : Char; // slot no default 1
RackNo : Char; // rack no default 0
end;
function load_tool(a:char; b:PChar; c:PAdressType):integer;stdcall;external'W95_S7.dll';
function db_read(a,b:integer;var c:integer;d:pointer): Integer;stdcall;external'W95_s7.dll';
function db_write(a,b:integer;var c:integer;d:pointer):integer;stdcall;external'W95_s7.dll';
function unload_tool():integer;stdcall;external'W95_s7.dll';
*/
/// <summary>
/// 定义MPI链接参数
/// </summary>
public struct PLCConnParam
{
/// <summary>
/// 定义CPU的MPI地址
/// </summary>
public byte Adress;
//public byte SegmentId; //保留为0
/// <summary>
/// 定义CPU的机架号
/// </summary>
public byte RackNo;
/// <summary>
/// 定义CPU的槽号
/// </summary>
public byte SlotNo;
}
/// <summary>
/// 与PLC建立连接,该函数必须在其他所有函数调用之前被调用
/// 在一个MPI网络中若有多个CPU时,可指定多个连接列。最后一列的所有参数须置0,以标志参数列结束。
/// e.g.一个MPI网中有两个CPU,他们的MPI地址分别为2和3,槽号均为2,机架号均为0,则可按如下方式调用:
/// byte[,] btr={{2,0,2,0},{3,0,2,0},{0,0,0,0}}; int err=load_tool(1, "s7online",btr);
/// </summary>
/// <param name="nr">连接数,在DOS,WIN3.1最多可以有4个,在WIN95以上最多可以有16个</param>
/// <param name="device">与PLC通讯的设备名称,一般为S7ONLINE</param>
/// <param name="adr_table">参数列表,4个值分别为MPI/DP地址,保留值=0,槽号,机架号</param>
/// <returns>0正常返回,非0为错误号</returns>
[DllImport("W95_S7.dll")]
public extern static int load_tool(byte nr, string device, byte[,] adr_table);
/// <summary>
/// 从DB中读取ushort数组(长度是WORD倍数,即双BYTE)
/// </summary>
/// <param name="dbno">DB块号</param>
/// <param name="dwno">DBW起始编号,=0表示DBW0,=1表示DBW2,跨度为WORD</param>
/// <param name="anzahl">读取的WORD长度(1个WORD==2个BYTE)</param>
/// <param name="buffer">返回值,ushort型buffer</param>
/// <returns>0正常返回,非0为错误号</returns>
[DllImport("W95_S7.dll")]
public extern static int db_read(int dbno, int dwno, ref int anzahl, ushort[] buffer);
/// <summary>
/// 向DB数据块中写入数据
/// </summary>
/// <param name="dbno">指定DB块号</param>
/// <param name="dwno">指定写入的起始字序号,=0表示DBW0,=1表示DBW2</param>
/// <param name="anzahl">指定写入的对象有多少个字</param>
/// <param name="buffer">写入值</param>
/// <returns>0正常返回,非0为错误号</returns>
[DllImport("W95_S7.dll")]
public extern static int db_write(int dbno, int dwno, ref int anzahl, ushort[] buffer);
/// <summary>
/// 断开与PLC的连接,必须在退出软件之前调用,否则PLC的连接一直被占用,影响下次连接
/// </summary>
/// <returns>0正常返回,非0为错误号</returns>
[DllImport("W95_S7.dll")]
public extern static int unload_tool();
}
}