Forward Message to AMS Port

"Explicit Messaging" is used to send information and data that does not require continuous updates. "Explicit Messaging" allows you to configure and monitor the parameters of a slave device in the Ethernet/IP network.

The "FwdMsgToAmsPort" feature allows acyclic requests from Ethernet/IP scanners to be processed.

The following example shows implementation of acyclic communication between a TwinCAT 3 controller and an RS Logix controller.

TwinCAT 3 implementation:

Requirement: Ethernet/IP driver version, min. V1.23
1. To enable the FwdMsgToAmsPort feature, enter the AmsPort of the PLC (in the example 851) in the slave/master settings (0x8000:2A/0xF800:2A) in TwinCAT.
Forward Message to AMS Port 1:
2. ADSRDWRT requests from the Ethernet/IP driver (IDGRP: 0x848180E9 IOFFS: SlaveId (Adapter)) to the PLC task are registered as indications and this enables them to be processed. The ADSRDWRTIND function block is used for this purpose.
The first entry in the indication registered by the Ethernet/IP driver is a 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

The same header is also used for the response.

3. The actual read/write data follows directly after the header (nDataLen <> 0 should be set according to the data length). The maximum supported data length is 992 bytes (+ 32-byte header = 1024 bytes).Potential classes/instances/attribute values

 

Min

Max

Class

1

0xFFFF

Instance

1

0xFFFF

Attribute

1

0xFFFF

4. After an indication has been processed, a response must be sent to the source device via the ADSRDWRTRES function block.
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

Implementation RS Logix 5000:

1. At the beginning you have to create a new module, either a "Generic Ethernet Module" or an EDS file exported from TwinCAT.
The advantage of the imported EDS file is that it already contains the size of the process data created in the TwinCAT configuration.
Forward Message to AMS Port 2:
2. In the settings of the attached module you may have to adjust the IP and the process data settings.
Forward Message to AMS Port 3:
3. To be able to send and receive messages acyclically, structures of the type "Messages" are necessary.
In the sample, one structure is used for sending and one for receiving. You must configure both structures accordingly for sending and for receiving.
4. Right-click on the tag SetMsg-Configure SetMsg to open the configuration settings. These are to be taken over as indicated in the screenshot.
The specifications Class, Instance and Attribute are freely selectable. At Service Type set Set Attributes Single. At Source Element, create an array whose contents are to be sent. Select the Source Length so that it does not exceed the length of the target variable created in TwinCAT.
Forward Message to AMS Port 4:
5. Right-click on the tag GetMsg - Configure GetMsg to open the configuration settings. These are to be taken over as indicated in the screenshot.
The specifications Class, Instance and Attribute are freely selectable. At Service Type set Get Attribute Single. Create an array at Destination Element that receives the acyclic messages. The size of the array is to be chosen according to the receiving messages.
Forward Message to AMS Port 5:
The following sample code sends requests to the Ethernet/IP driver of the TF6280, which forwards them to the TwinCAT PLC for further processing.

A single attribute value is read from the TwinCAT PLC with a positive edge at "bGet". In this sample the values "TestReadOnlyAttribute1, TestReadOnlyAttribute2 and TestReadOnlyAttribute3" can be read.
A single attribute value is written to the TwinCAT PLC with a positive edge at "bSet". In this sample the fourth attribute in the TwinCAT PLC can be described with the content "123Beckhoff567" and "HelloBeckhoff".

//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;

You can find the documented example as a TwinCAT project here: Download.