System Time and Clock

Basic System Timer

System Timer In most implementations, system time is provided by a timer interrupt. That timer interrupt runs at rate determined by CONFIG_USEC_PER_TICK (default 10000 microseconds or 100Hz. If CONFIG_SCHED_TICKLESS is selected, the default is 100 microseconds). The timer generates an interrupt each CONFIG_USEC_PER_TICK microseconds and increments a counter called g_system_ticks. g_system_ticks then provides a time-base for calculating up-time and elapsed time intervals in units of CONFIG_USEC_PER_TICK. The range of g_system_ticks is, by default, 32-bits. However, if the MCU supports type long long and CONFIG_SYSTEM_TIME16 is selected, a 64-bit system timer will be supported instead.

System Timer Accuracy On many system, the exact timer interval specified by CONFIG_USEC_PER_TICK cannot be achieved due to limitations in frequencies or in dividers. As a result, the time interval specified by CONFIG_USEC_PER_TICK may only be approximate and there may be small errors in the apparent up-time time. These small errors, however, will accumulate over time and after a long period of time may have an unacceptably large error in the apparent up-time of the MCU.

If the timer tick period generated by the hardware is not exactly CONFIG_USEC_PER_TICK and if there you require accurate up-time for the MCU, then there are measures that you can take:

  • Perhaps you can adjust CONFIG_USEC_PER_TICK to a different value so that an exactly CONFIG_USEC_PER_TICK can be realized.

  • Or you can use a technique known as Delta-Sigma Modulation. (Suggested by Uros Platise). Consider the example below.

Delta-Sigma Modulation Example. Consider this case: The system timer is a count-up timer driven at 32.768KHz. There are dividers that can be used, but a divider of one yields the highest accuracy. This counter counts up until the count equals a match value, then a timer interrupt is generated. The desire frequency is 100Hz (CONFIG_USEC_PER_TICK is 10000).

This exact frequency of 100Hz cannot be obtained in this case. In order to obtain that exact frequency a match value of 327.68 would have to be provided. The closest integer value is 328 but the ideal match value is between 327 and 328. The closest value, 328, would yield an actual timer frequency of 99.9Hz! That will may cause significant timing errors in certain usages.

Use of Delta-Sigma Modulation can eliminate this error in the long run. Consider this example implementation:

  1. Initially an accumulator is zero an the match value is programmed to 328:

    accumulator = 0;
    match = 328;
    
  2. On each timer interrupt, accumulator is updated with difference that, in this reflects, 100* the error in interval that just passed. So on the first timer interrupt, the accumulator would be updated like:

    if (match == 328)
      {
        accumulator += 32; // 100*(328 - 327.68)
      }
    else
      {
        accumulator -= 68; // (100*(327 - 327.68)
      }
    
  3. And on that same timer interrupt a new match value would be programmed:

    if (accumulator < 0)
      {
        match = 328;
      }
    else
      {
        match = 327;
      }
    

In this way, the timer interval is controlled from interrupt-to-interrupt to produce an average frequency of exactly 100Hz.

Hardware

To enable hardware module use the following configuration options:

CONFIG_RTC

Enables general support for a hardware RTC. Specific architectures may require other specific settings.

CONFIG_RTC_EXTERNAL

Most MCUs include RTC hardware built into the chip. Other RTCs, external MCUs, may be provided as separate chips typically interfacing with the MCU via a serial interface such as SPI or I2C. These external RTCs differ from the built-in RTCs in that they cannot be initialized until the operating system is fully booted and can support the required serial communications. CONFIG_RTC_EXTERNAL will configure the operating system so that it defers initialization of its time facilities.

CONFIG_RTC_DATETIME

There are two general types of RTC: (1) A simple battery backed counter that keeps the time when power is down, and (2) A full date / time RTC the provides the date and time information, often in BCD format. If CONFIG_RTC_DATETIME is selected, it specifies this second kind of RTC. In this case, the RTC is used to “seed”” the normal NuttX timer and the NuttX system timer provides for higher resolution time.

CONFIG_RTC_HIRES

