ztimer high level timer abstraction layer

High level timer abstraction layer. More...

Detailed Description

High level timer abstraction layer.

Introduction

ztimer provides a high level abstraction of hardware timers for application timing needs.

The basic functions of the ztimer module are ztimer_now(), ztimer_sleep(), ztimer_set() and ztimer_remove().

They all take a pointer to a clock device (or virtual timer device) as first parameter.

RIOT provides ZTIMER_USEC, ZTIMER_MSEC, ZTIMER_SEC by default, which can be used in an application by depending on the modules ztimer_usec, ztimer_msec or ztimer_sec. They will then automatically get configured.

Every ztimer clock allows multiple timeouts to be scheduled. They all provide unsigned 32bit range. In this documentation, a timeout or its corresponding struct will be called timer, and when the time out has passed, it has triggered.

As ztimer can use arbitrarily configurable backends, a ztimer clock instance can run at configurable frequencies. Throughout this documentation, one clock step is called tick. For the pre-defined clocks ZTIMER_USEC, ZTIMER_MSEC and ZTIMER_SEC, one clock tick corresponds to one microsecond, one millisecond or one second, respectively.

ztimer_now() returns the current clock tick count as uint32_t.

ztimer_sleep() pauses the current thread for the passed amount of clock ticks. E.g., ztimer_sleep(ZTIMER_SEC, 5); will suspend the currently running thread for five seconds.

ztimer_set() takes a ztimer_t object (containing a function pointer and void * argument) and an interval as arguments. After at least the interval (in number of ticks for the corresponding clock) has passed, the callback will be called in interrupt context. A timer can be cancelled using ztimer_remove().

Example:

#include "ztimer.h"
static void callback(void *arg)
{
puts(arg);
}
int main()
{
ztimer_t timeout = { .callback=callback, .arg="Hello ztimer!" };
ztimer_set(ZTIMER_SEC, &timeout, 2);
ztimer_sleep(ZTIMER_SEC, 5);
}

Design

clocks, virtual timers, chaining

The system is composed of clocks (virtual ztimer devices) which can be chained to create an abstract view of a hardware timer/counter device. Each ztimer clock acts as a operation on the next clock in the chain. At the end of each ztimer chain there is always some kind of counter device object.

Each clock device handles multiplexing (allowing multiple timers to be set) and extension to full 32bit.

Hardware interface submodules:

Filter submodules:

A common chain could be:

  1. ztimer_periph_timer (e.g., on top of an 1024Hz 16bit hardware timer)
  2. ztimer_convert_frac (to convert 1024 to 1000Hz)

This is how e.g., the clock ZTIMER_MSEC might be configured on a specific system.

Every clock in the chain can always be used on its own. E.g. in the example above, the ztimer_periph object can be used as ztimer clock with 1024Hz ticks in addition to the ztimer_convert_frac with 1000Hz.

Timer handling

Timers in ztimer are stored in a clock using a linked list for which each entry stores the difference to the previous entry in the timer (T[n]). The clock also stores the absolute time on which the relative offsets are based (B), effectively storing the absolute target time for each entry (as B + sum(T[0-n])). Storing the entries in this way allows all entries to use the full width of the used uint32_t, compared to storing the absolute time.

In order to prevent timer processing offset to add up, whenever a timer triggers, the list's absolute base time is set to the expected trigger time (B + T[0]). The underlying clock is then set to alarm at (now() + (now() - B) + T[1]). Thus even though the list is keeping relative offsets, the time keeping is done by keeping track of the absolute times.

Currently, a sorted singly linked list is used for storing the timers. This choice has some implications:

By making the list doubly-linked, removal of timer objects could be easily made a constant operation, at the price of another pointer per timer object (for "previous" element).

If deemed necessary, the linked list can be exchanged our augmented with another data structure providing better algorithmic guarantees. It remains to be shown whether the increased complexity would lead to better performance for any reasonable amount of active timers.

Clock extension

The API always allows setting full 32bit relative offsets for every clock.

