PLCSIM Advanced是西門子推出的一款功能強(qiáng)大的仿真軟件,目前最新發(fā)布的版本為4.0,但鑒于新版本可能存在未知的bug,故本文使用V3.0。
V3.0支持仿真1500PLC及ET 200SP,可實現(xiàn)Socket網(wǎng)絡(luò)通訊功能,也可實現(xiàn)PLC之間、PLC與設(shè)備直接的ModbusTCP等通訊。
V3.0安裝時需要先安裝WinPcap_4_1_3,V4.0則不需要。
以下為兩個版本的官網(wǎng)下載鏈接,下載時需要西門子賬號,可以免費注冊。
以下為V3.0下載鏈接:
PLCSIM Advanced V3.0
V3.0的兩個升級包(可選安裝)
以下為V4.0下載鏈接
PLCSIM Advanced V4.0
S7 Net Plus 簡介
西門子PLC通訊庫,支持200、200smart、300、400、1200、1500系列PLC。
說明文檔
配置PLCSIM Advanced
打開PLCSIM Advanced V3.0,如下圖:
Online Access要選擇右邊的PLCSIM Virtual Eth.Adapter,左側(cè)的PLCSIM不支持外部網(wǎng)絡(luò)訪問。
TCP/IP communication with 可選以太網(wǎng)或者是本地虛擬網(wǎng)卡。local即為本地虛擬網(wǎng)卡,是在安裝PLCSIM Advanced時自動安裝的網(wǎng)絡(luò)適配器。打開控制面板-->網(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型號。設(shè)置完成后點擊Start按鈕則會生成一個PLC實例。創(chuàng)建成功后就可以開始通訊仿真了。
Virtual SIMATIC Memory Ca為打開保存PLC歷史記錄的文件夾的按鈕。
如下圖所示,在Active PLC Instance(s)可以看到已成功創(chuàng)建的PLC。
下載測試DB塊
在TIA Protal軟件中,添加一個S7-1511的設(shè)備,然后在程序塊中添加一個新的DB塊,DB號設(shè)置為10。
打開設(shè)備的屬性 --> 防護(hù)與安全 -->連接機(jī)制,勾選“允許來自遠(yuǎn)程對象的PUT/GET通訊訪問”。
打開設(shè)備的屬性 --> PROFINET 接口 [X1] -->以太網(wǎng)地址,按需設(shè)置PLC的IP地址。
打開DB10的屬性,取消勾選“優(yōu)化塊的訪問”,并在DB10中新建如下圖所示的變量,編譯完成后則可以得到每個變量的偏移量,即此變量在DB10上的地址。
設(shè)置完成后,下載到剛剛使用PLCSIM Advanced創(chuàng)建的仿真PLC中,需要注意網(wǎng)段要設(shè)置成與仿真PLC同一網(wǎng)段。
引用S7NetPlus
創(chuàng)建一個測試程序,此處創(chuàng)建的是一個控制臺應(yīng)用程序。
在NuGet下載S7NetPlus,如下圖所示,版本可按需選擇
新建一個名為PLCInstance的類,創(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)用此類靜態(tài)方法時,創(chuàng)建新的實例 /// private class Nested { internal static readonly PLCInstance instance = null; static Nested() { instance = new PLCInstance(); } } ////// 私有PLC單例對象 /// 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ù)
S7NetPlus提供了多種讀寫的方式,可以讀取字節(jié)自行解析或者按照指定格式寫入字節(jié),也可以指定地址進(jìn)行讀寫,還可以使用變量、結(jié)構(gòu)體或者類進(jìn)行單個或者批量讀寫。
1、指定地址讀寫
這種方法可以在Read方法中以字符串形式傳入需要讀取的地址,返回的是Object類型的值,需要使用者自行做類型轉(zhuǎn)換。Write方法則同理,以字符串的形式指定需要寫入的地址,并在第二個參數(shù)傳入需要寫入的值,但是需要注意西門子PLC內(nèi)的數(shù)據(jù)類型與C#的數(shù)據(jù)類型的對應(yīng)。以下為讀寫DB10的0.0地址上的布爾量的值示例,此方式均支持讀取與寫入。
//讀取 bool result = (bool)plc.Read("DB10.DBX0.0"); //寫入 plc.Write("DB10.DBX0.0",!result);
雖然這種方式比較簡單且方便,但是它是作者不推薦的方式,文檔中原文如下:
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.
意思就是,這種方法會通過解析傳入的地址字符串來獲取需要讀寫的地址,對于使用者來說是非常簡單的使用方式,但是S7NetPlus會為每個通過這種方式讀寫的變量生成一個新的TCP請求,因此在讀寫多個變量時,執(zhí)行效率會比較低。
S7NetPlus使用的通訊本質(zhì)上是西門子的S7通訊,通過發(fā)送七層通訊報文來建立與西門子PLC的TCP連接,后續(xù)也是根據(jù)S7通訊的通訊協(xié)議生成并發(fā)送報文來實現(xiàn)PLC的數(shù)據(jù)讀寫。所以當(dāng)使用這種方式讀寫多個變量的時候,S7NetPlus內(nèi)部為每個變量重復(fù)建立新的S7連接與發(fā)送讀寫報文的操作,而不是單個連接成功建立后在這個連接上進(jìn)行批量的讀寫。
簡單理解就是這種方式效率比較低,會占用更多的資源。
2、解析讀寫
這種方法需要指定DB的類型、DB號、起始地址、PLC數(shù)據(jù)類型及讀取數(shù)量。雖然它需要傳入的參數(shù)變多了,但是當(dāng)需要讀取多個地址連續(xù)且類型相同的變量時,僅需修改最后的讀取數(shù)量,S7NetPlus就會自動讀取這一連串的地址,并按照指定的變量類型解析出對應(yīng)的值,文檔中后面說到的多類型變量批量讀取也是基于這種方法的。不過這種方式讀取PLC內(nèi)的字符串類型時,仍存在bug,所以當(dāng)需要讀寫字符串的時候,推薦使用本文后面提及的字節(jié)讀寫的方式。
示例如下:
//讀取 bool result = (bool)plc.Read(DataType.DataBlock, 10, 0, VarType.Bit, 1); //寫入 plc.Write(DataType.DataBlock, 10, 0, true);
Read:
第一個參數(shù)是DB的數(shù)據(jù)類型,可以是DB、定時器、計數(shù)器、Merker(內(nèi)存)、輸入、輸出。
第二個參數(shù)是DB號。
第三個參數(shù)是起始地址。
第四個參數(shù)是PLC內(nèi)該變量的類型。
第五個參數(shù)是需要讀取的個數(shù)。
Write:
第一個參數(shù)是DB的數(shù)據(jù)類型,可以是DB、定時器、計數(shù)器、Merker(內(nèi)存)、輸入、輸出。
第二個參數(shù)是DB號。
第三個參數(shù)是起始地址。
第四個參數(shù)是需要寫入的值。
3、字節(jié)讀寫
這種方法將會讀取指定DB塊上一段連續(xù)的地址上的字節(jié),不做任何解析直接以字節(jié)數(shù)組的形式返回。
第一個參數(shù)是DB的數(shù)據(jù)類型,可以是DB、定時器、計數(shù)器、Merker(內(nèi)存)、輸入、輸出。
第二個參數(shù)是DB號。
第三個參數(shù)是起始地址。
第四個參數(shù)是讀取的字節(jié)數(shù)。
要使用這種方式讀寫數(shù)據(jù),則需要非常熟悉PLC內(nèi)各類型數(shù)據(jù)存儲的格式,可以自行將讀取上來的字節(jié)進(jìn)行解析以獲得所需數(shù)據(jù)。
雖然這種方式理論上能讀寫任意的數(shù)據(jù),但是解析數(shù)據(jù)的過程會比較麻煩,所以若非萬不得已,個人建議盡量少用。
此處僅提供PLC內(nèi)String類型及WString類型的讀取示例。
//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中,一個String類型的變量占用256個字節(jié),但是第一個字節(jié)是總字符數(shù),第二個字節(jié)是當(dāng)前字符數(shù),所以真正的字符數(shù)據(jù)是從第三個字節(jié)開始的,共254個字節(jié)。
同理,WString類型其實就是雙字節(jié)的Sring,也就是說一個字符占用兩個字節(jié),所以一個WString類型的變量占用512個字節(jié),第一、二個字節(jié)是總字符數(shù),第三、四個字節(jié)是當(dāng)前字符數(shù),真正的字符數(shù)據(jù)是從第五個字節(jié)開始的,共508個字節(jié)。
按照以上示例的方法,讀取上來的字符串后面會帶很多個"?"的字符,那是因為后面的空字節(jié)也讀取上來了,正式使用時可以考慮使用.Replace("?", "")來去除,或者解析第二個字節(jié)來獲取字符長度進(jìn)而轉(zhuǎn)碼。
當(dāng)寫入字符串時,則需要根據(jù)不同的數(shù)據(jù)類型來生成對應(yīng)字符串的字節(jié)數(shù)組,然后將該數(shù)組寫入到指定地址中即可。
需要注意的是,String類型的編碼格式對應(yīng)的是ASCII,而WString的則是C#中的BigEndianUnicode格式。在WString中,由于總長度與當(dāng)前字符數(shù)是都是雙字節(jié)數(shù),所以在轉(zhuǎn)換成字節(jié)數(shù)組的時候存在高低字節(jié)順序問題。在這里就有一個大坑:這兩個變量在C#中轉(zhuǎn)換出來的字節(jié)數(shù)組跟PLC中存儲的,高低字節(jié)是反過來的。這也就是為什么下面的WString的示例中需要對總字符數(shù)和當(dāng)前字符數(shù)的兩個字節(jié)數(shù)組進(jìn)行反轉(zhuǎn)。
此處提供一種生成String類型和WString的字節(jié)數(shù)組的方法,可供參考:
////// 獲取西門子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; } /// /// 獲取西門子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; }
使用示例如下:
//寫入String string str = "Example"; plc.Write(DataType.DataBlock, 10, 0, GetPLCStringByteArray(str));
//寫入WString string str = "示例"; plc.Write(DataType.DataBlock, 10, 0, GetPLCWStringByteArray(str));
4、舊版本的字節(jié)讀取注意事項
舊版本的單次字節(jié)讀取是有字節(jié)數(shù)限制的,每一次讀取的最大字節(jié)數(shù)為200,如果需要讀寫更多的字節(jié),則需要多次讀寫并進(jìn)行拼接,以下提供兩種方法,可供參考:
////// 循環(huán)讀取 /// /// 要讀取的字節(jié)數(shù) /// DB號 /// 起始地址 ///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號 /// 起始地址 ///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; }
在讀取一兩千個字節(jié)的情況下,這兩種方法速度都差不多,遞歸會稍微快一點點。不過新版本沒有單次讀取限制,所以正常情況下是不需要這兩個方法的。
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)用此類靜態(tài)方法時,創(chuàng)建新的實例 /// private class Nested { internal static readonly PLCInstance instance = null; static Nested() { instance = new PLCInstance(); } } ////// 私有PLC單例對象 /// 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é)尾
本文簡單介紹了S7 Net Plus和PLCSIM Advanced的使用,以上內(nèi)容均由本人親自實踐得出的結(jié)果,但仍有可改進(jìn)的的地方。S7NetPlus的文檔也有非常詳細(xì)的介紹,如有更復(fù)雜的讀寫需求,可以參考文檔。
審核編輯:湯梓紅
-
plc
+關(guān)注
關(guān)注
5001文章
12942瀏覽量
459203 -
西門子
+關(guān)注
關(guān)注
93文章
2958瀏覽量
114824 -
仿真
+關(guān)注
關(guān)注
50文章
3971瀏覽量
132958 -
Advanced
+關(guān)注
關(guān)注
1文章
31瀏覽量
23209
原文標(biāo)題:PLC遇見IT:C#+S7Net+PLCSIM實現(xiàn)西門子PLC仿真通訊
文章出處:【微信號:智能制造之家,微信公眾號:智能制造之家】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。
發(fā)布評論請先 登錄
相關(guān)推薦
評論