emNet PTP MasterSlave (Sample)

From SEGGER Knowledge Base
Jump to navigation Jump to search
IP_PTP_Sample.c
Requires modifications Yes
Download IP_PTP_Sample.c

This Sample demonstrates time synchronization from a PTP master clock.

In the emNet shipments, four PTP samples are included in total. For ease of use, choosing the correct sample will already set the respective defines to enable the PTP Master or PTP Slave (or both). Otherwise, APP_ENABLE_PTP_MASTER and APP_ENABLE_PTP_SLAVE may be set in the application itself as well.

If the master component is available and enabled in the sample, we fallback to being our own master and periodically sending a time to the network for other slaves. Once a better clock is detected in the network, the master is stopped in the combined master/slave mode.

A master only configuration can be used as well. In slave mode, the application will print the current time every 5s.

NOTE: PTP needs to be activated in the stack by enabling IP_SUPPORT_PTP .

Code

/*********************************************************************
*                   (c) SEGGER Microcontroller GmbH                  *
*                        The Embedded Experts                        *
*                           www.segger.com                           *
**********************************************************************

-------------------------- END-OF-HEADER -----------------------------

Purpose : Sample program for embOS & emNet
          Demonstrates time synchronization from a PTP master clock.

          If the master component is available and enabled, we fallback
          to being our own master and periodically sending a time to
          the network for other slaves.
          Once a better clock is detected in the network, the master
          is stopped in the combined master/slave mode.

          A master only configuration can be used as well.

          In slave mode the application will print the current time
          every 5s.

          PTP needs to be activated in the stack by enabling IP_SUPPORT_PTP .

          Depending on the reference clock used, there could be some
          offset to consider. For example, a clock based on TAI has
          37s offset compared to UTC and 19s compared to GPS.

Notes:
  (1) The following requirements need to be met when including this
      sample into another C-module:

        - BSP_Init() for LED routines and IP_Init() for emNet are
          called before starting APP_MainTask() .

        - The optional IP_Connect() is called and a link state hook
          is installed if necessary for the emNet interface used
          before starting APP_MainTask() .

  (2) The following application defines can be overwritten when
      including this sample into another C-module:

        - APP_MAIN_STACK_SIZE
          Stack size in bytes to use for APP_MainTask() .

        - APP_TASK_STACK_OVERHEAD
          Additional task stack size [multiples of sizeof(int)] if some
          underlying processes require more task stack than default.

        - APP_ENABLE_PTP_MASTER
          Enables/disables the PTP master component.

        - APP_ENABLE_PTP_SLAVE
          Enables/disables the PTP slave component.

  (3) The following symbols can be used and renamed via preprocessor
      if necessary when including this sample into another C-module:

        - MainTask
          Main starting point of the sample when used as a
          standalone sample. Can be renamed to anything else to
          avoid multiple MainTask symbols and to skip common
          initializing steps done in every sample.

        - APP_MainTask
          Functional starting point of the sample itself. Typically
          called by the MainTask in this sample. Should be renamed
          via preprocessor to a more application specific name to
          avoid having multiple APP_MainTask symbols in linker map
          files for a better overview when including multiple samples
          this way.

        - APP_MainTCB
          Task-Control-Block used for the APP_MainTask when started
          as a task from the MainTask in this sample. Can/should be
          renamed via preprocessor to a more application specific name.

        - APP_MainStack
          Task stack used for the APP_MainTask when started as a task
          from the MainTask in this sample. Can/should be renamed via
          preprocessor to a more application specific name.
*/

#include "RTOS.h"
#include "BSP.h"
#include "IP.h"
#include "TaskPrio.h"

/*********************************************************************
*
*       Defines, configurable
*
**********************************************************************
*/

#ifndef   PTP_MEMSET
  #define PTP_MEMSET          memset
#endif

#ifndef   PTP_MEMCMP
  #define PTP_MEMCMP          memcmp
#endif

#ifndef   USE_RX_TASK
  #define USE_RX_TASK  0  // 0: Packets are read in ISR, 1: Packets are read in a task of its own.
#endif

//
// Sample features enable/disable
//
#ifndef   APP_ENABLE_PTP_MASTER
  #define APP_ENABLE_PTP_MASTER  (0)  // Enable the PTP "Simple master".
#endif
#ifndef   APP_ENABLE_PTP_SLAVE
  #define APP_ENABLE_PTP_SLAVE   (1)  // Enable the PTP slave.
