Timing in Zephyr is the way to control when things happen within the RTOS.
In an embedded system, controlling when things happen is just as important as what things happen.
We come from seeing Threads. A very common mistake when starting is using giant while loops to count time or using blocking delays that freeze the processor. In an RTOS like Zephyr, that’s a cardinal sin.
Today, we’ll see how to handle time efficiently, allowing the processor to go to sleep (saving battery) while waiting.
Active and Passive Waiting
First, we need to distinguish between two ways of waiting.
Active Waiting (k_busy_wait)
The processor stays in a loop “watching the clock”. It consumes 100% of the CPU and battery. No one else (except hardware interrupts) can run.
/* Waits 50 microseconds burning CPU cycles */
k_busy_wait(50);When to use it? Only for very short delays (microseconds) where the cost of context switching (sleeping and waking the thread) would be greater than the wait itself. For example, when communicating with a bit-banging sensor.
Passive Waiting (k_msleep)
The thread tells the Kernel: “Wake me up in 1 second”. The Kernel marks the thread as “Suspended”, removes the thread from the CPU, and runs another one (or puts the CPU in low-power mode).
/* Sleeps 1000 milliseconds freeing the CPU */
k_msleep(1000);This is the function you will use 99% of the time. Always remember to use k_msleep (milliseconds) or k_usleep (microseconds) instead of empty for loops.
Software Timers (k_timer)
So far, if we wanted to do something periodically, we put a k_msleep inside a while loop. But what if we want to execute a function every 100ms without occupying an entire thread for it?
For this, we have Timers.
A k_timer is a Kernel object that, when its counter reaches zero, executes a callback function.
Anatomy of a Timer
To use a timer we need:
Define it: Create the structure.
Initialize it: Tell it which function to execute when it expires.
Start it: Tell it how often it should fire.
Example: An LED controlled by a timer
Let’s make an LED blink using a Timer, without using any while loop in main.
#include <zephyr/kernel.h>
#include <zephyr/drivers/gpio.h>
/* LED configuration (same as in Blinky) */
#define LED0_NODE DT_ALIAS(led0)
static const struct gpio_dt_spec led = GPIO_DT_SPEC_GET(LED0_NODE, gpios);
/* 1. Define the expiration function (what the timer does) */
void my_timer_handler(struct k_timer *dummy)
{
/* CAUTION! This runs in INTERRUPT context (ISR) */
gpio_pin_toggle_dt(&led);
}
/* 2. Statically define the Timer */
/* K_TIMER_DEFINE(name, expiration_func, stop_func) */
K_TIMER_DEFINE(my_timer, my_timer_handler, NULL);
int main(void)
{
if (!gpio_is_ready_dt(&led)) {
return 0;
}
gpio_pin_configure_dt(&led, GPIO_OUTPUT_ACTIVE);
/* 3. Start the Timer */
/* k_timer_start(timer, initial_duration, period) */
/* Starts in 1 second, and repeats every 0.5 seconds */
k_timer_start(&my_timer, K_SECONDS(1), K_MSEC(500));
/* Main has nothing else to do, it can terminate or sleep */
return 0;
}The Interrupt Context (ISR)
Here comes the most difficult concept to understand about Timers in Zephyr.
The expiration function of k_timer runs in the system clock interrupt handler, not in a normal thread.
ISR context limitations:
- You cannot sleep: It is forbidden to use
k_sleep,k_mutex_lock, or any function that can block. If you do, the system will crash with a “Kernel Panic”. - Be fast: Get in, do your thing, and get out. Do not perform complex calculations or long
printks.
If you need to do something complex when the timer fires (e.g., read a slow I2C sensor or send over the network), the Timer should not do it directly. The Timer should wake up a Thread (using a semaphore or queue) so that the Thread does the heavy lifting.
Measuring Time (k_uptime_get)
Sometimes we don’t want to wait, we just want to know how much time has passed. Zephyr maintains an internal counter of the system since boot.
k_uptime_get(): Returns milliseconds (int64_t) since boot.k_cycle_get_32(): Returns clock cycles (for maximum precision).
Example of use to measure how long a function takes:
int64_t start_time = k_uptime_get();
slow_calculation_function();
int64_t duration = k_uptime_get() - start_time;
printk("The function took %lld ms\n", duration);