Forward Message to AMS Port via Explicit Messaging
„Explicit Messaging“ wird für die Übermittlung von Informationen und Daten verwendet, die keine kontinuierlichen Aktualisierungen erfordern. Mit „Explicit Messaging“ können Sie die Parameter eines Slave-Geräts im Ethernet/IP-Netzwerk konfigurieren und überwachen.
Mit dem Feature “FwdMsgToAmsPort” besteht die Möglichkeit, azyklische Anfragen von Ethernet/IP-Scannern via Explicit Messaging zu verarbeiten.
Das folgende Beispiel zeigt die Realisierung einer azyklischen Kommunikation zwischen einer TwinCAT 3 und einer RS Logix-Steuerung.
Realisierung TwinCAT 3:
- Voraussetzung: Ethernet/IP-Treiber-Version, min. V1.23
- 1. Zum Aktivieren des Features “FwdMsgToAmsPort” tragen Sie bei den Slave-/Master-Settings (0x8000:2A/0xF800:2A) in TwinCAT der AmsPort der PLC (im Beispiel 851) der SPS ein.
- 2. Es werden ADSRDWRT-Anfragen (ADSRDWRT-Requests) von dem Ethernet/IP™-Treiber (IDGRP: 0x848180E9 IOFFS: SlaveId (Adapter)) an die SPS-Task als Indications registriert und dies erlaubt deren Bearbeitung. Dazu wird der ADSRDWRTIND-Funktionsbaustein verwendet.
- In der vom Ethernet/IP™-Treiber registrierten Indication befindet sich als erstes ein 32 Byte (8xULONG) Header:

