ECS CustomOD Example exercise
1. ECS Setup.
Necessary hardware and sotware for this training
The same like in ECS SimpleConfig Example exercise. Follow the steps 1 till 3 and use here now netX 90 - EtherCAT Slave - customOD V3.0.0.1
2. Create an Object with a Subobject
go to the file: AppECS_DemoObjectDictionary.h
In the array g_tObjects[] all custom objects are defined. Go to object 0x4000 and copy this. Create a new object. Here the example is 0x4001:
{ .usIndex = 0x4000, ... }, { .usIndex = 0x4001, .bMaxNumOfSubObjs = ARRCNT(g_tSiObj_4001) - 1, .ulMaxFieldUnits = 1, .bObjectCode = ODV3_OBJCODE_RECORD, .usAccessFlags = 0, .bIndicationFlags = 0, .usDatatype = ECAT_OD_DTYPE_UNSIGNED8, .usAccessRights = ECAT_OD_READ_ALL, .pszName = "Hello World Object", .ptSi00 = &g_tSiObj_4001[0], .ptSiBreak = &g_tSiObj_4001[ ARRCNT(g_tSiObj_4001) ], }, };
and now create the subobjects for the object 0x4001 in the subobject structure g_tSiObj_4001
static const uint8_t s_b4001_NumElements; SUBOBJECT_DESCRIPTION_T g_tSiObj_4001[] = { { .bSubIndex = 0, .bIndicationFlags = 0, .usAccessRights = ECAT_OD_READ_ALL, .usDatatype = ECAT_OD_DTYPE_UNSIGNED8, .ulMaxFieldUnits = 1, .pszName = "Number of elements", .pvInitialValue = &s_b4001_NumElements, .ulInitialValueLength = sizeof(s_b4000_NumElements), }, { .bSubIndex = 1, .bIndicationFlags = ODV3_INDICATION_FLAGS_ALLOWED_ON_SUBOBJ, .usAccessRights = ECAT_OD_ACCESS_ALL, .usDatatype = ECAT_OD_DTYPE_BOOLEAN, .ulMaxFieldUnits = 1, .pszName = "Flag 1", }, }; static const uint8_t s_b4001_NumElements = ARRCNT(g_tSiObj_4001) - 1; /* SI 00 does not counts */
As soon as the application starts, it will do this steps:
The application calls in the file AppECS_DemoApplicationFunctions.c the function AppECS_OD_SendCreateObjectReq.
/* initialize channel to use the configured data */ App_SysPkt_AssembleChannelInitReq(ptPacket); ulRet = Pkt_SendReceivePacket(ptAppData, ECS_DEMO_CHANNEL_INDEX, ptPacket, TXRX_TIMEOUT); if( ulRet != CIFX_NO_ERROR ) return ulRet; /* Create Objects */ AppECS_OD_SendCreateObjectReq(ptAppData, &g_customOd); //ulRet = Pkt_SendReceivePacket( ptAppData->hChannel, &ptAppData->tPacket, TXRX_TIMEOUT ); if( ulRet != CIFX_NO_ERROR ) return ulRet;
the function AppECS_OD_SendCreateObjectReq takes all objects from the array g_tObjects[] and creates these objects in the ECS firmware.
After successfull creating of an object the application calls AppECS_OD_SendCreateSubObjectReq to create the subobjects.
The flag ODV3_INDICATION_FLAGS_ALLOWED_ON_SUBOBJ means here, that a wirte or read on this subobject from a EtherCAT master will be
forwarded to the application as Indication.
In the file AppECS_DemoApplicationFuctions.c is the function AppECS_Read_ObjectInd and AppECS_Write_ObjectInd.
Add here now a switch case for the object 0x4001:
First creatre
static uint8_t Object_4001_1=0x01;
AppECS_Read_ObjectInd:
case 0x4001: switch ( ptInd->tData.bSubIndex ) { case 0x0001: ptRes->tHead.ulLen = 9+1; ptRes->tData.ulTotalDataBytes=1; ptRes->tData.abData[0]= Object_4001_1; break; default: ptRes->tHead.ulLen = 9; ptRes->tData.ulTotalDataBytes=0; break; } break
AppECS_Write_ObjectInd:
case 0x4001: switch ( ptInd->tData.bSubIndex ) { case 0x0001: Object_4001_1 = ptInd->tData.abData[0] ; break; default: break; } break
Compile the netXStudio projekct with the changes and download the new application to the netX90.
Now start TwinCAT and create a new project.
Go to the netX90 EtherCAT slave and check the object list:
The object 0x4001 is now avaiable.
Doubleclick on 4001:01 Flag 1 and change the value for the subobject from ECS master.
Set a breakpoint in the function AppECS_Write_ObjectIInd for the object 0x4001. As soon as the change from ECM is done, the indication function will be called.
double click on advanced and force a read of all objects
set a breakpoint in AppECS_Read_ObjectInd for the object 0x4001
Changed Sources:
AppECS_DemoApplicationFunctions.c
3. Create a PDO
In the set configuration packet the flag ECAT_SET_CONFIG_COEFLAGS_USE_CUSTOM_OD is used. This allows to create a custom object dictionary.
This chepter describes how to create a new processdata object (PDO).
Go in the array g_tObjects[] to object 0x1600 and copy it and create object 0x1601
{ .usIndex = 0x1600, ... }, { .usIndex = 0x1601, .bMaxNumOfSubObjs = ARRCNT(s_ab1601_Elements), .bObjectCode = ODV3_OBJCODE_RECORD, .usAccessFlags = 0, .bIndicationFlags = 0, .usDatatype = ECAT_OD_DTYPE_PDO_MAPPING, .usAccessRights = ECAT_OD_ACCESS_ALL, /* no SimpleVar, therefore no ulMaxFieldUnits value */ .pszName = "1. RxPDO", /* no SimpleVar, therefore no initial value */ .ptSi00 = &g_tSiObj_1601[0], .ptSiBreak = &g_tSiObj_1601[ ARRCNT(g_tSiObj_1601) ], },
and now create the subobjects for the object 0x1601 in the subobject structure g_tSiObj_1601
static const uint32_t s_ab1601_Elements[] = { PDOMAPPING(0x2001, 1, 8), }; static const uint8_t s_b1601_NumElements = ARRCNT(s_ab1601_Elements); SUBOBJECT_DESCRIPTION_T g_tSiObj_1601[] = { { .bSubIndex = 0, .bIndicationFlags = 0, .usAccessRights = ECAT_OD_READ_ALL, .usDatatype = ECAT_OD_DTYPE_UNSIGNED8, .ulMaxFieldUnits = 1, .pszName = "Number of elements", .pvInitialValue = &s_b1601_NumElements, .ulInitialValueLength = sizeof(s_b1601_NumElements), }, { .bSubIndex = 1, .bIndicationFlags = 0, .usAccessRights = ECAT_OD_READ_ALL, .usDatatype = ECAT_OD_DTYPE_UNSIGNED32, .ulMaxFieldUnits = 1, .pszName = 0, .pvInitialValue = &s_ab1601_Elements[0], .ulInitialValueLength = sizeof(s_ab1601_Elements[0]), }, };
and link this with the SynManager object 0x1C12
static const uint16_t s_aus1C12_Entries[] = { 0x1600 , 0x1601 }; static const uint8_t s_b1C12_NumElements = ARRCNT(s_aus1C12_Entries); SUBOBJECT_DESCRIPTION_T tSiObj_1C12[] = { { .bSubIndex = 0, .bIndicationFlags = 0, .usAccessRights = ECAT_OD_READ_ALL, .usDatatype = ECAT_OD_DTYPE_UNSIGNED8, .ulMaxFieldUnits = 1, .pszName = "Number of elements", .pvInitialValue = &s_b1C12_NumElements, .ulInitialValueLength = sizeof(s_b1C12_NumElements), }, { .bSubIndex = 1, .bIndicationFlags = 0, .usAccessRights = ECAT_OD_READ_ALL, .usDatatype = ECAT_OD_DTYPE_UNSIGNED16, .ulMaxFieldUnits = 1, .pszName = 0, .pvInitialValue = &s_aus1C12_Entries[0], .ulInitialValueLength = sizeof(s_aus1C12_Entries[0]), }, { .bSubIndex = 2, .bIndicationFlags = 0, .usAccessRights = ECAT_OD_READ_ALL, .usDatatype = ECAT_OD_DTYPE_UNSIGNED16, .ulMaxFieldUnits = 1, .pszName = 0, .pvInitialValue = &s_aus1C12_Entries[1], .ulInitialValueLength = sizeof(s_aus1C12_Entries[1]), }, };
and change
{ .usIndex = 0x1C12, .bMaxNumOfSubObjs = ARRCNT(tSiObj_1C12) - 1, .bObjectCode = ODV3_OBJCODE_ARRAY, .usAccessFlags = 0, .bIndicationFlags = 0, .usDatatype = ECAT_OD_DTYPE_UNSIGNED16,/**< \todo what exactly shall be used here */ .usAccessRights = ECAT_OD_ACCESS_ALL, /* no SimpleVar, therefore no ulMaxFieldUnits value */ .pszName = "Sync Manager 1 PDO Assignment", /* no SimpleVar, therefore no initial value */ .ptSi00 = &tSiObj_1C12[0], .ptSiBreak = &tSiObj_1C12[ ARRCNT(tSiObj_1C12) ], },
and now create the custom specific opject 0x2001 in the array g_tObjects[]
{ .usIndex = 0x2000, ... }, { .usIndex = 0x2001, .bMaxNumOfSubObjs = ARRCNT(g_tSiObj_2001) - 1, .bObjectCode = ODV3_OBJCODE_RECORD, .usAccessFlags = ODV3_ACCESS_FLAGS_RXPDO_MAPPABLE, .bIndicationFlags = 0, .usDatatype = ECAT_OD_DTYPE_UNSIGNED8,/**< \todo what exactly shall be used here */ .usAccessRights = ECAT_OD_READ_ALL, /* no SimpleVar, therefore no ulMaxFieldUnits value */ .pszName = "Outputs", /* no SimpleVar, therefore no initial value */ .ptSi00 = &g_tSiObj_2001[0], .ptSiBreak = &g_tSiObj_2001[ ARRCNT(g_tSiObj_2001) ], },
and now create the subobjects for the object 0x2001 in the subobject structure g_tSiObj_2001
static const uint8_t s_b2001_NumElements; SUBOBJECT_DESCRIPTION_T g_tSiObj_2001[] = { { .bSubIndex = 0, .bIndicationFlags = 0, .usAccessRights = ECAT_OD_READ_ALL, .usDatatype = ECAT_OD_DTYPE_UNSIGNED8, .ulMaxFieldUnits = 1, .pszName = "Number of elements", .pvInitialValue = &s_b2001_NumElements, .ulInitialValueLength = sizeof(s_b2001_NumElements), }, { .bSubIndex = 1, .bIndicationFlags = 0, .usAccessRights = ECAT_OD_ACCESS_ALL, .usDatatype = ECAT_OD_DTYPE_UNSIGNED8, .ulMaxFieldUnits = 1, .pszName = "Outputdata0", .pvInitialValue = 0, .ulInitialValueLength = 0, }, }; static const uint8_t s_b2001_NumElements = ARRCNT(g_tSiObj_2001) - 1; /* SI 00 does not counts */
The last step is to change the outsize in the configuration packet
ptConfigReq->tData.tBasicCfg.ulProcessDataOutputSize = sizeof(APP_PROCESS_DATA_INPUT_T)+1;
Compile the netXStudio projekct with the changes and download the new application to the netX90.
Now start TwinCAT and create a new project.
Go to the netX90 EtherCAT slave and check the object list:
and under process data the new PDO is available.
Changed Sources:
AppECS_DemoApplicationFunctions.c
Do some last changes for a 4 byte size submodul.
Change the outsize in the configuration packet to + 4;
ptConfigReq->tData.tBasicCfg.ulProcessDataOutputSize = sizeof(APP_PROCESS_DATA_INPUT_T)+4;
And now change the subobjet 2001:1 from ECAT_OD_DTYPE_UNSIGNED8 to ECAT_OD_DTYPE_UNSIGNED32
SUBOBJECT_DESCRIPTION_T g_tSiObj_2001[] = { { .bSubIndex = 0, .bIndicationFlags = 0, .usAccessRights = ECAT_OD_READ_ALL, .usDatatype = ECAT_OD_DTYPE_UNSIGNED8, .ulMaxFieldUnits = 1, .pszName = "Number of elements", .pvInitialValue = &s_b2001_NumElements, .ulInitialValueLength = sizeof(s_b2001_NumElements), }, { .bSubIndex = 1, .bIndicationFlags = 0, .usAccessRights = ECAT_OD_ACCESS_ALL, .usDatatype = ECAT_OD_DTYPE_UNSIGNED32, .ulMaxFieldUnits = 1, .pszName = "Outputdata0", .pvInitialValue = 0, .ulInitialValueLength = 0, }, };
and change the mapping to
static const uint32_t s_ab1601_Elements[] = { PDOMAPPING(0x2001, 1, 32), };
Go to TwinCAT. Here is the last information.
After click on Load PDO info from device TwinCAT reads the latest informations about the PDOs from the device.
The subobject 2001:1 has no a size of 4 byte