張 毅
(東風汽車有限公司刃量具廠 刀量具制造管理部, 湖北 十匽 442002)
西門子S7-1200/1500系列PLC以太網(wǎng)通信,支持TCP、UDP、ISO_on_TCP通信協(xié)議。此時PLC作為服務(wù)器端,支持8個主動和8個被動連接。采用Delphi開發(fā)PLC客戶端組件,根據(jù)西門子TCP握手通信協(xié)議發(fā)送握手指令與PLC建立連接。當連接成功后,遵循TCP讀-寫協(xié)議,定時器間隔約0.5秒自動讀取PLC寄存器,并支持實時寫入PLC寄存器,實現(xiàn)完整的心跳通訊。
為實現(xiàn)上述功能,本研究采用Delphi 7開發(fā)了Windows規(guī)則DLL模塊,并在DLL內(nèi)部封裝了1個Form窗口、2個Timer定時器和1個TCPClient控件。當DLL被EXE加載時,由DLL啟動連接、發(fā)送套接字完成握手,通過內(nèi)部窗口函數(shù)定時讀PLC寄存器,實現(xiàn)寫PLC寄存器函數(shù),并完成接口封裝。上位機EXE通過調(diào)用DLL接口,完成實時讀寫PLC寄存器功能,程序功能設(shè)計原理如圖1所示。
本通信方式基于以太網(wǎng)卡TCP/IP通信協(xié)議。首先采用網(wǎng)線連接PC和PLC網(wǎng)口,使用TIA博途軟件為S7-1200/1500系列PLC配置 IP地址,例如將PLC端IP設(shè)為192.168.0.2,端口號0,并將PLC配置為TCP Server,不主動發(fā)送數(shù)據(jù)模式。在PC端,將本機IP地址設(shè)為與PLC位于同一網(wǎng)段,使用ping命令,確保能ping通PLC站。
圖1 EXE應(yīng)用程序調(diào)用DLL實現(xiàn)PLC心跳通信原理圖
Fig.1EXEapplicationcallsDLLtorealizePLCheartbeatcommunicationschematicdiagram
(1)發(fā)送與接收TCP握手報文,完成與PLC連接
在與S7-1200/1500系列PLC建立連接之前,需完成2次握手報文收發(fā),均為固定格式,代碼示例如下:
Var Buf_Rec:array[0..99] of Byte; //定義數(shù)據(jù)接收數(shù)組
發(fā)送第一次握手報文示例:
Const Buf_Send1:array[0..21] of Byte=
(3,0,0,22,17,224,0,0,0,1,0,193,2,16,0,194,2,3,1,192,1,10);
接收第一次握手報文返回數(shù)據(jù),判斷Buf_Rec[5]=208表示第一次握手成功,接收報文示例:
Buf_Rec[]=(3,0,0,22,17,208,0,1,0,17,0,192,1,10,193,2,16,0,194,2,3,1,…)
發(fā)送第二次握手報文示例:
Const Buf_Send2:array[0..24] of Byte =
(3,0,0,25,2,240,128,50,1,0,0,204,193,0,8,0,0,240,0,0,1,0,1,3,192);
接收第二次握手報文返回數(shù)據(jù),判斷Buf_Rec [0] <> 0表示第二次握手成功,握手返回報文示例:
Buf_Rec[]=( 3,0,0,27,2,240,128,50,3,0,0,204,193,0,8,0,0,0,0,240,0,0,1,0,1,0,240…)
發(fā)送代碼示例:
Form1.TcpClient1.SendBuf(Buf_Send1,SizeOf(Buf_Send1)); //發(fā)送第一次握手指令
Form1.TcpClient1.SendBuf(Buf_Send1,SizeOf(Buf_Send2)); //發(fā)送第二次握手指令
接收數(shù)據(jù)代碼示例:
Form1.TcpClient1.ReceiveBuf(Buf_Rec[0],80); //接收數(shù)據(jù)
(2)根據(jù)S7-1200/1500系列TCP讀數(shù)據(jù)協(xié)議,實現(xiàn)讀PLC寄存器值,讀指令代碼示例如下:
Const ar_bRead:array[0..66] of Byte = ($03,$00, //[0-1]報頭
$00,67, //[2-3]整條數(shù)據(jù)長度
$02,$F0,$80,$32, //[4-7]固定長度:4(協(xié)議類型)
$01, //[8]命令類型:發(fā)
$00,$00,$00,$01, //[9-12]標識序列號:1(可自定義,與返回數(shù)據(jù)一致)
$00,50,$00,$00, //[13-16]命令數(shù)據(jù)總長度:50
$04, //[17]命令起始符:4
$04, //[18]讀取數(shù)據(jù)塊個數(shù):4
$12,$0A,$10,$02, //[19-22]固定長度:4(讀取地址前綴)
$00,$01, //[23-24]讀取數(shù)據(jù)byte個數(shù):1(8位)
$00,200, //[25-26]讀取數(shù)據(jù)塊編號:200
$84, //[27]數(shù)據(jù)塊類型: DB
$00,$00,0, //[28-30]地址偏移量:0 (DB200.0) = 1 Byte
$12,$0A,$10,$02, //[31-34]固定長度:4(讀取地址前綴)
$00,$01, //[35-36]讀取數(shù)據(jù)byte個數(shù):1(8位)
$00,200, //[37-38]讀取數(shù)據(jù)塊編號:200
$84, //[39]數(shù)據(jù)塊類型: DB
$00,$00,17, //[40-42]地址偏移量:17 (DB200.2) = 17
$12,$0A,$10,$02, //[43-46]固定長度:4(讀取地址前綴)
$00,$01, //[47-48]讀取數(shù)據(jù)byte個數(shù):1(8位)
$00,200, //[49-50]讀取數(shù)據(jù)塊編號:200
$84, //[51]數(shù)據(jù)塊類型: DB
$00,$00,81, //[52-54]地址偏移量:81 (DB200.10) = 1 Byte
$12,$0A,$10,$02, //[43-46]固定長度:4(讀取地址前綴)
$00,$01, //[47-48]讀取數(shù)據(jù)byte個數(shù):1(8位)
$00,200, //[49-50]讀取數(shù)據(jù)塊編號:200
$84, //[51]數(shù)據(jù)塊類型: DB
$00,$00,89); //[52-54]地址偏移量:89 (DB200.11) = 1 Byte
Var Buf_Read:array[0..66] of Byte; //聲明動態(tài)讀數(shù)據(jù)指令數(shù)組
Move(ar_bRead,Buf_Read,SizeOf(Buf_Read)); //賦值
發(fā)送讀指令代碼示例: TcpClient1.SendBuf(Buf_Read,SizeOf(Buf_Read));
TCP讀指令只需通過Timer控件間隔0.5-0.75秒定時發(fā)送即可,接收數(shù)據(jù)與(1)所示相同,修改讀指令可對Buf_Read數(shù)組動態(tài)賦值。
(3)根據(jù)TCP寫數(shù)據(jù)協(xié)議,實現(xiàn)寫PLC寄存器操作,代碼示例如下:
Const ar_bWrite:array[0..35] of Byte = ($03,$00,//[0-1]固定報頭
$00,36, //[2-3]數(shù)據(jù)總長
$02,$F0,$80,$32, //[4-7]固定長度:4
$01, //[8]命令類型:發(fā)
$00,$00,$00,$09, //[9-12]標記序列號:9
$00,$0E, //[13-14]固定長度:2
$00,$05, //[15-16]有效數(shù)據(jù)長度:5(地址偏移量后面第一位開始計算)
$05, //[17]命令起始符
$01, //[18]寫數(shù)據(jù)塊個數(shù):1
$12,$0A,$10, //[19-21]固定長度:3 (返回數(shù)據(jù)前綴)
$02, //[22]寫入方式:$01按bit寫入,$02按byte(8位)寫入
$00,$01, //[23-24]寫入數(shù)據(jù)個數(shù):1 (byte方式可多寫,bit方式只能單個寫)
$00,200, //[25-26]寫入數(shù)據(jù)塊編號:200
$84, //[27]數(shù)據(jù)類型:DB塊
$00,$00,$09, //[28-30]地址偏移量(bit),此處按bit計算偏移量
$00,$04, //[31-32]寫入方式: $03按bit寫入, $04按byte(8位)寫入
$00,8, //[33-34]寫入bit的個數(shù):8
9); //[35]寫入的值
Var Buf_Write:array[0..35] of Byte; //聲明動態(tài)寫數(shù)據(jù)指令數(shù)組
Move(ar_bWrite,Buf_Write,SizeOf(Buf_Write)); //賦值
修改地址偏移量示例(按位計算地址):Buf_Write[30] := Byte(1);
修改寫入值示例:Buf_Write[35] := Byte(i_value);
發(fā)送寫數(shù)據(jù)指令代碼: TcpClient1.SendBuf(Buf_Write,SizeOf(Buf_Write));
接收數(shù)據(jù)與(2)所示相同。寫數(shù)據(jù)需通過DLL接口函數(shù)帶形參(a:寄存器地址,b:待修改的值) ,由EXE調(diào)用實現(xiàn)。
(4)在DLL中使用窗體
Delphi支持在DLL工程內(nèi)部使用窗體。當DLL被調(diào)用時,DLL中的窗體被加入調(diào)用方的EXE進程,并在DLL被釋放時銷毀資源。在本例中,首先在DLL工程中添加一個窗體Form1,在窗體上加入一個TcpClient控件和PLC通信,一個1 000 MS定時器處理PLC握手信號,一個650MS定時器用于向PLC定時接收和發(fā)送指令。然后在Form的Public部分申明被調(diào)用函數(shù)接口,提供給DLL接口函數(shù)使用。示例如下:
public
{ Public declarations }
function fun_ReceiveData():Boolean; //接收數(shù)據(jù)函數(shù)
procedure fun_SendRead; //發(fā)送讀指令
function fun_ReadHeartTime():Integer; //讀心跳次數(shù)
function fun_WriteParmData(i_addr,i_value:Integer):Integer; //寫操作
end;
在Form的implementation部分寫函數(shù)實現(xiàn)代碼,DLL接口函數(shù)可直接調(diào)用上述內(nèi)部窗口函數(shù),示例如下:
function m23s71200_readio(a:integer):integer;export;stdcall; //讀心跳次數(shù)
begin
Result := Form1.fun_ReadHeartTime(); //獲取心跳次數(shù)
end;
exports m23s71200_writeio; //聲明導(dǎo)出函數(shù)
由于DLL內(nèi)部封裝了窗體和定時器控件,在DLL被EXE加載時,內(nèi)部定時器可通過接口函數(shù)控制開啟或關(guān)閉,也可自動開啟定時器維持與PLC心跳通信。EXE定時從DLL模塊讀數(shù),并通過DLL接口實時向PLC寫值,此調(diào)用方式可簡化EXE程序設(shè)計,實現(xiàn)松耦合結(jié)構(gòu)。
傳統(tǒng)上位機軟件大多采用IO模塊、繼電器加采集卡等硬接線方式與PLC實現(xiàn)信號交互,處理的信息量較小,且占用大量PLC IO端口資源。本研究采用上位機軟件通過網(wǎng)線直接讀寫PLC內(nèi)部寄存器,可實時監(jiān)控PLC各IO口和DB塊信號狀態(tài),并通過實時寫寄存器值與S7-1200/1500系列PLC雙向交互信號。該方法不僅能簡化通訊設(shè)計,節(jié)約PLC IO資源,降低通信硬件成本,且能極大提高通信信息量,具有IO硬接線無法比擬的優(yōu)勢。
在工控領(lǐng)域,本研究采用的方法具有通訊設(shè)計簡單、實施成本低廉、方案靈活性好、通信信息量大等優(yōu)點,目前已在多個工程項目中得到成功應(yīng)用。