在这里很感谢吴向阳,在中国工控网上面发现了他的文章,因为我是从C#转到工控方向的,以前对PLC一点都不懂,刚接触这一行时,学习起来很吃力,看了他的这篇文章,让我对PLC的有了更加深入的了解。我的这个DCProdave.cs就是在他的基础上修改的,加了一些自己的东西。还望各位多多提意见,多多交流!
原文章地址:http://www.gongkong.com/webpage/paper/200507/8-A01D-6654393A02CC.htm
Prodave版本: PRODAVE6.0 - W95_S7.DLL
PLC模拟环境: PLCSIM V5.4
开发环境: VS.NET 2005
附件1:Prodave6.0手册[English] ,下载地址:https://files.cnblogs.com/J0YANG/Prodave.pdf
附件2:Prodave6.0动态库[W95_S7.DLL],下载地址: https://files.cnblogs.com/J0YANG/W95_S7.rar
附件3:DCProdave.cs[C#封装的源代码],下载地址: https://files.cnblogs.com/J0YANG/DCProdave.rar
一.从w95_s7.dll中导入PLC通讯函数的方法[DllImport]
在使用DllImport之前,必须引入InteropServices, 代码如下: using System.Runtime.InteropServices;
具体使用方法可以参考我的博客中转载的一篇文章 《C#(.net)中的DllImport用法[转] 》写的很不错,千万要注意C++数据类型到C#的对应关系,选用合适的类型。比如 char* 可以用string来转换,指针类型可以ref 或者数组。
原文地址:http://www.cnblogs.com/xumingming/archive/2008/10/10/1308248.html
二.定义结构体类型
2.1 PLC连接参数结构体
1
//定义结构体[连接PLC所需参数]
2
public struct PLCConnParam
3
}
2
3
2.2 PLC存储区域类别编号
1
//定义枚举类型[PLC的存储区域编号]
2
public enum PLCBlockType
3
}
2
3
三.常用函数详细讲解
3.1 建立PLC连接函数
首先从W95_S7.DLL中导入连接函数,访问权限为私有,C#将会对此函数进行封装,供外部调用,稍后讲解.
1
[,] adr_table);
说明:在一个MPI/DP网络中若有多个PLC时,可指定多个连接列。最后一列的所有参数须置0,以标志参数列结束。例如一个MPI/DP网中有两个PLC,他们的MPI地址分别为2和3,槽号均为2,机架号均为0,则可按如下方式调用:byte[,] ba={{2,0,2,0},{3,0,2,0},{0,0,0,0}}; int err=load_tool(1, "s7online",ba); 返回值为int型,如果返回0则表示执行成功,非零值,则需要根据错误号查找到错误具体信息,具体参照本文第五部分:错误代码字典
当然如果PLC使用的是DP网络时,只需要将Set PG/PC Interface中接口参数分配选为PLCSIM(PROFIBUS)即可,Prodave不需要做任何修改(当然PLC地址肯定也是DP口的地址哦),具体如下图:
个人不太习惯西门子的这种函数命名,索性就按照C#的常用习惯,进行一下简单的封装,供外部调用.
1
}
建立于PLC的连接,只需在数采程序启动的时候调用即可,并且只能打开一次,否则报错. 驱动设备名称"S7ONLINE",一般情况下是不会有变化的,所以这里就写死了.特别需要指出的是,这个函数的第一个参数(连接号),是指当前连接有多少个PLC连接(严格意义上来讲,是CPU的个数,因为有可能2个PLC共用1个CPU,之间通过IM467组态),激活连接并交换数据的时候,和这个值有点关系. 在建立连接的时候默认激活第1个连接.3.2 断开与PLC的连接
从W95_S7.DLL中导入函数,依然是私有,因为我要对所有的导入函数进行封装.
1
unload_tool();
关闭PLC的连接函数进行C#封装,没有改变任何代码,只是换了个函数名.
1
public static int Close()
2
}
2
3.3 激活连接,当前连接列中某个时刻有且只有1个PLC是激活状态.建立连接的时候,默认激活第1个连接.
1
no);
其参数与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通讯,在数采系统中,为了读取所有PLC的数据,采用定时循环激活每个PLC的连接,然后读取其数据.C#封装如下:
1
public static int ActiveConn(int connNO)
2
}
2
3.4 从DB块中读取字节数据(返回BYTE数组)
1
[] buffer);
C#封装如下:
1
}
这个函数是用的最多的一个函数,在数采系统中,习惯一次性的将所有需要用到数据,全部读到字节数组中,统一处理,避免不同时期凌乱读取造成的数据不一致.需要提醒的是,必须保证数据处理函数得到的数据,是PLC一次扫描周期内的.3.5 从DB中读取整数值(int32型)
1
buffer);
C#封装
1
}
这个函数读取的是一个整数,因为DB中有DBB,DBW,DBD3种数据类型,最大的DBD是4个字节,所以设计了这个函数,读取单个的整型值,不用再进行BYTE[]到INT的转换了.这里值得一提的是整数高位优先,还是低位优先的问题,字节数组的顺序切记要矫正,另外buffer = bbuf[0] * 0x1000000 + bbuf[1] * 0x10000 + bbuf[2] * 0x100 + bbuf[3];这行代码很有意思,16进制的字节进位是0x100.3.6 M,I,Q 3种块的读取函数类似(参数都是一样的),这里放在一起进行说明
M区读取函数
1
[] buffer);
I区读取函数,一直不明白为什么输入区(单词input)简称为I,而函数名却为A, 后来才晓得,这个A是德文的表示方法,(Pordave是西门子公司的东东).
1
[] buffer);
Q区读取函数,参数与I一样.
1
[DllImport("w95_s7.dll")]
2
private extern static int e_field_read(int no, int anzahl, byte[] buffer);
2
C#封装, M,I,Q 3种块的读取函数类似,这里放在一个函数里,利用枚举类型PLCBlockType进行区分
1
}
四, BYTE,INT,BOOL几种类型的数据转换函数
4.1 从32位整数中提取字节数组(4个byte)
1
}
4.2 从字节数据中提取bit数组(8个bit),以bool型数据代替位表示.
1
}
4.3 从字节数据中提取某一位的状态,以bool型返回
1
}
五.错误代码字典
1
}
由于错误代码比较多,这里只罗列了几个,详细信息请下载源代码DCProdave.cs进行查看,这里不再详述.六. DCProdave.cs应用举例
为保证数据的一致性,可以使用一个定时器,触发时间设为PLC扫描周期,在其触发事件中,把需要用到的PLC变量一次性读取.建立与PLC的连接,示例如下
1
PLCConnParam[] Conn=new PLCConnParam[2]; //MPI网中有2个PLC,地址分别为2,3
2
Conn[0] .Addres=2; Conn[0].Slot=2; Conn[0].Rack=0;
3
Conn[1] .Addres=3; Conn[1].Slot=2; Conn[1].Rack=0;
4
errCode= DCProdave.Open(1,Conn); //建立连接
5
errCode= DCProdave.ActiveConn(1); //激活第一个连接
6
errCode= DCProdave.GetDBByteData(2, 0, 6, buf, 0); //DB2.DBW0--DBW5 共6个字节的变量,从buf的0位存储
7
如果返回值不=0,则将错误写入日志
2
3
4
5
6
7
还有很多往PLC写入数据的函数,这里没有介绍,文中有很多不足之处,欢迎希望路过的各位XDJM在此留言.
邮箱: J0YANG@163.com