GPIO-KEY的实现原理及使用方法

本文详细介绍了Linux下使用GPIO作为按键的实现原理及其使用方法,包括定义GPIO按键、注册到系统、匹配驱动的过程,以及如何在应用程序中读取按键值。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

本文将以imx6q的板子和相应BSP代码来详细描述在linux下, 使用GPIO当做按键的实现原理及使用方法。

Linux 内核下的 drivers/input/keyboard/gpio_keys.c实现了一个体系结构无关的GPIO按键驱动,使用此按键驱动,只需在相应的板级支持包中(imx6q的是board-mx6q-sabresd.c)定义相关的数据即可。驱动的实现非常简单,但是较适合于实现独立式按键驱动。

gpio-keys是基于input架构实现的一个通用GPIO按键驱动。该驱动基于platform_driver架构,实现了驱动和设备分离,符合Linux设备驱动模型的思想。工程中的按键驱动我们一般都会基于gpio-keys来写,所以我们有必要对gpio_keys进行分析。

一.   GPIO-KEY的实现原理

1.      定义GPIO按键:

首先定义一个gpio_keys_button的数组, 该类型定义了一个具体的GPIO按键信息

arch\arm\mach-mx6\board-mx6q-sabresd.c:

static struct gpio_keys_buttonsabresd_buttons[] = {

GPIO_BUTTON(SABRESD_VOLUME_UP,KEY_VOLUMEUP, 1, "volume-up", 0, 1),

GPIO_BUTTON(SABRESD_VOLUME_DN,KEY_POWER, 1, "volume-down", 1, 1),

 

/*addby aaron 2015.6.29*/

#ifdef FEATURE_YUTONG_ICARD2

GPIO_BUTTON(SABRESD_KEY_MENU,248, 1, "menu-key", 0, 3),

GPIO_BUTTON(SABRESD_KEY_UP,249, 1, "up-key", 0, 3),

GPIO_BUTTON(SABRESD_KEY_DOWN,250, 1, "down-key", 0, 3),

GPIO_BUTTON(SABRESD_KEY_CONFIRM,251, 1, "confirm-key", 0, 3),

GPIO_BUTTON(SABRESD_KEY_ICCARD_DET,247, 1, "iccard-det", 0, 3),

#endif

/*endby aaron*/

};

 

struct gpio_keys_button类型如下, 这是对按键的描述:

include\linux\gpio_keys.h:

struct gpio_keys_button {

/*Configuration parameters */

unsignedint code;   /* input event code (KEY_*,SW_*) */

intgpio;

intactive_low;

constchar *desc;

unsignedint type;    /* input event type (EV_KEY,EV_SW, EV_ABS) */

int wakeup;               /* configure the button as awake-up source */

intdebounce_interval;    /* debounce ticksinterval in msecs */

boolcan_disable;

intvalue;           /* axis value for EV_ABS*/

};

 

宏 GPIO_BUTTON()就是初始化每个gpio_key的描述:

arch\arm\mach-mx6\board-mx6q-sabresd.c:

#define GPIO_BUTTON(gpio_num, ev_code,act_low, descr, wake, debounce)      \

{                                                                           \

.gpio                   = gpio_num,                                 \

.type                  = EV_KEY,                            \

.code                  = ev_code,                                    \

.active_low      = act_low,                                     \

.desc                  = "btn " descr,                             \

.wakeup            = wake,                                         \

.debounce_interval= debounce,                                 \

}

 

接着定义一个gpio_keys_platform_data变量:

arch\arm\mach-mx6\board-mx6q-sabresd.c:

staticstruct gpio_keys_platform_data new_sabresd_button_data = {

         .buttons   = new_sabresd_buttons,

         .nbuttons = ARRAY_SIZE(new_sabresd_buttons),

};

 

struct gpio_keys_platform_data的定义也在gpio_keys.h中:

include\linux\gpio_keys.h:

structgpio_keys_platform_data {

         struct gpio_keys_button *buttons;

         int nbuttons;

         unsigned int poll_interval;       /* polling interval in msecs -

                                                  for polling driver only */

         unsigned int rep:1;           /* enable input subsystem auto repeat*/

         int (*enable)(struct device *dev);

         void (*disable)(struct device *dev);

         const char *name;            /* input device name */

};

 

2.      把 1 中定义的new_sabresd_button_data 注册到系统中去:

我们把gpio_key当成一个platform_device设备, 所以要先定义一个platform_device设备:

arch\arm\mach-mx6\board-mx6q-sabresd.c:

staticstruct platform_device sabresd_button_device = {

         .name                ="gpio-keys", /*名字非常关键, 找驱动就靠它来匹配了*/

         .id              =-1,

         .num_resources  = 0,

};

 

