008 - Extending process data using RPC transport

Content

Introduction

Beginning with version 2.1 of the module firmware it is possible for all protocol stacks to extend the possible process data size using RPC (Remote Procedere Call) transport. This feature allows significantly increasing the size of process data beyond the boundaries of the cyclic channel (73 Byte). However, process data using the RPC transport come with a significant cut in performance.

The cyclic channel provides up to 73 Byte, however in the direction of output data additionally a 4 byte generic data provider is mapped, which contains status information (LEDs, connection status, Update counter).

Features and Limitations

Using RPC transport limits the update cycle of process data, which is mapped to RPC, to a smaller value. Typically, an update cycle of around 100 ms is possible (see figure below). For objects, mapped to the data mapper, the update cycle (depending on the application controller (AC)) is 1 ms.

The amount of data, which can be transferred using RPC depends on the used hardware and communication stack.

For PROFINET the maximum data size is 1434 Byte per direction restricted by the stack itself.

For EtherCAT the theoretical maximum data size is 1408 Byte, but it’s limited by the available memory.

For EtherNet/IP the maximum data size is 505 Byte O->T and 509 Byte T->O of implicit messaging. For explicit messaging, the assembly size is limitited to 1492 Byte per assembly.

Even if the update cycle of the process data is larger because of the RPC transport, the typically cycle time per communication stack can still be achieved. The update cycle and the cycle time are independet from each other.

The update cycle for process data mapped to RPC depends on the amount of data required to update. Following table shows typical values where the amount of data is mapped to both communication directions (input data and output data) and all data is required to be updated in one cycle. The time required can reach 310 ms, see table below.

used module size per direction

average update cycle

used module size per direction

average update cycle

Octet string 1434 Byte

310 ms

Octet string 1024 Byte

230 ms

Octet string 512 Byte

125 ms

Octet string 256 Byte

70 ms

Octet string 128 Byte

40 ms

 

Please note: Access to the RPC objects is not allowed in data mapper callback context. Those objects need to be accessed in the main loop context (e.g. appl_loop()).

Application considerations

For all 3 communication stacks cyclic indication functions are available, which signal the update of process data or the request to update process data. In each case it is essential for operation of the module to keep processing as minimal as possible. Any extensive time consuming processing will affect the responsiveness and update cycle accuracy of the application. Following examples fulfil this requirement for PROFINET, EtherNet/IP and EtherCAT.

Following examples apply to handling of objects mapped to the cyclic channel.

PROFINET:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 GOAL_STATUS_T appl_setup( void ) { /* ... */ /* enable GOAL_PNIO_CB_ID_NEW_IO_DATA callback for each arrived cyclic frame */ goal_pnioCyclicCtrl(pPnio, GOAL_TRUE); /* ... */ } /****************************************************************************/ /** Profinet Callback Handler * * This function collects all callbacks from the stack and decides if the * callback must be handled. */ static GOAL_STATUS_T appl_pnioCb( GOAL_PNIO_T *pHdlPnio, /**< PROFINET handle */ GOAL_PNIO_CB_ID_T id, /**< callback id */ GOAL_PNIO_CB_DATA_T *pCb /**< callback parameters */ ) { GOAL_STATUS_T res = GOAL_OK; /* result */ uint8_t iops; /* IO producer status */ UNUSEDARG(pHdlPnio); /* handle callback IDs */ switch (id) { case GOAL_PNIO_CB_ID_NEW_IO_DATA: if (GOAL_TRUE != flgAppReady) { break; } /* read data from output module */ res = goal_pnioDataOutputGet(pPnio, APPL_API, APPL_SLOT_4, APPL_SLOT_4_SUB_1, dataDm, APPL_SIZE_13_SUB_1_OUT, &iops); if (GOAL_RES_OK(res)) { /* copy data to input module */ res = goal_pnioDataInputSet(pPnio, APPL_API, APPL_SLOT_3, APPL_SLOT_3_SUB_1, dataDm, APPL_SIZE_3_SUB_1_IN , GOAL_PNIO_IOXS_GOOD); } /* log info in case of an error */ if (GOAL_RES_ERR(res)) { goal_logErr("failed to mirror IO data [0x%"FMT_x32"]", res); } /* read data from output module */ res = goal_pnioDataOutputGet(pPnio, APPL_API, APPL_SLOT_2, APPL_SLOT_2_SUB_1, dataDm, APPL_SIZE_11_SUB_1_OUT, &iops); if (GOAL_RES_OK(res)) { /* copy data to input module */ res = goal_pnioDataInputSet(pPnio, APPL_API, APPL_SLOT_1, APPL_SLOT_1_SUB_1, dataDm, APPL_SIZE_1_SUB_1_IN, GOAL_PNIO_IOXS_GOOD); } /* log info in case of an error */ if (GOAL_RES_ERR(res)) { goal_logErr("failed to mirror IO data [0x%"FMT_x32"]", res); } break; } } }

 

