Default System Timer

By default, a NuttX configuration includes 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:

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:

Tickless OS

The so-called Tickless OS provides one solution to 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, you 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.

Platform Support

In order to use the Tickless OS, you 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. You can see that implementation for the simulation in nuttx/arch/sim/src/up_tickless.c. There is another implementation for the Atmel SAMA5 at nuttx/arch/arm/src/sama5/sam_tickless.c. This Wiki will explain how you can provide the Tickless OS support for your platform.

Configuration Options

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

In the default configuration where system time is provided by a periodic timer interrupt, the default system timer is configure the timer for 100Hz or CONFIG_USEC_PER_TICK=10000. If CONFIG_SCHED_TICKLESS is selected, then there are no system timer interrupt. In this case, CONFIG_USEC_PER_TICK does not control any timer rates. Rather, it only determines the resolution of time reported by clock_systimer() 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 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. Error may ensue.

System Interfaces

Implementation Notes

Notice that with the CONFIG_SCHED_TICKLESS option, an implementation might require two hardware timers: (1) An interval timer to satisfy the requirements for up_timer_start() and up_timer_cancel(), and a (2) a counter to handle the requirement of up_timer_gettime().

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 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 used 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!

Imported Intefaces

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

up_timer_intialize()
    void up_timer_initialize(void);
up_timer_gettime()
    int up_timer_gettime(FAR struct timespec *ts);
up_alarm_cancel()
    int up_alarm_cancel(FAR struct timespec *ts);
up_alarm_start()
    int up_alarm_start(FAR const struct timespec *ts);
up_timer_cancel()
    int up_timer_cancel(FAR struct timespec *ts);
up_timer_start()
    int up_timer_start(FAR const struct timespec *ts);

Exported Interfaces

In addition, the following interface is provided by the RTOS for use by the platform specific code:

sched_alarm_expiration()
    void sched_timer_expiration(FAR const timespec *tc);
sched_timer_expiration()
    void sched_timer_expiration(void);

Issues

Let me describe an experience to illustrate some of the problems that you may run into:

I implemented the tickless mode on an MCU with 16-bit timers. Most of the clock sources were too fast for use with the 16-bit timer, so I tried using a 32.768 kHz clock source. This resulted in a time resolution of about 30.518 microseconds.

That time, 30.518 microseconds, can not be represented accurately with CONFIG_USEC_PER_TICK so I used the value 31 which is in error by about 0.6%.

What I found on a busy system is that the delay I get when executing this NSH command was actually about 5.5 seconds:

  nsh> sleep 10

This was very puzzling to me and cost me a lot of debug time. Finally, I disabled all competing usage of the interval timer by disabling features (the high priority work queue, by the way, is the most significant user of the interval timer). After disabling all competing usage, the NSH command sleep 10 resulted in a delay of about 10.3 seconds. Still not accurate but not so bad.

I concluded that the gross inaccuracy in the first case was due to the inaccuracies in the representation of the clock rate. 30.518 usec cannot be represented accurately. Each timing calculation results in a small error. When the interval timer is very busy, long delays will be divided into many small pieces and each small piece has a large error in the calculation. The cumulative error is the cause of the problem.

The moral of this story: Do not use timer sources that cannot be represented by CONFIG_USEC_PER_TICK.