Introduction | Theory | Lab | Course Home
This lab provides hands-on experience implementing interrupt handling, workqueues, and their interaction in Zephyr. You’ll build a complete interrupt-driven system that demonstrates real-world embedded programming patterns.
By completing this lab, you will:
IRQ_CONNECT()interrupt_lab/
├── CMakeLists.txt
├── prj.conf
├── src/
│ ├── main.c
│ ├── interrupt_handler.c
│ └── interrupt_handler.h
└── boards/
└── [board_name].overlay
Create CMakeLists.txt:
cmake_minimum_required(VERSION 3.20.0)
find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
project(interrupt_lab)
target_sources(app PRIVATE src/main.c src/interrupt_handler.c)
Create prj.conf:
CONFIG_GPIO=y
CONFIG_PRINTK=y
CONFIG_LOG=y
CONFIG_LOG_DEFAULT_LEVEL=3
CONFIG_KERNEL_LOG_LEVEL=3
CONFIG_ASSERT=y
Create boards/nrf52840dk_nrf52840.overlay (adjust for your board):
/ {
buttons {
compatible = "gpio-keys";
button0: button_0 {
gpios = <&gpio0 11 (GPIO_PULL_UP | GPIO_ACTIVE_LOW)>;
label = "Push button switch 0";
};
};
leds {
compatible = "gpio-leds";
led0: led_0 {
gpios = <&gpio0 13 GPIO_ACTIVE_LOW>;
label = "Green LED 0";
};
};
};
Create src/interrupt_handler.h:
#ifndef INTERRUPT_HANDLER_H
#define INTERRUPT_HANDLER_H
#include <zephyr/kernel.h>
#include <zephyr/drivers/gpio.h>
/* Button and LED definitions */
#define BUTTON_NODE DT_ALIAS(sw0)
#define LED_NODE DT_ALIAS(led0)
/* Function declarations */
int setup_gpio_interrupt(void);
void gpio_interrupt_handler(const struct device *dev,
struct gpio_callback *cb,
uint32_t pins);
void toggle_led(void);
/* Global semaphore for signaling */
extern struct k_sem button_pressed_sem;
#endif /* INTERRUPT_HANDLER_H */
Create src/interrupt_handler.c:
#include "interrupt_handler.h"
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(interrupt_handler, LOG_LEVEL_DBG);
/* Global semaphore */
K_SEM_DEFINE(button_pressed_sem, 0, 1);
/* GPIO callback structure */
static struct gpio_callback button_cb_data;
/* GPIO devices */
static const struct gpio_dt_spec button = GPIO_DT_SPEC_GET(BUTTON_NODE);
static const struct gpio_dt_spec led = GPIO_DT_SPEC_GET(LED_NODE);
/* LED toggle function */
void toggle_led(void)
{
static bool led_state = false;
led_state = !led_state;
gpio_pin_set_dt(&led, led_state);
LOG_INF("LED toggled: %s", led_state ? "ON" : "OFF");
}
void gpio_interrupt_handler(const struct device *dev,
struct gpio_callback *cb,
uint32_t pins)
{
LOG_INF("Button interrupt triggered on pin %d", pins);
/* Signal the main thread - interrupt-safe operation */
k_sem_give(&button_pressed_sem);
}
int setup_gpio_interrupt(void)
{
int ret;
if (!device_is_ready(button.port) || !device_is_ready(led.port)) {
LOG_ERR("GPIO devices not ready");
return -ENODEV;
}
/* Configure button as input with interrupt */
ret = gpio_pin_configure_dt(&button, GPIO_INPUT);
if (ret < 0) {
LOG_ERR("Failed to configure button pin: %d", ret);
return ret;
}
/* Configure LED as output */
ret = gpio_pin_configure_dt(&led, GPIO_OUTPUT_INACTIVE);
if (ret < 0) {
LOG_ERR("Failed to configure LED pin: %d", ret);
return ret;
}
/* Configure interrupt */
ret = gpio_pin_interrupt_configure_dt(&button, GPIO_INT_EDGE_TO_ACTIVE);
if (ret < 0) {
LOG_ERR("Failed to configure interrupt: %d", ret);
return ret;
}
/* Initialize callback */
gpio_init_callback(&button_cb_data, gpio_interrupt_handler,
BIT(button.pin));
/* Add callback */
ret = gpio_add_callback(button.port, &button_cb_data);
if (ret < 0) {
LOG_ERR("Failed to add callback: %d", ret);
return ret;
}
LOG_INF("GPIO interrupt setup complete");
return 0;
}
Create src/main.c:
#include <zephyr/kernel.h>
#include <zephyr/logging/log.h>
#include "interrupt_handler.h"
LOG_MODULE_REGISTER(main, LOG_LEVEL_DBG);
int main(void)
{
int ret;
LOG_INF("Starting Interrupt Management Lab");
/* Setup GPIO interrupt */
ret = setup_gpio_interrupt();
if (ret < 0) {
LOG_ERR("Failed to setup GPIO interrupt: %d", ret);
return ret;
}
LOG_INF("Press the button to trigger interrupt");
/* Main loop - wait for interrupts */
while (1) {
/* Wait for button press interrupt */
ret = k_sem_take(&button_pressed_sem, K_FOREVER);
if (ret == 0) {
LOG_INF("Button press detected in main thread");
toggle_led();
/* Add debounce delay */
k_msleep(200);
}
}
return 0;
}
Add to src/interrupt_handler.c:
/* Handler thread stack and priority */
#define HANDLER_STACK_SIZE 1024
#define HANDLER_PRIORITY 7
/* Handler thread function */
void handler_thread_func(void *arg1, void *arg2, void *arg3)
{
int count = 0;
LOG_INF("Handler thread started");
while (1) {
/* Wait for interrupt signal */
k_sem_take(&button_pressed_sem, K_FOREVER);
count++;
LOG_INF("Handler thread processing interrupt #%d", count);
/* Simulate complex processing */
k_msleep(100);
/* Perform LED operations */
toggle_led();
LOG_INF("Handler thread completed processing #%d", count);
}
}
/* Define handler thread */
K_THREAD_DEFINE(handler_thread, HANDLER_STACK_SIZE, handler_thread_func,
NULL, NULL, NULL, HANDLER_PRIORITY, 0, 0);
Update main.c to remove LED toggling (now handled by handler thread):
int main(void)
{
int ret;
LOG_INF("Starting Interrupt Management Lab - Handler Thread Version");
/* Setup GPIO interrupt */
ret = setup_gpio_interrupt();
if (ret < 0) {
LOG_ERR("Failed to setup GPIO interrupt: %d", ret);
return ret;
}
LOG_INF("Handler thread will process interrupts");
LOG_INF("Press the button to trigger interrupt");
/* Main thread can do other work */
while (1) {
LOG_INF("Main thread is running...");
k_msleep(5000); /* 5 second heartbeat */
}
return 0;
}
Add to src/interrupt_handler.c:
/* Work item for deferred processing */
static struct k_work button_work;
static struct k_work_delayable delayed_work;
/* Work handler function */
void button_work_handler(struct k_work *work)
{
static int work_count = 0;
work_count++;
LOG_INF("Workqueue processing button press #%d", work_count);
/* Perform LED toggle */
toggle_led();
/* Schedule delayed work */
k_work_schedule(&delayed_work, K_MSEC(1000));
}
/* Delayed work handler */
void delayed_work_handler(struct k_work *work)
{
LOG_INF("Delayed work executed - turning off LED");
/* Turn off LED */
const struct gpio_dt_spec led = GPIO_DT_SPEC_GET(LED_NODE);
gpio_pin_set_dt(&led, 0);
}
/* Updated interrupt handler */
void gpio_interrupt_handler(const struct device *dev,
struct gpio_callback *cb,
uint32_t pins)
{
LOG_INF("Button interrupt - submitting to workqueue");
/* Submit work to system workqueue - interrupt-safe */
k_work_submit(&button_work);
}
/* Initialize work items */
void init_workqueue(void)
{
k_work_init(&button_work, button_work_handler);
k_work_init_delayable(&delayed_work, delayed_work_handler);
LOG_INF("Workqueue initialized");
}
Update header file and main function to call init_workqueue().
Add timing measurement to interrupt handler:
#include <zephyr/sys/time_units.h>
void gpio_interrupt_handler(const struct device *dev,
struct gpio_callback *cb,
uint32_t pins)
{
static uint32_t last_time = 0;
uint32_t current_time = k_uptime_get_32();
/* Calculate time since last interrupt */
if (last_time != 0) {
uint32_t delta = current_time - last_time;
LOG_INF("Time since last interrupt: %u ms", delta);
}
last_time = current_time;
/* Submit work to workqueue */
k_work_submit(&button_work);
}
Add stress test functionality:
/* Stress test work item */
static struct k_work stress_work;
void stress_work_handler(struct k_work *work)
{
/* Simulate heavy processing */
for (int i = 0; i < 1000; i++) {
k_busy_wait(100); /* 100 microseconds */
}
LOG_INF("Stress work completed");
}
/* Submit stress work periodically */
void submit_stress_work(void)
{
k_work_submit(&stress_work);
}
Add a second button and handle multiple interrupt sources.
Implement a custom workqueue with different priority than system workqueue.
Track interrupt frequency and processing times.
Add comprehensive error handling for all interrupt operations.
| Issue | Possible Cause | Solution |
|---|---|---|
| No interrupt triggered | GPIO not configured correctly | Check device tree and pin configuration |
| System crashes in ISR | Using blocking API in ISR | Use only interrupt-safe APIs |
| Missed interrupts | ISR taking too long | Move processing to workqueue |
| LED not toggling | GPIO output not configured | Verify LED GPIO configuration |
| Build errors | Missing dependencies | Check prj.conf configuration |
After completing this lab, you should have:
This comprehensive lab provides practical experience with all aspects of interrupt management in Zephyr, preparing you for real-world embedded system development.