EtherNet/IP:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 /****************************************************************************/ /** EtherNet/IP Callback Handler * * This function collects all callbacks from the stack and decides if the * callback must be handled. */ GOAL_STATUS_T main_eipCallback( GOAL_EIP_T *pHdlEip, /**< GOAL Ethernet/ IP handle */ GOAL_EIP_CB_ID_T id, /**< callback id */ GOAL_EIP_CB_DATA_T *pCb /**< callback parameters */ ) { GOAL_STATUS_T res = GOAL_OK; /* return value */ UNUSEDARG(pHdlEip); UNUSEDARG(pCb); /* handle callback IDs */ switch (id) { case GOAL_EIP_CB_ID_ASSEMBLY_DATA_SEND: /* Inform the application about assembly object being sent next */ res = main_eipBeforeAssemblyDataSend( pHdlEip, pCb->data[0].instanceNr); break; case GOAL_EIP_CB_ID_ASSEMBLY_DATA_RECV: /* inform application on received data for an assembly object */ res = main_eipAfterAssemblyDataReceived( pHdlEip, pCb->data[0].instanceNr); break; default: break; } return res; }

 

EtherCAT:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 /****************************************************************************/ /** EtherCAT Callback Handler * * This function collects all callbacks from the stack and decides if the * callback must be handled. * * @retval GOAL_OK successful * @retval other failed */ GOAL_STATUS_T appl_ecatCallback( GOAL_ECAT_T *pHdlEcat, /**< GOAL EtherCAT handle */ GOAL_ECAT_CB_ID_T id, /**< callback id */ GOAL_ECAT_CB_DATA_T *pCb /**< callback parameters */ ) { GOAL_STATUS_T res = GOAL_OK; /* result */ GOAL_MA_LED_T *pMaLed; /* LED ma handle */ switch (id) { case GOAL_ECAT_CB_ID_RxPDO_RECEIVED: appl_ecatPdoReceived(pHdlEcat); break; case GOAL_ECAT_CB_ID_TxPDO_PREPARE: appl_ecatPdoTxPrepare(pHdlEcat); break; default: res = GOAL_OK; break; } return res; }

 

Now if process data is read or written using RPC functions, the situation changes. RPC requests are blocking requests that are originally intended for acyclic communication. There take time and a response time is not guaranteed. Therefore access to process data for objects mapped to RPC need to be handled differently. The next chapter will show this in detail.

Defining objects mapped to the RPC transport

PROFINET

The application decides whether the data mapper or RPC are used for transport of process data when submodule are plugged. Therefore 2 API functions, goal_pnioRpcSubmodPlug and goal_pnioDmSubmodPlug are available.

Plugging a submodule using data mapper transport:

1 2 3 4 5 6 7 8 9 10 11 12 /* skip requests for DAP slot (DAP & interfaces) */ switch (pCb->data[2].u16) { case APPL_SLOT_3: case APPL_SLOT_4: /* plug module & submodule into DM */ res = goal_pnioDmSubmodPlug(pPnio, pCb->data[1].u32, pCb->data[2].u16, pCb->data[3].u16, pCb->data[4].u32, pCb->data[5].u32); break; default : /* skip other modules */ return; }

 

Plugging a submodule using RPC transport:

1 2 3 4 5 6 7 8 9 10 11 /* skip requests for DAP slot (DAP & interfaces) */ switch (pCb->data[2].u16) { case APPL_SLOT_1: case APPL_SLOT_2: /* plug module & submodule (RPC) */ res = goal_pnioRpcSubmodPlug(pPnio, pCb->data[1].u32, pCb->data[2].u16, pCb->data[3].u16, pCb->data[4].u32, pCb->data[5].u32); break; default : /* skip other modules */ return; }

Creating an object by generic function goal_pnioSubmodPlug is equivalent to goal_pnioDmSubmodPlug, causing a data transport by DM.

EtherCAT

The application decides weather data mapper or RPC is used for transport when objects are created. Therefore 2 API function, goal_ecatdynOdSubIndexDmAdd and goal_ecatdynOdSubIndexRpcAdd are available.

