Timing Services
Zephyr provides various timing mechanisms for delays, timeouts, and periodic operations.
Time Units
Kernel Ticks
The fundamental time unit in Zephyr:
/* Get current tick count */
int64_t ticks = k_uptime_ticks();
/* Convert to milliseconds */
int64_t ms = k_ticks_to_ms_floor64(ticks);
/* Ticks per second */
uint32_t ticks_per_sec = CONFIG_SYS_CLOCK_TICKS_PER_SEC;
Timeout Values
/* Various timeout specifications */
k_timeout_t timeout;
timeout = K_NO_WAIT; /* Return immediately */
timeout = K_FOREVER; /* Wait forever */
timeout = K_MSEC(100); /* 100 milliseconds */
timeout = K_USEC(500); /* 500 microseconds */
timeout = K_SECONDS(5); /* 5 seconds */
timeout = K_MINUTES(1); /* 1 minute */
timeout = K_HOURS(1); /* 1 hour */
/* Absolute timeout */
int64_t target = k_uptime_get() + 1000;
timeout = K_TIMEOUT_ABS_MS(target);
Sleeping
Basic Sleep
#include <zephyr/kernel.h>
void my_thread(void *p1, void *p2, void *p3)
{
/* Sleep for duration */
k_sleep(K_MSEC(500));
/* Convenience functions */
k_msleep(500); /* Same as K_MSEC(500) */
k_usleep(1000); /* Microseconds */
}
Busy Wait (No Scheduling)
/* Busy wait - blocks CPU, no thread switch */
k_busy_wait(100); /* 100 microseconds */
k_busy_wait() wastes CPU cycles. Use only for very short delays where thread switching overhead is unacceptable.
Precise Periodic Timing
void periodic_thread(void *p1, void *p2, void *p3)
{
int64_t next_wake = k_uptime_get();
while (1) {
do_periodic_work();
/* Schedule next wake time */
next_wake += 100; /* 100ms period */
k_sleep(K_TIMEOUT_ABS_MS(next_wake));
}
}
Timers
Timers execute callbacks or signal threads at specified intervals.
Basic Timer
#include <zephyr/kernel.h>
void timer_callback(struct k_timer *timer)
{
/* Called in ISR context! */
/* Keep it short, use ISR-safe APIs only */
k_sem_give(&timer_sem);
}
K_TIMER_DEFINE(my_timer, timer_callback, NULL);
void start_timer(void)
{
/* Start: 1000ms initial, 500ms periodic */
k_timer_start(&my_timer, K_MSEC(1000), K_MSEC(500));
}
void stop_timer(void)
{
k_timer_stop(&my_timer);
}
Timer with Expiry Function
void timer_expiry(struct k_timer *timer)
{
/* Called when timer expires */
printk("Timer expired!\n");
}
void timer_stop_func(struct k_timer *timer)
{
/* Called when timer is stopped */
printk("Timer stopped\n");
}
K_TIMER_DEFINE(my_timer, timer_expiry, timer_stop_func);
One-Shot Timer
/* Fire once after 1 second */
k_timer_start(&my_timer, K_SECONDS(1), K_NO_WAIT);
Timer Status
/* Get remaining time */
uint32_t remaining = k_timer_remaining_get(&my_timer);
/* Get status (times fired since last check) */
uint32_t status = k_timer_status_get(&my_timer);
/* Sync (wait for timer) */
uint32_t synced = k_timer_status_sync(&my_timer);
Using Timer User Data
struct my_data {
int id;
int count;
};
void timer_callback(struct k_timer *timer)
{
struct my_data *data = k_timer_user_data_get(timer);
data->count++;
}
struct my_data timer_data = {.id = 1, .count = 0};
void setup(void)
{
k_timer_user_data_set(&my_timer, &timer_data);
k_timer_start(&my_timer, K_MSEC(100), K_MSEC(100));
}
Uptime Functions
/* Milliseconds since boot */
int64_t ms = k_uptime_get();
/* 32-bit version (wraps after ~49 days) */
uint32_t ms32 = k_uptime_get_32();
/* Delta time */
int64_t start = k_uptime_get();
do_work();
int64_t elapsed = k_uptime_delta(&start);
printk("Work took %lld ms\n", elapsed);
Cycle Counting
For high-resolution timing:
/* Hardware cycles */
uint32_t start = k_cycle_get_32();
do_work();
uint32_t cycles = k_cycle_get_32() - start;
/* Convert to nanoseconds */
uint64_t ns = k_cyc_to_ns_floor64(cycles);
Timeout Handling in APIs
Most Zephyr APIs accept timeouts:
/* Semaphore with timeout */
int ret = k_sem_take(&my_sem, K_MSEC(500));
if (ret == -EAGAIN) {
/* Timeout occurred */
}
/* Message queue with timeout */
ret = k_msgq_get(&my_queue, &data, K_SECONDS(1));
if (ret == -EAGAIN) {
/* Timeout occurred */
}
Common Patterns
Watchdog-Style Timer
K_TIMER_DEFINE(watchdog, watchdog_expired, NULL);
void watchdog_expired(struct k_timer *timer)
{
printk("Watchdog timeout! System may be stuck.\n");
/* Take recovery action */
}
void feed_watchdog(void)
{
/* Restart timer - prevents expiry */
k_timer_start(&watchdog, K_SECONDS(5), K_NO_WAIT);
}
void main_loop(void)
{
k_timer_start(&watchdog, K_SECONDS(5), K_NO_WAIT);
while (1) {
do_work();
feed_watchdog(); /* Reset watchdog */
}
}
Debounce Timer
K_TIMER_DEFINE(debounce_timer, debounce_expired, NULL);
static volatile bool button_pressed;
void button_isr(const void *arg)
{
/* Start debounce timer */
k_timer_start(&debounce_timer, K_MSEC(50), K_NO_WAIT);
}
void debounce_expired(struct k_timer *timer)
{
/* Read stable button state */
button_pressed = gpio_pin_get_dt(&button);
}
Rate Limiting
static int64_t last_action_time;
#define MIN_INTERVAL_MS 100
bool can_perform_action(void)
{
int64_t now = k_uptime_get();
if (now - last_action_time >= MIN_INTERVAL_MS) {
last_action_time = now;
return true;
}
return false;
}
Configuration
# prj.conf
# System clock frequency
CONFIG_SYS_CLOCK_TICKS_PER_SEC=1000
# High resolution timers
CONFIG_HRTIMER=y
# Tickless kernel (power saving)
CONFIG_TICKLESS_KERNEL=y
Best Practices
- Use appropriate precision - Don’t use microseconds when milliseconds suffice
- Avoid busy waiting - Use
k_sleep()or timers instead - Timer callbacks are ISR context - Keep them short
- Use absolute timeouts for periodic tasks - Prevents drift
- Check timeout return values - Handle -EAGAIN appropriately
Example Code
See the complete Timers Example demonstrating timer creation, periodic callbacks, and timing patterns.
Next Steps
Learn about Workqueues for deferred work processing.