BME554L - Fall 2025
Duke University
September 29, 2025
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.
Main threadBut we can define our own threads for a variety of purposes!
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 */
K_THREAD_DEFINE(heartbeat_thread_id, 1024, heartbeat_thread, NULL, NULL, NULL, 5, 0, 0);
extern void heartbeat_thread(void *, void *, void *) {
while (1) {
k_msleep(250); // scheduler can run other tasks now
gpio_pin_toggle_dt(&heartbeat_led);
k_msleep(750); // scheduler can run other tasks now
gpio_pin_toggle_dt(&heartbeat_led);
}
}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 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 with CONFIG_EVENTS=y.
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.
k_event structure.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 */
K_THREAD_DEFINE(temp_too_high_thread_id, 1024, temp_too_high_thread, NULL, NULL, NULL, 5, 0, 0);
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()
LOG_INF("Temperature Event Posted: %d", events); // bit array mask output as an int
shut_down_system(); // do something in response to the temperature event, like change states
}
}