Creating an object with data mapper transport:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 /************************************************/ /* 0x2001:0x01 dout_dm_1 */ /************************************************/ if (GOAL_RES_OK(res)) { uint32ValueMin = 0x00000000; uint32ValueDef = 0x00000000; uint32ValueMax = 0xFFFFFFFF; res = goal_ecatdynOdSubIndexDmAdd( pHdlEcat, 0x2001, 0x01, GOAL_ECAT_DATATYPE_UNSIGNED32, EC_OBJATTR_RD | EC_OBJATTR_WR | EC_OBJATTR_RXPDOMAPPING | EC_OBJATTR_MAN | EC_OBJATTR_NUMERIC, (uint8_t *) &uint32ValueDef, (uint8_t *) &uint32ValueMin, (uint8_t *) &uint32ValueMax, 4, (uint8_t *) &dout_dm[0]); }

 

Creating an object with RPC transport:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 /************************************************/ /* 0x2003:0x04 dout_rpc_4 */ /************************************************/ if (GOAL_RES_OK(res)) { res = goal_ecatdynOdSubIndexRpcAdd( pHdlEcat, 0x2003, 0x04, GOAL_ECAT_DATATYPE_OCTETSTRING, EC_OBJATTR_RD | EC_OBJATTR_WR | EC_OBJATTR_RXPDOMAPPING | EC_OBJATTR_MAN | EC_OBJATTR_NO_DFLT, (uint8_t *) obj2003_sub04, NULL, NULL, 31); }

Creating an object by generic function goal_ecatdynOdSubIndexAdd is equivalent to goal_ecatdynOdSubIndexDmAdd, causing a data transport by DM.

EtherNet/IP

The application decides weather data mapper or RPC is used for transport when objects are created. Therefore 2 API function, goal_eipCreateAssemblyObjectDm and goal_eipCreateAssemblyObjectRpc are available.

Creating an object with data mapper transport:

1 2 3 4 5 6 /* Input Assembly */ res = goal_eipCreateAssemblyObjectDm(pHdlEip, GOAL_APP_ASM_ID_INPUT, GOAL_APP_ASM_SIZE_INPUT); if (GOAL_RES_ERR(res)) { goal_logErr("failed to create Input Assembly"); return res; }

 

Creating an object with RPC transport:

1 2 3 4 5 6 /* Input Assembly */ res = goal_eipCreateAssemblyObjectRpc(pHdlEip, GOAL_APP_ASM_ID_INPUT, GOAL_APP_ASM_SIZE_INPUT); if (GOAL_RES_ERR(res)) { goal_logErr("failed to create Input Assembly"); return res; }

Creating an assembly object by generic function goal_eipCreateAssemblyObject is equivalent to goal_eipCreateAssemblyObjectDm, causing a data transport by DM.

Accessing objects mapped to the RPC transport

As mentioned before, accessing the process data of RPC mapped objects should be handled carefully.

 

Do not access process data of objects mapped to the RPC transport from the stack callback function.

 

The following pattern should be used to access process data mapped to RPC. Following example shows handling of process data mapped to RPC for EtherCAT:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 /****************************************************************************/ /* Local defines */ /****************************************************************************/ #define APPL_TIMEOUT_TRIGGER_VAL 100 /**< loop timer */ /****************************************************************************/ /* Local Variables */ /****************************************************************************/ static GOAL_TIMESTAMP_T tsTout; /**< timeout timestamp */ /****************************************************************************/ /** Application Setup * * Setup the application. */ GOAL_STATUS_T appl_setup( void ) { tsTout = goal_timerTsGet() + APPL_TIMEOUT_TRIGGER; return GOAL_OK; } /** Main Loop * * This function must implement the application logic and must not block. It is * called in a loop (the GOAL loop) and if state tracking is necessary it * should use static or global variables. */ void appl_loop( void ) { GOAL_STATUS_T res; /* result */ GOAL_TIMESTAMP_T tsCur; /* current timestamp */ uint32_t dataSize; /* data size */ uint8_t subIndex; /* subIndex */ int idx; /* index */ /* get current timestamp */ tsCur = goal_timerTsGet(); if (tsTout <= tsCur) { /* update timeout value */ tsTout = goal_timerTsGet() + APPL_TIMEOUT_TRIGGER_VAL; /* mirror output data to input data */ for (subIndex = 1; subIndex < 5; subIndex++) { /* read data from output module */ dataSize = 31; res = goal_ecatObjValGet(pHdlEcat, 0x2003, subIndex, (uint8_t *) &dataRpc[0], &dataSize); if (GOAL_RES_ERR(res)) { return; } /* copy data to input module */ res = goal_ecatObjValSet(pHdlEcat, 0x2002, subIndex, (uint8_t *) &dataRpc[0], dataSize); if (GOAL_RES_ERR(res)) { return; } } } }