/**********************************************************************************
* Class for Communicate to PLC with Prodave
* Create by J0YANG
* Last Modify Date:2009-08-11
* ********************************************************************************/
/****************************与PLC通讯应用实例****************************************
为保证数据的一致性,可以使用一个定时器,触发时间设为PLC扫描周期,在其触发事件中,把需要用到的PLC变量
一次性读取.建立与PLC的连接,示例如下
PLCConnParam[] Conn=new PLCConnParam[2]; //MPI网中有2个PLC,地址分别为2,3
Conn[0] .Addres=2; Conn[0].Slot=2; Conn[0].Rack=0;
Conn[1] .Addres=3; Conn[1].Slot=2; Conn[1].Rack=0;
errCode= DCProdave.Open(1,Conn); //建立连接
errCode= DCProdave.ActiveConn(1); //激活第一个连接
errCode= DCProdave.GetDBByteData(2, 0, 6, buf, 0); //DB2.DBW0--DBW5 共6个字节的变量,从buf的0位存储
if(errCode!=0){//DCLog.Write(DCProdave.GetErrInfo(errCode),"log.txt");}//如果返回值不=0,则将错误写入日志
* *********************************************************************************/
using System;
using System.Text;
using System.Collections;
using System.Collections.Generic;
using System.Runtime.InteropServices; //DllImport需要用到的库
namespace DCProdaveCS
{
#region 定义结构体[连接PLC所需参数]
public struct PLCConnParam
{
public byte Addres; // 定义CPU的MPI/DP地址
//public byte SegmentId; // 保留为0
public byte Rack; // 定义CPU的机架号
public byte Slot; // 定义CPU的槽号
}
#endregion
#region 定义枚举类型[PLC的存储区域编号]
public enum PLCBlockType
{
I = 1, //Input bytes
Q = 2, //Output bytes
M = 3, //Flag bytes
T = 4, //Timer words
Z = 5, //Counter words
D = 6, //Data from DB
}
#endregion
public class DCProdave
{
/// 静态构造函数
/// 在创建第一个实例或引用任何静态成员,函数之前,将自动调用静态构造函数来初始化类
///
static DCProdave(){ }
#region 导入w95_s7.dll中的通讯函数
/// 与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);
///
/// 连接数,在DOS,WIN3.1最多可以有4个,在WIN95以上最多可以有16个
/// 与PLC通讯的设备名称,一般为S7ONLINE
/// 参数列表,4个值分别为MPI/DP地址,保留值=0,槽号,机架号
/// 0正常返回,非0为错误号
[DllImport("w95_s7.dll")]
private extern static int load_tool(byte nr, string device, byte[,] adr_table);
/// 断开与PLC的连接,必须退出数采软件之前调用,否则PLC的连接一直被占用,影响下次连接
///
/// 0正常返回,非0为错误号
[DllImport("w95_s7.dll")]
private extern static int unload_tool();
/// 激活与MPI网中的哪个CPU通讯,load_tool后默认激活第一个CPU连接
/// 其参数与load_tool中参数adr_table所传递的连接参数顺序对应譬如byte[,] btr={{2,0,2,0},{3,0,2,0},{0,0,0,0}}
/// new_ss(1)则激活第1个连接即与MPI地址为2的PLC通讯,类似的new_ss(2)则激活与MPI地址为3的PLC通讯,
///
/// 连接号,对应于参数adr_table所传递的连接参数顺序
/// 0正常返回,非0为错误号,若激活的连接在MPI网中没有,则返回错误号517
[DllImport("w95_s7.dll")]
private extern static int new_ss(byte no);
/// 从DB中读取BYTE数组(长度是WORD倍数,即双BYTE)
///
/// DB块号
/// DBW起始编号,=0表示DBW0,=1表示DBW2,跨度为WORD
/// 读取的WORD长度(1个WORD==2个BYTE)
/// 返回值,BYTE型buffer
/// 0正常返回,非0为错误号
[DllImport("w95_s7.dll")]
private extern static int db_read(int dbno, int dwno, ref int anzahl, byte[] buffer);
/// 从DB中读取INT数据(DBW:INT16 或者 DBD:INT32),最多4个字节的整数
///
/// DB块号
/// DBW起始编号,0表示DBW0,1表示DBW2,跨度为WORD
/// 读取的WORD长度(1个WORD==2个BYTE) 2:DBW , 4:DBD
/// 返回值,int型整数(十进制)
/// 0正常返回,非0为错误号
[DllImport("w95_s7.dll")]
private extern static int db_read(int dbno, int dwno, ref int anzahl, ref int buffer);
/// 从DB中读取BYTE数组(字节数可以是任意长度的)
///
/// DB块号
/// DBB起始编号,0表示DBB0,1表示DBB1,跨度为BYTE
/// 读取的BYTE长度(任意长度,可以为奇数)
/// 返回值,BYTE型buffer
/// 0正常返回,非0为错误号
[DllImport("w95_s7.dll")]
private extern static int d_field_read(int blockno, int no, int amount, byte[] buffer);
/// 测试DB块的状态 0:存在 !0:不存在
///
/// buffer[123]!=0表明存在DB123
/// 0正常返回,非0为错误号
[DllImport("w95_s7.dll")]
private extern static int db_buch(ushort[] buffer);
/// 读取PLC中的M字节数据
///
/// 指定M字节号,譬如要读取MB10的值,则指定no等于10
/// 指定读取的字节数,譬如需要读取MB10至MB14之间的值,则可指定为5
/// 返回获取的值,这是一个十进制的值,如果需要获取某一个M位的状态,需要把它转换成二进制
/// 0正常返回,非0为错误号
[DllImport("w95_s7.dll")]
private extern static int m_field_read(int no, int anzahl, byte[] buffer);
/// 获取MB变量的位状态值
///
/// 指定M字节号
/// 指定位号,范围为0-7,返回值
/// 大于0表示该位为1,=0表示该位为0
/// 0正常返回,非0为错误号
[DllImport("w95_s7.dll")]
private extern static int mb_bittest(int mbno, int bitno, ref byte retwert);
/// 读取Output值
///
/// QB号
/// 读出多少个QB字节
/// 返回读出的值,十进制
///
[DllImport("w95_s7.dll")]
private extern static int a_field_read(int no, int anzahl, byte[] buffer);
/// 读取Input的值
///
/// IB号
/// 读出多少个IB字节
/// 返回读出的值,十进制
/// 0正常返回,非0为错误号
[DllImport("w95_s7.dll")]
private extern static int e_field_read(int no, int anzahl, byte[] buffer);
/// 获取PLC的运行状态
///
/// 返回0或者1, 0表示Run;1表示Stop或者Restart
/// 0正常返回,非0为错误号
[DllImport("w95_s7.dll")]
private extern static int ag_zustand(ref byte buffer);
#region 往PLC写入数据,在数采系统中不允许的函数
//复位MB变量的位状态,指定M字节号,指定位号范围为0-7
//[DllImport("w95_s7.dll")] private extern static int mb_resetbit(short mbno, short bitno);
//置位MB变量的位状态,指定M字节号,指定位号,范围为0-7
//[DllImport("w95_s7.dll")] private extern static int mb_setbit(short mbno, short bitno);
// 写入Output值,no为QB号,anzahl为有多少个QB字节需要写入,buffer为指定写入的值
//[DllImport("w95_s7.dll")] private extern static int a_field_write(int no, int anzahl, byte[] buffer);
/// 向DB中写入数据
///
/// 指定DB块号
/// 指定写入的起始字序号,=0表示DBW0,=1表示DBW2
/// 指定写入的对象有多少个字
/// 写入值
/// 0正常返回,非0为错误号
//[DllImport("w95_s7.dll")] private extern static int db_write(int dbno, int dwno, ref int anzahl, ref long buffer);
/// 向PLC中的M字节写入值
///
/// 指定M字节号,比如要读取MB10的值,则指定no等于10
/// 指定写入的字节数
/// 向PLC中写入的值,十进制数据
/// 0正常返回,非0为错误号
//[DllImport("w95_s7.dll")] private extern static int m_field_write(short no, short anzahl, ref long buffer);
#endregion
#endregion
#region 自定义与PLC的通讯函数,在w95_s7.dll的基础上进行封装
/// 建立连接,同一个连接只容许调用一次
///
/// 连接号connNo为1-4
/// 连接参数,PLCConnParam定义的参数结构体
/// 返回10进制错误号,0表示没有错误
public static int Open(byte connNo, PLCConnParam[] connParam)
{
int PLCCPUCnt = connParam.Length;
if (PLCCPUCnt <= 0) //传递参数不正确
{
return -1;
}
byte[,] btr = new byte[PLCCPUCnt + 1, 4]; //多分配1个,用于存放0作为连接结束标记
//转换连接表
for (int i = 0; i < connParam.Length; i++)
{
btr[i, 0] = connParam[i].Addres;
btr[i, 1] = 0;
btr[i, 2] = connParam[i].Slot;
btr[i, 3] = connParam[i].Rack;
}
btr[connParam.Length, 0] = 0;
btr[connParam.Length, 1] = 0;
btr[connParam.Length, 2] = 0;
btr[connParam.Length, 3] = 0;
//调用初始化函数,打开连接
int errCode = load_tool(connNo, "S7ONLINE", btr);
return errCode;
}
/// 断开与PLC的连接,必须退出数采软件之前调用,否则PLC的连接一直被占用,影响下次连接
///
///
public static int Close()
{
return unload_tool();
}
/// 激活连接,设定与MPI网中的哪一个CPU通讯
///
/// 与load_tool中的参数adr_table所传递的连接参数顺序对应
/// 若激活的连接在MPI网中没有,则返回错误号517
public static int ActiveConn(int connNO)
{
return new_ss((byte)connNO);
}
/// 从DB块中读取整型数据
/// 要读取DB2.DBW6,则DB块号为2,DBB号为6,字节长度为2
/// 要读取DB2.DBD6,则DB块号为2,DBB号为6,字节长度为4
///
/// DB块号,如:DB2
/// DBB的起始字节号,如DBW2则从2开始读,由于是WORD(2个BYTE),DBB号必须为偶数
/// 要读取的BYTE数,必须是偶数(这里只能是2和4,在PLC中只有DBW,DBD两种整数)
/// INT32型缓存区,存储读取的十进制数据
/// 返回值 0:成功 非0:错误代码
public static int GetDBInt32Data(int DBBlockNO, int DBBNO, int DBByteAmount, ref int buffer)
{
int DBWNO = DBBNO / 2;
int DBWordAmount = DBByteAmount / 2;
int errCode = db_read(DBBlockNO, DBWNO, ref DBWordAmount, ref buffer);
byte[] bbuf = new byte[4];
GetByteFromInt32(buffer, bbuf, true);
buffer = bbuf[0] * 0x1000000 + bbuf[1] * 0x10000 + bbuf[2] * 0x100 + bbuf[3];
return errCode;
}
/// 读取DB块的WORD数据
///
/// DB块号,如:DB2
/// DBW的起始字节号,如DBW2则从2开始读,由于是WORD(2个BYTE),DBW号必须为偶数
/// 要读取的WORD数,如从DBW2--DBW5,共2个WORD(4个BYTE)
/// BYTE型缓存区,存储读取的数据
/// 返回值 0:成功 非0:错误代码
public static int GetDBWordData(int DBBlockNO , int DBWNO , int DBWordAmount , byte[] buffer)
{
return db_read(DBBlockNO, DBWNO, ref DBWordAmount, buffer);
}
/// 读取DB块的BYTE数据
///
/// DB块号,如:DB2
/// DB数据的起始字节,如DBB2则从2开始读
/// 要读取的字节数,如从DBB2--DBB5,共4个字节
/// BYTE型缓存区,存储读取的数据
/// 数据缓存区的起始位置
/// 返回值 0:成功 非0:错误代码
public static int GetDBByteData(int DBBlockNO, int DBBNO, int DBByteAmount, byte[] buffer, int StartIndex)
{
byte[] bBufTemp = new byte[DBByteAmount];
int errCode=d_field_read(DBBlockNO, DBBNO, DBByteAmount, bBufTemp);
for(int i=0;i从M区读取位状态数据
///
/// M区号,如M10
/// 要读取的位号,如M10.2则位号为2
/// 位状态, 0:false,1:true
/// 0正常,非0为错误号
public static int GetMBitData(int MBlockNO, int MBitNO,ref bool MBitState)
{
int errCode;
if (MBlockNO >= 0 && MBitNO >= 0 && MBitNO <=7 )
{
byte rtn = 0;
errCode = mb_bittest(MBlockNO, MBitNO, ref rtn);
MBitState = (rtn == 0) ? false : true;
return errCode;
}
else
{
return -1; //越界,超出8个字节
}
}
/// 从M,I,Q区中读取字节数组
///
/// Block类别,在枚举PLCBlockType中定义,如要读取M区的值,则blockType=PLCBlockType.M
/// 区号,如IB10,MB10...
/// 要读取的字节数量,如IB10--IB14共5个字节
/// byte[]类型的buffer
/// byte[]存储的起始位置
/// 0正常返回,非0为错误号
public static int GetMIQByteData(PLCBlockType blockType, int BlockNO, int ByteAmount, byte[] bbuf, int StartIndex)
{
int errCode = 0;
byte[] bBufTemp = new byte[ByteAmount]; //局部变量,不用担心内存释放的问题. C++程序员看到"new"估计很谨慎.
switch (blockType) //根据块类别,调用相应的块读取函数.
{
case PLCBlockType.M: errCode = m_field_read(BlockNO, ByteAmount, bBufTemp); break;
case PLCBlockType.I : errCode = e_field_read(BlockNO, ByteAmount, bBufTemp); break;
case PLCBlockType.Q: errCode = a_field_read(BlockNO, ByteAmount, bBufTemp); break;
}
for (int i = 0; i < ByteAmount; i++) //由于C#中对指针有所限制,从数组指定的起始位置,逐个赋值.
{
bbuf[i + StartIndex] = bBufTemp[i];
}
return errCode;
}
/// 从long型数据中提取byte字节数组
///
/// 源数据(long型)
/// 字节数组,存放提取的Byte数据
/// 起始位置
/// 提取的字节数
/// long型源数据是否高位优先,如果不是,则进行反向提取
public static void GetByteFromInt32(int ibuf, byte[] bbuf , bool isBigEndian)
{
if (isBigEndian) //高位优先,则反向提取.
{
for (int i = 0; i <=3; i++) //Int32只有4个字节
{
bbuf[i] = (byte)(ibuf & 0x000000ff); //取低位字节
ibuf >>= 8; //右移8位
}
}
else //低位优先,按顺序提取.
{
for (int i = 3; i >= 0; i--)
{
bbuf[i] = (byte)(ibuf & 0x000000ff);
ibuf >>= 8;
}
}
}
/// 从Byte数据中取得所有bit的值(1Byte=8Bit , false:0 , true:1)
///
/// 源数据(Byte型),其中的8个bit位,从右到左0--7编号
/// bit数组,存放Byte中的8个bit的值,0:false, 1:true
/// 在bit数组中存放的起始位置
public static void GetBitFromByte(byte byteData, bool[] bitArray, int startIndex)
{
byte[] byteArray = new byte[1];
byteArray[0] = byteData;
System.Collections.BitArray BA = new System.Collections.BitArray(byteArray);
for (int i = 0; i <= 7; i++) //依次取8个位,逐个赋值
{
bitArray[startIndex + i] = BA.Get(i);
}
}
/// 从Byte数据中取得某一位bit的值(false:0 , true:1)
///
/// 源数据(Byte型),其中的8个bit位,从右到左0--7编号
/// bit位编号,从右到左以0--7编号
/// bit值,以bool型返回,false:0 , true:1
public static void GetBitFromByte(byte byteData, int bitNo, ref bool bitData)
{
if (bitNo >= 0 && bitNo <= 7) //位号必须在0~7之间
{
byte[] byteArray = new byte[1];
byteArray[0] = byteData;
System.Collections.BitArray BA = new System.Collections.BitArray(byteArray);
bitData = BA.Get(bitNo);
}
}
/// 根据错误代码返回错误信息
/// 例如int errCode=ActiveConn(1); sring errInfo = GetErrInfo(err);
///
/// 错误码
/// 错误信息
public static 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";
}
}
#endregion
}
}