返回首页

Button GPIO 学习笔记

概述

有两个 Button 示例,展示了不同的按钮检测方法:

  1. basic/button - 使用 Input 子系统(推荐新方法)
  2. gpio/button_interrupt - 使用 GPIO 中断(传统方法)

示例 1:basic/button(Input 子系统)

1.1 核心代码

static const struct led_dt_spec led0 = LED_DT_SPEC_GET_OR(DT_ALIAS(led0), {0});

static void button_input_cb(struct input_event *evt, void *user_data)
{
    if (evt->sync == 0) return;  // 等待同步事件
    
    printk("Button %d %s at %u\n",
           evt->code,
           evt->value ? "pressed" : "released",
           k_cycle_get_32());
    
    if (led0.dev != NULL) {
        led_set_brightness_dt(&led0, evt->value ? 100 : 0);
    }
}

INPUT_CALLBACK_DEFINE(NULL, button_input_cb, NULL);

1.2 工作机制

┌────────────────────────────────────────────────────┐
│  物理按钮按下                                       │
│        ↓                                           │
│  Input 子系统 (GPIO/按键矩阵驱动)                    │
│        ↓                                           │
│  去抖动处理                                         │
│        ↓                                           │
│  触发 input_event (类型/代码/值/时间戳)              │
│        ↓                                           │
│  调用 button_input_cb 回调                          │
└────────────────────────────────────────────────────┘

1.3 事件结构

struct input_event {
    uint32_t type;    // 事件类型 (EV_KEY)
    uint32_t code;    // 按键代码
    int32_t  value;   // 0=released, 1=pressed
    uint32_t sync;    // 1=同步点,表示事件包完成
};

1.4 优点


示例 2:gpio/button_interrupt(GPIO 中断)

2.1 核心代码

static const struct gpio_dt_spec button = GPIO_DT_SPEC_GET_OR(SW0_NODE, gpios, {0});
static struct gpio_callback button_cb_data;

void button_pressed(const struct device *dev, struct gpio_callback *cb, uint32_t pins)
{
    printk("Button pressed at %u\n", k_cycle_get_32());
}

int main(void)
{
    // 1. 配置 GPIO 为输入
    ret = gpio_pin_configure_dt(&button, GPIO_INPUT);
    
    // 2. 配置中断(上升沿触发)
    ret = gpio_pin_interrupt_configure_dt(&button, GPIO_INT_EDGE_TO_ACTIVE);
    
    // 3. 初始化回调结构
    gpio_init_callback(&button_cb_data, button_pressed, BIT(button.pin));
    
    // 4. 注册回调
    gpio_add_callback(button.port, &button_cb_data);
}

2.2 工作机制

┌────────────────────────────────────────────────────┐
│  物理按钮按下                                       │
│        ↓                                           │
│  GPIO 引脚电平变化                                   │
│        ↓                                           │
│  GPIO 中断触发                                       │
│        ↓                                           │
│  button_pressed 回调执行                            │
└────────────────────────────────────────────────────┘

2.3 中断类型

GPIO_INT_EDGE_TO_ACTIVE      // 上升沿(高电平有效)
GPIO_INT_EDGE_TO_INACTIVE    // 下降沿
GPIO_INT_EDGE_BOTH           // 双边沿
GPIO_INT_LEVEL_HIGH          // 高电平
GPIO_INT_LEVEL_LOW           // 低电平

2.4 需要软件去抖动

// 示例:简单去抖动
static uint32_t last_press_time = 0;

void button_pressed(const struct device *dev, struct gpio_callback *cb, uint32_t pins)
{
    uint32_t now = k_cycle_get_32();
    if ((now - last_press_time) < k_ms_to_cyc_ceil32(50)) {  // 50ms 去抖动
        return;  // 忽略抖动
    }
    last_press_time = now;
    printk("Button pressed!\n");
}

3. 配置对比

3.1 Input 子系统方式 (prj.conf)

CONFIG_GPIO=y
CONFIG_INPUT=y      # 启用 Input 子系统
CONFIG_LED=y

3.2 GPIO 中断方式 (prj.conf)

CONFIG_GPIO=y       # 只需 GPIO 支持

4. 设备树配置

4.1 按钮定义

/ {
    aliases {
        sw0 = &button0;
        led0 = &led0;
    };
    
    buttons {
        button0: button_0 {
            gpios = <&gpio0 23 (GPIO_PULL_UP | GPIO_ACTIVE_LOW)>;
            label = "Push button switch 0";
        };
    };
};

4.2 Input 子系统映射(可选)

/ {
    gpio-keys {
        compatible = "gpio-keys";
        button0: button_0 {
            gpios = <&gpio0 23 GPIO_ACTIVE_LOW>;
            label = "Push button switch 0";
            zephyr,code = <INPUT_BTN_0>;  // 映射到标准输入码
        };
    };
};

5. 两种方法对比

特性Input 子系统GPIO 中断
抽象层级高(硬件无关)低(直接 GPIO)
代码量更少更多
去抖动自动处理需软件实现
多平台移植✅ 好⚠️ 需适配
复杂度简单中等
适用场景通用输入精确控制 GPIO

6. 关键 API 总结

Input 子系统 API

INPUT_CALLBACK_DEFINE(device, callback, user_data);  // 注册回调
struct input_event { type, code, value, sync };      // 事件结构

GPIO API

gpio_pin_configure_dt(&spec, GPIO_INPUT);                           // 配置输入
gpio_pin_interrupt_configure_dt(&spec, GPIO_INT_EDGE_TO_ACTIVE);   // 配置中断
gpio_init_callback(&cb, callback, pins);                            // 初始化回调
gpio_add_callback(port, &cb);                                       // 注册回调

7. 关键问题总结

问题答案
推荐使用哪种方式?Input 子系统(basic/button),更抽象、可移植
按钮按下检测?evt->value == 1 (Input) 或边沿中断 (GPIO)
去抖动怎么处理?Input 自动处理;GPIO 需软件实现
如何获取按钮状态?gpio_pin_get_dt()
支持长按检测?Input 支持;GPIO 需自己计时

8. 实际应用建议

简单按钮检测

// Input 方式 - 推荐
INPUT_CALLBACK_DEFINE(NULL, button_cb, NULL);
static void button_cb(struct input_event *evt, void *user_data)
{
    if (evt->code == INPUT_BTN_0 && evt->value) {
        // 按钮按下
    }
}

需要精确 GPIO 控制

// GPIO 方式
ret = gpio_pin_interrupt_configure_dt(&button, GPIO_INT_EDGE_BOTH);
gpio_init_callback(&cb_data, my_callback, BIT(button.pin));
gpio_add_callback(button.port, &cb_data);

9. 学习心得

  1. Input 子系统:Zephyr 推荐的新方法,抽象硬件细节
  2. GPIO 中断:底层控制,适合特殊需求
  3. 设备树配置:按钮硬件描述在 DTS 中,软件无关
  4. 去抖动:Input 自动处理是亮点

总结:四个示例都已完成学习笔记!

示例文件
UART echo_botnotes/UART_echo_bot_Learning_Notes.md
BLE peripheral_uartnotes/BLE_peripheral_uart_Learning_Notes.md
SPI spi_flashnotes/SPI_spi_flash_Learning_Notes.md
Button GPIOnotes/Button_GPIO_Learning_Notes.md

小白 🤖 2026-03-14