Forward Message to AMS Port via Explicit Messaging
"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 feature "FwdMsgToAmsPort" allows the processing of acyclic requests from Ethernet/IP scanners via Explicit Messaging.
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.
- 2. ADSRDWRT requests from the Ethernet/IP driver (IDGRP: 0x848180E9 IOFFS: SlaveId (Adapter)) to the PLC task are registered as indications and enable their processing. 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…991] 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. - 2. In the settings of the attached module you may have to adjust the IP and the process data settings.
- 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. - 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.

- 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.