GPIO 输入
学习目标
- 掌握 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
案例操作指导
第一步:编译
第二步:烧录和接线
按键一端接 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封装了底层中断,回调运行在中断上下文中,约束相同。