/********************************************************************************** * 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 } }