In some cases (e.g., a hardware timer only allowing getting/setting smaller values or a conversion which would overflow uint32_t for large intervals), ztimer takes care of extending timers. This is enabled automatically for every ztimer clock that has a "max_value" setting smaller than 2**32-1. If a ztimer_set() would overflow that value, intermediate intervals of length (max_value / 2) are set until the remaining interval fits into max_value. If extension is enabled for a clock, ztimer_now() uses interval checkpointing, storing the current time and corresponding clock tick value on each call and using that information to calculate the current time. This ensures correct ztimer_now() values if ztimer_now() is called at least once every "max_value" ticks. This is ensured by scheduling intermediate callbacks every (max_value / 2) ticks (even if no timeout is configured).

Reliability

Care has been taken to avoid any unexpected behaviour of ztimer. In particular, ztimer tries hard to avoid underflows (setting a backend timer to a value at or behind the current time, causing the timer interrupt to trigger one whole timer period too late). This is done by always setting relative timeouts to backend timers, with interrupts disabled and ensuring that very small values don't cause underflows.

Configuration and convention

As timer hardware and capabilities is diverse and ztimer allows configuring and using arbitrary clock backends and conversions, it is envisioned to provide default configurations that application developers can assume to be available.

These are implemented by using pointers to ztimer clocks using default names.

For now, there are:

ZTIMER_USEC: clock providing microsecond ticks

ZTIMER_MSEC: clock providing millisecond ticks, using a low power timer if available on the platform

ZTIMER_SEC: clock providing second time, possibly using epoch semantics

These pointers are defined in ztimer.h and can be used like this:

ztimer_now(ZTIMER_USEC);

They also need to be added to USEMODULE using the names ztimer_usec, ztimer_msec and ztimer_sec.

Some notes on ztimer's accuracy

  1. ztimer should wait "at least" the specified timeout
  2. due to its implementation details, expect +-1 clock tick systemic inaccuracy for all clocks.
  3. for the predefined clocks (ZTIMER_USEC, ZTIMER_MSEC, ZTIMER_SEC), tick conversion might be applied using ztimer_convert_*, causing errors due to integer conversion and rounding. In particular, most RTT's closest match for milliseconds are 1024Hz, which will be converted using convert_frac to provide the 1ms clock.
  4. Some platforms don't have any timer that can be configured to 1us. E.g., the fe310 (hifive1/b) only supports a 32kHz timer, and most atmegas only support 250kHz. In order to not completely break all applications using ZTIMER_USEC, that clock will only provide ~30.5ms respectively 4us maximum accuracy on those boards. With DEVELHELP=1, a warning will be printed at boot time.
  5. Due to +-1 systemic inaccuracies, it is advisable to use ZTIMER_MSEC for second timers up to 49 days (instead of ZTIMER_SEC).

Modules

 ztimer frequency conversion modules
 ztimer frequency conversion modules
 
 ztimer mock clock backend
 ztimer mock clock backend
 
 ztimer overhead utility
 ztimer overhead measurement functionality
 
 ztimer periph/rtc backend
 ztimer periph/rtc backend
 
 ztimer periph/rtt backend
 ztimer periph/rtt backend
 
 ztimer periph/timer backend
 ztimer periph/timer backend
 

Files

file  config.h
 ztimer default configuration
 
file  periodic.h
 Periodic ztimer API.
 
file  ztimer.h
 ztimer API
 

Data Structures

struct  ztimer_base
 Minimum information for each timer. More...
 
struct  ztimer_t
 ztimer structure More...
 
struct  ztimer_ops_t
 ztimer backend method structure More...
 
struct  ztimer_clock
 ztimer device structure More...
 

Macros

#define ZTIMER_CLOCK_NO_REQUIRED_PM_MODE   (UINT8_MAX)
 Disables interaction with pm_layered for a clock.
 
#define MSG_ZTIMER   0xc83e
 msg type used by ztimer_msg_receive_timeout
 

Typedefs

typedef struct ztimer_base ztimer_base_t
 ztimer_base_t forward declaration
 
typedef struct ztimer_clock ztimer_clock_t
 ztimer_clock_t forward declaration
 
typedef uint32_t ztimer_now_t
 type for ztimer_now() result
 

Functions

void ztimer_handler (ztimer_clock_t *clock)
 main ztimer callback handler
 
void ztimer_set (ztimer_clock_t *clock, ztimer_t *timer, uint32_t val)
 Set a timer on a clock. More...
 
void ztimer_remove (ztimer_clock_t *clock, ztimer_t *timer)
 Remove a timer from a clock. More...
 
