BME554L - Spring 2026
Duke University
February 16, 2026
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.K_EVENT_DEFINE(temp_events);
#define TEMP_TOO_HIGH_EVENT BIT(0)
#define TEMP_TOO_LOW_EVENT BIT(1)
// somewhere in your code, you can post an event
k_event_post(&temp_events, TEMP_TOO_HIGH_EVENT);The k_event bit array is 32-bits long, so you can define up to 32 events in a single bit array.
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
}
}Sequence diagrams visualize how components interact over time.
Useful for documenting the flow of messages between threads, ISR callbacks, and kernel services.
Participants in a Zephyr firmware application may include:
Threads (e.g., main, worker threads)
ISR Callbacks
Kernel Event Bit Arrays
Kernel Timers
@startuml
participant "Button ISR\nCallback" as ISR
participant "Kernel Event\nBit Array" as KEvents
participant "Worker Thread" as Worker
Worker -> KEvents : k_event_wait()\n[blocking]
activate Worker #LightBlue
note over ISR : Button Pressed
ISR -> KEvents : k_event_post(BUTTON_EVENT)
KEvents --> Worker : BUTTON_EVENT received
deactivate Worker
Worker -> Worker : process event\n(e.g., change state)
@endumlSequence Diagram Example