TYPE DUT_MsgToAmsPortHeader:
STRUCT
nServiceCode:UDINT;
nClassId:UDINT;
nInstanceId:UDINT;
nAttributeId:UDINT;
nReservedId:UDINT;
nGeneralStatus:UDINT;
nAdditionalStatus:UDINT;
nDataLen:UDINT;
END_STRUCT
END_TYPE
TYPE DUT_IncomingMsgRequest:
STRUCT
reqHdr:DUT_MsgtoAmsPortHeader;
reqData:ARRAY [0…991] OF BYTE;
END_STRUCT
END_TYPE
TYPE DUT_OutgoingMsgResponse:
STRUCT
resHdr:DUT_magToAmsPortHeader;
resData:ARRAY [0…991] OF BYTE;
End_Struct
END_TYPE
Derselbe Header wird auch für das Response verwendet.
- 3. Die eigentlichen Schreib-/Lesedaten folgen direkt nach dem Header (nDataLen <> 0 ist entsprechend der Datenlänge zu setzen). Die maximal unterstützte Datenlänge beträgt 992 Byte (+ 32 Byte Header = 1024 Byte).Mögliche Klassen/Instanzen/Attribut Werte
| Min | Max |
Klasse | 1 | 0xFFFF |
Instanz | 1 | 0xFFFF |
Attribut | 1 | 0xFFFF |
- 4. Nachdem eine Indication bearbeitet wurde, muss eine Antwort über den ADSRDWRTRES-Funktionsbaustein an das Quellgerät gesendet werden.
PROGRAM MAIN
VAR
i : INT;
IdxGroup : UDINT; //Ethernet/IP-Treiber -> 16#848180E9
IdxOffset : UDINT; //SlaveId (Adapter) bzw. 0xFFFF (Scanner)
fbADSRDWRTINDEX : ADSRDWRTINDEX;
fbAdsRdWrRes : ADSRDWRTRES;
request : DUT_IncomingMsgRequest;
response : DUT_OutgoingMsgResponse;
nResponseLen : UINT;
nAdsResult : UDINT:=0;
nAdsResponsesSent : UDINT;
Attributes : ARRAY [1..4] OF STRING :=['TestReadOnlyAttribute1','TestReadOnlyAttribute2','TestReadOnlyAttribute3','TestReadWriteAttribute4'];
END_VAR
CASE i OF
0: //check for ADSReadWrite-Requests
fbADSRDWRTINDEX (
CLEAR:=FALSE ,
MINIDXGRP:= 16#84000000,
VALID=> ,
NETID=> ,
PORT=> ,
INVOKEID=> ,
IDXGRP=> ,
IDXOFFS=> ,
);
IF fbADSRDWRTINDEX.VALID THEN
IdxGroup:= fbADSRDWRTINDEX.IDXGRP;
IdxOffset:= fbADSRDWRTINDEX.IDXOFFS ;
MEMSET(ADR(request), 0, SIZEOF(request));
MEMSET(ADR(response), 0, SIZEOF(response));
nResponseLen:=0;
//check for Indication Request = Ethernet/IP-driver -> 16#848180E9
IF IdxGroup = 16#848180E9 THEN
//check for Indication.datalength >= DUT_MsgToAmsPortHeader
IF fbADSRDWRTINDEX.WRTLENGTH >= SIZEOF(request.reqHdr) THEN
MEMCPY(ADR(request.reqHdr), fbADSRDWRTINDEX.DATAADDR, SIZEOF(request.reqHdr));
END_IF
//check for Indication.datalength > DUT_MsgToAmsPortHeader >>> save additional data
IF fbADSRDWRTINDEX.WRTLENGTH > SIZEOF(request.reqHdr) THEN
MEMCPY(ADR(request.reqData), fbADSRDWRTINDEX.DATAADDR+SIZEOF(request.reqHdr), fbADSRDWRTINDEX.WRTLENGTH-SIZEOF(request.reqHdr));
END_IF
i:=10;
ELSE
i:=20;
END_IF
END_IF
10: //new Ind from EthIp-Drv received
response.resHdr.nServiceCode := request.reqHdr.nServiceCode OR CONST.CN_SC_REPLY_MASK;
response.resHdr.nGeneralStatus := 0;
response.resHdr.nAdditionalStatus := 0;
response.resHdr.nDataLen := 0;
IF request.reqHdr.nServiceCode = CONST.CN_SC_GET_ATTR_SINGLE OR request.reqHdr.nServiceCode = CONST.CN_SC_SET_ATTR_SINGLE THEN
i:=11;
ELSE
response.resHdr.nGeneralStatus := CONST.CN_GRC_BAD_SERVICE;
nResponseLen := SIZEOF(response.resHdr);
i:=20;
END_IF
11: //case decision for request
CASE request.reqHdr.nClassId OF
16#1000: //erlaubte Beispiel Class 0x10000
CASE request.reqHdr.nInstanceId OF
16#1: //erlaubte Beispiel Instance 0x1
CASE request.reqHdr.nAttributeId OF //Attributes 1-4 erlaubt; only attr 4 is settable
1,2,3: IF request.reqHdr.nServiceCode = CONST.CN_SC_SET_ATTR_SINGLE THEN
response.resHdr.nGeneralStatus := CONST.CN_GRC_ATTR_NOT_SETTABLE;
nResponseLen := SIZEOF(response.resHdr);
i:=20;
ELSE
i:=12;
END_IF
4: IF request.reqHdr.nServiceCode = CONST.CN_SC_SET_ATTR_SINGLE THEN
i:=14;
ELSE
i:=12;
END_IF
ELSE
response.resHdr.nGeneralStatus := CONST.CN_GRC_UNDEFINED_ATTR;
nResponseLen := SIZEOF(response.resHdr);
i:=20;
END_CASE
ELSE
response.resHdr.nGeneralStatus := CONST.CN_GRC_BAD_CLASS_INSTANCE;
nResponseLen := SIZEOF(response.resHdr);
i:=20;
END_CASE
ELSE
response.resHdr.nGeneralStatus := CONST.CN_GRC_BAD_CLASS_INSTANCE;
nResponseLen := SIZEOF(response.resHdr);
i:=20;
END_CASE
12: //GetAttribute
response.resHdr.nGeneralStatus := CONST.CN_GRC_SUCCESS;
MEMCPY(ADR(response.resData), ADR(Attributes[request.reqHdr.nAttributeId]), SIZEOF(Attributes[request.reqHdr.nAttributeId]));
response.resHdr.nDataLen := INT_TO_UINT(LEN(Attributes[request.reqHdr.nAttributeId]));
nResponseLen := UDINT_TO_UINT(response.resHdr.nDataLen) + SIZEOF(response.resHdr);
i:=20;
14: //SetAttribute
response.resHdr.nGeneralStatus := CONST.CN_GRC_SUCCESS;
IF request.reqHdr.nDataLen <= SIZEOF(STRING)-1 THEN
MEMCPY(ADR(Attributes[request.reqHdr.nAttributeId]), ADR(request.reqData), request.reqHdr.nDataLen);
ELSE
response.resHdr.nGeneralStatus := CONST.CN_GRC_BAD_DATA;
END_IF
nResponseLen := SIZEOF(response.resHdr);
i:=20;
20: //response to Ethernet/IP-driver
fbAdsRdWrRes(
NETID:= fbADSRDWRTINDEX.NETID ,
PORT:= fbADSRDWRTINDEX.PORT ,
INVOKEID:= fbADSRDWRTINDEX.INVOKEID ,
RESULT:=nAdsResult ,
LEN:=nResponseLen,
DATAADDR:=ADR(Response) ,
RESPOND:=TRUE );
i:=21;
nAdsResponsesSent:=nAdsResponsesSent+1;
fbADSRDWRTINDEX (CLEAR:=TRUE);
21: i:=0;
fbAdsRdWrRes(RESPOND:=FALSE);
END_CASE
Realisierung RS Logix 5000:
- 1. Zu Beginn müssen Sie ein neues Modul anlegen, entweder ein „Generic Ethernet Module“ oder ein aus TwinCAT exportiertes EDS-File.
Der Vorteil der importierten EDS-File ist der, dass dieses bereits die Größe der Prozessdaten, welche in der TwinCAT-Konfiguration angelegt worden sind, beinhaltet. - 2. In den Einstellungen des angefügten Moduls müssen Sie gegebenenfalls die IP- und die Prozessdateneinstellungen anpassen.
- 3. Um azyklisch Nachrichten zu senden und empfangen zu können, sind Strukturen vom Typ „Messages“ notwendig.
Im Beispiel dient eine Struktur zum Senden und eine zum Empfangen. Beide Strukturen müssen Sie entsprechend für das Senden bzw. für das Empfangen konfigurieren. - 4. Mit einem Klick der rechten Maustaste auf den Tag SetMsg-Configure SetMsg öffnen Sie die Konfigurationseinstellungen. Diese sind wie im Screenshot angegeben zu übernehmen.
Die Angaben Class, Instance und Attribute sind frei wählbar. Bei Service Type stellen Sie Set Attribute Single ein. Legen Sie unter Source Element ein Array an, dessen Inhalt versendet werden soll. Wählen Sie die Source Length so, dass diese nicht die Länge der in TwinCAT angelegten Ziel-Variable überschreitet. - 5. Mit einem Klick der rechten Maustaste auf den Tag GetMsg - Configure GetMsg öffnen Sie die Konfigurationseinstellungen. Diese sind wie im Screenshot angegeben zu übernehmen.
Die Angaben Class, Instance und Attribute sind frei wählbar. Bei Service Type stellen Sie Get Attribute Single ein. Legen Sie unter Destination Element ein Array an, welches die azyklischen Nachrichten empfängt. Die Größe des Arrays ist entsprechend den empfangenden Nachrichten zu wählen.