#endif


#if IP_SUPPORT_IPV6
#define PTP_MASTER_MULTICAST_ADDR  "ff05::181"   // IPv6 multicast address used as destination by the master clock.
#endif

#if IP_SUPPORT_PTP == 0
  #error "PTP support IP_SUPPORT_PTP must be activated"
#endif

#define JAN_2017   1483228800uL // EPOCH is 1st Jan 1970.

//
// Task stack sizes that might not fit for some interfaces (multiples of sizeof(int)).
//
#ifndef   APP_MAIN_STACK_SIZE
  #define APP_MAIN_STACK_SIZE      (1024)
#endif
#ifndef   APP_TASK_STACK_OVERHEAD
  #define APP_TASK_STACK_OVERHEAD  (0)
#endif
#ifndef   PTP_STACK_SIZE
  #define PTP_STACK_SIZE           (APP_MAIN_STACK_SIZE + APP_TASK_STACK_OVERHEAD)
#endif

/*********************************************************************
*
*       Static data
*
**********************************************************************
*/

static IP_HOOK_ON_STATE_CHANGE _StateChangeHook;

//
// Task stacks and Task-Control-Blocks.
//
static OS_STACKPTR int APP_MainStack[PTP_STACK_SIZE / sizeof(int)];            // Stack of the starting point of this sample.
static OS_TASK         APP_MainTCB;                                            // Task-Control-Block of the IP_Task.

static OS_STACKPTR int _IPStack[(TASK_STACK_SIZE_IP_TASK + 128)/sizeof(int)];  // Stack of the IP_Task.
static OS_TASK         _IPTCB;                                                 // Task-Control-Block of the IP_Task.

#if USE_RX_TASK
static OS_STACKPTR int _IPRxStack[TASK_STACK_SIZE_IP_RX_TASK/sizeof(int)];     // Stack of the IP_RxTask.
static OS_TASK         _IPRxTCB;                                               // Task-Control-Block of the IP_RxTask.
#endif

//
// PTP sample specifics.
//
static IP_PTP_CONTEXT _PTP_Context;
#if APP_ENABLE_PTP_MASTER
static IP_PTP_MASTER  _PTP_Master;
#endif
#if APP_ENABLE_PTP_SLAVE
static IP_PTP_SLAVE   _PTP_Slave;
#endif

/*********************************************************************
*
*       Prototypes
*
**********************************************************************
*/

#ifdef __cplusplus
extern "C" {     /* Make sure we have C-declarations in C++ programs */
#endif
void MainTask(void);
#ifdef __cplusplus
}
#endif

/*********************************************************************
*
*       Local functions
*
**********************************************************************
*/

/*********************************************************************
*
*       _OnStateChange()
*
* Function description
*   Callback that will be notified once the state of an interface
*   changes.
*
* Parameters
*   IFaceId   : Zero-based interface index.
*   AdminState: Is this interface enabled ?
*   HWState   : Is this interface physically ready ?
*/
static void _OnStateChange(unsigned IFaceId, U8 AdminState, U8 HWState) {
  //
  // Check if this is a disconnect from the peer or a link down.
  // In this case call IP_Disconnect() to get into a known state.
  //
  if (((AdminState == IP_ADMIN_STATE_DOWN) && (HWState == 1)) ||  // Typical for dial-up connection e.g. PPP when closed from peer. Link up but app. closed.
      ((AdminState == IP_ADMIN_STATE_UP)   && (HWState == 0))) {  // Typical for any Ethernet connection e.g. PPPoE. App. opened but link down.
    IP_Disconnect(IFaceId);                                       // Disconnect the interface to a clean state.
  }
}

