实战二:外设中断与下半部处理——GPIO 仿真、按键中断与工作队列
在真实嵌入式系统中,外部按键或传感器 DRDY 引脚会触发 GPIO 硬件中断(ISR)。由于 ISR 上下文中严禁休眠或执行耗时操作,工业级固件通常在 ISR(上半部)中仅调用 k_work_reschedule(),将消抖和业务处理延后到系统工作队列线程(下半部)中执行。
本节我们在纯软件模拟的 qemu_cortex_a53 上,通过 app.overlay 挂载 zephyr,gpio-emul 仿真控制器,用标准 GPIO 驱动模型与中断接口 与 工作队列 接口完整实现这一链路。
Step 1:编写 app.overlay 挂载虚拟 LED 与按键
创建 smart_node/app.overlay,定义 报警灯(led0)与 用户按键(sw0):
#include <zephyr/dt-bindings/gpio/gpio.h>
/ {
aliases {
led0 = &alarm_led;
sw0 = &user_button;
};
gpio_emul0: gpio_emul {
compatible = "zephyr,gpio-emul";
gpio-controller;
#gpio-cells = <2>;
ngpios = <8>;
rising-edge;
falling-edge;
status = "okay";
};
leds {
compatible = "gpio-leds";
alarm_led: led_0 {
gpios = <&gpio_emul0 0 GPIO_ACTIVE_HIGH>;
label = "Alarm LED 0";
};
};
buttons {
compatible = "gpio-keys";
user_button: button_0 {
gpios = <&gpio_emul0 1 (GPIO_PULL_UP | GPIO_ACTIVE_LOW)>;
label = "User Button 0";
};
};
};
备注
根据 zephyr,gpio-emul.yaml 绑定规则,必须在 gpio_emul0 节点中显式声明 rising-edge; 和 falling-edge; 布尔属性,仿真驱动才允许配置边沿触发中断。
在 smart_node/prj.conf 中开启 GPIO 驱动与仿真器:
CONFIG_LOG=y
CONFIG_SHELL=y
CONFIG_GPIO=y
CONFIG_GPIO_EMUL=y
Step 2:实现 GPIO ISR 上半部与延迟工作队列下半部
编写 smart_node/src/main.c:
上半部(ISR):
button_pressed_isr被 GPIO 边沿中断触发时,翻转报警灯并通过k_work_reschedule(&debounce_work, K_MSEC(50))安排 50ms 后的下半部工作。下半部(Workqueue 线程):
debounce_work_handler在系统工作队列线程中运行(k_is_in_isr() == 0),安全执行消抖后的状态上报。按键仿真命令:注册
node press命令,调用gpio_emul_input_set()模拟物理按键电平翻转。
#include <zephyr/kernel.h>
#include <zephyr/drivers/gpio.h>
#include <zephyr/drivers/gpio/gpio_emul.h>
#include <zephyr/logging/log.h>
#include <zephyr/shell/shell.h>
LOG_MODULE_REGISTER(smart_node_gpio, LOG_LEVEL_INF);
static const struct gpio_dt_spec alarm_led = GPIO_DT_SPEC_GET(DT_ALIAS(led0), gpios);
static const struct gpio_dt_spec user_btn = GPIO_DT_SPEC_GET(DT_ALIAS(sw0), gpios);
static struct gpio_callback btn_cb_data;
static atomic_t press_count = ATOMIC_INIT(0);
/* 下半部:运行在系统工作队列线程上下文 (in_isr=0) */
static void debounce_work_handler(struct k_work *work)
{
ARG_UNUSED(work);
int led_val = gpio_emul_output_get(alarm_led.port, alarm_led.pin);
LOG_INF("[Workqueue] Debounced button event processed (in_isr=%d, count=%ld, led=%d)",
k_is_in_isr(), atomic_get(&press_count), led_val);
}
K_WORK_DELAYABLE_DEFINE(debounce_work, debounce_work_handler);
/* 上半部:GPIO 硬件中断服务例程 (in_isr=1),严禁阻塞 */
static void button_pressed_isr(const struct device *dev, struct gpio_callback *cb,
uint32_t pins)
{
ARG_UNUSED(dev);
ARG_UNUSED(cb);
ARG_UNUSED(pins);
long cnt = atomic_inc(&press_count) + 1;
gpio_pin_toggle_dt(&alarm_led);
int led_val = gpio_emul_output_get(alarm_led.port, alarm_led.pin);
LOG_INF("[ISR] Button interrupt! count=%ld, alarm_led=%d", cnt, led_val);
/* 投递/重置 50ms 延迟工作项 */
k_work_reschedule(&debounce_work, K_MSEC(50));
}
static int cmd_node_press(const struct shell *sh, size_t argc, char **argv)
{
ARG_UNUSED(argc);
ARG_UNUSED(argv);
shell_print(sh, "Simulating physical button press on %s pin %d...",
user_btn.port->name, user_btn.pin);
/* GPIO_ACTIVE_LOW:物理低电平 (0) 为按下,高电平 (1) 为松开 */
gpio_emul_input_set(user_btn.port, user_btn.pin, 0);
k_msleep(20);
gpio_emul_input_set(user_btn.port, user_btn.pin, 1);
return 0;
}
SHELL_STATIC_SUBCMD_SET_CREATE(sub_node,
SHELL_CMD_ARG(press, NULL, "Simulate button press interrupt", cmd_node_press, 1, 0),
SHELL_SUBCMD_SET_END
);
SHELL_CMD_REGISTER(node, &sub_node, "Smart Node GPIO commands", NULL);
int main(void)
{
if (!gpio_is_ready_dt(&alarm_led) || !gpio_is_ready_dt(&user_btn)) {
LOG_ERR("GPIO devices not ready");
return -ENODEV;
}
gpio_pin_configure_dt(&alarm_led, GPIO_OUTPUT_INACTIVE);
gpio_pin_configure_dt(&user_btn, GPIO_INPUT);
gpio_emul_input_set(user_btn.port, user_btn.pin, 1);
gpio_pin_interrupt_configure_dt(&user_btn, GPIO_INT_EDGE_TO_ACTIVE);
gpio_init_callback(&btn_cb_data, button_pressed_isr, BIT(user_btn.pin));
gpio_add_callback_dt(&user_btn, &btn_cb_data);
LOG_INF("GPIO initialized: LED on pin %d, Button on pin %d",
alarm_led.pin, user_btn.pin);
/* 启动后自动演示一次按键触发 */
k_msleep(200);
gpio_emul_input_set(user_btn.port, user_btn.pin, 0);
k_msleep(20);
gpio_emul_input_set(user_btn.port, user_btn.pin, 1);
return 0;
}
Step 3:在 qemu_cortex_a53 上验证中断与延迟工作时序
编译并运行后,可以在终端中输入 node press 触发中断,观察 [ISR] 与 50ms 后的 [Workqueue] 协同执行:
*** Booting Zephyr OS build 321e39a45766 ***
[00:00:00.010,000] <inf> smart_node_gpio: GPIO initialized: LED on pin 0, Button on pin 1
[00:00:00.220,000] <inf> smart_node_gpio: [ISR] Button interrupt! count=1, alarm_led=1
[00:00:00.270,000] <inf> smart_node_gpio: [Workqueue] Debounced button event processed (in_isr=0, count=1, led=1)
uart:~$ node press
Simulating physical button press on gpio_emul pin 1...
[00:00:00.980,000] <inf> smart_node_gpio: [ISR] Button interrupt! count=2, alarm_led=0
[00:00:01.030,000] <inf> smart_node_gpio: [Workqueue] Debounced button event processed (in_isr=0, count=2, led=0)