.. _tutorial_step2: 实战二:外设中断与下半部处理——GPIO 仿真、按键中断与工作队列 ############################################################################ 在真实嵌入式系统中,外部按键或传感器 DRDY 引脚会触发 GPIO 硬件中断(ISR)。由于 **ISR 上下文中严禁休眠或执行耗时操作**,工业级固件通常在 ISR(上半部)中仅调用 ``k_work_reschedule()``,将消抖和业务处理延后到系统工作队列线程(下半部)中执行。 本节我们在纯软件模拟的 ``qemu_cortex_a53`` 上,通过 ``app.overlay`` 挂载 ``zephyr,gpio-emul`` 仿真控制器,用标准 :doc:`/develop/driver/gpio` 与 :doc:`/develop/kernel/workq` 接口完整实现这一链路。 Step 1:编写 ``app.overlay`` 挂载虚拟 LED 与按键 ================================================ 创建 ``smart_node/app.overlay``,定义 报警灯(``led0``)与 用户按键(``sw0``): .. code-block:: devicetree #include / { 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"; }; }; }; .. note:: 根据 ``zephyr,gpio-emul.yaml`` 绑定规则,必须在 ``gpio_emul0`` 节点中显式声明 ``rising-edge;`` 和 ``falling-edge;`` 布尔属性,仿真驱动才允许配置边沿触发中断。 在 ``smart_node/prj.conf`` 中开启 GPIO 驱动与仿真器: .. code-block:: ini 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()`` 模拟物理按键电平翻转。 .. code-block:: c #include #include #include #include #include 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]`` 协同执行: .. code-block:: console *** Booting Zephyr OS build 321e39a45766 *** [00:00:00.010,000] smart_node_gpio: GPIO initialized: LED on pin 0, Button on pin 1 [00:00:00.220,000] smart_node_gpio: [ISR] Button interrupt! count=1, alarm_led=1 [00:00:00.270,000] 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] smart_node_gpio: [ISR] Button interrupt! count=2, alarm_led=0 [00:00:01.030,000] smart_node_gpio: [Workqueue] Debounced button event processed (in_isr=0, count=2, led=0) 相关参考 ======== * :doc:`/develop/driver/gpio` * :doc:`/develop/kernel/workq` * :doc:`/develop/kernel/system_thread`