If CONFIG_RTC_DATETIME not selected, then the simple, battery backed counter is used. There are two different implementations of such simple counters based on the time resolution of the counter: The typical RTC keeps time to resolution of 1 second, usually supporting a 32-bit time_t value. In this case, the RTC is used to “seed” the normal NuttX timer and the NuttX timer provides for higher resolution time. If CONFIG_RTC_HIRES is enabled in the NuttX configuration, then the RTC provides higher resolution time and completely replaces the system timer for purpose of date and time.

CONFIG_RTC_FREQUENCY

If CONFIG_RTC_HIRES is defined, then the frequency of the high resolution RTC must be provided. If CONFIG_RTC_HIRES is not defined, CONFIG_RTC_FREQUENCY is assumed to be one.

CONFIG_RTC_ALARM

Enable if the RTC hardware supports setting of an alarm. A callback function will be executed when the alarm goes off

which requires the following base functions to read and set time:

  • up_rtc_initialize(). Initialize the built-in, MCU hardware RTC per the selected configuration. This function is called once very early in the OS initialization sequence. NOTE that initialization of external RTC hardware that depends on the availability of OS resources (such as SPI or I2C) must be deferred until the system has fully booted. Other, RTC-specific initialization functions are used in that case.

  • up_rtc_time(). Get the current time in seconds. This is similar to the standard time() function. This interface is only required if the low-resolution RTC/counter hardware implementation selected. It is only used by the RTOS during initialization to set up the system time when CONFIG_RTC is set but neither CONFIG_RTC_HIRES nor CONFIG_RTC_DATETIME are set.

  • up_rtc_gettime(). Get the current time from the high resolution RTC clock/counter. This interface is only supported by the high-resolution RTC/counter hardware implementation. It is used to replace the system timer (g_system_ticks).

  • up_rtc_settime(). Set the RTC to the provided time. All RTC implementations must be able to set their time based on a standard timespec.

System Tick and Time

The system tick is represented by g_system_ticks.

Running at rate of system base timer, used for time-slicing, and so forth.

If hardware RTC is present (CONFIG_RTC) and and high-resolution timing is enabled (CONFIG_RTC_HIRES), then after successful initialization variables are overridden by calls to up_rtc_gettime() which is running continuously even in power-down modes.

In the case of CONFIG_RTC_HIRES is set the g_system_ticks keeps counting at rate of a system timer, which however, is disabled in power-down mode. By comparing this time and RTC (actual time) one may determine the actual system active time. To retrieve that variable use:

Tickless OS

Default System Timer. By default, a NuttX configuration uses a periodic timer interrupt that drives all system timing. The timer is provided by architecture-specific code that calls into NuttX at a rate controlled by CONFIG_USEC_PER_TICK. The default value of CONFIG_USEC_PER_TICK is 10000 microseconds which corresponds to a timer interrupt rate of 100 Hz.

On each timer interrupt, NuttX does these things:

  • Increments a counter. This counter is the system time and has a resolution of CONFIG_USEC_PER_TICK microseconds.

  • Checks if it is time to perform time-slice operations on tasks that have select round-robin scheduling.

  • Checks for expiration of timed events.

What is wrong with this default system timer? Nothing really. It is reliable and uses only a small fraction of the CPU band width. But we can do better. Some limitations of default system timer are, in increasing order of importance:

  • Overhead: Although the CPU usage of the system timer interrupt at 100Hz is really very low, it is still mostly wasted processing time. On most timer interrupts, there is really nothing that needs to be done other than incrementing the counter.

  • Resolution: Resolution of all system timing is also determined by CONFIG_USEC_PER_TICK. So nothing can be timed with resolution finer than 10 milliseconds by default. To increase this resolution, CONFIG_USEC_PER_TICK can be reduced. However, then the system timer interrupts use more of the CPU bandwidth processing useless interrupts.

  • Power Usage: But the biggest issue is power usage. When the system is IDLE, it enters a light, low-power mode (for ARMs, this mode is entered with the wfi or wfe instructions for example). But each interrupt awakens the system from this low power mode. Therefore, higher rates of interrupts cause greater power consumption.

