Forward Message to AMS Port

„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 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.
Forward Message to AMS Port 1:
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…9991] 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.
Forward Message to AMS Port 2:
2. In den Einstellungen des angefügten Moduls müssen Sie gegebenenfalls die IP- und die Prozessdateneinstellungen anpassen.
Forward Message to AMS Port 3:
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. Die Source Length wählen Sie so, dass diese nicht die Länge der in TwinCAT angelegten Ziel-Variable überschreitet.
Forward Message to AMS Port 4:
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.
Forward Message to AMS Port 5:
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.