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