Kernel Clocks

The kernel’s clocks are the foundation for all of its time-based services.

Concepts

The kernel supports two distinct clocks.

  • The 32-bit hardware clock is a high precision counter that tracks time in unspecified units called cycles. The duration of a cycle is determined by the board hardware used by the kernel, and is typically measured in nanoseconds.
  • The 64-bit system clock is a counter that tracks the number of ticks that have elapsed since the kernel was initialized. The duration of a tick is configurable, and typically ranges from 1 millisecond to 100 milliseconds.

The kernel also provides a number of variables that can be used to convert the time units used by the clocks into standard time units (e.g. seconds, milliseconds, nanoseconds, etc), and to convert between the two types of clock time units.

The system clock is used by most of the kernel’s time-based services, including kernel timer objects and the timeouts supported by other kernel object types. For convenience, the kernel’s APIs allow time durations to be specified in milliseconds, and automatically converts them to the corresponding number of ticks.

The hardware clock can be used to measure time with higher precision than that provided by kernel services based on the system clock.

Clock Limitations

The system clock’s tick count is derived from the hardware clock’s cycle count. The kernel determines how many clock cycles correspond to the desired tick frequency, then programs the hardware clock to generate an interrupt after that many cycles; each interrupt corresponds to a single tick.

Note

Configuring a smaller tick duration permits finer-grained timing, but also increases the amount of work the kernel has to do to process tick interrupts since they occur more frequently. Setting the tick duration to zero disables both kernel clocks, as well as their associated services.

Any millisecond-based time interval specified using a kernel API represents the minimum delay that will occur, and may actually take longer than the amount of time requested.

For example, specifying a timeout delay of 100 ms when attempting to take a semaphore means that the kernel will never terminate the operation and report failure before at least 100 ms have elapsed. However, it is possible that the operation may take longer than 100 ms to complete, and may either complete successfully during the additional time or fail at the end of the added time.

The amount of added time that occurs during a kernel object operation depends on the following factors.

  • The added time introduced by rounding up the specified time interval when converting from milliseconds to ticks. For example, if a tick duration of 10 ms is being used, a specified delay of 25 ms will be rounded up to 30 ms.
  • The added time introduced by having to wait for the next tick interrupt before a delay can be properly tracked. For example, if a tick duration of 10 ms is being used, a specified delay of 20 ms requires the kernel to wait for 3 ticks to occur (rather than only 2), since the first tick can occur at any time from the next fraction of a millisecond to just slightly less than 10 ms; only after the first tick has occurred does the kernel know the next 2 ticks will take 20 ms.

Implementation

Measuring Time with Normal Precision

This code uses the system clock to determine how much time has elapsed between two points in time.

s64_t time_stamp;
s64_t milliseconds_spent;

/* capture initial time stamp */
time_stamp = k_uptime_get();

/* do work for some (extended) period of time */
...

/* compute how long the work took (also updates the time stamp) */
milliseconds_spent = k_uptime_delta(&time_stamp);

Measuring Time with High Precision

This code uses the hardware clock to determine how much time has elapsed between two points in time.

u32_t start_time;
u32_t stop_time;
u32_t cycles_spent;
u32_t nanoseconds_spent;

/* capture initial time stamp */
start_time = k_cycle_get_32();

/* do work for some (short) period of time */
...

/* capture final time stamp */
stop_time = k_cycle_get_32();

/* compute how long the work took (assumes no counter rollover) */
cycles_spent = stop_time - start_time;
nanoseconds_spent = SYS_CLOCK_HW_CYCLES_TO_NS(cycles_spent);

Suggested Uses

Use services based on the system clock for time-based processing that does not require high precision, such as timer objects or Thread Sleeping.

Use services based on the hardware clock for time-based processing that requires higher precision than the system clock can provide, such as Busy Waiting or fine-grained time measurements.

Note

The high frequency of the hardware clock, combined with its 32-bit size, means that counter rollover must be taken into account when taking high-precision measurements over an extended period of time.

Configuration

Related configuration options:

API Reference

group clock_apis

Defines

K_NO_WAIT

Generate null timeout delay.

This macro generates a timeout delay that that instructs a kernel API not to wait if the requested operation cannot be performed immediately.