Tickless OS. The so-called Tickless OS provides one solution to this issue. The basic concept here is that the periodic, timer interrupt is eliminated and replaced with a one-shot, interval timer. It becomes event driven instead of polled: The default system timer is a polled design. On each interrupt, the NuttX logic checks if it needs to do anything and, if so, it does it.

Using an interval timer, one can anticipate when the next interesting OS event will occur, program the interval time and wait for it to fire. When the interval time fires, then the scheduled activity is performed.

Tickless Platform Support

In order to use the Tickless OS, one must provide special support from the platform-specific code. Just as with the default system timer, the platform-specific code must provide the timer resources to support the OS behavior. Currently these timer resources are only provided on a few platforms. An example implementation is for the simulation is at nuttx/arch/sim/src/up_tickless.c. There is another example for the Atmel SAMA5 at nuttx/arch/arm/src/sama5/sam_tickless.c. These paragraphs will explain how to provide the Tickless OS support to any platform.

Tickless Configuration Options

  • CONFIG_ARCH_HAVE_TICKLESS: If the platform provides support for the Tickless OS, then this setting should be selected in the Kconfig file for the architecture. Here is what the selection looks in the arch/Kconfig file for the simulated platform:

    config ARCH_SIM
       bool "Simulation"
       select ARCH_HAVE_TICKLESS
       ---help---
               Linux/Cygwin user-mode simulation.
    

    When the simulation platform is selected, ARCH_HAVE_TICKLESS is automatically selected, informing the configuration system that Tickless OS options can be selected.

  • CONFIG_SCHED_TICKLESS: If CONFIG_ARCH_HAVE_TICKLESS is selected, then you will be able to use this option to enable the Tickless OS features in NuttX.

  • CONFIG_SCHED_TICKLESS_ALARM: The tickless option can be supported either via a simple interval timer (plus elapsed time) or via an alarm. The interval timer allows programming events to occur after an interval. With the alarm, you can set a time in the future and get an event when that alarm goes off. This option selects the use of an alarm.

    The advantage of an alarm is that it avoids some small timing errors; the advantage of the use of the interval timer is that the hardware requirement may be simpler.

  • CONFIG_USEC_PER_TICK: This option is not unique to Tickless OS operation, but changes its relevance when the Tickless OS is selected. In the default configuration, where system time is provided by a periodic timer interrupt, the default system timer is configured for 100Hz, that is, CONFIG_USEC_PER_TICK=10000. If CONFIG_SCHED_TICKLESS is selected, then there are no system timer interrupts. In this case, CONFIG_USEC_PER_TICK does not control any timer rates. Rather, it only determines the resolution of time reported by clock_systime_ticks() and the resolution of times that can be set for certain delays including watchdog timers and delayed work.

    In this case there is still a trade-off: It is better to have the CONFIG_USEC_PER_TICK as low as possible for higher timing resolution. However, the time is currently held in unsigned int. On some systems, this may be 16-bits in width but on most contemporary systems it will be 32-bits. In either case, smaller values of CONFIG_USEC_PER_TICK will reduce the range of values that delays that can be represented. So the trade-off is between range and resolution (you could also modify the code to use a 64-bit value if you really want both).

    The default, 100 microseconds, will provide for a range of delays up to 120 hours.

    This value should never be less than the underlying resolution of the timer. Errors may ensue.

Tickless Imported Interfaces

The interfaces that must be provided by the platform specified code are defined in include/nuttx/arch.h, listed below, and summarized in the following paragraphs:

  • <arch>_timer_initialize() Initializes the timer facilities. Called early in the initialization sequence (by up_initialize()).

  • up_timer_gettime(): Returns the current time from the platform specific time source.

The tickless option can be supported either via a simple interval timer (plus elapsed time) or via an alarm. The interval timer allows programming events to occur after an interval. With the alarm, you can set a time in* the future and get an event when that alarm goes off.

If CONFIG_SCHED_TICKLESS_ALARM is defined, then the platform code must provide the following:

  • up_alarm_cancel(): Cancels the alarm.

  • up_alarm_start(): Enables (or re-enables) the alarm.