void ztimer_set_msg (ztimer_clock_t *clock, ztimer_t *timer, uint32_t offset, msg_t *msg, kernel_pid_t target_pid)
 Post a message after a delay. More...
 
int ztimer_msg_receive_timeout (ztimer_clock_t *clock, msg_t *msg, uint32_t timeout)
 receive a message (blocking, with timeout) More...
 
ztimer_now_t _ztimer_now_extend (ztimer_clock_t *clock)
 ztimer_now() for extending timers
 
static ztimer_now_t ztimer_now (ztimer_clock_t *clock)
 Get the current time from a clock. More...
 
void ztimer_periodic_wakeup (ztimer_clock_t *clock, uint32_t *last_wakeup, uint32_t period)
 Suspend the calling thread until the time (last_wakeup + period) More...
 
void ztimer_sleep (ztimer_clock_t *clock, uint32_t duration)
 Put the calling thread to sleep for the specified number of ticks. More...
 
static void ztimer_spin (ztimer_clock_t *clock, uint32_t duration)
 Busy-wait specified duration. More...
 
void ztimer_set_wakeup (ztimer_clock_t *clock, ztimer_t *timer, uint32_t offset, kernel_pid_t pid)
 Set a timer that wakes up a thread. More...
 
void ztimer_set_timeout_flag (ztimer_clock_t *clock, ztimer_t *timer, uint32_t timeout)
 Set timeout thread flag after timeout. More...
 
void ztimer_update_head_offset (ztimer_clock_t *clock)
 Update ztimer clock head list offset.
 
void ztimer_init (void)
 Initialize the board-specific default ztimer configuration.
 
static void ztimer_init_extend (ztimer_clock_t *clock)
 Initialize possible ztimer extension intermediate timer. More...
 

Variables

ztimer_clock_t *const ZTIMER_USEC
 Default ztimer microsecond clock.
 
ztimer_clock_t *const ZTIMER_MSEC
 Default ztimer millisecond clock.
 
ztimer_clock_t *const ZTIMER_USEC_BASE
 Base ztimer for the microsecond clock (ZTIMER_USEC) More...
 
ztimer_clock_t *const ZTIMER_MSEC_BASE
 Base ztimer for the millisecond clock (ZTIMER_MSEC) More...
 

Function Documentation

◆ ztimer_init_extend()

static void ztimer_init_extend ( ztimer_clock_t clock)
inlinestatic

Initialize possible ztimer extension intermediate timer.

This will basically just set a timer to (clock->max_value >> 1), if max_value is not UINT32_MAX.

This is called automatically by all ztimer backends and extension modules.

Definition at line 535 of file ztimer.h.

◆ ztimer_msg_receive_timeout()

int ztimer_msg_receive_timeout ( ztimer_clock_t clock,
msg_t msg,
uint32_t  timeout 
)

receive a message (blocking, with timeout)

Similar to msg_receive(), but with a timeout parameter. The function will return after waiting at most timeout ticks.

Note
: This might function might leave a message with type MSG_ZTIMER in the thread's message queue, which must be handled (ignored).
Parameters
[in]clockztimer clock to operate on
[out]msgpointer to buffer which will be filled if a message is received
[in]timeoutrelative timeout, in clock time units
Returns
>=0 if a message was received
-ETIME on timeout

◆ ztimer_now()

static ztimer_now_t ztimer_now ( ztimer_clock_t clock)
inlinestatic

Get the current time from a clock.

Parameters
[in]clockztimer clock to operate on
Returns
Current count on clock

Definition at line 422 of file ztimer.h.

◆ ztimer_periodic_wakeup()

void ztimer_periodic_wakeup ( ztimer_clock_t clock,
uint32_t *  last_wakeup,
uint32_t  period 
)

Suspend the calling thread until the time (last_wakeup + period)

This function can be used to create periodic wakeups.

When the function returns, last_wakeup is set to (last_wakeup + period).

last_wakeup should be set to ztimer_now(clock) before first call of the function.

If the time (last_wakeup + period) has already passed, the function sets last_wakeup to last_wakeup + period and returns immediately.

Parameters
[in]clockztimer clock to operate on
[in]last_wakeupbase time stamp for the wakeup
[in]periodtime in ticks that will be added to last_wakeup

◆ ztimer_remove()

void ztimer_remove ( ztimer_clock_t clock,
ztimer_t timer 
)

Remove a timer from a clock.

