運(yùn)用VC#編程通過OPC方式實(shí)現(xiàn)PC機(jī)與西門子PLC通訊—
運(yùn)用VC#編程通過OPC方式實(shí)現(xiàn)PC機(jī)與西門子PLC通訊--同步篇
1、 OPC服務(wù)介紹
西門子提供的最新軟件:Simatic Net PC-Software CD 2005為各種組態(tài)軟件的開發(fā)提供了一個(gè)統(tǒng)一的平臺(tái),它建立的PC站既為一些組態(tài)軟件,如:WinCC、Protol等提供了與PLC的通訊平臺(tái),也提供了一套編程接口,可使用高級(jí)語言編程通過Simatic Net訪問PLC數(shù)據(jù)。本文討論的主要就是這個(gè)編程接口,最新版的Simatic NET支持五種編程方式:
<1>、ActiveX控件
提供了一系列數(shù)據(jù)訪問控件,以便于向VB6這種語言使用控件的方式與PLC通訊。
<2>、OPC自動(dòng)化
為VB6、Dephi等語言運(yùn)用OLE 自動(dòng)化的方式進(jìn)行編程。
<3>、OPC用戶接口
這是專門為VC++提供的一種高效編程方式,其靈活程度與執(zhí)行效率比前面的兩種方式均要高得多。
<4>、針對(duì)微軟的.NET平臺(tái)的OPC用戶接口
這也是一種非常靈活的編程接口,不過它針對(duì)的是.NET平臺(tái),其提供了大量的.NET類庫(kù),以便于像VC#、VB.NET等高級(jí)語言編程。本文將詳細(xì)的介紹該接口。
<5>、OPL XML接口
顧名思義,主要是針對(duì)XML編程的。
對(duì)于<2>、<3>、<4>編程方式,他們各自又可以分為同步訪問方式和異步訪問方式。按西門子的文檔解釋:同步通訊指的是當(dāng)一個(gè)客戶在訪問服務(wù)器時(shí),其他客戶的訪問必須等待,直到服務(wù)器處理完該客戶的請(qǐng)求,才能繼續(xù)進(jìn)行下一個(gè)服務(wù),異步訪問與之正好相反,本文主要講的是同步編程篇,異步篇以后再提供。
2、 配置OPC服務(wù)器
要進(jìn)行編程,必須先配置服務(wù)器。本文以Prfibus DP網(wǎng)絡(luò)為例,介紹PC站的配置。其內(nèi)容主要來自西門子文檔。
需要的軟件:
Step7 V5.3
Simatic Net PC-Software CD 2005
需要的硬件:
至少為CP5611或以上級(jí)別,筆記本可以為CP5511,帶DP口的S7-300 PLC(若使用Simatic NET的仿真功能可以不需要這些硬件,后面會(huì)介紹到)
<1>、組態(tài)一個(gè)S7站,配置Profibus DP網(wǎng)絡(luò),其DP地址設(shè)為3,并下載到PLC,然后把網(wǎng)線由MPI口轉(zhuǎn)到DP口。S7站的配置這里就不介紹了。
<2>、在 Step7 V5.3中建立一個(gè)新工程,插入一個(gè)PC站,并把該P(yáng)C站的名字改成與你的計(jì)算機(jī)名字相同。打開該P(yáng)C站的硬件組態(tài)界面。插入OPC服務(wù)器和連接卡CP5611(或者CP5511),他們?cè)赑C槽中處的位置可以任意,
注:在插入CP5611時(shí),應(yīng)該選擇與組態(tài)S7站一樣的Profibus網(wǎng)絡(luò),并將網(wǎng)絡(luò)地址設(shè)為2,一定不要與PLC的地址沖突。
然后點(diǎn)擊下面工具條標(biāo)為紅色的按鈕:
選中”O(jiān)PC Server”,然后插入一個(gè)新的連接,
在彈出的對(duì)話框中選擇連接類型為S7 Connection,
在OK后,然后在新對(duì)話框的紅色標(biāo)志位置輸入3,表示PLC的地址,
并選擇Address Details…,設(shè)置CPU的槽號(hào)為2,
OK后,然后編譯并保存。
<3>、然后建立OPC服務(wù)器,有兩種方式,本文介紹較簡(jiǎn)單的一種。
打開,Simatic Net中的Station Configurator,一般安裝后,他會(huì)自動(dòng)啟動(dòng),并點(diǎn)擊Import Station…按鈕,找到你剛才在Step 7中建立PC站時(shí)創(chuàng)建的
XDBs文件夾下的XDB文件,然后導(dǎo)入成功。
<4>、可以使用Simatic Net中的OPC Scout,并選擇Simatic NET服務(wù),然后在它下面創(chuàng)建組,然后在組下創(chuàng)建變量,這樣可以監(jiān)控PLC數(shù)據(jù),VC#編程不需要使用該程序,但熟悉使用OPC Scout有利于了解Simatic Net中的編程結(jié)構(gòu)。
說明:打開Simatic Net中的Configuration Console,選中S7進(jìn)行如下的配置后,可以不需要PLC、CP5611等并可以模擬,
上面的所有步驟,均可在Configuration Console下,PC Station的根樹下,選擇相應(yīng)的幫助文檔得到。
3、 OPC編程
<1>、西門子的變量結(jié)構(gòu)如下:
----------------------服務(wù)器------------------------------
/ OPC.SimaticNet OPCServer.Wincc .... (一系列類型的服務(wù)器)
/ Group1 Group2 Group3 ...(把更新時(shí)間一致的變量統(tǒng)一為一個(gè)組)
/ Item1 Item2 ... (變量:I、Q、M、DB等,指向網(wǎng)絡(luò)中某個(gè)PC站OPC Server服務(wù)的某個(gè)連接)
-----------------------------------------------------------------------------------------------------------------
第一層是不同種類的服務(wù)器,如:OPC.SimaticNET類型,OPC.SimaticNET.DP類型,OPCServer.WinCC等一系列類型,這里選擇OPC.SimaticNET類型。
第二層是Group,一個(gè)服務(wù)器下可以有多個(gè)組,可以把組理解為掃描周期相同的一系列變量的集合。在開發(fā)組態(tài)界面時(shí),可以把一個(gè)界面中的所有變量統(tǒng)一到一個(gè)組中。
第三層是Item,項(xiàng)是指向網(wǎng)絡(luò)中某個(gè)PC站OPC Server服務(wù)的某個(gè)連接的一系列變量,如:I、Q、M、DB等
<2>、項(xiàng)的命名
項(xiàng)即Item,在S7連接中針對(duì)的直接是PLC中的變量,因此它的命名很重要:
格式: :[]
其中的protocolID表示連接類型,在上面的組態(tài)PC站時(shí)可以選擇,這里應(yīng)該與它一致,類型有9種,最常用的為S7,即S7連接,其他類型請(qǐng)參看文檔。
Connectionname:顧名思義,即在上面的組態(tài)PC站時(shí)產(chǎn)生的連接名,如果使用仿真功能,連接名為DEMO Variablename:變量名有一系列規(guī)則,這里舉例說明,讀者也可以使用OPC Scout創(chuàng)建變量,學(xué)習(xí)程序是如何生成變量名的。
S7:[DEMO]MB1 :表示連接類型為S7,連接名為DEMO(這里為仿真),變量為MB1
S7:[DEMO]QB0,3: 表示為從QB0開始的三個(gè)連續(xù)變量。
S7:[DEMO]DB10,X4.6 :表示DB10的DBX4.6。
<3>、添加引用
在VC#開發(fā)環(huán)境中添加對(duì)OpcRcw.Da庫(kù)的引用引用,該庫(kù)屬于.NET庫(kù),不屬于COM庫(kù),西門子雖然編寫了類庫(kù),以提供對(duì).NET平臺(tái)的支持,但這些類庫(kù)仍然難于編程,
里面包含了大量的在托管和非托管區(qū)傳輸數(shù)據(jù),因此我們需要在它的基礎(chǔ)上再開發(fā)一個(gè)類庫(kù),以簡(jiǎn)化以后的編程,首先在類的開頭使用命名空間:
using System.Runtime.InteropServices;
using OpcRcw.Da;
using System.Collections;
<4>、編程
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 Hashtable(11); //用于記錄組名和組ID號(hào)
private System.Collections.Hashtable hitemsID=new Hashtable(17); //用于記錄項(xiàng)名和項(xiàng)ID號(hào)
private Guid iidRequiredInterface;
private int hClientGroup = 0; //客戶組號(hào)
private int hClientItem=0; //Item號(hào)
2、 創(chuàng)建服務(wù)器,編寫Open()方法
/// 創(chuàng)建一個(gè)OPC Server接口
///
/// 返回錯(cuò)誤信息
/// 若為true,創(chuàng)建成功,否則創(chuàng)建失敗
public bool Open(out string error)
{
error="";bool success=true;
Type svrComponenttyp ;
//獲取 OPC Server COM 接口
iidRequiredInterface = typeof(IOPCItemMgt).GUID;
svrComponenttyp = System.Type.GetTypeFromProgID(serverType);
try
{
//創(chuàng)建接口
pIOPCServer =(IOPCServer)System.Activator.CreateInstance(svrComponenttyp);
error="";
}
catch (System.Exception err) //捕捉失敗信息
{
error="錯(cuò)誤信息:"+err.Message;success=false;
}
Return true;
}
3、 在服務(wù)器上添加用于添加Group的函數(shù)
/// 添加組
/// 組名
/// /創(chuàng)建時(shí),組是否被激活
/// //組的刷新頻率,以ms為單位
/// 返回錯(cuò)誤信息
/// 若為true,添加成功,否則添加失敗
public bool AddGroup(string groupName,int bActive,int updateRate,out string error)
{
error="";
int dwLCID = 0x407; //本地語言為英語
int pRevUpdateRate;
float deadband = 0;
// 處理非托管COM內(nèi)存
GCHandle hDeadband;
IntPtr pTimeBias = IntPtr.Zero;
hDeadband = GCHandle.Alloc(deadband,GCHandleType.Pinned);
try
{
pIOPCServer.AddGroup(groupName, //組名
bActive, //創(chuàng)建時(shí),組是否被激活
updateRate, //組的刷新頻率,以ms為單位
hClientGroup, //客戶號(hào)
pTimeBias, //這里不使用
(IntPtr)hDeadband,
dwLCID, //本地語言
out nSvrGroupID, //移去組時(shí),用到的組ID號(hào)
out pRevUpdateRate, //返回組中的變量改變時(shí)的最短通知時(shí)間間隔
ref iidRequiredInterface,
out pobjGroup1); //指向要求的接口
hClientGroup=hClientGroup+1;
int groupID=nSvrGroupID;
groupsID.Add(groupName,groupID);
}
catch (System.Exception err) //捕捉失敗信息
{
error="錯(cuò)誤信息:"+err.Message;
}
finally
{
if (hDeadband.IsAllocated) hDeadband.Free();
}
if(error=="")
return true;
else
return false;
}
4、 向指定的組中添加變量的函數(shù)
/// 添加多個(gè)項(xiàng)到組
///
/// 指定組名
/// 指定項(xiàng)名
/// 由函數(shù)返回的服務(wù)器確定的項(xiàng)ID號(hào)
/// 無錯(cuò)誤,返回true,否則返回false
public bool AddItems(string groupName,string[] itemsName,int[] itemsID)
{
bool success=true;
OPCITEMDEF[] ItemDefArray=new OPCITEMDEF[itemsName.Length];
for(int i=0;i<ITEMSNAME.LENGTH;I++)
{
hClientItem=hClientItem+1;
ItemDefArray[i].szAccessPath = ""; // 可選的通道路徑,對(duì)于Simatiic Net不需要。
ItemDefArray[i].szItemID = itemsName[i]; // ItemID, see above
ItemDefArray[i].bActive = 1; // item is active
ItemDefArray[i].hClient = hClientItem; // client handle
ItemDefArray[i].dwBlobSize = 0; // blob size
ItemDefArray[i].pBlob = IntPtr.Zero; // pointer to blob
ItemDefArray[i].vtRequestedDataType = 2; //Word數(shù)據(jù)類型
}
//初始化輸出參數(shù)
IntPtr pResults = IntPtr.Zero;
IntPtr pErrors = IntPtr.Zero;
try
{
// 添加項(xiàng)到組
((IOPCItemMgt)GetGroupByName(groupName)).AddItems(itemsName.Length,ItemDefArray,out pResults,out pErrors);
// Unmarshal to get the server handles out fom the m_pItemResult
// after checking the errors
int[] errors = new int[itemsName.Length];
Marshal.Copy(pErrors, errors, 0,itemsName.Length);
IntPtr pos = pResults;
for(int i=0;i
{
if (errors[i] == 0)
{
OPCITEMRESULT result = (OPCITEMRESULT)Marshal.PtrToStructure(pos, typeof(OPCITEMRESULT));
itemsID[i] = result.hServer;
this.hitemsID.Add(itemsName[i],result.hServer);
pos = new IntPtr(pos.ToInt32() + Marshal.SizeOf(typeof(OPCITEMRESULT)));
}
else
{
success=false;
break;
}
}
}
catch (System.Exception err) // catch for error in adding items.
{
success=false;
}
finally
{
// 釋放非托管內(nèi)存
if(pResults != IntPtr.Zero)
{
Marshal.FreeCoTaskMem(pResults);
pResults = IntPtr.Zero;
}
if(pErrors != IntPtr.Zero)
{
Marshal.FreeCoTaskMem(pErrors);
pErrors = IntPtr.Zero;
}
}
return success;
}
說明:使用該函數(shù)時(shí),在類的開頭,應(yīng)該先聲明整數(shù)數(shù)據(jù),以用于保存由本函數(shù)返回的服務(wù)器對(duì)每一項(xiàng)分配的Item ID號(hào):
5、 向指定組中指定的一系列項(xiàng)變量寫入數(shù)據(jù)的公開方法
///
/// 一次性寫入多個(gè)值
///
/// 指定組名
/// 由服務(wù)器給每個(gè)項(xiàng)分配的標(biāo)志號(hào)
/// 一系列值
/// 無錯(cuò)誤,返回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(itemID.Length,itemID,values,out pErrors);
int[] errors = new int[itemID.Length];
Marshal.Copy(pErrors, errors, 0,itemID.Length);
for(int i=0;i
{
if (errors[i] != 0)
{
pErrors = IntPtr.Zero;
success=false;
}
}
}
catch(System.Exception error)
{
success=false;
}
}
return success;
}
注:參數(shù)int[] itemID應(yīng)該是與AddItems函數(shù)中的int[] itemsID參數(shù)相對(duì)應(yīng)。
6、 編寫獲取變量值的函數(shù)
/// 一次性讀取多個(gè)數(shù)據(jù)
/// 指定組名
/// >由服務(wù)器給每個(gè)項(xiàng)分配的標(biāo)志號(hào)
/// 返回的值
/// 無錯(cuò)誤,返回true,否則返回false
public bool Read(string groupName,int[] itemID,object[] result)
{
bool success=true;
//指向非托管內(nèi)存
//指向非托管內(nèi)存
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 int[itemID.Length];
Marshal.Copy(pErrors, errors, 0,itemID.Length);
OPCITEMSTATE[] pItemState=new OPCITEMSTATE[itemID.Length];
IntPtr pos = pItemValues;
for(int i=0;i
{
if (errors[i] == 0)
{
//從非托管區(qū)封送數(shù)據(jù)到托管區(qū)
pItemState[i] = (OPCITEMSTATE)Marshal.PtrToStructure(pos,typeof(OPCITEMSTATE));
pos = new IntPtr(pos.ToInt32() + Marshal.SizeOf(typeof(OPCITEMSTATE)));
result[i]=pItemState[i].vDataValue;
}
}
}
catch(System.Exception error)
{
return false;
}
}
return success;
}
注:同Write()函數(shù)一樣,參數(shù)int[] itemID應(yīng)該是與AddItems函數(shù)中的int[] itemsID參數(shù)相對(duì)應(yīng)。
通過給類編寫上面的幾個(gè)最重要的函數(shù),我們已經(jīng)可以讀寫PLC數(shù)據(jù)了,下面給出例子。
創(chuàng)建一個(gè)C#工程,添加對(duì)上面開發(fā)的類庫(kù)的引用,并在窗體類的開頭,聲名:
int[] nt=new int[2];int[] nt1=new int[2];
S7Connection.SynServer server;
其中的SynServer即為上面開發(fā)的類。
<1>、創(chuàng)建服務(wù)器接口
在程序初始化處,添加:
server =new S7Connection.SynServer(S7Connection.ServerType.OPC_SimaticNET);
<2>、打開連接
string err;
server.Open(out err);
<3>、添加組
server.AddGroup("maiker",1,350,out err);
server.AddGroup("maiker1",1,350,out err);
<4>、添加項(xiàng)(即變量),同樣在程序的初始化中,將一系列項(xiàng)添加到他們各自得組。
string[] m1={"S7:[DEMO]MB1","S7:[DEMO]MW3"};
string[] m2={"S7:[DEMO]MB6","S7:[DEMO]MW8"};
server.AddItems("maiker",m1,nt);
server.AddItems("maiker1",m2,nt1);
<5>、讀寫數(shù)據(jù),這里以寫數(shù)據(jù)為例:
obj[0]=this.textBox2.Text;
obj[1]=this.textBox3.Text;
if(radioButton1.Checked)
{
server.Write("maiker",nt,obj);
}
else if(radioButton2.Checked)
{
server.Write("maiker1",nt1,obj);
}
至此并完成了數(shù)據(jù)的通訊,如何,只要你把類庫(kù)開發(fā)完善,在它的基礎(chǔ)上再開發(fā),會(huì)異常簡(jiǎn)單,本人已開發(fā)了完善的類庫(kù),上面的類庫(kù)只是把最重要的部分講解出來,我曾經(jīng)在網(wǎng)上求助過很多次這方面的知識(shí),無人應(yīng)答。唉!太不容易了,等待Simatic NET軟件花費(fèi)了我一個(gè)月的時(shí)間,然后讀幾百頁的英文文檔,到開發(fā)程序,并測(cè)試花費(fèi)了我一個(gè)星期的空閑時(shí)間,寫這篇文章,又花費(fèi)了我一個(gè)晚上的時(shí)間,不過我還是愿意把這些摸索出來的東西發(fā)給大家。
1、 OPC服務(wù)介紹
西門子提供的最新軟件:Simatic Net PC-Software CD 2005為各種組態(tài)軟件的開發(fā)提供了一個(gè)統(tǒng)一的平臺(tái),它建立的PC站既為一些組態(tài)軟件,如:WinCC、Protol等提供了與PLC的通訊平臺(tái),也提供了一套編程接口,可使用高級(jí)語言編程通過Simatic Net訪問PLC數(shù)據(jù)。本文討論的主要就是這個(gè)編程接口,最新版的Simatic NET支持五種編程方式:
<1>、ActiveX控件
提供了一系列數(shù)據(jù)訪問控件,以便于向VB6這種語言使用控件的方式與PLC通訊。
<2>、OPC自動(dòng)化
為VB6、Dephi等語言運(yùn)用OLE 自動(dòng)化的方式進(jìn)行編程。
<3>、OPC用戶接口
這是專門為VC++提供的一種高效編程方式,其靈活程度與執(zhí)行效率比前面的兩種方式均要高得多。
<4>、針對(duì)微軟的.NET平臺(tái)的OPC用戶接口
這也是一種非常靈活的編程接口,不過它針對(duì)的是.NET平臺(tái),其提供了大量的.NET類庫(kù),以便于像VC#、VB.NET等高級(jí)語言編程。本文將詳細(xì)的介紹該接口。
<5>、OPL XML接口
顧名思義,主要是針對(duì)XML編程的。
對(duì)于<2>、<3>、<4>編程方式,他們各自又可以分為同步訪問方式和異步訪問方式。按西門子的文檔解釋:同步通訊指的是當(dāng)一個(gè)客戶在訪問服務(wù)器時(shí),其他客戶的訪問必須等待,直到服務(wù)器處理完該客戶的請(qǐng)求,才能繼續(xù)進(jìn)行下一個(gè)服務(wù),異步訪問與之正好相反,本文主要講的是同步編程篇,異步篇以后再提供。
2、 配置OPC服務(wù)器
要進(jìn)行編程,必須先配置服務(wù)器。本文以Prfibus DP網(wǎng)絡(luò)為例,介紹PC站的配置。其內(nèi)容主要來自西門子文檔。
需要的軟件:
Step7 V5.3
Simatic Net PC-Software CD 2005
需要的硬件:
至少為CP5611或以上級(jí)別,筆記本可以為CP5511,帶DP口的S7-300 PLC(若使用Simatic NET的仿真功能可以不需要這些硬件,后面會(huì)介紹到)
<1>、組態(tài)一個(gè)S7站,配置Profibus DP網(wǎng)絡(luò),其DP地址設(shè)為3,并下載到PLC,然后把網(wǎng)線由MPI口轉(zhuǎn)到DP口。S7站的配置這里就不介紹了。
<2>、在 Step7 V5.3中建立一個(gè)新工程,插入一個(gè)PC站,并把該P(yáng)C站的名字改成與你的計(jì)算機(jī)名字相同。打開該P(yáng)C站的硬件組態(tài)界面。插入OPC服務(wù)器和連接卡CP5611(或者CP5511),他們?cè)赑C槽中處的位置可以任意,
注:在插入CP5611時(shí),應(yīng)該選擇與組態(tài)S7站一樣的Profibus網(wǎng)絡(luò),并將網(wǎng)絡(luò)地址設(shè)為2,一定不要與PLC的地址沖突。
然后點(diǎn)擊下面工具條標(biāo)為紅色的按鈕:
選中”O(jiān)PC Server”,然后插入一個(gè)新的連接,
在彈出的對(duì)話框中選擇連接類型為S7 Connection,
在OK后,然后在新對(duì)話框的紅色標(biāo)志位置輸入3,表示PLC的地址,
并選擇Address Details…,設(shè)置CPU的槽號(hào)為2,
OK后,然后編譯并保存。
<3>、然后建立OPC服務(wù)器,有兩種方式,本文介紹較簡(jiǎn)單的一種。
打開,Simatic Net中的Station Configurator,一般安裝后,他會(huì)自動(dòng)啟動(dòng),并點(diǎn)擊Import Station…按鈕,找到你剛才在Step 7中建立PC站時(shí)創(chuàng)建的
XDBs文件夾下的XDB文件,然后導(dǎo)入成功。
<4>、可以使用Simatic Net中的OPC Scout,并選擇Simatic NET服務(wù),然后在它下面創(chuàng)建組,然后在組下創(chuàng)建變量,這樣可以監(jiān)控PLC數(shù)據(jù),VC#編程不需要使用該程序,但熟悉使用OPC Scout有利于了解Simatic Net中的編程結(jié)構(gòu)。
說明:打開Simatic Net中的Configuration Console,選中S7進(jìn)行如下的配置后,可以不需要PLC、CP5611等并可以模擬,
上面的所有步驟,均可在Configuration Console下,PC Station的根樹下,選擇相應(yīng)的幫助文檔得到。
3、 OPC編程
<1>、西門子的變量結(jié)構(gòu)如下:
----------------------服務(wù)器------------------------------
/ OPC.SimaticNet OPCServer.Wincc .... (一系列類型的服務(wù)器)
/ Group1 Group2 Group3 ...(把更新時(shí)間一致的變量統(tǒng)一為一個(gè)組)
/ Item1 Item2 ... (變量:I、Q、M、DB等,指向網(wǎng)絡(luò)中某個(gè)PC站OPC Server服務(wù)的某個(gè)連接)
-----------------------------------------------------------------------------------------------------------------
第一層是不同種類的服務(wù)器,如:OPC.SimaticNET類型,OPC.SimaticNET.DP類型,OPCServer.WinCC等一系列類型,這里選擇OPC.SimaticNET類型。
第二層是Group,一個(gè)服務(wù)器下可以有多個(gè)組,可以把組理解為掃描周期相同的一系列變量的集合。在開發(fā)組態(tài)界面時(shí),可以把一個(gè)界面中的所有變量統(tǒng)一到一個(gè)組中。
第三層是Item,項(xiàng)是指向網(wǎng)絡(luò)中某個(gè)PC站OPC Server服務(wù)的某個(gè)連接的一系列變量,如:I、Q、M、DB等
<2>、項(xiàng)的命名
項(xiàng)即Item,在S7連接中針對(duì)的直接是PLC中的變量,因此它的命名很重要:
格式: :[]
其中的protocolID表示連接類型,在上面的組態(tài)PC站時(shí)可以選擇,這里應(yīng)該與它一致,類型有9種,最常用的為S7,即S7連接,其他類型請(qǐng)參看文檔。
Connectionname:顧名思義,即在上面的組態(tài)PC站時(shí)產(chǎn)生的連接名,如果使用仿真功能,連接名為DEMO Variablename:變量名有一系列規(guī)則,這里舉例說明,讀者也可以使用OPC Scout創(chuàng)建變量,學(xué)習(xí)程序是如何生成變量名的。
S7:[DEMO]MB1 :表示連接類型為S7,連接名為DEMO(這里為仿真),變量為MB1
S7:[DEMO]QB0,3: 表示為從QB0開始的三個(gè)連續(xù)變量。
S7:[DEMO]DB10,X4.6 :表示DB10的DBX4.6。
<3>、添加引用
在VC#開發(fā)環(huán)境中添加對(duì)OpcRcw.Da庫(kù)的引用引用,該庫(kù)屬于.NET庫(kù),不屬于COM庫(kù),西門子雖然編寫了類庫(kù),以提供對(duì).NET平臺(tái)的支持,但這些類庫(kù)仍然難于編程,
里面包含了大量的在托管和非托管區(qū)傳輸數(shù)據(jù),因此我們需要在它的基礎(chǔ)上再開發(fā)一個(gè)類庫(kù),以簡(jiǎn)化以后的編程,首先在類的開頭使用命名空間:
using System.Runtime.InteropServices;
using OpcRcw.Da;
using System.Collections;
<4>、編程
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 Hashtable(11); //用于記錄組名和組ID號(hào)
private System.Collections.Hashtable hitemsID=new Hashtable(17); //用于記錄項(xiàng)名和項(xiàng)ID號(hào)
private Guid iidRequiredInterface;
private int hClientGroup = 0; //客戶組號(hào)
private int hClientItem=0; //Item號(hào)
2、 創(chuàng)建服務(wù)器,編寫Open()方法
/// 創(chuàng)建一個(gè)OPC Server接口
///
/// 返回錯(cuò)誤信息
/// 若為true,創(chuàng)建成功,否則創(chuàng)建失敗
public bool Open(out string error)
{
error="";bool success=true;
Type svrComponenttyp ;
//獲取 OPC Server COM 接口
iidRequiredInterface = typeof(IOPCItemMgt).GUID;
svrComponenttyp = System.Type.GetTypeFromProgID(serverType);
try
{
//創(chuàng)建接口
pIOPCServer =(IOPCServer)System.Activator.CreateInstance(svrComponenttyp);
error="";
}
catch (System.Exception err) //捕捉失敗信息
{
error="錯(cuò)誤信息:"+err.Message;success=false;
}
Return true;
}
3、 在服務(wù)器上添加用于添加Group的函數(shù)
/// 添加組
/// 組名
/// /創(chuàng)建時(shí),組是否被激活
/// //組的刷新頻率,以ms為單位
/// 返回錯(cuò)誤信息
/// 若為true,添加成功,否則添加失敗
public bool AddGroup(string groupName,int bActive,int updateRate,out string error)
{
error="";
int dwLCID = 0x407; //本地語言為英語
int pRevUpdateRate;
float deadband = 0;
// 處理非托管COM內(nèi)存
GCHandle hDeadband;
IntPtr pTimeBias = IntPtr.Zero;
hDeadband = GCHandle.Alloc(deadband,GCHandleType.Pinned);
try
{
pIOPCServer.AddGroup(groupName, //組名
bActive, //創(chuàng)建時(shí),組是否被激活
updateRate, //組的刷新頻率,以ms為單位
hClientGroup, //客戶號(hào)
pTimeBias, //這里不使用
(IntPtr)hDeadband,
dwLCID, //本地語言
out nSvrGroupID, //移去組時(shí),用到的組ID號(hào)
out pRevUpdateRate, //返回組中的變量改變時(shí)的最短通知時(shí)間間隔
ref iidRequiredInterface,
out pobjGroup1); //指向要求的接口
hClientGroup=hClientGroup+1;
int groupID=nSvrGroupID;
groupsID.Add(groupName,groupID);
}
catch (System.Exception err) //捕捉失敗信息
{
error="錯(cuò)誤信息:"+err.Message;
}
finally
{
if (hDeadband.IsAllocated) hDeadband.Free();
}
if(error=="")
return true;
else
return false;
}
4、 向指定的組中添加變量的函數(shù)
/// 添加多個(gè)項(xiàng)到組
///
/// 指定組名
/// 指定項(xiàng)名
/// 由函數(shù)返回的服務(wù)器確定的項(xiàng)ID號(hào)
/// 無錯(cuò)誤,返回true,否則返回false
public bool AddItems(string groupName,string[] itemsName,int[] itemsID)
{
bool success=true;
OPCITEMDEF[] ItemDefArray=new OPCITEMDEF[itemsName.Length];
for(int i=0;i<ITEMSNAME.LENGTH;I++)
{
hClientItem=hClientItem+1;
ItemDefArray[i].szAccessPath = ""; // 可選的通道路徑,對(duì)于Simatiic Net不需要。
ItemDefArray[i].szItemID = itemsName[i]; // ItemID, see above
ItemDefArray[i].bActive = 1; // item is active
ItemDefArray[i].hClient = hClientItem; // client handle
ItemDefArray[i].dwBlobSize = 0; // blob size
ItemDefArray[i].pBlob = IntPtr.Zero; // pointer to blob
ItemDefArray[i].vtRequestedDataType = 2; //Word數(shù)據(jù)類型
}
//初始化輸出參數(shù)
IntPtr pResults = IntPtr.Zero;
IntPtr pErrors = IntPtr.Zero;
try
{
// 添加項(xiàng)到組
((IOPCItemMgt)GetGroupByName(groupName)).AddItems(itemsName.Length,ItemDefArray,out pResults,out pErrors);
// Unmarshal to get the server handles out fom the m_pItemResult
// after checking the errors
int[] errors = new int[itemsName.Length];
Marshal.Copy(pErrors, errors, 0,itemsName.Length);
IntPtr pos = pResults;
for(int i=0;i
{
if (errors[i] == 0)
{
OPCITEMRESULT result = (OPCITEMRESULT)Marshal.PtrToStructure(pos, typeof(OPCITEMRESULT));
itemsID[i] = result.hServer;
this.hitemsID.Add(itemsName[i],result.hServer);
pos = new IntPtr(pos.ToInt32() + Marshal.SizeOf(typeof(OPCITEMRESULT)));
}
else
{
success=false;
break;
}
}
}
catch (System.Exception err) // catch for error in adding items.
{
success=false;
}
finally
{
// 釋放非托管內(nèi)存
if(pResults != IntPtr.Zero)
{
Marshal.FreeCoTaskMem(pResults);
pResults = IntPtr.Zero;
}
if(pErrors != IntPtr.Zero)
{
Marshal.FreeCoTaskMem(pErrors);
pErrors = IntPtr.Zero;
}
}
return success;
}
說明:使用該函數(shù)時(shí),在類的開頭,應(yīng)該先聲明整數(shù)數(shù)據(jù),以用于保存由本函數(shù)返回的服務(wù)器對(duì)每一項(xiàng)分配的Item ID號(hào):
5、 向指定組中指定的一系列項(xiàng)變量寫入數(shù)據(jù)的公開方法
///
/// 一次性寫入多個(gè)值
///
/// 指定組名
/// 由服務(wù)器給每個(gè)項(xiàng)分配的標(biāo)志號(hào)
/// 一系列值
/// 無錯(cuò)誤,返回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(itemID.Length,itemID,values,out pErrors);
int[] errors = new int[itemID.Length];
Marshal.Copy(pErrors, errors, 0,itemID.Length);
for(int i=0;i
{
if (errors[i] != 0)
{
pErrors = IntPtr.Zero;
success=false;
}
}
}
catch(System.Exception error)
{
success=false;
}
}
return success;
}
注:參數(shù)int[] itemID應(yīng)該是與AddItems函數(shù)中的int[] itemsID參數(shù)相對(duì)應(yīng)。
6、 編寫獲取變量值的函數(shù)
/// 一次性讀取多個(gè)數(shù)據(jù)
/// 指定組名
/// >由服務(wù)器給每個(gè)項(xiàng)分配的標(biāo)志號(hào)
/// 返回的值
/// 無錯(cuò)誤,返回true,否則返回false
public bool Read(string groupName,int[] itemID,object[] result)
{
bool success=true;
//指向非托管內(nèi)存
//指向非托管內(nèi)存
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 int[itemID.Length];
Marshal.Copy(pErrors, errors, 0,itemID.Length);
OPCITEMSTATE[] pItemState=new OPCITEMSTATE[itemID.Length];
IntPtr pos = pItemValues;
for(int i=0;i
{
if (errors[i] == 0)
{
//從非托管區(qū)封送數(shù)據(jù)到托管區(qū)
pItemState[i] = (OPCITEMSTATE)Marshal.PtrToStructure(pos,typeof(OPCITEMSTATE));
pos = new IntPtr(pos.ToInt32() + Marshal.SizeOf(typeof(OPCITEMSTATE)));
result[i]=pItemState[i].vDataValue;
}
}
}
catch(System.Exception error)
{
return false;
}
}
return success;
}
注:同Write()函數(shù)一樣,參數(shù)int[] itemID應(yīng)該是與AddItems函數(shù)中的int[] itemsID參數(shù)相對(duì)應(yīng)。
通過給類編寫上面的幾個(gè)最重要的函數(shù),我們已經(jīng)可以讀寫PLC數(shù)據(jù)了,下面給出例子。
創(chuàng)建一個(gè)C#工程,添加對(duì)上面開發(fā)的類庫(kù)的引用,并在窗體類的開頭,聲名:
int[] nt=new int[2];int[] nt1=new int[2];
S7Connection.SynServer server;
其中的SynServer即為上面開發(fā)的類。
<1>、創(chuàng)建服務(wù)器接口
在程序初始化處,添加:
server =new S7Connection.SynServer(S7Connection.ServerType.OPC_SimaticNET);
<2>、打開連接
string err;
server.Open(out err);
<3>、添加組
server.AddGroup("maiker",1,350,out err);
server.AddGroup("maiker1",1,350,out err);
<4>、添加項(xiàng)(即變量),同樣在程序的初始化中,將一系列項(xiàng)添加到他們各自得組。
string[] m1={"S7:[DEMO]MB1","S7:[DEMO]MW3"};
string[] m2={"S7:[DEMO]MB6","S7:[DEMO]MW8"};
server.AddItems("maiker",m1,nt);
server.AddItems("maiker1",m2,nt1);
<5>、讀寫數(shù)據(jù),這里以寫數(shù)據(jù)為例:
obj[0]=this.textBox2.Text;
obj[1]=this.textBox3.Text;
if(radioButton1.Checked)
{
server.Write("maiker",nt,obj);
}
else if(radioButton2.Checked)
{
server.Write("maiker1",nt1,obj);
}
至此并完成了數(shù)據(jù)的通訊,如何,只要你把類庫(kù)開發(fā)完善,在它的基礎(chǔ)上再開發(fā),會(huì)異常簡(jiǎn)單,本人已開發(fā)了完善的類庫(kù),上面的類庫(kù)只是把最重要的部分講解出來,我曾經(jīng)在網(wǎng)上求助過很多次這方面的知識(shí),無人應(yīng)答。唉!太不容易了,等待Simatic NET軟件花費(fèi)了我一個(gè)月的時(shí)間,然后讀幾百頁的英文文檔,到開發(fā)程序,并測(cè)試花費(fèi)了我一個(gè)星期的空閑時(shí)間,寫這篇文章,又花費(fèi)了我一個(gè)晚上的時(shí)間,不過我還是愿意把這些摸索出來的東西發(fā)給大家。
文章版權(quán)歸西部工控xbgk所有,未經(jīng)許可不得轉(zhuǎn)載。