If CONFIG_SCHED_TICKLESS_ALARM is notdefined, then the platform code must provide the following verify similar functions:

  • up_timer_cancel(): Cancels the interval timer.

  • up_timer_start(): Starts (or re-starts) the interval timer.

Note that a platform-specific implementation would probably require two hardware timers: (1) A interval timer to satisfy the requirements of up_timer_start() and up_timer_cancel(), and (2) a counter to handle the requirement of up_timer_gettime(). Ideally, both timers would run at the rate determined by CONFIG_USEC_PER_TICK (and certainly never slower than that rate).

Since timers are a limited resource, the use of two timers could be an issue on some systems. The job could be done with a single timer if, for example, the single timer were kept in a free-running mode at all times. Some timer/counters have the capability to generate a compare interrupt when the timer matches a comparison value but also to continue counting without stopping. If your hardware supports such counters, one might use the CONFIG_SCHED_TICKLESS_ALARM option and be able to simply set the comparison count at the value of the free running timer PLUS the desired delay. Then you could have both with a single timer: An alarm and a free-running counter with the same timer!

In addition to these imported interfaces, the RTOS will export the following interfaces for use by the platform-specific interval timer implementation:

  • nxsched_alarm_expiration(): called by the platform-specific logic when the alarm expires.

  • nxsched_timer_expiration(): called by the platform-specific logic when the interval time expires.

void archname_timer_initialize(void)

Initializes all platform-specific timer facilities. This function is called early in the initialization sequence by up_initialize(). On return, the current up-time should be available from up_timer_gettime() and the interval timer is ready for use (but not actively timing). The naming will depend on the architecture so for STM32 archname will be stm32.

Returns:

Zero (OK) on success; a negated errno value on failure.

Assumptions: Called early in the initialization sequence before any special concurrency protections are required.

int up_timer_gettime(FAR struct timespec *ts)

Return the elapsed time since power-up (or, more correctly, since <arch>_timer_initialize() was called). This function is functionally equivalent to clock_gettime() for the clock ID CLOCK_MONOTONIC. This function provides the basis for reporting the current time and also is used to eliminate error build-up from small errors in interval time calculations.

Parameters:
  • ts – Provides the location in which to return the up-time..

Returns:

Zero (OK) on success; a negated errno value on failure.

Assumptions: Called from the normal tasking context. The implementation must provide whatever mutual exclusion is necessary for correct operation. This can include disabling interrupts in order to assure atomic register operations.

int up_alarm_cancel(FAR struct timespec *ts)

Cancel the alarm and return the time of cancellation of the alarm. These two steps need to be as nearly atomic as possible. nxsched_timer_expiration() will not be called unless the alarm is restarted with up_alarm_start(). If, as a race condition, the alarm has already expired when this function is called, then time returned is the current time.

Parameters:
  • ts – Location to return the expiration time. The current time should be returned if the timer is not active. ts may be NULL in which case the time is not returned

Returns:

Zero (OK) on success; a negated errno value on failure.

Assumptions: May be called from interrupt level handling or from the normal tasking level. interrupts may need to be disabled internally to assure non-reentrancy.

int up_alarm_start(FAR const struct timespec *ts)

Start the alarm. nxsched_timer_expiration() will be called when the alarm occurs (unless up_alarm_cancel is called to stop it).

Parameters:
  • ts – The time in the future at the alarm is expected to occur. When the alarm occurs the timer logic will call nxsched_timer_expiration().

Returns:

Zero (OK) on success; a negated errno value on failure.

Assumptions: May be called from interrupt level handling or from the normal tasking level. Interrupts may need to be disabled internally to assure non-reentrancy.

int up_timer_cancel(FAR struct timespec *ts)

Cancel the interval timer and return the time remaining on the timer. These two steps need to be as nearly atomic as possible. nxsched_timer_expiration() will not be called unless the timer is restarted with up_timer_start(). If, as a race condition, the timer has already expired when this function is called, then that pending interrupt must be cleared so that nxsched_timer_expiration() is not called spuriously and the remaining time of zero should be returned.

param ts:

