Introduction | Theory | Lab | Course Home
Zephyr’s interrupt handling is built upon a modular and flexible architecture. It allows you to connect interrupts to specific handler functions that are executed when the interrupt signal occurs.
#include <zephyr/irq.h>
#include <zephyr/kernel.h>
void my_isr(const void *arg)
{
/* ISR implementation - keep minimal */
printk("Interrupt triggered!\n");
k_sem_give(&data_ready_sem); // Signal the main thread
}
IRQ_CONNECT(MY_IRQ, MY_IRQ_PRIORITY, my_isr, NULL, 0);
irq_enable(MY_IRQ);
MY_IRQ: A symbolic name representing the interrupt line. This should correspond to the physical line connected to the interrupt controller.MY_IRQ_PRIORITY: A symbolic name representing the priority of the interrupt. Lower values indicate higher priority.my_isr: The name of the interrupt handler function.NULL: Pointer to an argument that will be passed to the interrupt handler.0: The interrupt flag. Typically set to 0 for basic interrupt handling.It is absolutely critical to use only the interrupt-safe APIs within your interrupt handlers. Failure to do so can lead to unpredictable behavior and system crashes.
Safe in ISR:
k_sem_give(): Signals a semaphore.k_msgq_put(..., K_NO_WAIT): Puts a message onto a message queue.k_work_submit(): Submits a work item to a workqueue.k_poll_signal_raise(): Raises a signal for polling.NOT Safe in ISR:
k_sem_take() with timeoutk_mutex_lock()k_sleep() (Any blocking operation)Handler threads are specialized threads designed to receive and process data from interrupt handlers. They provide a safe and controlled environment for handling interrupt-driven tasks.
#include <zephyr/irq.h>
#include <zephyr/kernel.h>
// Define handler thread
K_THREAD_DEFINE(handler_thread, 1024, handler_thread_func, NULL, NULL, NULL,
K_PRIO_COOP(7), 0, 0);
void handler_thread_func(void *arg1, void *arg2, void *arg3)
{
while (1) {
// Wait for interrupt signal
k_sem_take(&data_ready_sem, K_FOREVER);
// Process data received from the interrupt
printk("Handler thread processing interrupt data\n");
// Perform complex processing that shouldn't be done in ISR
process_interrupt_data();
}
}
Work queues are a flexible and efficient mechanism for deferred processing of tasks. They allow tasks to signal the need for processing, and a separate thread handles the actual processing.
k_work_queue_init(): Initializes a workqueue.k_work_queue_start(): Starts the workqueue, creating the thread that handles work items.k_work_submit(): Submits a work item to the workqueue.k_work_submit_to_queue(): Submits a work item to a specific queue.#include <zephyr/irq.h>
#include <zephyr/kernel.h>
// Define work item
K_WORK_DEFINE(my_work, work_handler);
// Work handler function
void work_handler(struct k_work *work)
{
printk("Processing work item\n");
// Perform the actual processing
handle_interrupt_processing();
}
// In the ISR, submit work
void my_isr(const void *arg)
{
// Minimal ISR - just submit work
k_work_submit(&my_work);
}
#define WORKQUEUE_STACK_SIZE 1024
#define WORKQUEUE_PRIORITY 5
K_THREAD_STACK_DEFINE(workqueue_stack, WORKQUEUE_STACK_SIZE);
static struct k_work_q custom_work_q;
void init_custom_workqueue(void)
{
k_work_queue_init(&custom_work_q);
k_work_queue_start(&custom_work_q, workqueue_stack,
K_THREAD_STACK_SIZEOF(workqueue_stack),
WORKQUEUE_PRIORITY, NULL);
}
// Submit to custom workqueue
void submit_to_custom_queue(void)
{
k_work_submit_to_queue(&custom_work_q, &my_work);
}
void setup_dynamic_interrupt(void)
{
int ret = irq_connect_dynamic(MY_IRQ, MY_IRQ_PRIORITY,
my_dynamic_isr, NULL, 0);
if (ret < 0) {
printk("Failed to connect dynamic interrupt\n");
return;
}
irq_enable(MY_IRQ);
}
Zephyr supports multiple interrupt priority levels. Lower numerical values indicate higher priority:
#define HIGH_PRIORITY_IRQ 0
#define MEDIUM_PRIORITY_IRQ 5
#define LOW_PRIORITY_IRQ 10
IRQ_CONNECT(TIMER_IRQ, HIGH_PRIORITY_IRQ, timer_isr, NULL, 0);
IRQ_CONNECT(UART_IRQ, MEDIUM_PRIORITY_IRQ, uart_isr, NULL, 0);
IRQ_CONNECT(GPIO_IRQ, LOW_PRIORITY_IRQ, gpio_isr, NULL, 0);
Zephyr supports interrupt nesting, where higher priority interrupts can preempt lower priority ones:
void high_priority_isr(const void *arg)
{
// This can interrupt lower priority ISRs
handle_critical_event();
}
void low_priority_isr(const void *arg)
{
// Can be interrupted by higher priority ISRs
handle_normal_event();
}
void robust_isr(const void *arg)
{
int ret;
// Check hardware status
if (!is_interrupt_valid()) {
return; // Spurious interrupt
}
// Use interrupt-safe API with error checking
ret = k_msgq_put(&my_msgq, &data, K_NO_WAIT);
if (ret != 0) {
// Handle queue full condition
increment_overflow_counter();
}
// Clear interrupt source
clear_interrupt_flag();
}
// Efficient ISR with minimal processing
void efficient_isr(const void *arg)
{
// Read hardware register once
uint32_t status = read_status_register();
// Quick decision making
if (status & CRITICAL_FLAG) {
k_work_submit(&critical_work);
} else {
k_work_submit(&normal_work);
}
// Clear interrupt quickly
write_status_register(status);
}
This theory foundation provides the essential knowledge needed to implement robust interrupt handling in Zephyr applications.