然后把我们定义的gpio_key 跟这个platform_device设备绑定在一起:

arch\arm\mach-mx6\board-mx6q-sabresd.c:

platform_device_add_data(&sabresd_button_device,

                    &new_sabresd_button_data,

                             sizeof(new_sabresd_button_data));

 

最后注册到系统中去:

arch\arm\mach-mx6\board-mx6q-sabresd.c:

platform_device_register(&sabresd_button_device);

 

其中, platform_device_add_data()函数如下:

drivers/base/platform.c:

intplatform_device_add_data(struct platform_device *pdev, const void *data,

                                 size_t size)

{

         void *d = NULL;

 

         if (data) {

                   d = kmemdup(data, size,GFP_KERNEL); /*分配memory,并把data的内容拷贝进去*/

                   if (!d)

                            return -ENOMEM;

         }

 

         kfree(pdev->dev.platform_data);

         pdev->dev.platform_data = d;  /*把gpio_key绑定到这个platform_device上去*/

         return 0;

}

 

3.      匹配驱动:

当用platform_device_register()把 gpio_key的platform_device的添加到系统中去后, 系统会去匹配是否有合适的驱动, 这里对应的驱动就是:gpio_keys.c, 很显然这是一个platform_driver的驱动:

drivers/input/keyboard/gpio_keys.c:

static struct platform_drivergpio_keys_device_driver = {

.probe                = gpio_keys_probe,

.remove            = __devexit_p(gpio_keys_remove),

.driver                = {

           .name       = "gpio-keys",   /*发现没有, 名字跟设备的名字一模一样*/

           .owner     = THIS_MODULE,

#ifdef CONFIG_PM

           .pm  = &gpio_keys_pm_ops,

#endif

}

};

系统能找到这个驱动, 主要就是因为他们的名字都是: gpio-keys, 这个很关键。 接下来我们就来分析一下这个驱动, 首先找到设备后, 会调用probe函数, 这里就是gpio_keys_probe();

在gpio_keys_probe()函数中, 会注册一个input设备, 并创建相应的设备文件。

 

二. GPIO_KEY使用

使用方式比较简单,和普通的文件操作一样, 先打开设备文件, 再读文件获取键值即可:

1.      打开设备文件,

我的设备上gpio_key对应的设备文件是/dev/input/event0, 不同的平台设备文件可能会有差异, 如果不清楚对应的设备文件, 可以用下面的命令来查看:

 

打开设备代码如下:

/*1.key device*/

fd_key= open(KEY_DEVICE_FILE, O_RDONLY);

if(fd_key< 0) {

           LOGE("can'topen key device file");

           returnfd_key;

}

 

2.      获取按键值及按键类型:

 

         struct input_event key_evt;

 

monitor_key:

         ret = read(fd_key, (unsigned char*)&key_evt, sizeof(struct input_event)); /*阻塞型读函数*/

         if(ret < 0) {

                   LOGE("read key eventfailed :%d", ret);

         }

         /*filter unknown key*/

         else if(key_evt.code != 248&&     /*KEY_MENU*/ 

                             key_evt.code != 249 &&    /*KEY_UP*/

                             key_evt.code != 250 &&    /*KEY_DOWN*/

                             key_evt.code != 251 &&    /*KEY_CONFIRM*/

                             key_evt.code != 247){     /*ICCARD detect pin*/

                   LOGE("unknown key code:%d", key_evt.code);

                   goto monitor_key;

         }

         else {  /*valid key*/

           /*

            * key_val[0..7] = key code

            * key_val[8] = key value: 0 - released, 1 - pressed.

            */

                   key_val = ((int8_t)key_evt.value<< 8) | ((uint8_t)key_evt.code);

                   //LOGE("get key eventcode:%d, value:%d, type:%d, %d", key_evt.code, key_evt.value,key_evt.type, key_val);

         }

 

         return key_val;

 

实际上就是调用一个阻塞型的读函数, 所以这个函数尽量放在单独的一个线程中处理, 键值就是在前面板级支持包中定义并注册的值。

 