This will place timer in the timer targets queue for clock.

This function does nothing if timer is not found in the timer queue of clock.

Parameters
[in]clockztimer clock to operate on
[in]timertimer entry to remove

◆ ztimer_set()

void ztimer_set ( ztimer_clock_t clock,
ztimer_t timer,
uint32_t  val 
)

Set a timer on a clock.

This will place timer in the timer targets queue of clock.

Note
The memory pointed to by timer is not copied and must remain in scope until the callback is fired or the timer is removed via ztimer_remove
Parameters
[in]clockztimer clock to operate on
[in]timertimer entry to set
[in]valtimer target (relative ticks from now)

◆ ztimer_set_msg()

void ztimer_set_msg ( ztimer_clock_t clock,
ztimer_t timer,
uint32_t  offset,
msg_t msg,
kernel_pid_t  target_pid 
)

Post a message after a delay.

This function sets a timer that will send a message offset ticks from now.

Note
The memory pointed to by timer and msg will not be copied, i.e. *timer and *msg needs to remain valid until the timer has triggered.
Parameters
[in]clockztimer clock to operate on
[in]timerztimer timer struct to use
[in]offsetticks from now
[in]msgpointer to msg that will be sent
[in]target_pidpid the message will be sent to

◆ ztimer_set_timeout_flag()

void ztimer_set_timeout_flag ( ztimer_clock_t clock,
ztimer_t timer,
uint32_t  timeout 
)

Set timeout thread flag after timeout.

This function will set THREAD_FLAG_TIMEOUT on the current thread after timeout usec have passed.

Parameters
[in]clockztimer clock to operate on
[in]timertimer struct to use
[in]timeouttimeout in ztimer_clock's ticks

◆ ztimer_set_wakeup()

void ztimer_set_wakeup ( ztimer_clock_t clock,
ztimer_t timer,
uint32_t  offset,
kernel_pid_t  pid 
)

Set a timer that wakes up a thread.

This function sets a timer that will wake up a thread when the timer has expired.

Parameters
[in]clockztimer clock to operate on
[in]timertimer struct to work with.
[in]offsetclock ticks from now
[in]pidpid of the thread that will be woken up

◆ ztimer_sleep()

void ztimer_sleep ( ztimer_clock_t clock,
uint32_t  duration 
)

Put the calling thread to sleep for the specified number of ticks.

Parameters
[in]clockztimer clock to use
[in]durationduration of sleep, in ztimer time units

◆ ztimer_spin()

static void ztimer_spin ( ztimer_clock_t clock,
uint32_t  duration 
)
inlinestatic

Busy-wait specified duration.

Note
: This blocks lower priority threads. Use only for very short delays.
Parameters
[in]clockztimer clock to use
[in]durationduration to spin, in clock time units

Definition at line 475 of file ztimer.h.

Variable Documentation

◆ ZTIMER_MSEC_BASE

ztimer_clock_t* const ZTIMER_MSEC_BASE

Base ztimer for the millisecond clock (ZTIMER_MSEC)

This ztimer will reference the counter device object at the end of the chain of ztimer_clock_t for ZTIMER_MSEC.

If ztimer_periph_rtt is not used then ZTIMER_MSEC_BASE will reference the same base as ZTIMER_USEC_BASE.

If the base counter device object's frequency (CONFIG_ZTIMER_MSEC_BASE_FREQ) is not 1KHz then ZTIMER_MSEC will be converted on top of this one. Otherwise they will reference the same ztimer_clock.

To avoid chained conversions its better to base new ztimer_clock on top of ZTIMER_MSEC_BASE running at CONFIG_ZTIMER_MSEC_BASE_FREQ.

◆ ZTIMER_USEC_BASE

ztimer_clock_t* const ZTIMER_USEC_BASE

Base ztimer for the microsecond clock (ZTIMER_USEC)

This ztimer will reference the counter device object at the end of the chain of ztimer_clock_t for ZTIMER_USEC.

If the base counter device object's frequency (CONFIG_ZTIMER_USEC_BASE_FREQ) is not 1MHz then ZTIMER_USEC will be converted on top of this one. Otherwise they will reference the same ztimer_clock.

To avoid chained conversions its better to base new ztimer_clock on top of ZTIMER_USEC_BASE running at CONFIG_ZTIMER_USEC_BASE_FREQ.