/*********************************************************************
*
*       _OnInfo()
*
* Function description
*   Callback that will be notified when a PTP event occurred.
*
* Parameters
*   IFaceId: Zero-based interface index.
*   pInfo  : Pointer to an element of IP_PTP_INFO with a summary
*            of the correction appplied. Use pInfo->Type for
*            a selection what part of the pInfo->Data union is
*            relevant for this event.
*/
static void _OnInfo(unsigned IFaceId, IP_PTP_INFO* pInfo) {
  char Sign;

  IP_USE_PARA(IFaceId);

  switch (pInfo->Type) {
  case IP_PTP_INFO_TYPE_CORRECTION:
  {
    const char*                   sSetCorrectDescription;
    const char*                   sSkipped;
    const char*                   sState;
          IP_PTP_CORRECTION_INFO* pCorrection;

    pCorrection = &pInfo->Data.Correction;
    //
    // Parse the PTP state before the correction was applied.
    //
    switch(pCorrection->State) {
    case PTP_STATE_UNCALIBRATED: sState = "uncalibrated"; break;
    case PTP_STATE_SLAVE:        sState = "slave"; break;
    default:                     sState = "other"; break;
    }
    //
    // Parse if this was a positive or negative correction.
    //
    if ((pCorrection->Flags & IP_PTP_FLAGS_SIGNED_MASK) != 0u) {
      Sign = '-';
    } else {
      Sign = ' ';
    }
    //
    // Output summary.
    //
    sSkipped = "";
    if ((pInfo->Data.Correction.Flags & IP_PTP_FLAGS_SET_TIME_MASK) != 0u) {
      sSetCorrectDescription = "Set time in";
    } else {
      if ((pInfo->Data.Correction.Flags & IP_PTP_FLAGS_COARSE_CORRECTION_MASK) == 0u) {
        sSkipped = " (fine adjust only)";
      }
      sSetCorrectDescription = "Correction applied onto";
    }
    IP_Logf_Application("%s state \"%s\": %c%lus, %luns%s", sSetCorrectDescription, sState, Sign, pCorrection->Seconds, pCorrection->Nanoseconds, sSkipped);
    break;
  }
  case IP_PTP_INFO_TYPE_OFFSET:
  {
    IP_PTP_OFFSET_INFO* pOffset;

    pOffset = &pInfo->Data.Offset;
    //
    // Parse if this is a positive or negative offset (to the master).
    //
    if ((pOffset->Flags & IP_PTP_FLAGS_SIGNED_MASK) != 0u) {
      Sign = '-';
    } else {
      Sign = ' ';
    }
    //
    // Output offset.
    //
    IP_Logf_Application("Offset (from master): %c%lus, %luns", Sign, pOffset->Seconds, pOffset->Nanoseconds);
    break;
  }
  case IP_PTP_INFO_TYPE_MASTER_CHANGED:
  case IP_PTP_INFO_TYPE_MASTER_UPDATED:
  case IP_PTP_INFO_TYPE_MASTER_RESET:
  {
    IP_PTP_MASTER_INFO* pMaster;
    IP_PTP_MASTER_INFO* pMasterOld;

    pMaster    = &pInfo->Data.MasterNewOld.New;
    pMasterOld = &pInfo->Data.MasterNewOld.Old;
    //
    // Output information about the new master or its latest parameters updated.
    //
    IP_Logf_Application("New master or updated parameters available: MasterId: %.2X:%.2X:%.2X:%.2X:%.2X:%.2X:%.2X:%.2X, UTC offset: %lus", pMaster->abGrandmasterIdentity[0],
                                                                                                                                           pMaster->abGrandmasterIdentity[1],
                                                                                                                                           pMaster->abGrandmasterIdentity[2],
                                                                                                                                           pMaster->abGrandmasterIdentity[3],
                                                                                                                                           pMaster->abGrandmasterIdentity[4],
                                                                                                                                           pMaster->abGrandmasterIdentity[5],
                                                                                                                                           pMaster->abGrandmasterIdentity[6],
                                                                                                                                           pMaster->abGrandmasterIdentity[7],
                                                                                                                                           pMaster->UtcOffset);
    //
    // React to being our own master (every slave starts as its own master) upon init
    // or due to timeout of the foreign master we previously selected from the network.
    //
    if (pInfo->Type == IP_PTP_INFO_TYPE_MASTER_RESET) {
      //
      // Examples to identify what has happened and why there was a reset of the selected master (if there was one yet).
      //
      if (IP_IsAllZero(pMasterOld->abGrandmasterIdentity, sizeof(pMasterOld->abGrandmasterIdentity)) != 0u) {
        IP_Logf_Application("Running as our own master (free running) after init.");
      } else {
        U8* pClockIdentity;  // Pointer to 8 byte ClockIdentity.

        //
        // Fallback to being our own master from a previously selected foreign master.
        // This can also be checked by comparing pInfo->Data.Master with pInfo->Data.MasterOld .
        // In case this is a fallback from a foreign master the content of both differs.
        // If this was an internal data reset for whatever reason, new and old data will be
        // the same as it is a fallback to the already in use DefaultDS parameters.
        //
        pClockIdentity = IP_PTP_GetDefaultDsClockIdentity(pInfo->pContext, NULL, 0u);
        if (PTP_MEMCMP(pClockIdentity, pMaster->abGrandmasterIdentity, sizeof(pMaster->abGrandmasterIdentity)) == 0) {
          IP_Logf_Application("Fallback to being our own master.");
        }
      }
    }
#if APP_ENABLE_PTP_MASTER
    //
    // React to selecting a different foreign master from the network.
    // This can be used to remove the master role from this device
    // once another (more precise) master has been identified in the network.
    // This step is optional and the master role can actually keep on running
    // as a more precise master will be automatically selected based on his
    // capabilities sent in his ANNOUNCE message.
    //
    if (pInfo->Type == IP_PTP_INFO_TYPE_MASTER_CHANGED) {
      IP_PTP_MASTER_Remove(&_PTP_Master);
    }
#endif
    break;
  }
  default:
    //
    // Unknown event, do nothing.
    //
    break;
  }
}