Location to return the remaining time. Zero should be returned if the timer is not active.

return:

Zero (OK) on success; a negated errno value on failure.

Assumptions: May be called from interrupt level handling or from the normal tasking level. interrupts may need to be disabled internally to assure non-reentrancy.

int up_timer_start(FAR const struct timespec *ts)

Start the interval timer. nxsched_timer_expiration() will be called at the completion of the timeout (unless up_timer_cancel() is called to stop the timing).

param ts:

Provides the time interval until nxsched_timer_expiration() is called.

return:

Zero (OK) on success; a negated errno value on failure.

Assumptions: May be called from interrupt level handling or from the normal tasking level. Interrupts may need to be disabled internally to assure non-reentrancy.

Watchdog Timer Interfaces

NuttX provides a general watchdog timer facility. This facility allows the NuttX user to specify a watchdog timer function that will run after a specified delay. The watchdog timer function will run in the context of the timer interrupt handler. Because of this, a limited number of NuttX interfaces are available to he watchdog timer function. However, the watchdog timer function may use mq_send(), sigqueue(), or kill() to communicate with NuttX tasks.

int wd_start(FAR struct wdog_s *wdog, int delay, wdentry_t wdentry, wdparm_t arg)

This function adds a watchdog to the timer queue. The specified watchdog function will be called from the interrupt level after the specified number of ticks has elapsed. Watchdog timers may be started from the interrupt level.

Watchdog times execute in the context of the timer interrupt handler.

Watchdog timers execute only once.

To replace either the timeout delay or the function to be executed, call wd_start again with the same wdog; only the most recent wd_start() on a given watchdog ID has any effect.

Parameters:
  • wdog – Watchdog ID

  • delay – Delay count in clock ticks

  • wdentry – Function to call on timeout

  • arg – The parameter to pass to wdentry.

NOTE: The parameter must be of type wdparm_t.

Returns:

Zero (OK) is returned on success; a negated errno value is return to indicate the nature of any failure.

Assumptions/Limitations: The watchdog routine runs in the context of the timer interrupt handler and is subject to all ISR restrictions.

POSIX Compatibility: This is a NON-POSIX interface. VxWorks provides the following comparable interface:

STATUS wdStart (WDOG_ID wdog, int delay, FUNCPTR wdentry, int parameter);

Differences from the VxWorks interface include:

  • The present implementation supports multiple parameters passed to wdentry; VxWorks supports only a single parameter. The maximum number of parameters is determined by

int wd_cancel(FAR struct wdog_s *wdog)

This function cancels a currently running watchdog timer. Watchdog timers may be canceled from the interrupt level.

Parameters:
  • wdog – ID of the watchdog to cancel.

Returns:

OK or ERROR

POSIX Compatibility: This is a NON-POSIX interface. VxWorks provides the following comparable interface:

STATUS wdCancel (WDOG_ID wdog);
int wd_gettime(FAR struct wdog_s *wdog)

Returns the time remaining before the specified watchdog expires.

Parameters:
  • wdog – Identifies the watchdog that the request is for.

Returns:

The time in system ticks remaining until the watchdog time expires. Zero means either that wdog is not valid or that the wdog has already expired.

typedef void (*wdentry_t)(wdparm_t arg)

Watchdog Timer Callback: when a watchdog expires, the callback function with this type is called.

The argument is passed as scalar wdparm_t values. For systems where the sizeof(pointer) < sizeof(uint32_t), the following union defines the alignment of the pointer within the uint32_t. For example, the SDCC MCS51 general pointer is 24-bits, but uint32_t is 32-bits (of course).

We always have sizeof(pointer) <= sizeof(uintptr_t) by definition.

union wdparm_u
{
  FAR void     *pvarg; /* The size one generic point */
  uint32_t      dwarg; /* Big enough for a 32-bit value in any case */
  uintptr_t     uiarg; /* sizeof(uintptr_t) >= sizeof(pointer) */
};

#if UINTPTR_MAX >= UINT32_MAX
typedef uintptr_t wdparm_t;
#else
typedef uint32_t  wdparm_t;
#endif