PLCSIM Advanced是西門(mén)子推出的一款功能強(qiáng)大的仿真軟件,目前最新發(fā)布的版本為4.0,但鑒于新版本可能存在未知的bug,故本文使用V3.0。
V3.0支持仿真1500PLC及ET 200SP,可實(shí)現(xiàn)Socket網(wǎng)絡(luò)通訊功能,也可實(shí)現(xiàn)PLC之間、PLC與設(shè)備直接的ModbusTCP等通訊。
V3.0安裝時(shí)需要先安裝WinPcap_4_1_3,V4.0則不需要。
以下為兩個(gè)版本的官網(wǎng)下載鏈接,下載時(shí)需要西門(mén)子賬號(hào),可以免費(fèi)注冊(cè)。
以下為V3.0下載鏈接:
PLCSIM Advanced V3.0
V3.0的兩個(gè)升級(jí)包(可選安裝)
以下為V4.0下載鏈接
PLCSIM Advanced V4.0
S7 Net Plus 簡(jiǎn)介
西門(mén)子PLC通訊庫(kù),支持200、200smart、300、400、1200、1500系列PLC。
說(shuō)明文檔
配置PLCSIM Advanced
打開(kāi)PLCSIM Advanced V3.0,如下圖:
Online Access要選擇右邊的PLCSIM Virtual Eth.Adapter,左側(cè)的PLCSIM不支持外部網(wǎng)絡(luò)訪問(wèn)。
TCP/IP communication with 可選以太網(wǎng)或者是本地虛擬網(wǎng)卡。local即為本地虛擬網(wǎng)卡,是在安裝PLCSIM Advanced時(shí)自動(dòng)安裝的網(wǎng)絡(luò)適配器。打開(kāi)控制面板-->網(wǎng)絡(luò)和 Internet-->網(wǎng)絡(luò)連接,Siemens PLCSIM Virtual Ethernet Adapter就是此虛擬網(wǎng)卡。使用虛擬網(wǎng)卡只能在本機(jī)進(jìn)行通訊仿真,而使用以太網(wǎng)則可以在局域網(wǎng)內(nèi)進(jìn)行仿真通訊。
Start Virtual S7-1500 PLC為PLC設(shè)置,包括IP地址、子網(wǎng)掩碼、默認(rèn)網(wǎng)關(guān)及PLC型號(hào)。設(shè)置完成后點(diǎn)擊Start按鈕則會(huì)生成一個(gè)PLC實(shí)例。創(chuàng)建成功后就可以開(kāi)始通訊仿真了。
Virtual SIMATIC Memory Ca為打開(kāi)保存PLC歷史記錄的文件夾的按鈕。
如下圖所示,在Active PLC Instance(s)可以看到已成功創(chuàng)建的PLC。
下載測(cè)試DB塊
在TIA Protal軟件中,添加一個(gè)S7-1511的設(shè)備,然后在程序塊中添加一個(gè)新的DB塊,DB號(hào)設(shè)置為10。
打開(kāi)設(shè)備的屬性 --> 防護(hù)與安全 -->連接機(jī)制,勾選“允許來(lái)自遠(yuǎn)程對(duì)象的PUT/GET通訊訪問(wèn)”。
打開(kāi)設(shè)備的屬性 --> PROFINET 接口 [X1] -->以太網(wǎng)地址,按需設(shè)置PLC的IP地址。
打開(kāi)DB10的屬性,取消勾選“優(yōu)化塊的訪問(wèn)”,并在DB10中新建如下圖所示的變量,編譯完成后則可以得到每個(gè)變量的偏移量,即此變量在DB10上的地址。
設(shè)置完成后,下載到剛剛使用PLCSIM Advanced創(chuàng)建的仿真PLC中,需要注意網(wǎng)段要設(shè)置成與仿真PLC同一網(wǎng)段。
引用S7NetPlus
創(chuàng)建一個(gè)測(cè)試程序,此處創(chuàng)建的是一個(gè)控制臺(tái)應(yīng)用程序。
在NuGet下載S7NetPlus,如下圖所示,版本可按需選擇
新建一個(gè)名為PLCInstance的類(lèi),創(chuàng)建PLC單例。
class PLCInstance { private PLCInstance() { plcObj = new Plc(CpuType.S71500, "192.168.10.230", 0, 1); } ////// PLC單例 /// public static PLCInstance Instance { get { return Nested.instance; } } ////// 防止調(diào)用此類(lèi)靜態(tài)方法時(shí),創(chuàng)建新的實(shí)例 /// private class Nested { internal static readonly PLCInstance instance = null; static Nested() { instance = new PLCInstance(); } } ////// 私有PLC單例對(duì)象 /// private static Plc plcObj; ////// 連接至PLC并返回連接狀態(tài) /// ///private bool ConnectToPLC() { try { plcObj.Open(); return plcObj.IsConnected ? true : false; } catch (Exception) { return false; } } /// /// 關(guān)閉連接 /// private void Disconnect() { plcObj.Close(); } }
讀寫(xiě)數(shù)據(jù)
S7NetPlus提供了多種讀寫(xiě)的方式,可以讀取字節(jié)自行解析或者按照指定格式寫(xiě)入字節(jié),也可以指定地址進(jìn)行讀寫(xiě),還可以使用變量、結(jié)構(gòu)體或者類(lèi)進(jìn)行單個(gè)或者批量讀寫(xiě)。
1、指定地址讀寫(xiě)
這種方法可以在Read方法中以字符串形式傳入需要讀取的地址,返回的是Object類(lèi)型的值,需要使用者自行做類(lèi)型轉(zhuǎn)換。Write方法則同理,以字符串的形式指定需要寫(xiě)入的地址,并在第二個(gè)參數(shù)傳入需要寫(xiě)入的值,但是需要注意西門(mén)子PLC內(nèi)的數(shù)據(jù)類(lèi)型與C#的數(shù)據(jù)類(lèi)型的對(duì)應(yīng)。以下為讀寫(xiě)DB10的0.0地址上的布爾量的值示例,此方式均支持讀取與寫(xiě)入。
//讀取 bool result = (bool)plc.Read("DB10.DBX0.0"); //寫(xiě)入 plc.Write("DB10.DBX0.0",!result);
雖然這種方式比較簡(jiǎn)單且方便,但是它是作者不推薦的方式,文檔中原文如下:
This method reads a single variable from the plc, by parsing the string and returning the correct result. While this is the easiest method to get started, is very inefficient because the driver sends a TCP request for every variable.
意思就是,這種方法會(huì)通過(guò)解析傳入的地址字符串來(lái)獲取需要讀寫(xiě)的地址,對(duì)于使用者來(lái)說(shuō)是非常簡(jiǎn)單的使用方式,但是S7NetPlus會(huì)為每個(gè)通過(guò)這種方式讀寫(xiě)的變量生成一個(gè)新的TCP請(qǐng)求,因此在讀寫(xiě)多個(gè)變量時(shí),執(zhí)行效率會(huì)比較低。
S7NetPlus使用的通訊本質(zhì)上是西門(mén)子的S7通訊,通過(guò)發(fā)送七層通訊報(bào)文來(lái)建立與西門(mén)子PLC的TCP連接,后續(xù)也是根據(jù)S7通訊的通訊協(xié)議生成并發(fā)送報(bào)文來(lái)實(shí)現(xiàn)PLC的數(shù)據(jù)讀寫(xiě)。所以當(dāng)使用這種方式讀寫(xiě)多個(gè)變量的時(shí)候,S7NetPlus內(nèi)部為每個(gè)變量重復(fù)建立新的S7連接與發(fā)送讀寫(xiě)報(bào)文的操作,而不是單個(gè)連接成功建立后在這個(gè)連接上進(jìn)行批量的讀寫(xiě)。
簡(jiǎn)單理解就是這種方式效率比較低,會(huì)占用更多的資源。
2、解析讀寫(xiě)
這種方法需要指定DB的類(lèi)型、DB號(hào)、起始地址、PLC數(shù)據(jù)類(lèi)型及讀取數(shù)量。雖然它需要傳入的參數(shù)變多了,但是當(dāng)需要讀取多個(gè)地址連續(xù)且類(lèi)型相同的變量時(shí),僅需修改最后的讀取數(shù)量,S7NetPlus就會(huì)自動(dòng)讀取這一連串的地址,并按照指定的變量類(lèi)型解析出對(duì)應(yīng)的值,文檔中后面說(shuō)到的多類(lèi)型變量批量讀取也是基于這種方法的。不過(guò)這種方式讀取PLC內(nèi)的字符串類(lèi)型時(shí),仍存在bug,所以當(dāng)需要讀寫(xiě)字符串的時(shí)候,推薦使用本文后面提及的字節(jié)讀寫(xiě)的方式。
示例如下:
//讀取 bool result = (bool)plc.Read(DataType.DataBlock, 10, 0, VarType.Bit, 1); //寫(xiě)入 plc.Write(DataType.DataBlock, 10, 0, true);
Read:
第一個(gè)參數(shù)是DB的數(shù)據(jù)類(lèi)型,可以是DB、定時(shí)器、計(jì)數(shù)器、Merker(內(nèi)存)、輸入、輸出。
第二個(gè)參數(shù)是DB號(hào)。
第三個(gè)參數(shù)是起始地址。
第四個(gè)參數(shù)是PLC內(nèi)該變量的類(lèi)型。
第五個(gè)參數(shù)是需要讀取的個(gè)數(shù)。
Write:
第一個(gè)參數(shù)是DB的數(shù)據(jù)類(lèi)型,可以是DB、定時(shí)器、計(jì)數(shù)器、Merker(內(nèi)存)、輸入、輸出。
第二個(gè)參數(shù)是DB號(hào)。
第三個(gè)參數(shù)是起始地址。
第四個(gè)參數(shù)是需要寫(xiě)入的值。
3、字節(jié)讀寫(xiě)
這種方法將會(huì)讀取指定DB塊上一段連續(xù)的地址上的字節(jié),不做任何解析直接以字節(jié)數(shù)組的形式返回。
第一個(gè)參數(shù)是DB的數(shù)據(jù)類(lèi)型,可以是DB、定時(shí)器、計(jì)數(shù)器、Merker(內(nèi)存)、輸入、輸出。
第二個(gè)參數(shù)是DB號(hào)。
第三個(gè)參數(shù)是起始地址。
第四個(gè)參數(shù)是讀取的字節(jié)數(shù)。
要使用這種方式讀寫(xiě)數(shù)據(jù),則需要非常熟悉PLC內(nèi)各類(lèi)型數(shù)據(jù)存儲(chǔ)的格式,可以自行將讀取上來(lái)的字節(jié)進(jìn)行解析以獲得所需數(shù)據(jù)。
雖然這種方式理論上能讀寫(xiě)任意的數(shù)據(jù),但是解析數(shù)據(jù)的過(guò)程會(huì)比較麻煩,所以若非萬(wàn)不得已,個(gè)人建議盡量少用。
此處僅提供PLC內(nèi)String類(lèi)型及WString類(lèi)型的讀取示例。
//String讀取 byte[] data = plc.ReadBytes(DataType.DataBlock, 10, 2, 254); string result = Encoding.Default.GetString(data);
//Wstring讀取 byte[] data = plc.ReadBytes(DataType.DataBlock, 10, 4, 508); string result = Encoding.BigEndianUnicode.GetString(data);
在S7-1500中,一個(gè)String類(lèi)型的變量占用256個(gè)字節(jié),但是第一個(gè)字節(jié)是總字符數(shù),第二個(gè)字節(jié)是當(dāng)前字符數(shù),所以真正的字符數(shù)據(jù)是從第三個(gè)字節(jié)開(kāi)始的,共254個(gè)字節(jié)。
同理,WString類(lèi)型其實(shí)就是雙字節(jié)的Sring,也就是說(shuō)一個(gè)字符占用兩個(gè)字節(jié),所以一個(gè)WString類(lèi)型的變量占用512個(gè)字節(jié),第一、二個(gè)字節(jié)是總字符數(shù),第三、四個(gè)字節(jié)是當(dāng)前字符數(shù),真正的字符數(shù)據(jù)是從第五個(gè)字節(jié)開(kāi)始的,共508個(gè)字節(jié)。
按照以上示例的方法,讀取上來(lái)的字符串后面會(huì)帶很多個(gè)"?"的字符,那是因?yàn)楹竺娴目兆止?jié)也讀取上來(lái)了,正式使用時(shí)可以考慮使用.Replace("?", "")來(lái)去除,或者解析第二個(gè)字節(jié)來(lái)獲取字符長(zhǎng)度進(jìn)而轉(zhuǎn)碼。
當(dāng)寫(xiě)入字符串時(shí),則需要根據(jù)不同的數(shù)據(jù)類(lèi)型來(lái)生成對(duì)應(yīng)字符串的字節(jié)數(shù)組,然后將該數(shù)組寫(xiě)入到指定地址中即可。
需要注意的是,String類(lèi)型的編碼格式對(duì)應(yīng)的是ASCII,而WString的則是C#中的BigEndianUnicode格式。在WString中,由于總長(zhǎng)度與當(dāng)前字符數(shù)是都是雙字節(jié)數(shù),所以在轉(zhuǎn)換成字節(jié)數(shù)組的時(shí)候存在高低字節(jié)順序問(wèn)題。在這里就有一個(gè)大坑:這兩個(gè)變量在C#中轉(zhuǎn)換出來(lái)的字節(jié)數(shù)組跟PLC中存儲(chǔ)的,高低字節(jié)是反過(guò)來(lái)的。這也就是為什么下面的WString的示例中需要對(duì)總字符數(shù)和當(dāng)前字符數(shù)的兩個(gè)字節(jié)數(shù)組進(jìn)行反轉(zhuǎn)。
此處提供一種生成String類(lèi)型和WString的字節(jié)數(shù)組的方法,可供參考:
////// 獲取西門(mén)子PLC字符串?dāng)?shù)組--String /// /// ///private byte[] GetPLCStringByteArray(string str) { byte[] value = Encoding.Default.GetBytes(str); byte[] head = new byte[2]; head[0] = Convert.ToByte(254); head[1] = Convert.ToByte(str.Length); value = head.Concat(value).ToArray(); return value; } /// /// 獲取西門(mén)子PLC字符串?dāng)?shù)組--WString /// /// ///private byte[] GetPLCWStringByteArray(string str) { byte[] value = Encoding.BigEndianUnicode.GetBytes(str); byte[] head = BitConverter.GetBytes((short)508); byte[] length = BitConverter.GetBytes((short)str.Length); Array.Reverse(head); Array.Reverse(length); head = head.Concat(length).ToArray(); value = head.Concat(value).ToArray(); return value; }
使用示例如下:
//寫(xiě)入String string str = "Example"; plc.Write(DataType.DataBlock, 10, 0, GetPLCStringByteArray(str));
//寫(xiě)入WString string str = "示例"; plc.Write(DataType.DataBlock, 10, 0, GetPLCWStringByteArray(str));
4、舊版本的字節(jié)讀取注意事項(xiàng)
舊版本的單次字節(jié)讀取是有字節(jié)數(shù)限制的,每一次讀取的最大字節(jié)數(shù)為200,如果需要讀寫(xiě)更多的字節(jié),則需要多次讀寫(xiě)并進(jìn)行拼接,以下提供兩種方法,可供參考:
////// 循環(huán)讀取 /// /// 要讀取的字節(jié)數(shù) /// DB號(hào) /// 起始地址 ///private byte[] CyclicReadMultipleBytes(int numBytes, int db, int startByteAdr = 0) { byte[] resultBytes = new byte[0]; int index = startByteAdr; while (numBytes > 0) { var maxToRead = Math.Min(numBytes, 200); byte[] bytes = plc.ReadBytes(DataType.DataBlock, db, index, maxToRead); if (bytes == null) return null; resultBytes = resultBytes.Concat(bytes).ToArray(); numBytes -= maxToRead; index += maxToRead; } return resultBytes; } /// /// 遞歸讀取 /// /// 要讀取的字節(jié)數(shù) /// DB號(hào) /// 起始地址 ///public static byte[] RecursiveReadMultipleBytes(int numBytes, int db, int startByteAdr = 0) { byte[] result = new byte[0]; if (numBytes > 200) { byte[] temp = plc.ReadBytes(DataType.DataBlock, db, startByteAdr, 200); numBytes -= 200; result = temp.Concat(RecursiveReadMultipleBytes(numBytes, db, startByteAdr + 200)).ToArray(); } else { byte[] temp = plc.ReadBytes(DataType.DataBlock, db, startByteAdr, numBytes); result = result.Concat(temp).ToArray(); return result; } return result; }
在讀取一兩千個(gè)字節(jié)的情況下,這兩種方法速度都差不多,遞歸會(huì)稍微快一點(diǎn)點(diǎn)。不過(guò)新版本沒(méi)有單次讀取限制,所以正常情況下是不需要這兩個(gè)方法的。
5、其余讀取方式
其它的讀取方式可參考文檔,本文不再贅述。
讀取數(shù)據(jù)示例
PLCInstance:
using S7.Net; using System; using System.Text; namespace S7NetPlusExample { class PLCInstance { private PLCInstance() { plcObj = new Plc(CpuType.S71500, "192.168.10.230", 0, 1); } ////// PLC單例 /// public static PLCInstance Instance { get { return Nested.instance; } } ////// 防止調(diào)用此類(lèi)靜態(tài)方法時(shí),創(chuàng)建新的實(shí)例 /// private class Nested { internal static readonly PLCInstance instance = null; static Nested() { instance = new PLCInstance(); } } ////// 私有PLC單例對(duì)象 /// private static Plc plcObj; ////// 連接至PLC并返回連接狀態(tài) /// ///private bool ConnectToPLC() { try { plcObj.Open(); return plcObj.IsConnected ? true : false; } catch (Exception) { return false; } } /// /// 關(guān)閉連接 /// private void Disconnect() { plcObj.Close(); } ////// 讀取示例數(shù)據(jù) /// ///public string GetPLCInfo() { if (ConnectToPLC()) { StringBuilder sbr = new StringBuilder(); //讀取BOOL值 bool boolResult = (bool)plcObj.Read(DataType.DataBlock, 10, 0, VarType.Bit, 1); //讀取Int值 int intResult = (short)plcObj.Read(DataType.DataBlock, 10, 2, VarType.Int, 1); //讀取Real值 float realResult = (float)plcObj.Read(DataType.DataBlock, 10, 4, VarType.Real, 1); //讀取String值 byte[] stringData = plcObj.ReadBytes(DataType.DataBlock, 10, 10, 254); string stringResult = Encoding.Default.GetString(stringData); //讀取WString byte[] wstringData = plcObj.ReadBytes(DataType.DataBlock, 10, 268, 508); string wstringResult = Encoding.BigEndianUnicode.GetString(wstringData); Disconnect(); sbr.AppendLine($"{boolResult}"); sbr.AppendLine($"{intResult}"); sbr.AppendLine($"{realResult}"); sbr.AppendLine($"{stringResult}"); sbr.AppendLine($"{wstringResult}"); return sbr.ToString(); } else { return "連接PLC失敗"; } } } }
主程序:
using System; namespace S7NetPlusExample { class Program { static void Main(string[] args) { Console.WriteLine(PLCInstance.Instance.GetPLCInfo()); Console.ReadKey(); } } }
運(yùn)行結(jié)果:
結(jié)尾
本文簡(jiǎn)單介紹了S7 Net Plus和PLCSIM Advanced的使用,以上內(nèi)容均由本人親自實(shí)踐得出的結(jié)果,但仍有可改進(jìn)的的地方。S7NetPlus的文檔也有非常詳細(xì)的介紹,如有更復(fù)雜的讀寫(xiě)需求,可以參考文檔。
審核編輯:湯梓紅
-
plc
+關(guān)注
關(guān)注
5013文章
13323瀏覽量
464026 -
西門(mén)子
+關(guān)注
關(guān)注
94文章
3048瀏覽量
116036 -
仿真
+關(guān)注
關(guān)注
50文章
4099瀏覽量
133717 -
Advanced
+關(guān)注
關(guān)注
1文章
34瀏覽量
23257
原文標(biāo)題:PLC遇見(jiàn)IT:C#+S7Net+PLCSIM實(shí)現(xiàn)西門(mén)子PLC仿真通訊
文章出處:【微信號(hào):智能制造之家,微信公眾號(hào):智能制造之家】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。
發(fā)布評(píng)論請(qǐng)先 登錄
相關(guān)推薦
評(píng)論