PNS Isochronous Example
1. Example project
Basis for this example is the Extended Config Example netX 90 - PROFINET IO Device - extendedConfig V2.2.0.0 because an acyclic handling will be necessary to get the isochronous data parameter.
Example for netX90:
netXStudio_PNSV5_extendedConfig isochronous - V1.0.0.2.zip
Basis for this example is the Extended Config Example netX 90 - PROFINET IO Device - extendedConfig V3.0.0.0
netXStudio_PNSV5_extendedConfig isochronous - V1.1.0.0.zip
2. Changes for this example
There are many changes necessary. An interrupt function for sync0 signal is necessary which shall start timers to do the IO data update at the correct time point. For this, the following things have changed:
- The GSDML is changed to support isochronous mode and the output module gets the option to let it run isochronous.
- The IO data handler function is split in 2 IO data handler. One for inputs and one for outputs. Each handler has its timer which runs in RT and IRT connection with 1ms. Only in IRT isochronous connection the timer timings will be changed.
- The set OEM parameter service with parameter 6 is called up before channel init is called up to map the Profinet cycle counter in the input data (plc to device).
- The set trigger type request is called up before channel init to disable FreeRun mode.
- The hardware configuration maps the MMIO to xtrigger0/Sync0 and the main function now calls up some functions to activate an interrupt service routine for xtrigger0.
- In the function for write record indication the parameter with index 0x8030 has been added.
- In the function for parameter end indication the values from index 0x8030 are used to calculate new timer settings.
3. Index 0x8030
As soon as a submodule supports isochronous mode, the synchronous mode for this submodule can be activated in the plc. The plc will write down the parameter with index 0x8030 in the connection establishment. The function for write record indication is necessary to receive the parameter with the index 0x8030 from the plc.
For each isochronous submodule a parameter with Index 0x8030 (Isochronous Mode Data) will be sent from the plc to the device. The parameter with Index 0x8030 (isochronous mode data) consists of these informations:
typedef __HIL_PACKED_PRE struct ISOCHRONOUS_DATA_Ttag
{
uint16_t usType;
uint16_t usLen;
uint8_t bMajor;
uint8_t bMinor;
uint8_t abPadding1[2];
uint16_t usSlot;
uint16_t usSubSlot;
uint16_t usCACF;
uint16_t usDataCycle;
uint32_t ulIO_Input;
uint32_t ulIO_Output;
uint32_t ulIO_InputValid;
uint32_t ulIO_OutputValid;
}__HIL_PACKED_POST PNS_IF_PDU_ISOCHRONOUS_DATA_T;
Essential are the slot and the Subslot information. With this the application knows which IO data are requested to run as isochronous.
The values for Data cycle, IO_Input, IO_Output and CACF need to be saved in the application. After receiving all 0x8030 parameter over the function write record indication, the application needs to calculate timings in the parameter end indication.
4. Calculate Time for call of Output Handler
The ulIO_Output from 0x8030 is the T_IO_Output time in this picture:
T_IO_Output_Min + Spare Time in the picture is the time the device needs to get outputs and apply them. In the GSDML T_IO_Output_Min + Spare Time is added like this:
<IsochroneMode IsochroneModeRequired="false" T_DC_Base="8" T_DC_Max="16" T_DC_Min="1" T_IO_Base="1000" T_IO_InputMin="125" T_IO_OutputMin="60" />
T_IO_Output_Min + spare is an application-specific timing. Start with setting this value to a high value. Later this value can be measured and reduced in the GSDML.
T_IO_Output - T_IO_Output_Min = This is the time the plc has calculated for the network and other time offsets to achieve that all isochronous devices apply data at the same point of time. This time can be different in each network setup.
What does the application developer need to do now?
The application gets in the parameter with Index 0x8030 the time for T_IO_Output. T_IO_Output starts with Sync0 signal and the application needs to apply data if time point of T_IO_Output is reached. The application needs to start a timer with sync0 signal. Now the question ist, what time shall be used to configure the timer? Here are some options:
Option 1: The timer is set to T_IO_Output - T_IO_Output_Min. The application calls xChannelIORead and processes the data and applies it to the outputs.
There is a lot Jitter possible because the time for processing the data can be different.
Option 2:
It is better to call up xChannelIORead and data processing as soon as possible and to save the data for apply outputs until the correct point of time has come. Measure only the application-specific time to apply outputs. T_AO = time apply outputs which will be a short time.
Set the timer to T_IO_Output - T_AO. Then the application only needs to apply outputs as soon as the timer interrupt is reached and there will be less jitter.
Parameter end indication:
In this example the calculation for the times which are used for the output data handler timer is done in the parameter end indication.
T_IO_Output_Event = s_ab_RecordIsochronusData_Slot2_Subslot1.ulIO_Output -T_AO; //in nanoseconds
with T_AO = 0
The application developer needs to set a value for T_AO. He can take, for example, the same value as in the GSDML T_IO_OutputMin="60". The value T_AO must be in nanoseconds.
5. Calculate Time for call of Input Handler
The ulIO_Input from 0x8030 is the T_IO_Input time in this picture:
T_IO_Input_Min time in the picture is the time the device needs for the inputs. In the GSDML T_IO_Input_Min is added like this:
<IsochroneMode IsochroneModeRequired="false" T_DC_Base="8" T_DC_Max="16" T_DC_Min="1" T_IO_Base="1000" T_IO_InputMin="125" T_IO_OutputMin="60" />
T_IO_Input_Min is an application-specific timing. In the development process it is good to start with setting this value to a high value. Later this value can be measured and reduced in the GSDML.
What does the application developer need to do now?
The application gets in the parameter with Index 0x8030 the time for T_IO_Input. But the application needs the time from beginning with Sync0 signal. With the cycletime, the application needs to calculate the correct time for the timer.
This calculation will be done in the parameter end indication function:
T_IO_Input_Event = ((((uint32_t) s_ab_RecordIsochronusData_Slot2_Subslot1.usDataCycle) * 3125)*10) - T_IO_Input; //in nanosecond
6. Some more changes in GSDML
In the DAP:
Change in the GSDML the line like this:<CertificationInfo ApplicationClass="Isochronous" ConformanceClass="C" NetloadClass="II"/>
Add "IsochroneModeInRT_Classes="RT_CLASS_3" in the InterfaceSubmoduleItem.
Add IsochroneMode in the module, that shall support isochronal mode. Put it behind </ModuleInfo>
<IsochroneMode IsochroneModeRequired="false" T_DC_Base="8" T_DC_Max="16" T_DC_Min="1" T_IO_Base="1000" T_IO_InputMin="125" T_IO_OutputMin="60" />
The Timings T_IO_InputMin="125" T_IO_OutputMin="60" are device-specific and need to be measured from the developer.
7. Interrupt HIF Command and Timer
Map mmio to the xc_trigger0
and add in main.c the interrupt function for XCTIGGER_IRQHandler.
The XCTIGGER_IRQHandler starts 2 timers for the 2 IO data handler.
void XCTRIGGER0_IRQHandler(void) { ((trigger_irq_app_Type*) trigger_irq_app_BASE)->trigger_irq_raw_b.xc_trigger_out_edge=1u; if (Get_Timer_Configuration() == 1) { // Start timer for Input and Output update functions HOSTAL_Callbacks_Enable(); HOSTAL_Callbacks_Enable2(); } DRV_NVIC_ClearPendingIRQ(trigger_out_edge0_IRQn); } int main() { int32_t lRet = CIFX_NO_ERROR; libcInit(); if(CIFX_NO_ERROR == lRet) { lRet = InitializeToolkit(); } if(CIFX_NO_ERROR == lRet) { // activate XCTRIGGER0_IRQHandler(); Set MMIO3 in Hardware Configuration file to xtrigger0 Set_Timer_Configuration_false(); ((trigger_irq_app_Type*) trigger_irq_app_BASE)->trigger_irq_cfg_b.xc_trigger_out_polarity = 1u; // 1 = falling edge ((trigger_irq_app_Type*) trigger_irq_app_BASE)->trigger_irq_msk_set_b.xc_trigger_out_edge = 1u; DRV_NVIC_EnableIRQ(trigger_out_edge0_IRQn); lRet = App_CifXApplicationDemo(); } while(1); /** do not return from main() */ }
7. Use set oem service parameter 6 and set trigger type request before channel init
//Deactivate Freerun void AppPNS_HandleSetTriggerTypeReq( CIFX_PACKET* ptPkt) { HIL_SET_TRIGGER_TYPE_REQ_T *tSetTriggerReq= (HIL_SET_TRIGGER_TYPE_REQ_T *) ptPkt; memset(tSetTriggerReq,0,sizeof(HIL_SET_TRIGGER_TYPE_REQ_T)); tSetTriggerReq->tHead.ulCmd=HIL_SET_TRIGGER_TYPE_REQ; tSetTriggerReq->tHead.ulLen=sizeof(HIL_SET_TRIGGER_TYPE_REQ_DATA_T); tSetTriggerReq->tHead.ulDest=HIL_PACKET_DEST_DEFAULT_CHANNEL; tSetTriggerReq->tData.usPdInHskTriggerType=HIL_TRIGGER_TYPE_PDIN_TIMED_ACTIVATION; tSetTriggerReq->tData.usPdOutHskTriggerType=HIL_TRIGGER_TYPE_PDOUT_TIMED_LATCH; tSetTriggerReq->tData.usSyncHskTriggerType=HIL_TRIGGER_TYPE_SYNC_NONE; } //Activate stack cycle counter to get it with xChannelIORead at DPM offset 6. void AppPNS_SetOEM6( CIFX_PACKET* ptPkt ) { PNS_IF_SET_OEM_PARAMETERS_REQ_T *ptSetOEMReq = ( PNS_IF_SET_OEM_PARAMETERS_REQ_T* )ptPkt; memset( ptSetOEMReq, 0, sizeof(PNS_IF_SET_OEM_PARAMETERS_REQ_T)); ptSetOEMReq->tHead.ulDest = HIL_PACKET_DEST_DEFAULT_CHANNEL; ptSetOEMReq->tHead.ulCmd = PNS_IF_SET_OEM_PARAMETERS_REQ; ptSetOEMReq->tHead.ulLen = 4 + sizeof(PNS_IF_SET_OEM_PARAMETERS_TYPE_6_T); ptSetOEMReq->tData.ulParameterType=PNS_IF_SET_OEM_PARAMETERS_TYPE_6; ptSetOEMReq->tParam.tType6Param.usIRTCycleCounterOffset= 0x6; }
8. Write record indication for parameter 0x8030
To receive the parameter with index 0x8030 in the function write record indication add the following code:
else if (ptWriteRecordInd->tData.ulApi == 0 && ptWriteRecordInd->tData.ulSlot == 2 && ptWriteRecordInd->tData.ulSubslot == 1 && ptWriteRecordInd->tData.ulIndex == 0x8030) //Isochronous Mode Data { if (ptWriteRecordInd->tData.hDeviceHandle == 0 ) //PNS_IF_DEVICE_HANDLE_UNSPEC_OR_SUPERV_DA { /* access is denied for non io/io-supervisor ars */ ptWriteRecordRes->tData.ulPnio = 0xDF80B600; } else if (ptWriteRecordInd->tData.ulLenToWrite != sizeof ( PNS_IF_PDU_ISOCHRONOUS_DATA_T )) { /* bad length */ ptWriteRecordRes->tData.ulPnio = 0xDF80B100; } else { /* record data ok and will be used */ value1=*(ptWriteRecordInd->tData.abRecordData+12); value2=*(ptWriteRecordInd->tData.abRecordData+13); CACF= ( uint16_t ) value1 << 8 | ( uint16_t )value2; value1=*(ptWriteRecordInd->tData.abRecordData+14); value2=*(ptWriteRecordInd->tData.abRecordData+15); s_ab_RecordIsochronusData_Slot2_Subslot1.usDataCycle= ( uint16_t ) value1 << 8 | ( uint16_t )value2; value1=*(ptWriteRecordInd->tData.abRecordData+16); value2=*(ptWriteRecordInd->tData.abRecordData+17); value3=*(ptWriteRecordInd->tData.abRecordData+18); value4=*(ptWriteRecordInd->tData.abRecordData+19); T_IO_Input= ( uint32_t ) value1 << 24 | ( uint32_t )value2 <<16 | ( uint32_t )value3 <<8 | ( uint32_t ) value4; s_ab_RecordIsochronusData_Slot2_Subslot1.ulIO_Input= T_IO_Input; value1=*(ptWriteRecordInd->tData.abRecordData+20); value2=*(ptWriteRecordInd->tData.abRecordData+21); value3=*(ptWriteRecordInd->tData.abRecordData+22); value4=*(ptWriteRecordInd->tData.abRecordData+23); T_IO_Output= ( uint32_t ) value1 << 24 | ( uint32_t )value2 <<16 | ( uint32_t )value3 <<8 | ( uint32_t ) value4; s_ab_RecordIsochronusData_Slot2_Subslot1.ulIO_Output= T_IO_Output; Set_Isochron_Data_available_true(); ptWriteRecordRes->tData.ulPnio = 0; ptWriteRecordRes->tData.ulWriteLen = sizeof ( PNS_IF_PDU_ISOCHRONOUS_DATA_T ); } }
9. Parameter end indication
Disable the 1ms Input data handler and 1ms output data handler. Calculate the new timings for IO data handler timers and reconfigure the timers. The timers will be start with sny0/xtrigger0 signal in the interrupt service XCTRIGGER0_IRQHandler.
void AppPNS_HandleParameterEndInd( APP_DATA_T* ptAppData) { if (Get_Isochron_Data_available() == 1) { //calculate Timongs for isocron application T_IO_Input_Event = ((((uint32_t) s_ab_RecordIsochronusData_Slot2_Subslot1.usDataCycle) * 3125)*10) - T_IO_Input; //in nano seconds T_IO_Output_Event = s_ab_RecordIsochronusData_Slot2_Subslot1.ulIO_Output -T_AO; //in nano seconds HOSTAL_Callbacks_Disable(); // Disable 1ms timer HOSTAL_Callbacks_Disable2(); // Disable 1ms timer HOSTAL_Init_Isochron_Input_Timer( T_IO_Input_Event); // set Timer with new timing HOSTAL_Init_Isochron_Output_Timer( T_IO_Output_Event); // set Timer with new timing Set_Timer_Configuration_true(); } PNS_IF_PARAM_END_RSP_T* ptParameterEndRes=( PNS_IF_PARAM_END_RSP_T*) &(ptAppData->tPacket); ptParameterEndRes->tHead.ulCmd |= 0x01; //PNS_IF_PARAM_END_RES; ptParameterEndRes->tHead.ulLen = 8; ptParameterEndRes->tHead.ulSta = SUCCESS_HIL_OK; ptParameterEndRes->tData.fSendApplicationReady = 1; (void)Pkt_SendPacket(ptAppData->hChannel[0], &ptAppData->tPacket, TX_TIMEOUT); return; }
10. IO data handlers
In the input IO data handler not much has changed. The input data handler now reads the input data and the stack cycle counter:
App_stackcyclecounter=ptAppData→tInputData.stackcyclecounter;
Another change of this input data handler is, that in case of isochronous connection, the timer will be disabled because the time shall restart with XCTRIGGER0_IRQHandler.
The output data handler is different. In case of isochronous connection, the application needs to check if the correct cycle is reached. For that, the Index 0x8030 has the CACF value. If this value is not 1, the application needs to check for the correct cycle like this: if((App_stackcyclecounter % (Get_Isochron_DataCycle() * Get_CACF()))==0)
In the output data handler, in case of isochronous connection, the timer will be disabled because the time shall restart with XCTRIGGER0_IRQHandler.
// Do update of RT modules isochron modules in the if cycle counter matches to CACF if((App_stackcyclecounter % (Get_Isochron_DataCycle() * Get_CACF()))==0) { HOSTAL_Sensor_GetData(0, &ptAppData->tOutputData.usSensor_1_Output); ptAppData->tOutputData.bSensor_1_State = HOSTAL_Sensor_GetState(0, &ptAppData->tAcyclicData.usSensor1_StatusCode); HOSTAL_Sensor_GetData(1, &ptAppData->tOutputData.usSensor_2_Output); ptAppData->tOutputData.bSensor_2_State = HOSTAL_Sensor_GetState(1, &ptAppData->tAcyclicData.usSensor2_StatusCode); ptAppData->tOutputData.bActuator_1_State = HOSTAL_Actuator_GetState(0, &ptAppData->tAcyclicData.usActuator1_StatusCode); ptAppData->tOutputData.bActuator_2_State = HOSTAL_Actuator_GetState(1, &ptAppData->tAcyclicData.usActuator2_StatusCode); lRet = xChannelIOWrite(ptAppData->hChannel[0], 0, 0, sizeof(ptAppData->tOutputData), &ptAppData->tOutputData, 0); if(lRet != CIFX_NO_ERROR) { /** Something failed? * Reason for error could be: * 1) netX is not "ready" yet. May happen during startup. * 2) netX is not "running" yet. May happen during startup in case the netX is not fully configured yet. * 3) netX has not yet established an IO connection. */ } } else { // If an only RT module exists, do the update of this data. This example does not have 2 modules. } HOSTAL_Callbacks_Disable2(); // Disable Timer. Timer will be restarted as soon as Syn0/XCTRIGGER0_IRQHandler() comes
11. SYCON.net cifX Card and netHost
Follow these steps:
PNS Configure Profinet IO Controller in IRT mode
and for isochronous only activate the isochronous mode:
12. TIA Portal
Create first a Synchronous Cycle OB.
Go to the device and set it to IRT mode. Under domain settings it is possible to set the sendclock for the plc.
Go to the isochronous mode. Activate isochronous mode and the modules that shall run isochronous. Here the plc send clock is set to 500µs and the application cycle is 1ms. That will lead to a CACF = 2.
Go to the output submodule and connect it with the Synchronous Cycle OB.