Zephyr: Threads and Kernel Events
BME554L -Fall 2025 - Palmeri
Threads
- Threads are the basic unit of execution in Zephyr.
- Threads are scheduled by the kernel.
- Threads can be in one of three states:
- Ready: The thread is ready to run.
- Running: The thread is currently running.
- Blocked: The thread is waiting for an event to occur.
We’ve Already Used Threads
Main
thread- Kernel timer threads
But we can define our own threads for a variety of purposes!
- Wait for a task to complete, then do something else.
- Example: Wait for data to save to an SD card, then do something else, but don’t want the kernel to wait.
Example Use Case: Heartbeat LED Toggle at 25% Duty Cycle
- This is not as easy to implement with a single kernel timer.
- But we can use a thread to toggle the LED at 25% duty cycle and not hit the kernel to do so:
/* 1024 byte stack, handler, NULL, NULL, NULL, priority 5, no time slice, no delay */
(heartbeat_thread_id, 1024, error_thread, NULL, NULL, NULL, 5, 0, 0);
K_THREAD_DEFINE
extern void heartbeat_thread(void *, void *, void *) {
while (1) {
(250); // scheduler can run other tasks now
k_msleep(&heartbeat_led);
gpio_pin_toggle_dt(750); // scheduler can run other tasks now
k_msleep(&heartbeat_led);
gpio_pin_toggle_dt}
}
How to Suspend/Resume Threads
(heartbeat_thread_id);
k_thread_suspend(heartbeat_thread_id); k_thread_resume
Threads vs. Timers
- The thread can have blocking calls (e.g.,
k_msleep
) without blocking other threads. - The scheduler can run other threads while this thread is sleeping (or yields).
- The scheduler can preempt the thread at any time with higher priority tasks (e.g., ISR callbacks, higher-priority threads).
- No need for a separate handler function.
- More difficult to start/stop/reset than a timer.
- Need explicitly allocated stack space.
- Difficult to coordinate high-accuracy timing with other tasks/threads.
Kernel Events
- Kernel events can be used to indicate that “something” has happened, which may dictate the function of the state machine.
- Button press
- Sensor crossing a threshold
- Timer expiring
- Enabled in
prj.conf
withCONFIG_EVENTS=y
.
Kernel Event Bit Arrays
- Kernel events are stored in a bit array (an array, where each bit (
0/1
) indicates (False/True
) if an event has occured). - When an event occurs, it is posted (
k_event_post
). - A state machine can wait (
k_event_wait
) for an event (or events) to occur. - Events can be cleared (
k_event_clear
) after they are processed.
Why Kernel Events over Boolean Variables?
- Consume less memory.
- Easier to pass between threads / functions.
- Can wait for multiple events to occur without having to conditionally test for them.
How to Define Kernel Events
- Kernel events are defined in a
k_event
structure.
(temp_events);
K_EVENT_DEFINE
#define TEMP_TOO_HIGH_EVENT BIT(0)
#define TEMP_TOO_LOW_EVENT BIT(1)
// somewhere in your code, you can post an event
(&temp_events, TEMP_TOO_HIGH_EVENT); k_event_post
Waiting for Events
- With kernel events, we can now implement a thread that waits for an event to occur and then changes the state of the state machine.
- Threads are started when your firmware is initialized; it does not need to be “called” (like a function).
/* 1024 byte stack, handler, NULL, NULL, NULL, priority 5, no time slice, no delay */
(temp_too_high_thread_id, 1024, temp_too_high_thread, NULL, NULL, NULL, 5, 0, 0);
K_THREAD_DEFINE
extern void temp_too_high_thread(void *, void *, void *) {
// need to loop to keep the thread running after the first error occurs
while (1) {
/* &temp_events is a pointer to an event bit array
0xF is an example of a bit mask of events in the array to wait for - any, not all
true clears all of the events that may have previously been posted before waiting
K_FOREVER means wait indefinitely (this could be a finite period of time instead)
*/
uint32_t events = k_event_wait(&temp_events, 0xF, true, K_FOREVER);
// can also define the bit mask as a logical operation of the individual bits
// uint32_t events = k_event_wait(&temp_events, TEMP_TOO_HIGH_EVENT | TEMP_TOO_LOW EVENT, true, K_FOREVER);
// events is an int representation of the bit mask of the events that were posted
// if you want to wait for **ALL** events in the mask, use
// k_event_wait_all() instead of k_event_wait()
("Temperature Event Posted: %d", events); // bit array mask output as an int
LOG_INF
(); // do something in response to the temperature event, like change states
shut_down_system}
}