- Der folgende Beispielcode sendet Anfragen an den Ethernet/IP™-Treiber der TF6280, welcher diese an die TwinCAT-PLC zur weiteren Verarbeitung weiterleitet.
Mit einer positiven Flanke an „bGet“ wird ein einzelner Attributwert aus der TwinCAT PLC gelesen. In diesem Beispiel können die Werte „TestReadOnlyAttribute1, TestReadOnlyAttribute2 und TestReadOnlyAttribute3“ gelesen werden.
Mit einer positiven Flanke an „bSet“ wird ein einzelner Attributwert in der TwinCAT PLC geschrieben. In diesem Beispiel kann das vierte Attribut in der TwinCAT PLC mit dem Inhalt „123Beckhoff567“ und „HelloBeckhoff“ beschrieben werden.
//GetAttribute
IF bGet THEN
bGet:=0;
iCase:=20+iGet;
END_IF;
//SetAttribute
IF bSet AND iOldCase=5 THEN
bSet:=0;
iCase:=6;
ELSIF bSet AND iOldCase=6 THEN
bSet:=0;
iCase:=5;
END_IF;
CASE iCase OF
5: //HelloBeckhoff --> (ASCII)
iOldCase:=iCase;
TxData[0]:=72; //H
TxData[1]:=101; //e
TxData[2]:=108; //l
TxData[3]:=108; //l
TxData[4]:=111; //o
TxData[5]:=66; //B
TxData[6]:=101; //e
TxData[7]:=99; //c
TxData[8]:=107; //k
TxData[9]:=104; //h
TxData[10]:=111; //o
TxData[11]:=102; //f
TxData[12]:=102; //f
iCase:=10;
6: //123Beckhoff567 --> (ASCII)
iOldCase:=iCase;
TxData[0]:=49; //1
TxData[1]:=50; //2
TxData[2]:=51; //3
TxData[3]:=66; //B
TxData[4]:=101; //e
TxData[5]:=99; //c
TxData[6]:=107; //k
TxData[7]:=104; //h
TxData[8]:=111; //o
TxData[9]:=102; //f
TxData[10]:=102; //f
TxData[11]:=52; //4
TxData[12]:=53; //5
TxData[13]:=54; //6
iCase:=10;
10: //SetAttribute
msg(SetMsg);
IF SetMsg.DN OR SetMsg.ER THEN
FOR iLoop:=0 TO 80 DO
TxData[iLoop]:=0;
end_FOR;
iCase:=0;
END_IF;
20: //TestReadOnlyAttribute1
GetMsg.Class:=16#1000;
GetMsg.Instance:=16#01;
GetMsg.Attribute:=16#01;
iCase:=30;
21: //TestReadOnlyAttribute2
GetMsg.Class:=16#1000;
GetMsg.Instance:=16#01;
GetMsg.Attribute:=16#02;
iCase:=30;
22: //TestReadOnlyAttribute3
GetMsg.Class:=16#1000;
GetMsg.Instance:=16#01;
GetMsg.Attribute:=16#03;
iCase:=30;
30: //GetAttribue
msg(GetMsg);
IF GetMsg.DN OR GetMsg.ER then
iGet:=iGet+1;
IF iGet >= 3 THEN
iGet:=0;
END_IF;
iCase:=0;
END_IF;
END_CASE;
Hier finden Sie das dokumentierte Beispiel als TwinCAT-Projekt: Download.