#if APP_ENABLE_PTP_SLAVE

/*********************************************************************
*
*       _PrettyPrint()
*
* Function description
*   Print the current date and time.
*
* Parameters
*   Status : Status of the API call.
*   Seconds: Number of seconds since the EPOCH.
*/
static void _PrettyPrint(int Status, U32 Seconds) {
  static const unsigned DaysPerMonth[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
  static const unsigned DaysPerYear[]  = {365 /*2017*/, 365 /*2018*/, 365 /*2019*/, 366 /*2020*/};
               U32       Minutes;
               U32       Hours;
               U32       Days;
               U32       Month;
               U32       Year;
               unsigned  i;

  if ((Status != 0) && (Status != 1)) {
    IP_Logf_Application("Not synced");
  } else {
    Seconds -= JAN_2017;
    Minutes  = Seconds / 60;
    Seconds -= Minutes * 60;
    Hours    = Minutes / 60;
    Minutes -= Hours * 60;
    Days     = Hours / 24;
    Hours   -= Days * 24;
    Year     = 2017;
    //
    i = 0;
    for (;;) {
      if (Days < DaysPerYear[i]) {
        break;
      }
      Days -= DaysPerYear[i];
      i++;
      Year++;
      if (i >= 4u) {
        i = 0;
      }
    }
    //
    for (Month = 0u; Month < 12u; Month++) {
      i = 0u;
      //
      // Check the leap year.
      //
      if (Month == 1u) {
        if (DaysPerYear[(Year - 2017) % 4u] == 366) {
          i = 1u;
        }
      }
      if (Days < (DaysPerMonth[Month] + i)) {
        break;
      }
      Days -= DaysPerMonth[Month] + i;
    }
    //
    IP_Logf_Application("--- TAI time: %d.%02d.%02d  %02d:%02d:%02d ---", Year, Month + 1, Days + 1, Hours, Minutes, Seconds);
  }
}

#endif  // APP_ENABLE_PTP_SLAVE

/*********************************************************************
*
*       _PTP_Task()
*
* Function description
*   PTP task starting master/slave and requesting the current
*   timestamp from a PTP server in slave role.
*/
static void _PTP_Task(void) {
  IP_PTP_TIMESTAMP Timestamp;
  int              IFaceId;
#if APP_ENABLE_PTP_SLAVE
  int              Status;
#endif
#if IP_SUPPORT_IPV6
  IPV6_ADDR        MulticastAddr;
  int              r;
#endif

  IFaceId = IP_INFO_GetNumInterfaces() - 1;  // Get the last registered interface ID as this is most likely the interface we want to use in this sample.
#if IP_SUPPORT_IPV6
  //
  // For IPv6 you need to know the multicast destination address that
  // is used by your master clock. Unlike IPv4 this is unfortunately
  // not done automatically.
  //
   r = IP_IPV6_ParseIPv6Addr(PTP_MASTER_MULTICAST_ADDR, &MulticastAddr);
   if (r != 0) {
     for (;;) {
       //
       // Error in IPv6 address notation.
       //
     }
   }
   r = IP_ICMPV6_MLD_AddMulticastAddr(IFaceId, &MulticastAddr);
   if (r != 1) {
     for (;;) {
       //
       // Error adding IPv6 address.
       //
     }
   }
#endif
  //
  // Configure and start PTP.
  //
  PTP_MEMSET(&_PTP_Context, 0, sizeof(_PTP_Context));
  IP_PTP_Init(&_PTP_Context, &IP_PTP_Profile_1588_2008);
#if APP_ENABLE_PTP_MASTER
  PTP_MEMSET(&_PTP_Master, 0, sizeof(_PTP_Master));
  IP_PTP_OC_AddMasterFallbackLogic(&_PTP_Master);
  IP_PTP_MASTER_Add(&_PTP_Context, &_PTP_Master, IFaceId);
#endif
#if APP_ENABLE_PTP_SLAVE
  PTP_MEMSET(&_PTP_Slave, 0, sizeof(_PTP_Slave));
  IP_PTP_OC_AddSlaveFallbackLogic(&_PTP_Slave);
  IP_PTP_SLAVE_Add(&_PTP_Context , &_PTP_Slave , IFaceId);
#endif
  IP_PTP_OC_SetInfoCallback(_OnInfo);
  IP_PTP_Start(&_PTP_Context);
  //
  // Set a rough start time to have any useful time at all.
  // In this case we simply use a timestamp from February 2019.
  // Other even more precise start timestamps could come from
  // SNTP clients or a battery backed up RTC.
  //
  // Please be aware that _PrettyPrint() as it is will not
  // show the PTP timestamp for a state that is not synchronized.
  //
  PTP_MEMSET(&Timestamp, 0, sizeof(Timestamp));
  Timestamp.Seconds     = 1551196467uL;
  Timestamp.Nanoseconds = 917091479uL;
  IP_PTP_SetTime(&Timestamp);
  //
  for (;;) {
#if APP_ENABLE_PTP_SLAVE
    //
    // Check PTP time every 5 seconds.
    //
    Status = IP_PTP_GetTime(&Timestamp);
    //
    // Print the current time if the synchronization with the server clock is done.
    //
    _PrettyPrint(Status, Timestamp.Seconds);
    //
#endif
    OS_Delay(5000);
  }
}

/*********************************************************************
*
*       APP_MainTask()
*
*  Function description
*    Sample starting point.
*/
static void APP_MainTask(void) {
  OS_SetTaskName(OS_GetTaskID(), "PTP task");
  IP_AddLogFilter(IP_MTYPE_APPLICATION);
  _PTP_Task();
}

/*********************************************************************
*
*       Global functions
*
**********************************************************************
*/

/*********************************************************************
*
*       MainTask()
*
*  Function description
*    Main sample starting point when running one individual sample.
*
*  Additional information
*    Runs the sample in a task of its own to decouple it from not
*    knowing the required task stack size of this sample when
*    starting from main() . This task typically is terminated once
*    it has fulfilled its purpose.
*
*    This routine initializes everything that might be common with
*    other samples of the same kind. These initializations can then
*    be skipped by not starting from MainTask() but APP_MainTask()
*    instead.
*/
void MainTask(void) {
  int IFaceId;

  //
  // Initialize the IP stack
  //
  IP_Init();
  //
  // Start TCP/IP task
  //
  OS_CREATETASK(&_IPTCB,   "IP_Task",   IP_Task,   APP_TASK_PRIO_IP_TASK  , _IPStack);
#if USE_RX_TASK
  OS_CREATETASK(&_IPRxTCB, "IP_RxTask", IP_RxTask, APP_TASK_PRIO_IP_RXTASK, _IPRxStack);  // Start the IP_RxTask, optional.
#endif
  IP_AddStateChangeHook(&_StateChangeHook, _OnStateChange);                               // Register hook to be notified on disconnects.
  IFaceId = IP_INFO_GetNumInterfaces() - 1;                                               // Get the last registered interface ID as this is most likely the interface we want to use in this sample.
  IP_Connect(IFaceId);                                                                    // Connect the interface if necessary.
  //
  // IPv4 address configured ?
  //
  while (IP_IFaceIsReadyEx(IFaceId) == 0) {
    BSP_ToggleLED(0);
    OS_Delay(200);
  }
  BSP_ClrLED(0);
  //
  // Start the sample itself.
  //
  OS_CREATETASK(&APP_MainTCB, "APP_MainTask", APP_MainTask, APP_TASK_PRIO_MAINTASK, APP_MainStack);
  OS_TASK_Terminate(NULL);
}

/*************************** End of file ****************************/