Clocks and Timers

These interfaces contain definitions related to clock and timer handling. They include interfaces to initialize and read a clock for generating regular interrupts, definitions for setting the frequency of the clock, and support for short timed delays.

Clock Control

HAL_CLOCK_INITIALIZE( period )
HAL_CLOCK_RESET( vector, period )
HAL_CLOCK_READ( pvalue )

These macros provide control over a clock or timer device that may be used by the kernel to provide time-out, delay and scheduling services. The clock is assumed to be implemented by some form of counter that is incremented or decremented by some external source and which raises an interrupt when it reaches a predetermined value.

HAL_CLOCK_INITIALIZE() initializes the timer device to interrupt at the given period. The period is essentially the value used to initialize the timer counter and must be calculated from the timer frequency and the desired interrupt rate. The timer device should generate an interrupt every period cycles.

HAL_CLOCK_RESET() re-initializes the timer to provoke the next interrupt. This macro is only really necessary when the timer device needs to be reset in some way after each interrupt.

HAL_CLOCK_READ() reads the current value of the timer counter and puts the value in the location pointed to by pvalue. The value stored will always be the number of timer cycles since the last interrupt, and hence ranges between zero and the initial period value. If this is a count-down cyclic timer, some arithmetic may be necessary to generate this value.

Microsecond Delay

HAL_DELAY_US(us)

This macro provides a busy loop delay for the given number of microseconds. It is intended mainly for controlling hardware that needs short delays between operations. Code which needs longer delays, of the order of milliseconds, should instead use higher-level functions such as cyg_thread_delay. The macro implementation should be thread-safe. It can also be used in ISRs or DSRs, although such usage is undesirable because of the impact on interrupt and dispatch latency.

The macro should never delay for less than the specified amount of time. It may delay for somewhat longer, although since the macro uses a busy loop this is a waste of CPU cycles. Of course the code invoking HAL_DELAY_US may get interrupted or timesliced, in which case the delay may be much longer than intended. If this is unacceptable then the calling code must take preventative action such as disabling interrupts or locking the scheduler.

There are three main ways of implementing the macro:

  1. a counting loop, typically written in inline assembler, using an outer loop for the microseconds and an inner loop that consumes approximately 1us. This implementation is automatically thread-safe and does not impose any dependencies on the rest of the system, for example it does not depend on the system clock having been started. However it assumes that the CPU clock speed is known at compile-time or can be easily determined at run-time.

  2. monitor one of the hardware clocks, usually the system clock. Usually this clock ticks at a rate independent of the CPU so calibration is easier. However the implementation relies on the system clock having been started, and assumes that no other code is manipulating the clock hardware. There can also be complications when the system clock wraps around.

  3. a combination of the previous two. The system clock is used during system initialization to determine the CPU clock speed, and the result is then used to calibrate a counting loop. This has the disadvantage of significantly increasing the system startup time, which may be unacceptable to some applications. There are also complications if the system startup code normally runs with the cache disabled because the instruction cache will greatly affect any calibration loop.

Clock Frequency Definition

CYGNUM_HAL_RTC_NUMERATOR
CYGNUM_HAL_RTC_DENOMINATOR
CYGNUM_HAL_RTC_PERIOD

These macros are defined in the CDL for each platform and supply the necessary parameters to specify the frequency at which the clock interrupts. These parameters are usually found in the CDL definitions for the target platform, or in some cases the CPU variant.

CYGNUM_HAL_RTC_NUMERATOR and CYGNUM_HAL_RTC_DENOMINATOR specify the resolution of the clock interrupt. This resolution involves two separate values, the numerator and the denominator. The result of dividing the numerator by the denominator should correspond to the number of nanoseconds between clock interrupts. For example a numerator of 1000000000 and a denominator of 100 means that there are 10000000 nanoseconds (or 10 milliseconds) between clock interrupts. Expressing the resolution as a fraction minimizes clock drift even for frequencies that cannot be expressed as a simple integer. For example a frequency of 60Hz corresponds to a clock resolution of 16666666.66... nanoseconds. This can be expressed accurately as 1000000000 over 60.

CYGNUM_HAL_RTC_PERIOD specifies the exact value used to initialize the clock hardware, it is the value passed as a parameter to HAL_CLOCK_INITIALIZE() and HAL_CLOCK_RESET(). The exact meaning of the value and the range of legal values therefore depends on the target hardware, and the hardware documentation should be consulted for further details.

The default values for these macros in all HALs are calculated to give a clock interrupt frequency of 100Hz, or 10ms between interrupts. To change the clock frequency, the period needs to be changed, and the resolution needs to be adjusted accordingly. As an example consider the i386 PC target. The default values for these macros are:

CYGNUM_HAL_RTC_NUMERATOR     1000000000
CYGNUM_HAL_RTC_DENOMINATOR   100
CYGNUM_HAL_RTC_PERIOD        11932

To change to, say, a 200Hz clock the period needs to be halved to 5966, and to compensate the denominator needs to be doubled to 200. To change to a 1KHz interrupt rate change the period to 1193 and the denominator to 1000.

Some HALs make this process a little easier by deriving the period arithmetically from the denominator. This calculation may also involve the CPU clock frequency and possibly other factors. For example in the ARM AT91 variant HAL the period is defined by the following expression:

((CYGNUM_HAL_ARM_AT91_CLOCK_SPEED/32) / CYGNUM_HAL_RTC_DENOMINATOR)

In this case it is not necessary to change the period at all, just change the denominator to select the desired clock frequency. However, note that for certain choices of frequency, rounding errors in this calculation may result in a small clock drift over time. This is usually negligible, but if perfect accuracy is required, it may be necessary to adjust the frequency or period by hand.

2017-02-09
Documentation license for this page: Open Publication License