<think>好的,用户问到了`gpio-key,wakeup`的作用,我需要详细解释这个属性在设备树中的用途。首先,我应该回忆一下Linux设备树中关于GPIO按键的配置选项。记得`gpio-key,wakeup`通常用于指定该按键是否能够唤醒系统。 接下来,我需要确定用户可能的背景。用户之前询问过GPIO按键被识别为两次按下的问题,现在进一步询问特定属性的作用,可能是想深入理解设备树配置,或者解决与唤醒相关的问题。用户可能正在调试电源管理或系统唤醒功能,或者遇到了按键唤醒不工作的情况。 用户的问题直接指向`gpio-key,wakeup`的作用,我需要明确这个属性的功能。根据Linux内核文档,这个属性用于标记该GPIO按键是否可以作为唤醒源,当系统处于睡眠状态时,按下该按键可以唤醒系统。需要确认这个属性如何在内核中处理,以及相关的配置要求。 同时,我需要考虑用户可能的误解,比如是否所有GPIO都支持唤醒功能,或者是否需要额外的硬件或软件配置。例如,某些SoC可能要求特定的GPIO引脚才能作为唤醒源,或者需要配置中断类型为边沿触发。 还要提醒用户在使用这个属性时需要注意的事项,比如确保GPIO控制器支持唤醒功能,正确配置中断触发方式,以及内核配置中是否启用了唤醒源支持。此外,可能还需要检查电源管理框架的配置,比如是否启用了suspend-to-RAM等功能。 最后,应该给出一个实际的设备树配置示例,说明如何正确使用`gpio-key,wakeup`属性,并解释每个相关部分的作用。这样用户可以参考示例进行配置,避免常见的错误。</think>在Linux设备树配置中,`gpio-key,wakeup` 是一个特殊属性,用于**控制按键的唤醒功能**。以下是深度解析: --- ### 一、核心作用 `gpio-key,wakeup` 属性标记该GPIO按键是否具备**唤醒系统**的能力。当系统处于以下低功耗状态时: - **挂起到内存**(Suspend-to-RAM) - **挂起到空闲**(Suspend-to-Idle) - **深度休眠状态** 配置了该属性的按键被按下后,会触发**唤醒中断**,将系统恢复到正常工作状态。 --- ### 二、技术实现原理 1. **中断配置** GPIO按键驱动会将该GPIO配置为**边沿触发中断**(上升沿/下降沿),具体由 `GPIO_ACTIVE_LOW/HIGH` 决定。 2. **电源管理集成** 内核电源管理子系统(PM)会为带有 `wakeup` 标志的GPIO注册**唤醒源**(Wakeup Source),例如: ```c device_init_wakeup(dev, true); // 驱动中实际调用的API ``` 3. **硬件要求** - GPIO控制器必须支持**保持唤醒能力**(部分SoC只有特定GPIO引脚支持) - 需要正确配置GPIO复用器(Pinmux)的**低功耗状态保持** --- ### 三、典型应用场景 1. **手机/平板电源键** 通过`gpio-key,wakeup`实现按电源键唤醒屏幕 2. **嵌入式设备遥控唤醒** 红外遥控器通过GPIO按键唤醒休眠设备 3. **工业设备快速恢复** 在低功耗模式下通过物理按键立即唤醒系统 --- ### 四、配置示例与注意事项 #### 设备树片段 ```dts vol_up { ... gpios = <&portb 16 GPIO_ACTIVE_LOW>; gpio-key,wakeup; // 关键配置 ... }; ``` #### 必须配合的配置 1. **内核配置** 确保启用以下选项: ```kconfig CONFIG_PM=y CONFIG_PM_SLEEP=y CONFIG_INPUT_KEYBOARD=y CONFIG_KEYBOARD_GPIO=y ``` 2. **硬件验证** 使用 `cat /sys/kernel/debug/wakeup_sources` 查看已注册唤醒源: ```bash # 示例输出 name active_count event_count wakeup_count active_since gpio-keys 3 5 2 0 ``` 3. **调试命令** 查看唤醒事件统计: ```bash dmesg | grep "wakeup by IRQ" ``` --- ### 五、常见问题排查 1. **无法唤醒系统** - 检查GPIO是否支持唤醒功能(查阅SoC手册) - 确认系统实际进入了支持唤醒的休眠状态 - 使用示波器测量GPIO电平是否达到触发条件 2. **意外唤醒** 添加 `debounce-interval` 去抖配置(推荐30-50ms): ```dts debounce-interval = <50>; ``` 3. **唤醒后系统不稳定** 检查GPIO中断处理函数是否包含`IRQF_NO_SUSPEND`标志: ```c // 正确的中断注册应包含此标志 request_irq(irq, handler, IRQF_TRIGGER_FALLING | IRQF_NO_SUSPEND, ...); ``` --- ### 六、进阶开发提示 1. **多级唤醒控制** 通过 `wakeup-source` 属性与 `/sys/power/wake_lock` 配合,实现分级唤醒策略 2. **功耗优化** 唤醒后及时调用 `pm_relax()` 释放唤醒锁,避免阻止系统再次休眠 3. **安全机制** 对关键唤醒按键实施**防误触算法**(如长按1秒才触发唤醒) 通过合理使用 `gpio-key,wakeup`,可以构建高效的电源管理系统,典型场景下可将设备待机功耗降低至微安级。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值