Return
Timeout delay value.

K_MSEC(ms)

Generate timeout delay from milliseconds.

This macro generates a timeout delay that that instructs a kernel API to wait up to ms milliseconds to perform the requested operation.

Return
Timeout delay value.
Parameters
  • ms: Duration in milliseconds.

K_SECONDS(s)

Generate timeout delay from seconds.

This macro generates a timeout delay that that instructs a kernel API to wait up to s seconds to perform the requested operation.

Return
Timeout delay value.
Parameters
  • s: Duration in seconds.

K_MINUTES(m)

Generate timeout delay from minutes.

This macro generates a timeout delay that that instructs a kernel API to wait up to m minutes to perform the requested operation.

Return
Timeout delay value.
Parameters
  • m: Duration in minutes.

K_HOURS(h)

Generate timeout delay from hours.

This macro generates a timeout delay that that instructs a kernel API to wait up to h hours to perform the requested operation.

Return
Timeout delay value.
Parameters
  • h: Duration in hours.

K_FOREVER

Generate infinite timeout delay.

This macro generates a timeout delay that that instructs a kernel API to wait as long as necessary to perform the requested operation.

Return
Timeout delay value.

k_cycle_get_32

Read the hardware clock.

This routine returns the current time, as measured by the system’s hardware clock.

Return
Current hardware clock up-counter (in cycles).

SYS_CLOCK_HW_CYCLES_TO_NS(X)

Compute nanoseconds from hardware clock cycles.

This macro converts a time duration expressed in hardware clock cycles to the equivalent duration expressed in nanoseconds.

Return
Duration in nanoseconds.
Parameters
  • X: Duration in hardware clock cycles.

Functions

s64_t k_uptime_get(void)

Get system uptime.

This routine returns the elapsed time since the system booted, in milliseconds.

Note
While this function returns time in milliseconds, it does not mean it has millisecond resolution. The actual resolution depends on :option:CONFIG_SYS_CLOCK_TICKS_PER_SEC config option, and with the default setting of 100, system time is updated in increments of 10ms.
Return
Current uptime in milliseconds.

int k_enable_sys_clock_always_on(void)

Enable clock always on in tickless kernel.

This routine enables keeping the clock running (that is, it always keeps an active timer interrupt scheduled) when there are no timer events programmed in tickless kernel scheduling. This is necessary if the clock is used to track passage of time (e.g. via k_uptime_get_32()), otherwise the internal hardware counter may roll over between interrupts.

Return Value
  • prev_status: Previous status of always on flag

void k_disable_sys_clock_always_on(void)

Disable clock always on in tickless kernel.

This routine disables keeping the clock running when there are no timer events programmed in tickless kernel scheduling. To save power, this routine should be called immediately when clock is not used to track time.

u32_t k_uptime_get_32(void)

Get system uptime (32-bit version).

This routine returns the lower 32-bits of the elapsed time since the system booted, in milliseconds.

This routine can be more efficient than k_uptime_get(), as it reduces the need for interrupt locking and 64-bit math. However, the 32-bit result cannot hold a system uptime time larger than approximately 50 days, so the caller must handle possible rollovers.

Note
While this function returns time in milliseconds, it does not mean it has millisecond resolution. The actual resolution depends on :option:CONFIG_SYS_CLOCK_TICKS_PER_SEC config option, and with the default setting of 100, system time is updated in increments of 10ms.
Return
Current uptime in milliseconds.

static s64_t k_uptime_delta(s64_t *reftime)

Get elapsed time.

This routine computes the elapsed time between the current system uptime and an earlier reference time, in milliseconds.

Return
Elapsed time.
Parameters
  • reftime: Pointer to a reference time, which is updated to the current uptime upon return.

static u32_t k_uptime_delta_32(s64_t *reftime)

Get elapsed time (32-bit version).

This routine computes the elapsed time between the current system uptime and an earlier reference time, in milliseconds.

This routine can be more efficient than k_uptime_delta(), as it reduces the need for interrupt locking and 64-bit math. However, the 32-bit result cannot hold an elapsed time larger than approximately 50 days, so the caller must handle possible rollovers.

Return
Elapsed time.
Parameters
  • reftime: Pointer to a reference time, which is updated to the current uptime upon return.