1、运用 VC# 编程通过 OPC 方式实现 PC 机与西门子 PLC 通讯 1 、 OPC 服务介 绍 西门子提供的最新软件:Simatic Net PC-Software CD 2005 为 各种组态软件的开发 提供了一个统一的平台,它建立的 PC 站既为一些组态软件,如:WinCC 、Protol 等提 供了与 PLC 的通讯平台, 也提供了一套编程接口, 可使用高级语言编程通过 Simatic Net 访问 PLC 数 据。本文讨论的主要就是这个编程接口, 最新版的 Simatic NET 支持五种编 程方式: 、ActiveX 控件 提供了一系列数据访问控件, 以便于 向 VB6 这种
2、语言使用控件的方式与 PLC 通讯。 、OPC 自动 化 为 VB6 、Dephi 等语言运 用 OLE 自动 化的方式进行编程。 、OPC 用 户接口 这是专门为 VC+ 提供的 一种高效编程方式, 其灵活程度与执行效率比前面的两种 方式均要高得多。 、针对微 软的.NET 平台的 OPC 用户接口 这也是一种非常灵活的编程接口, 不 过它针对的是.NET 平台, 其提供了大 量的.NET 类库,以便于像 VC# 、VB.NET 等高级语言编程。本文将详细的介绍该接口。 、OPL XML 接口 顾名思义,主要是针对 XML 编程的。 对于 、 、 编程 方式, 他们各自又可以分为同步访问方式
3、和异步访问方式。 按西门子的文档解释: 同步通讯指的是当一个客户在访问服务器时, 其他客户的访问必 须等待, 直到服务器处理完该客户的请求, 才能继续进行下一个服务, 异步访问与之正 好相反,本文主要讲的是同步编程篇,异步篇以后再提供。 2 、 配置 OPC 服务 器 要进行编程, 必须先配置服务器。 本文以 Prfibus DP 网络为例, 介绍 PC 站的配置。 其 内容主要来自西门子文档。 需要的软件: S t ep7 V 5. 3 Simatic Net PC-Software CD 2005 需要的硬件: 至少为 CP5611 或以上级 别, 笔记本可 以为 CP5511, 带 DP
4、 口的 S7-300 PLC( 若使用 Simatic NET 的仿真功能可以不需要这些硬件,后面会介绍到) 、 组态一 个 S7 站, 配 置 Profibus DP 网络, 其 DP 地址设为 3 , 并下载到 PLC, 然后把网线由 MPI 口转到 DP 口。S7 站的配置这 里就不介绍了。 、在 Step7 V5.3 中建 立一个新工程,插入一个 PC 站,并把该 PC 站的名字 改成与你的计算机名字相同。打开该 PC 站的硬件组态界面。插入 OPC 服务器和连接 卡 CP5611( 或者 CP5511), 他们在 PC 槽中处的位置可以任意,如下图: 注:在插入 CP5611 时,
5、应该选择与组态 S7 站一 样的 Profibus 网络,并将网络 地址设为 2 , 一定不要与 PLC 的地址冲 突。 然后点击下面工具条标为红色的按钮: 选中”OPC Server” ,然后 插入一个新的连接,如下图: 在弹出的对话框中选择连接类型为 S7 Connection, 如下图: 在 OK 后,然后在新对话框的红色标志位置输入 3 ,表示 PLC 的地址, 如下图: 并选择 Address Details, 设置 CPU 的槽号为 2 ,如 下图: OK 后,然后编译并保存。 、然后建 立 OPC 服务器,有两种方式,本文介绍较简单的一种。 打开, Simatic Net 中的
6、 Station Configurator , 一般安 装后, 他会自动启动, 并点击 Import Station 按钮 ,找到你刚才在 Step 7 中建立 PC 站时创建的 XDBs 文件夹下的 XDB 文件,然后导入成功。 、 可以使用 Simatic Net 中的 OPC Scout , 并选择 Simatic NET 服 务, 然后在它下面 创建组,然后在组下创建变量,这样可以监控 PLC 数据,VC# 编程不需要使用该程序,但 熟悉使用 OPC Scout 有利 于了解 Simatic Net 中的编 程结构。 说 明:打开 Simatic Net 中的 Configuratio
7、n Console ,选中 S7 进 行如下的配置后, 可以不需要 PLC 、CP5611 等并可以模拟,如下图: 上面的所有步骤,均可在 Configuration Console 下,PC Station 的根树下 ,选择相应的帮助 文档得到。 3、 OPC 编程 、西门子 的变量结构如下: - 服务器 - / / OPC.SimaticNet OPCServer.Wincc (一系列类型的服务 器) / / Group1 Group2 Group3 .(把更新时间一致的变量统一为一个组) / Item1 Item2 . (变量:I、Q、M、DB等,指向网络中某个PC 站OPC Serv
8、er服务的某个连接) - - 第一层是不同种类的服务器,如:OPC.SimaticNET 类型,OPC.SimaticNET.DP 类型, OPCServer.WinCC等一系列类型,这里选择OPC.SimaticNET类型。 第二层是Group,一个服务器下可以有多个组, 可以把组理解为扫描周期相同的一系列变量的集合。 在开发组态界面时,可以把一个界面中的所有变量统一到一个组中。 第三层是Item,项是指向网络中某个PC 站OPC Server服务的某个连接的一系列变量,如:I、Q、 M、DB等 、项的命 名 项即 Item, 在 S7 连接中 针对的直接是 PLC 中的 变量,因此它的命名
9、很重要: 格式: : 其中的protocolID 表示 连接 类型,在 上面 的组态PC 站 时可以选 择, 这里应该 与它 一致, 类型有9 种, 最常用的 为S7 ,即S7 连 接 ,其他类 型请 参看文档 。 Connectionname : 顾名思 义, 即 在上面 的组态PC 站 时产生的 连接 名, 如 果使用 仿真功 能,连接 名为DEMO Variablename: 变量名有 一 系列规则 ,这 里举例说 明, 读者也可 以使 用OPC Scout 创建 变量,学 习程 序是如何 生成 变量名的 。 S7:DEMOMB1 : 表示连接类型为S7, 连接名为DEMO ( 这里为仿
10、真) , 变量 为MB1 S7:DEMOQB0,3: 表示为从QB0 开始的三个 连续变量。 S7:DEMODB10,X4.6 :表示DB10 的DBX4.6 。 、 添加引用 在VC# 开发环境 中添加对OpcRcw.Da库的引用 引用,该库属于.NET 库,不属于COM 库,西门子虽然编写了类库,以提供对.NET 平台的支持,但这些类库仍然难于编程, 里面包含了大量的在托管和非托管区传输数据,因此我们需要在它的基础上再开发一个类 库,以简化以后的编程,首先在类的开头使用命名空间: using System.Runtime.InteropServices; using OpcRcw.Da;
11、using System.Collections; 、编程 1 、 在类的开头部分生名变量 private string serverType=“; private IOPCServer pIOPCServer; / OPC server接口 private Object pobjGroup1; / Pointer to group object private int nSvrGroupID; / server group handle for the added group private System.Collections.Hashtable groupsID=new Hashtabl
12、e(11); /用于记录组名 和组ID号 private System.Collections.Hashtable hitemsID=new Hashtable(17); /用于记录项名和 项ID号 private Guid iidRequiredInterface; private int hClientGroup = 0; /客户组号 private int hClientItem=0; /Item号 2 、 创建服务器,编写 Open() 方法 / / 创建一个OPC Server接口 / / 返回错误信息 / 若为true,创建成功,否则创建失败 public bool Open(ou
13、t string error) error=“;bool success=true; Type svrComponenttyp ; /获取 OPC Server COM 接口 iidRequiredInterface = typeof(IOPCItemMgt).GUID; svrComponenttyp = System.Type.GetTypeFromProgID(serverType); try /创建接口 pIOPCServer =(IOPCServer)System.Activator.CreateInstance(svrComponenttyp); error=“; catch (S
14、ystem.Exception err) /捕捉失败信息 error=“错误信息:“+err.Message;success=false; Return true; 3、 在服务器上添加用于添加Group的函数 / / 添加组 / / 组名 / /创建时,组是否被激活 / /组的刷新频率,以ms为单位 / 返回错误信息 / 若为true,添加成功,否则添加失败 public bool AddGroup(string groupName,int bActive,int updateRate,out string error) error=“; int dwLCID = 0x407; /本地语言为
15、英语 int pRevUpdateRate; float deadband = 0; / 处理非托管COM内存 GCHandle hDeadband; IntPtr pTimeBias = IntPtr.Zero; hDeadband = GCHandle.Alloc(deadband,GCHandleType.Pinned); try pIOPCServer.AddGroup(groupName, /组名 bActive, /创建时,组是否被激活 updateRate, /组的刷新频率,以ms为单 位 hClientGroup, /客户号 pTimeBias, /这里不使用 (IntPtr)
16、hDeadband, dwLCID, /本地语言 out nSvrGroupID, /移去组时,用到的组ID号 out pRevUpdateRate, /返回组中的变量改变时的 最短通知时间间隔 ref iidRequiredInterface, out pobjGroup1); /指向要求的接口 hClientGroup=hClientGroup+1; int groupID=nSvrGroupID; groupsID.Add(groupName,groupID); catch (System.Exception err) /捕捉失败信息 error=“错误信息:“+err.Message;
17、 finally if (hDeadband.IsAllocated) hDeadband.Free(); if(error=“) return true; else return false; 4、 向指定的组中添加变量的函数 / / 添加多个项到组 / / 指定组名 / 指定项名 / 由函数返回的服务器确定的项ID号 / 无错误,返回true,否则返回false public bool AddItems(string groupName,string itemsName,int itemsID) bool success=true; OPCITEMDEF ItemDefArray=new
18、OPCITEMDEFitemsName.Length; for(int i=0;iitemsName.Length;i+) hClientItem=hClientItem+1; ItemDefArrayi.szAccessPath = “; / 可选的通道路 径,对于Simatiic Net不需要。 ItemDefArrayi.szItemID = itemsNamei; / ItemID, see above ItemDefArrayi.bActive = 1; / item is active ItemDefArrayi.hClient = hClientItem; / client ha
19、ndle ItemDefArrayi.dwBlobSize = 0; / blob size ItemDefArrayi.pBlob = IntPtr.Zero; / pointer to blob ItemDefArrayi.vtRequestedDataType = 2; /Word数据类型 /初始化输出参数 IntPtr pResults = IntPtr.Zero; IntPtr pErrors = IntPtr.Zero; try / 添加项到组 (IOPCItemMgt)GetGroupByName(groupName).AddItems(itemsName.Length,Item
20、DefArray,out pResults,out pErrors); / Unmarshal to get the server handles out fom the m_pItemResult / after checking the errors int errors = new intitemsName.Length; Marshal.Copy(pErrors, errors, 0,itemsName.Length); IntPtr pos = pResults; for(int i=0;iitemsName.Length;i+) /循环检查错误 if (errorsi = 0) O
21、PCITEMRESULT result = (OPCITEMRESULT)Marshal.PtrToStructure(pos, typeof(OPCITEMRESULT); itemsIDi = result.hServer; this.hitemsID.Add(itemsNamei,result.hServer); pos = new IntPtr(pos.ToInt32() + Marshal.SizeOf(typeof(OPCITEMRESULT); else success=false; break; catch (System.Exception err) / catch for
22、error in adding items. success=false; finally / 释放非托管内存 if(pResults != IntPtr.Zero) Marshal.FreeCoTaskMem(pResults); pResults = IntPtr.Zero; if(pErrors != IntPtr.Zero) Marshal.FreeCoTaskMem(pErrors); pErrors = IntPtr.Zero; return success; 说明:使用该函数时,在类的开头,应该先声明整数数据,以用于保存由本函数返回的服务器对 每一项分配的Item ID号: 5
23、、 向指定组中指定的一系列项变量写入数据的公开方法 / / 一次性写入多个值 / / 指定组名 / 由服务器给每个项分配的标志号 / 一系列值 / 无错误,返回true,否则返回false public bool Write(string groupName,int itemID,object values) bool success=true; IntPtr pErrors = IntPtr.Zero; if(GetGroupByName(groupName) != null) try /同步写入 (IOPCSyncIO)GetGroupByName(groupName).Write(ite
24、mID.Length,itemID,values,out pErrors); int errors = new intitemID.Length; Marshal.Copy(pErrors, errors, 0,itemID.Length); for(int i=0;i / 一次性读取多个数据 / / 指定组名 / 由服务器给每个项分配的标志号 / 返回的值 / 无错误,返回true,否则返回false public bool Read(string groupName,int itemID,object result) bool success=true; /指向非托管内存 /指向非托管内存
25、 IntPtr pItemValues = IntPtr.Zero; IntPtr pErrors = IntPtr.Zero; if(GetGroupByName(groupName)!=null) try /同步读取 (IOPCSyncIO)GetGroupByName(groupName).Read(OPCDATASOURCE.OPC_DS_DEVICE,itemID.Length, itemID,out pItemValues,out pErrors); int errors = new intitemID.Length; Marshal.Copy(pErrors, errors, 0
26、,itemID.Length); OPCITEMSTATE pItemState=new OPCITEMSTATEitemID.Length; IntPtr pos = pItemValues; for(int i=0;i 、创建服 务器接口 在程序初始化处,添加: server =new S7Connection.SynServer(S7Connection.ServerType.OPC_SimaticNET); 、打开 连接 string err; server.Open(out err); 、添加组 server.AddGroup(“maiker“,1,350,out err); ser
27、ver.AddGroup(“maiker1“,1,350,out err); 、添加项(即变量),同样在程序的初始化中,将一系列项添加到他们各自得组。 string m1=“S7:DEMOMB1“,“S7:DEMOMW3“; string m2=“S7:DEMOMB6“,“S7:DEMOMW8“; server.AddItems(“maiker“,m1,nt); server.AddItems(“maiker1“,m2,nt1); 、读写数据,这里以写数据为例: obj0=this.textBox2.Text; obj1=this.textBox3.Text; if(radioButton1
28、.Checked) server.Write(“maiker“,nt,obj); else if(radioButton2.Checked) server.Write(“maiker1“,nt1,obj); 至此并完成了数据的通讯,如何,只要你把类库开发完善,在它的基础上再开发, 会异常简单,本人已开发了完善的类库,上面的类库只是把最重要 的部分讲解出来, 我曾经在网上求助过很多次这方面的知识, 无人应答。 唉! 太不容易 了,等待 Simatic NET 软 件花费了我一个月的时间,然后读几百页的英文文档,到开发 程序, 并测试花费了我一个星期的空闲时间, 写这篇文章, 又花费了我一个晚上的时间 , 不过我还是愿意把这些摸索出来的东西发给大家。