跳转至

GPIO 输入

按键检测 — GPIO 输入 + 中断 | 前置:GPIO 输出ISR 三级分发

学习目标

  • 掌握 GPIO 输入模式的配置——上拉/下拉的选择依据
  • 掌握 uapi_gpio_register_isr_func() 注册 GPIO 中断回调
  • 理解中断触发模式——上升沿、下降沿、双边沿的使用场景
  • 能够在 WS63 上用中断方式检测按键,替代轮询

基本概念

GPIO 输入做什么

GPIO 输入读取引脚上的电平——高电平(通常 > 2.0V)读为 1,低电平(< 0.8V)读为 0。用于检测按键、开关量传感器、外部触发信号。

flowchart LR
    K[按键/传感器] -->|电平| P[物理引脚]
    P --> G[GPIO 控制器]
    G -->|读寄存器| C[CPU]

上拉和下拉

悬空的 GPIO 引脚电平不确定(可能受电磁干扰来回跳)。必须通过内部或外部电阻拉到确定电平:

模式 默认电平 按键接线 按下时电平
上拉(Pull-Up) 按键 → GND 低(下降沿)
下拉(Pull-Down) 按键 → VCC 高(上升沿)

上拉最常见——按键一端接 GPIO,一端接 GND,内部上拉电阻保持未按时为高电平。按下的瞬间高→低,产生下降沿中断。

中断触发模式

触发模式 触发条件 适用场景
上升沿 低→高 按键释放检测、外部设备就绪信号
下降沿 高→低 按键按下检测(最常用)
双边沿 变化即触发 旋转编码器、脉冲计数

中断 vs 轮询

对比项 中断 轮询
CPU 占用 按键不来完全休眠 每隔 N ms 检查一次
响应速度 μs 级 取决于轮询间隔(通常 10~100ms)
功耗 持续唤醒
复杂度 需理解中断上下文约束 简单

涉及 API

API 用途 头文件
uapi_pin_set_mode(pin, HAL_PIO_FUNC_GPIO) 设置引脚为 GPIO 功能 pinctrl.h
uapi_gpio_init() 初始化 GPIO 子系统 gpio.h
uapi_gpio_set_dir(pin, GPIO_DIRECTION_INPUT) 设置引脚为输入方向 gpio.h
uapi_gpio_register_isr_func(pin, trigger, callback) 注册 GPIO 中断回调 gpio.h

中断回调中不能阻塞、不能调 printf——数据通过消息队列或信号量传给任务处理。详见 ISR 三级分发

案例说明

做什么

配置 GPIO 引脚为输入(上拉),注册下降沿中断回调。按键按下时 ISR 记录事件并通过信号量通知任务,任务打印按键信息。

规格与功能

规格项 说明
引脚 GPIO 输入 + 内部上拉
触发模式 下降沿(按键按下高→低)
ISR 动作 记录时间戳 + sem_up 通知任务
任务动作 sem_down 等待 → printf
消抖 tasklet 50ms(见中断处理篇)

程序运行流程:引脚复用 → GPIO 初始化 → 设方向 + 上拉 → 注册 ISR → 按键按下 → ISR sem_up → 任务打印。

案例流程

sequenceDiagram
    participant K as 按键
    participant G as GPIO
    participant I as ISR
    participant S as 信号量
    participant T as 任务

    K->>G: 按下——电平 高→低
    G->>I: 下降沿触发 ISR
    Note over I: 清中断<br/>记录时间戳
    I->>S: osal_sem_up
    Note over I: ISR 返回 < 5μs

    S->>T: 唤醒任务
    T->>T: printf key pressed

案例操作指导

第一步:编译

fbb build ws63-liteos-app

第二步:烧录和接线

按键一端接 GPIO 引脚,一端接 GND(上拉模式)。

第三步:验证

按一下按键,串口输出 key pressed。快速连按不丢事件(信号量 + 队列配合)。

关键配置

参数 说明
输入模式 上拉 按键接 GND,未按时为高电平
触发模式 下降沿 按键按下瞬间触发
ISR 耗时 < 5μs 只记录时间戳 + 发信号量
消抖 50ms(tasklet) 机械按键典型消抖窗口

代码详解

#include "pinctrl.h"
#include "gpio.h"
#include "soc_osal.h"
#include "osal_semaphore.h"
#include "app_init.h"

static osal_semaphore g_key_sem;
static volatile unsigned int g_key_timestamp = 0;

/* GPIO 中断回调——下降沿触发 */
static void key_isr_callback(pin_t pin, uintptr_t param)
{
    (void)pin;
    (void)param;
    g_key_timestamp = osal_get_tick();   // 记录时间戳
    osal_sem_up(&g_key_sem);             // 非阻塞通知任务
    /* ISR 中绝不调 printf / osal_msleep! */
}

/* 按键处理任务 */
static int key_task_handler(void *data)
{
    (void)data;
    int count = 0;
    while (1) {
        osal_sem_down(&g_key_sem);       // 阻塞等按键
        count++;
        printf("key pressed: %d (tick=%u)\n",
               count, g_key_timestamp);
    }
    return 0;
}

static void key_entry(void)
{
    uapi_pin_set_mode(CONFIG_KEY_PIN, HAL_PIO_FUNC_GPIO);
    uapi_gpio_init();
    uapi_gpio_set_dir(CONFIG_KEY_PIN, GPIO_DIRECTION_INPUT);

    /* 注册下降沿中断 */
    uapi_gpio_register_isr_func(CONFIG_KEY_PIN,
                                GPIO_EDGE_FALLING,
                                key_isr_callback);

    osal_sem_init(&g_key_sem, 0);  // 初值 0

    osal_task *task = osal_kthread_create(
        (osal_kthread_handler)key_task_handler,
        NULL, "KeyTask", 4096);
    osal_kthread_set_priority(task, OSAL_TASK_PRIORITY_HIGH);
}
app_run(key_entry);

GPIO 中断回调签名 void callback(pin_t pin, uintptr_t param) 与 OSAL 中断 osal_irq_handler 不同——uapi_gpio_register_isr_func 封装了底层中断,回调运行在中断上下文中,约束相同。