往期内容
本专栏往期内容:
- input子系统的框架和重要数据结构详解-CSDN博客
- input device和input handler的注册以及匹配过程解析-CSDN博客
- input device和input handler的注册以及匹配过程解析-CSDN博客
- 编写一个简单的Iinput_dev框架-CSDN博客
I2C子系统专栏:
- 专栏地址:IIC子系统_憧憬一下的博客-CSDN博客
- 具体芯片的IIC控制器驱动程序分析:i2c-imx.c-CSDN博客
– 末篇,有往期内容观看顺序总线和设备树专栏:
- 专栏地址:总线和设备树_憧憬一下的博客-CSDN博客
- 设备树与 Linux 内核设备驱动模型的整合-CSDN博客
– 末篇,有往期内容观看顺序
前言
-
Linux 4.x内核
- Documentation\devicetree\bindings\input\gpio-keys.txt📎gpio-keys.txt
- drivers\input\keyboard\gpio_keys.c 📎gpio_keys.c
设备树
- IMX6ULL:Linux-4.9.88/arch/arm/boot/dts/100ask_imx6ull-14x14.dts
内核提供的input device下层的input_de硬件相关驱动,分析的是内核提供给的gpio_keys.c文件,也就是看看其是如何去实现input_dev层的驱动程序的编写的。
1. 驱动程序框架
2. 设备树示例
gpio-keys {
compatible = "gpio-keys";
pinctrl-names = "default";
user1 {
label = "User1 Button";
gpios = <&gpio5 1 GPIO_ACTIVE_LOW>;
# 用GPIO来描述引脚,可以在驱动程序中根据编号来获取注册中断,可以根据电平来判断按键是按下还是松开
#它是用gpiod_to_irq,传入gpio编号获得中断号的
# 使用这种方式使用gpio_isr中断处理函数
gpio-key,wakeup;
linux,code = <KEY_1>; // 具体产生事件的按键, keybit
};
user2 {
label = "User2 Button";
gpios = <&gpio4 14 GPIO_ACTIVE_LOW>;
gpio-key,wakeup;
linux,code = <KEY_2>;
};
};
gpio-keys@0 {
compatible = "gpio-keys";
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_gpio_keys>;
status = "okay";
Key0{
label = "Key 0";
gpios = <&gpio5 1 GPIO_ACTIVE_HIGH>; # 有效电平
linux,code = <KEY_1>;
};
};
gpio-keys@1 {
compatible = "gpio-keys";
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_gpio_key1>;
status = "okay";
Key0{
label = "Key 1";
gpios = <&gpio1 18 GPIO_ACTIVE_HIGH>;
linux,code = <KEY_2>;
};
};
3. gpio_keys.c驱动程序分析
-
Linux 4.x内核
- Documentation\devicetree\bindings\input\gpio-keys.txt📎gpio-keys.txt
- drivers\input\keyboard\gpio_keys.c📎gpio_keys.c
3.1 套路
-
根据设备树获得硬件信息:哪个GPIO、对于什么按键
-
分配/设置/注册input_dev结构体
-
request_irq: 在中断处理函数中确定按键值、上报按键值
- 有两种IRQ函数
- gpio_keys_gpio_isr:设备树中的用gpios来描述用到的引脚
- gpio_keys_irq_isr:设备树中的用interrupts来描述用到的引脚
3.2 probe函数
在之前讲解input_dev下层框架编写的时候有提到过,在这里进一步讲解。
static int gpio_keys_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev; // 获取设备结构体
const struct gpio_keys_platform_data *pdata = dev_get_platdata(dev); // 从平台设备获取平台数据
struct gpio_keys_drvdata *ddata; // 驱动私有数据结构
struct input_dev *input; // 输入设备结构体
size_t size; // 计算需要分配的内存大小
int i, error; // 循环计数器和错误码
int wakeup = 0; // 标志位,指示是否支持唤醒功能
// 如果 pdata 为 NULL,则从设备树获取平台数据
if (!pdata) {
pdata = gpio_keys_get_devtree_pdata(dev);
if (IS_ERR(pdata))
return PTR_ERR(pdata); // 返回错误码
}
// 计算 gpio_keys_drvdata 和按钮数据结构的大小
size = sizeof(struct gpio_keys_drvdata) +
pdata->nbuttons * sizeof(struct gpio_button_data);
// 分配驱动私有数据内存
ddata = devm_kzalloc(dev, size, GFP_KERNEL);
if (!ddata) {
dev_err(dev, "failed to allocate state\n"); // 分配失败,输出错误信息
return -ENOMEM; // 返回内存不足错误码
}
// 分配输入设备
input = devm_input_allocate_device(dev);
if (!input) {
dev_err(dev, "failed to allocate input device\n"); // 分配失败,输出错误信息
return -ENOMEM; // 返回内存不足错误码
}
ddata->pdata = pdata; // 保存平台数据指针
ddata->input = input; // 保存输入设备指针
mutex_init(&ddata->disable_lock); // 初始化互斥锁
// 将驱动数据指针与平台设备相关联
platform_set_drvdata(pdev, ddata);
input_set_drvdata(input, ddata); // 将驱动数据与输入设备关联
// 设置输入设备名称和物理路径
input->name = pdata->name ? : pdev->name; // 如果 pdata 中有名称则使用,否则使用平台设备名称
input->phys = "gpio-keys/input0"; // 设置物理路径
input->dev.parent = &pdev->dev; // 设置设备的父设备
input->open = gpio_keys_open; // 设置打开设备的函数
input->close = gpio_keys_close; // 设置关闭设备的函数
// 设置输入设备的 ID
input->id.bustype = BUS_HOST; // 设置总线类型
input->id.vendor = 0x0001; // 设置厂商 ID
input->id.product = 0x0001; // 设置产品 ID
input->id.version = 0x0100; // 设置版本号
// 启用 Linux 输入子系统的自动重复功能
if (pdata->rep)
__set_bit(EV_REP, input->evbit); // 设置 EV_REP 事件位
// 遍历每个按钮并设置
for (i = 0; i < pdata->nbuttons; i++) {
const struct gpio_keys_button *button = &pdata->buttons[i]; // 获取当前按钮信息
struct gpio_button_data *bdata = &ddata->data[i]; // 获取按钮数据
error = gpio_keys_setup_key(pdev, input, bdata, button); // 设置按键,里面包括设置了中断函数
//--------(1)
if (error)
return error; // 返回错误码
if (button->wakeup) // 如果按钮支持唤醒功能
wakeup = 1; // 设置唤醒标志
}
// 创建 sysfs 组,用于导出按键和开关
error = sysfs_create_group(&pdev->dev.kobj, &gpio_keys_attr_group);
if (error) {
dev_err(dev, "Unable to export keys/switches, error: %d\n", error); // 输出错误信息
return error; // 返回错误码
}
// 注册输入设备
error = input_register_device(input);
if (error) {
dev_err(dev, "Unable to register input device, error: %d\n", error); // 输出错误信息
goto err_remove_group; // 错误处理,移除 sysfs 组
}
// 初始化唤醒设备功能
device_init_wakeup(&pdev->dev, wakeup);
return 0; // 返回成功
err_remove_group:
// 在错误情况下移除 sysfs 组
sysfs_remove_group(&pdev->dev.kobj, &gpio_keys_attr_group);
return error; // 返回错误码
}
其中error = gpio_keys_setup_key(pdev, input, bdata, button);
:
static int gpio_keys_setup_key(struct platform_device *pdev,
struct input_dev *input,
struct gpio_button_data *bdata,
const struct gpio_keys_button *button)
{
const char *desc = button->desc ? button->desc : "gpio_keys"; // 设置按钮描述
struct device *dev = &pdev->dev; // 获取设备结构体
irq_handler_t isr; // 中断处理程序
unsigned long irqflags; // 中断标志
int irq; // 中断号
int error; // 错误码
bdata->input = input; // 保存输入设备指针
bdata->button = button; // 保存按钮数据指针
spin_lock_init(&bdata->lock); // 初始化自旋锁
/*
* 处理传统的 GPIO 编号,获取 GPIO 资源并
* 转换为 GPIO 描述符。
*/
if (gpio_is_valid(button->gpio)) { // 检查 GPIO 是否有效
unsigned flags = GPIOF_IN; // 设置 GPIO 为输入模式
if (button->active_low) // 如果按钮是低电平有效
flags |= GPIOF_ACTIVE_LOW; // 设置活动低标志
// 请求 GPIO 资源
error = devm_gpio_request_one(&pdev->dev, button->gpio, flags, desc);
if (error < 0) { // 请求失败
dev_err(dev, "Failed to request GPIO %d, error %d\n",
button->gpio, error);
return error; // 返回错误码
}
// 将 GPIO 转换为 GPIO 描述符
bdata->gpiod = gpio_to_desc(button->gpio);
if (!bdata->gpiod) // 如果转换失败
return -EINVAL; // 返回无效参数错误
// 设置防抖动间隔
if (button->debounce_interval) {
error = gpiod_set_debounce(bdata->gpiod,
button->debounce_interval * 1000);
/* 如果 gpiolib 不支持防抖动,则使用定时器 */
if (error < 0)
bdata->software_debounce = button->debounce_interval; // 使用软件防抖动
}
// 设置中断号
if (button->irq) {
bdata->irq = button->irq; // 如果已指定 IRQ,直接使用
} else {
// 获取 GPIO 的中断号
irq = gpiod_to_irq(bdata->gpiod);
if (irq < 0) { // 如果获取失败
error = irq; // 保存错误码
dev_err(dev,
"Unable to get irq number for GPIO %d, error %d\n",
button->gpio, error);
return error; // 返回错误码
}
bdata->irq = irq; // 保存中断号
}
// 初始化延迟工作队列
INIT_DELAYED_WORK(&bdata->work, gpio_keys_gpio_work_func);
isr = gpio_keys_gpio_isr; // 设置中断处理程序
irqflags = IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING; // 设置中断触发方式
} else { // 如果 GPIO 无效
if (!button->irq) { // 如果没有指定 IRQ
dev_err(dev, "No IRQ specified\n");
return -EINVAL; // 返回无效参数错误
}
bdata->irq = button->irq; // 使用指定的 IRQ
if (button->type && button->type != EV_KEY) { // 检查按钮类型
dev_err(dev, "Only EV_KEY allowed for IRQ buttons.\n");
return -EINVAL; // 返回无效参数错误
}
bdata->release_delay = button->debounce_interval; // 设置释放延迟
setup_timer(&bdata->release_timer,
gpio_keys_irq_timer, (unsigned long)bdata); // 设置定时器
isr = gpio_keys_irq_isr; // 设置中断处理程序
irqflags = 0; // 没有中断标志
}
// 设置输入设备的能力
input_set_capability(input, button->type ?: EV_KEY, button->code);
/*
* 安装自定义操作以取消释放定时器和
* 工作队列项。
*/
error = devm_add_action(&pdev->dev, gpio_keys_quiesce_key, bdata);
if (error) {
dev_err(&pdev->dev,
"failed to register quiesce action, error: %d\n",
error);
return error; // 返回错误码
}
/*
* 如果平台指定按钮可以禁用,
* 我们不希望它共享中断线。
*/
if (!button->can_disable)
irqflags |= IRQF_SHARED; // 设置共享标志
// 请求、注册中断
error = devm_request_any_context_irq(&pdev->dev, bdata->irq,
isr, irqflags, desc, bdata);
if (error < 0) { // 请求失败
dev_err(dev, "Unable to claim irq %d; error %d\n",
bdata->irq, error);
return error; // 返回错误码
}
return 0; // 返回成功
}
其中设置中断处理函数主要是分两种方法:
-
一种是使用传统的GPIO编号来请、设置中断,其采用的中断处理函数是
gpio_keys_gpio_isr
- 如果
button->gpio
是有效的 GPIO 编号,代码会使用devm_gpio_request_one
来请求 GPIO 并转换为 GPIO 描述符。 - 接下来,它通过
gpiod_to_irq
获取 GPIO 的中断号,通常采用 GPIO 的上升沿和下降沿触发来处理中断,这种方式的中断处理函数是gpio_keys_gpio_isr
。
- 如果
-
另一种则是使用中断控制器的方式来设置GPIO引脚的中断处理函数
gpio_keys_irq_isr
- 如果不使用传统的 GPIO 编号,且提供了中断号(
button->irq
),代码会直接使用这个中断号,而不是通过 GPIO 获取中断号。 - 在这种情况下,按钮类型必须是
EV_KEY
,并且代码会将gpio_keys_irq_isr
设置为中断处理函数。
- 如果不使用传统的 GPIO 编号,且提供了中断号(
这两种方式的中断处理函数另开小点解析,看下文
3.3 gpio_keys_gpio_isr分析
理想状况是:按下、松开按键,各产生一次中断,也只产生一次中断。但是对于机械开关,它的金属弹片会反复震动。GPIO电平会反复变化,最后才稳定。一般是几十毫秒才会稳定。如果不处理抖动的话,用户只操作一次按键,会发生多次中断,驱动程序可能会上报多个数据。
怎么处理按键抖动?
- 在按键中断程序中,可以循环判断几十亳秒,发现电平稳定之后再上报
- 使用定时器
显然第1种方法太耗时,违背“中断要尽快处理”的原则,你的系统会很卡。
怎么使用定时器?看下图:
核心在于:在GPIO中断中并不立刻记录按键值,而是修改定时器超时时间,10ms后再处理。如果10ms内又发生了GPIO中断,那就认为是抖动,这时再次修改超时时间为10ms。只有10ms之内再无GPIO中断发生,那么定时器的函数才会被调用。在定时器函数中上报按键值。
接下来看看函数:
static irqreturn_t gpio_keys_gpio_isr(int irq, void *dev_id)
{
struct gpio_button_data *bdata = dev_id; // 将传入的设备 ID 转换为 gpio_button_data 结构体指针
BUG_ON(irq != bdata->irq); // 如果中断号不匹配,则触发内核故障
// 如果按钮支持唤醒功能,则保持设备唤醒
if (bdata->button->wakeup)
pm_stay_awake(bdata->input->dev.parent);
// 在延迟工作队列中安排工作,以处理按钮按下事件
mod_delayed_work(system_wq,
&bdata->work,
msecs_to_jiffies(bdata->software_debounce)); // 使用软件去抖动时间
//函数mod_delayed_work --------(1)
//留意一下函数mod_delayed_work的参数bdata->work函数 -----(2)
return IRQ_HANDLED; // 表示中断已被处理
}
(1)mod_delayed_work
:
\Linux-4.9.88\kernel\workqueue.c
static inline bool mod_delayed_work(struct workqueue_struct *wq,
struct delayed_work *dwork,
unsigned long delay)
{
return mod_delayed_work_on(WORK_CPU_UNBOUND, wq, dwork, delay);
}
//mod_delayed_work_on如下:
bool mod_delayed_work_on(int cpu, struct workqueue_struct *wq,
struct delayed_work *dwork, unsigned long delay)
{
unsigned long flags; // 用于保存中断状态的变量
int ret; // 用于保存返回值的变量
do {
// 尝试获取 pending 状态(即检查该工作是否已经被排队)
// 如果工作正在处理,ret 将为 -EAGAIN
ret = try_to_grab_pending(&dwork->work, true, &flags);
} while (unlikely(ret == -EAGAIN)); // 如果返回 -EAGAIN,继续尝试
if (likely(ret >= 0)) { // 如果成功获取 pending 状态
__queue_delayed_work(cpu, wq, dwork, delay); // 将延迟工作加入指定的工作队列
local_irq_restore(flags); // 恢复之前保存的中断状态
}
/* -ENOENT from try_to_grab_pending() becomes %true */
return ret; // 返回 ret 的值,-ENOENT 将被视为 true(表示工作未找到)
}
- 其中
__queue_delayed_work(cpu, wq, dwork, delay);
如下:
static void __queue_delayed_work(int cpu, struct workqueue_struct *wq,
struct delayed_work *dwork, unsigned long delay)
{
struct timer_list *timer = &dwork->timer; // 获取定时器
struct work_struct *work = &dwork->work; // 获取工作结构体
// 检查工作队列、定时器的函数指针和数据、定时器是否正在使用以及工作结构体是否为空
WARN_ON_ONCE(!wq); // 确保工作队列存在
WARN_ON_ONCE(timer->function != delayed_work_timer_fn ||
timer->data != (unsigned long)dwork); // 确保定时器正确设置
WARN_ON_ONCE(timer_pending(timer)); // 确保定时器没有正在运行
WARN_ON_ONCE(!list_empty(&work->entry)); // 确保工作结构体未在其他列表中
/*
* 如果 @delay 为 0,则立即将 @dwork->work 入队。这是为了优化和正确性。
* 定时器最早的到期时间是下一个时钟滴答,因此当 @delay 为 0 时,用户依赖于
* 不存在延迟。
*/
if (!delay) {
__queue_work(cpu, wq, &dwork->work); // 直接入队工作
return; // 退出函数
}
// 设置定时器的启动信息
timer_stats_timer_set_start_info(&dwork->timer);
// 设置工作队列和 CPU
dwork->wq = wq;
dwork->cpu = cpu;
// 计算定时器到期时间
timer->expires = jiffies + delay; // 将定时器的到期时间设定为当前时间加上延迟
// 如果指定的 CPU 不为 WORK_CPU_UNBOUND,使用该 CPU 添加定时器
if (unlikely(cpu != WORK_CPU_UNBOUND))
add_timer_on(timer, cpu); // 在指定的 CPU 上添加定时器
else
add_timer(timer); // 否则在全局上下文中添加定时器
}
- 大概就是汇聚中断一起处理,因为按键可能会产生抖动,也就是会有多个中断连续发送,所以需要汇聚起来再去判断,通过
add_timer / add_timer_on
添加定时器,没中断了,超时了就会去调用timer->function,也就是之前在probe() --->gpio_keys_setup_key()
时对data->work->timer->function
所设置的,具体看下面
(2)来看一下mod_delayed_work
函数的参数&bdata->work
:
- probe函数中的gpio_keys_setup_key函数中有设置:
NIT_DELAYED_WORK(&bdata->work, gpio_keys_gpio_work_func);
就是将bdata->work->timer->function
设置为gpio_keys_gpio_work_func
函数:
static void gpio_keys_gpio_work_func(struct work_struct *work)
{
// 使用 container_of 宏获取包含该工作结构的 gpio_button_data 结构指针
struct gpio_button_data *bdata =
container_of(work, struct gpio_button_data, work.work);
// 调用函数报告 GPIO 按钮事件
gpio_keys_gpio_report_event(bdata);
// 如果按钮被设置为唤醒,调用 pm_relax 以允许系统休眠
if (bdata->button->wakeup)
pm_relax(bdata->input->dev.parent);
}
汇聚中断:为了避免在抖动期间产生过多的中断和不必要的输入事件,内核通过工作队列(work queue)机制来汇聚中断事件。即在中断处理程序中,通常只会安排一个工作项来稍后处理,而不是立即进行事件处理。
延迟工作:gpio_keys_gpio_work_func
函数的执行是在中断上下文之外的工作队列上下文中进行的。这样,工作函数可以在一定的延迟后统一处理按钮的状态,而不是逐个处理中断。这种做法可以有效减少由于抖动引起的错误输入。
工作流程
- 中断触发:当 GPIO 按钮被按下或松开时,会触发中断。
- 中断处理程序:在中断处理程序中,调用
mod_delayed_work
函数,将按钮的工作项放入工作队列中,并设置一个去抖动的延迟时间(如bdata->software_debounce
)。 - 汇聚与判断:工作队列中的
gpio_keys_gpio_work_func
函数会在指定的延迟后被执行。在此函数中,按钮的状态会被检查和更新,确保只有最终的有效状态会被报告。
3.4 gpio_keys_irq_isr分析
有个变量key_pressed,用来表示当前按键状态:初始值是false,表示按键没有被按下。
-
发生中断
- 上报"按下的值":input_event(input, EV_KEY, button->code, 1); input_sync(input);
- 如果不延迟(!bdata->release_delay)
- 马上上报"松开的值":input_event(input, EV_KEY, button->code, 0); input_sync(input);
- 如果延迟(bdata->release_delay)
- 启动定时器,过若干毫秒再上报"松开的值"
-
所以,使用gpio_keys_irq_isr时,一次中断就会导致上报2个事件:按下、松开
-
缺点:无法准确判断一个按键确实已经被松开了
static irqreturn_t gpio_keys_irq_isr(int irq, void *dev_id)
{
// 从 dev_id 中获取按钮数据结构
struct gpio_button_data *bdata = dev_id;
const struct gpio_keys_button *button = bdata->button; // 获取按钮信息
struct input_dev *input = bdata->input; // 获取输入设备
unsigned long flags; // 用于保存中断标志
// 确保触发的 IRQ 与当前按钮的 IRQ 匹配
BUG_ON(irq != bdata->irq);
// 加锁以保护共享数据,保存当前中断状态
spin_lock_irqsave(&bdata->lock, flags);
// 如果按键尚未被按下
if (!bdata->key_pressed) {
// 如果按钮被标记为可以唤醒设备
if (bdata->button->wakeup)
pm_wakeup_event(bdata->input->dev.parent, 0); // 触发设备唤醒
// 发送按下事件
input_event(input, EV_KEY, button->code, 1); // 1 表示按下
input_sync(input); // 确保事件被发送
// 如果没有设置释放延迟
if (!bdata->release_delay) {
// 立即发送松开事件
input_event(input, EV_KEY, button->code, 0); // 0 表示松开
input_sync(input); // 确保事件被发送
goto out; // 跳转到解锁部分
}
// 如果设置了释放延迟,标记按键为已按下
bdata->key_pressed = true;
}
// 如果设置了释放延迟,则重新设置释放定时器
if (bdata->release_delay)
mod_timer(&bdata->release_timer,
jiffies + msecs_to_jiffies(bdata->release_delay)); // 设置定时器
out:
// 解锁并恢复中断状态
spin_unlock_irqrestore(&bdata->lock, flags);
return IRQ_HANDLED; // 返回中断已处理
}
3.5 两函数对比
- 中断处理函数的基本概念
- 中断处理函数是操作系统内核用来处理硬件中断信号的程序。当特定的事件发生(如按键被按下或松开),中断处理函数会被调用以响应这些事件。
gpio_keys_gpio_isr
函数
-
主要目标:处理来自 GPIO 按键的中断,并且利用定时器去抖动。
-
按键状态管理:没有持久化按键状态(如
key_pressed
)。 -
中断触发:
- 当 GPIO 按键被按下时,触发中断。
-
处理中断:
- 调用
mod_delayed_work
函数,将按键事件处理的工作项排入工作队列,设置延迟时间(如bdata->software_debounce
)。 - 在这个延迟期间,如果再有中断触发,则认为是抖动,再次修改定时器超时时间。
- 调用
-
事件报告:
- 在延迟工作函数(
gpio_keys_gpio_work_func
)中,将最终的按键状态(按下或松开)报告给输入子系统。
- 在延迟工作函数(
-
优点:
- 通过汇聚中断和使用定时器,可以有效处理按键抖动。
- 减少了对系统的压力,因为不需要立即处理每次中断。
-
缺点:
- 可能会引入一些延迟,尤其是在高频率按键操作时。
gpio_keys_irq_isr
函数
-
主要目标:直接处理 GPIO 按键的按下和释放事件,同时管理按键状态(
key_pressed
)。 -
中断触发:
- 当 GPIO 按键被按下时,触发中断。
-
处理中断:
-
检查按键当前是否已经被标记为按下(
key_pressed
)。 -
如果未按下,发送按下事件(
input_event(input, EV_KEY, button->code, 1)
),并同步输入设备。 -
检查是否有释放延迟(
release_delay
):- 如果没有,立即发送松开事件(
input_event(input, EV_KEY, button->code, 0)
)。 - 如果有,启动释放定时器,以便稍后报告松开事件。
- 如果没有,立即发送松开事件(
-
-
按键状态管理:
- 在按键被按下时,将
key_pressed
标志设置为true
,以防止多次报告按下事件。
- 在按键被按下时,将
-
优点:
- 可以立即报告按下和松开事件,适合于不需要去抖动的场景。
- 更加直接,适用于简单的按键逻辑。
-
缺点:
- 无法准确判断一个按键是否真正松开,可能会导致误报。
- 由于直接处理每个中断,可能会在按键抖动期间产生大量中断,增加了 CPU 的负担。
特性 | gpio_keys_gpio_isr | gpio_keys_irq_isr |
---|---|---|
中断触发 | 由 GPIO 引脚的状态变化触发 | 由中断控制器或 GPIO 引脚的状态变化触发 |
事件处理 | 将工作项排入工作队列,并使用定时器进行去抖动 | 立即报告按下和松开事件,并管理按键状态 |
事件数量 | 通常处理一次中断后只报告一个最终事件 | 一次中断可能报告两个事件(按下和松开) |
状态管理 | 无状态管理,依赖工作队列中延迟处理 | 使用 key_pressed 标志进行状态管理 |
去抖动机制 | 使用定时器进行去抖动,汇聚中断 | 直接在中断中处理,不具备去抖动机制 |
gpio_keys_gpio_isr
适用于需要去抖动和减少中断负担的场景,通过使用定时器和工作队列来延迟事件处理,避免由于抖动而产生的重复事件。gpio_keys_irq_isr
更加直接,可能会在高频率按键操作中引入问题,因为它无法正确处理抖动并可能导致错误的输入报告。