因項目需要PLC與PC連接,傳輸一些狀態和控制信息。為了最快的響應速度,保險的方式是采用I/O直接連接。但這需要額外增加I/O卡,而且和PLC的I/O功能有些重復。那么,網絡通信是否可行呢?本文將進行一些實驗。
概述
使用的PLC是目前主流的西門子S7-1200. 它支持的網絡標準/協議很多,比如PROFINET, PROFIBUS等,還可以間接連接Modbus設備。每個標準下都有很多服務/協議,詳情可以參考Communication with SIMATIC 。但這些標準有些是用于西門子的設備互聯的,不一定適用于PC。
下圖是 TIA Portal V14 中通信相關的指令,也可以作為線索。
和PC的通信,一種方式是使用OPC server,但它是基于OLE/COM的,只能用于Windows。有些軟件比如LabView提供了和西門子PLC通信的支持。跨平臺的開源的方案,有一個是Snap7。我們可以先試試這個。另外可以嘗試最原始的TCP協議。
Snap7
Snap7是針對西門子S7協議的。PLC不需任何配置就是S7的server,而我們只需要利用Snap7 lib,就可以讓PC作為S7 client,讀/寫服務器端的數據塊。
數據塊映射
數據塊分為輸入區(DI, AI),輸出區(DQ, AQ),程序數據塊(DB)等等。下圖中,DB3是測試程序的數據塊。
用Snap7包中自帶的(編譯好的)測試程序可以查看/修改它的值。
訪問設置
在讀寫前需要進行配置和權限的設置:禁用塊優化,給予完全訪問權限,詳見 Snap7的文檔。
還有一個文檔中沒提到的設置:允許來自遠程對象的PUT/GET通信訪問。否則會出現 "Function not available", "function refused by CPU"之類的錯誤。
Python版的Snap7
有時使用腳本語言會更方便一些。python-snap7就是一個Snap7 lib的Python封裝。因為只是接口層的封裝,對速度的影響很小。
安裝時需要先安裝Snap7的庫,再用pip安裝python-snap7。有些平臺沒有現成的Snap7的庫,需要自己編譯。反正樹莓派上我是自己編譯的。 實測Python2和Python3都可以工作。
核心調用代碼如下。因為I/O只有兩字節,就直接讀/寫兩字節了。
import snap7
from snap7.snap7types import S7AreaDB, S7AreaPA, S7AreaPE
class S7Client:
def __init__(self, ip, slot=1, rack=0):
self.client = snap7.client.Client()
self.client.connect(ip, rack, slot)
def readDI(self):
area = S7AreaPE
db = 0
start = 0
amount = 2
ba = self.client.read_area(area, db, start, amount)
d = ba[1]
d <<= 8
d |= ba[0]
return d
def writeDQ(self, data):
area = S7AreaPA
db = 0
start = 0
amount = 2
ba = bytearray(amount)
ba[0] = data & 0xff
ba[1] = data >> 8
self.client.write_area(area, db, start, ba)
速度測試
循環讀/寫DQ,看看總耗時。示意代碼如下:
def testWriteLoop(self, count):
d = 0
self.log.info("Write DQ from: %04x", d)
t1 = time.time()
while d < count:
self.plc.writeDQ(d)
d += 1
t2 = time.time()
self.log.info("Write DQ till: %04x. Average: %.2fms",
(d - 1), (t2 - t1) * 1000 / d)
可以看到單次讀/寫的平均時間略高于9ms.
下圖是最低位的波形。10個周期對應于20次寫,耗時約182ms。高低電平不對稱的問題后面再說。
反向通信
如果PC做Snap7的服務器,則PLC需要使用GET/PUT指令讀/寫PC端的數據。既然都是S7協議,我們假設它的速度和正向是相當的,暫且跳過,先試試另一類型的通信。
原始的TCP通信
S7-1200支持開放式用戶通信,即基于TCP,但不屬于任何標準應用層協議的,完全由用戶自己定義的協議。
實驗設計
- PC端作為服務器:實際測試使用樹莓派充當PC的角色。
- PLC端發送數據:由一個DI來觸發數據發送。
- 樹莓派開啟數據發送:通過控制一個GPIO來開關繼電器,進而改變PLC端的DI(信號1);
- 樹莓派在收到數據后,改變另一個GPIO的狀態(信號2)作為標志;
- 比較信號2和信號1的時間差。
PC端
PC端作為服務器,監聽某一端口。在Linux上,可以用命令行工具netcat進行調試。
開兩個窗口:
- netcat -l 2000: 監聽端口2000
- netcat localhost 2000: 與本機2000端口連接
一個窗口輸入字符,另一個窗口就會顯示出來。
然后,用Python socket寫一個類似的服務器端程序,核心代碼如下:
import socket
self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.sock.bind(('', self.args.port))
self.sock.listen(1)
self.conn, addr = self.sock.accept()
data = self.conn.recv(32)
self.log.info("Received: %02X %02X", data[0], data[1])
可以用 netcat 測試這個服務器程序。
PLC端
PLC端使用TSEND_C發送數據。
- 由trigger觸發數據發送,trigger對應于數字輸入,比如DI0.1.
- trigger同時觸發一個計數器。TSEND發送這個計數器的值,這樣PC每次收到的數據是遞增的。
- CONT設為TRUE,保持連接,這樣速度最快。
在網絡連接設置中指定PC端的IP地址和端口號,端口號要和服務器監聽的端口號一致。由PLC主動發起連接。
初步的結果
下圖中,黃色為PLC端的輸入(信號1),綠色為樹莓派上收到數據后的輸出(信號2)。
都以上升沿作為標志。兩者的時間差不到9ms。
可以更快嗎?
通信負載
由通信引起的循環負荷:默認是20%,取值可以從15%到50%。改變這個值,發現對通信時間并沒有影響。
輸入濾波器
這個值默認是6.4ms,它是用來過濾按鍵抖動的。但對于電路觸發(非人工/機械按鍵)的情況,這個抖動可以設得很小。
將它調小至0.1ms,整個耗時降低了約6ms. 通信耗時不到3ms了。
循環時間
PLC的運行方式是不斷循環去讀取輸入,執行程序塊,更新輸出的模式。循環周期過長,是否會影響網絡通信呢?
通過在線診斷,可以看到循環時間最長為4ms,通常都在1~2ms。
這說明循環時間并不是瓶頸。而且反過來,循環時間比通信時間還短(即使輸入濾波器為6.4ms,通信時間9ms時,循環時間依然是1~2ms),這說明通信和循環似乎是分頭執行的。
其它
本來還想試一下中斷執行方式的,但把通信程序塊放到中斷響應里執行并沒有成功。考慮到對于PLC的百兆網口,3ms已經夠快了,就沒再折騰了。
還試驗了一下,在PLC上單純地增加一個計數器或反復翻轉輸出電平,每次操作耗時大約也是3ms。
順便說一句,在PLC的數字輸出上,卻看不到電平的翻轉(看到的總是高電平)。前面有一張“遞增寫DQ時DQ0.0的波形”圖,18ms的周期,基本上已看不到電平下降到0了。感覺PLC的輸出頻率并不高,甚至可能有高頻濾波。
結語
從PLC的眾多網絡通信方式中,本文試驗了簡單易行并且跨平臺的兩種方式,用來和PC通信。
- 使用基于S7協議的Snap7庫,在讀寫PLC時大約耗時9ms.
- 使用開放式的TCP協議,PLC向PC發送數據最快不到3ms.
考慮到S7-1200只是百兆網絡,這個速度應該是不錯的,可以滿足大部分需要。