48 使用linux内核源码里的按键轮询驱动<Polled GPIO buttons>

本文介绍了一种基于Linux内核的轮询型GPIO按键驱动的设计与实现过程,包括配置选项、驱动源码分析及平台设备描述等关键内容。

这个设备驱动适用于,每个按键是连接到一个io口, 驱动里是通过定时查询io口的电平来确定按键的状态的,不使用io口的中断功能/
需要在linux内核配置里选上相关的配置。在内核源码目录下:

    make menuconfig ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf-

    Device Drivers  --->
        Input device support  --->
            [*]   Keyboards  --->
                <*>   Polled GPIO buttons
选择上后,再编内核,再使用新的内核镜像启动系统

使用新内核启动后,可以查看出设备驱动是否已选择上:
/sys/bus/platform/drivers/目录下应有”gpio-keys-polled”目录

驱动源码在”drivers/input/keyboard/gpio_keys_polled.c”, 里面是一个平台驱动,我们只要写平台设备描述硬件的资源与此驱动匹配即可.

236 static struct platform_driver gpio_keys_polled_driver = {
237     .probe  = gpio_keys_polled_probe,
238     .remove = __devexit_p(gpio_keys_polled_remove),
239     .driver = {
240         .name   = DRV_NAME, // "gpio-keys-polled"
241         .owner  = THIS_MODULE,
242     },
243 };
244 module_platform_driver(gpio_keys_polled_driver);


//通过阅读平台驱动的probe函数,可得知我们写的平台设备应提供具本哪些硬件信息.
103 static int __devinit gpio_keys_polled_probe(struct platform_device *pdev)
104 {
105     struct gpio_keys_platform_data *pdata = pdev->dev.platform_data;
106     struct device *dev = &pdev->dev;
107     struct gpio_keys_polled_dev *bdev;
108     struct input_polled_dev *poll_dev;
109     struct input_dev *input;

    ...
    //轮询型输入设备对象的初始化

    //注册轮询型的输入设备
    error = input_register_polled_device(poll_dev);

    ...
215 }

/////////////////////////////////////

//通过probe函数,可以确定我们写平台设备时只需通过platform_data成员提供平台驱动所需的信息,无需再提供resource.
//再确定结构体gpio_keys_platform_data的每个成员的作用即可,如不清楚具体用途,可以在驱动代码里通过查看对成员值的访问推出用途.

//平台设备的写法与前一个按键驱动方法完全一样, 注意平台设备的名字需与现平台驱动的名字一致.
//还有platform_data数据里需要设poll_interval轮询间隔时间
///////////////////////////////////////

轮询型的输入设备的工作原理分析:

1) 轮询的实现是用工作队列的方式来实现的.

   "include/linux/workqueue.h"
   300 extern struct workqueue_struct *system_freezable_wq; //有工作队列头指针

   "kernel/workqueue.c" 
   256 struct workqueue_struct *system_freezable_wq __read_mostly; //声明指针变量
    ...
   262 EXPORT_SYMBOL_GPL(system_freezable_wq); //导出指针变量

   3914     system_freezable_wq = alloc_workqueue("events_freezable",
   3915                           WQ_FREEZABLE, 0); //分配工作队列头的空间

2) 每个struct input_polled_dev对象表示轮询型的输入设备对象

    struct input_polled_dev {
        ...
        void (*poll)(struct input_polled_dev *dev); //设备驱动实现的轮询函数,在工作任务里触发调用
        ...
        struct input_dev *input; //基于输入设备扩展而来的input_polled_dev类型
        ...
        struct delayed_work work; //用于加入工作队列的工作任务
    };

3) 在设备驱动的probe函数里对轮询输入设备的初始化

    poll_dev = input_allocate_polled_device();
      ...
    poll_dev->poll = gpio_keys_polled_poll; // poll函数里检查按键io口的状态,并汇报按键.
    poll_dev->poll_interval = pdata->poll_interval;
      ...
    input = poll_dev->input; //输入设备的初始化
     ...

    input_register_polled_device(poll_dev); //注册轮询输入设备

4) 注册轮询输入设备时所作的工作

    int input_register_polled_device(struct input_polled_dev *dev) //传入轮询输入设备对象的地址
    {
        struct input_dev *input = dev->input; //获取输入设备对象的地址
        int error;

        input_set_drvdata(input, dev);
        INIT_DELAYED_WORK(&dev->work, input_polled_device_work); //初始化用于工作队列的延时工作任务,当dev->work工作任务得到调用时, input_polled_device_work函数就会得到调用
        //前面我们用的工作队列,只要工作任务加入队列后,等待条件满足并唤醒队列才会得到调用. 但这里相当于定时执行一次.
        ...
        input->open = input_open_polled_device; //在输入设备的设备文件open时,被触发调用
        input->close = input_close_polled_device;//在设备文件close时,被触发调用.

        error = input_register_device(input); //注册输入设备
        ...
    }

5) 轮询输入设备里的工作队列被调用的过程

    static int input_open_polled_device(struct input_dev *input) //当设备文件open时,此函数会被调用
    {
        struct input_polled_dev *dev = input_get_drvdata(input);
        ...
        input_polldev_queue_work(dev); // 把轮询输入设备的工作任务加入队列
        ...
        return 0;
    }

    static void input_polldev_queue_work(struct input_polled_dev *dev)
    {
        unsigned long delay;

        delay = msecs_to_jiffies(dev->poll_interval);
        if (delay >= HZ)
        delay = round_jiffies_relative(delay);

        queue_delayed_work(system_freezable_wq, &dev->work, delay); //把工作任务加入队列,并指定多久时间后执行. 经过delay时间后,input_polled_device_work函数就会被调用. 
    }

    static void input_polled_device_work(struct work_struct *work)
    {
        struct input_polled_dev *dev =
        container_of(work, struct input_polled_dev, work.work);

        dev->poll(dev); //调用轮询输入设备对象的poll函数,即gpio_keys_polled_poll函数
        input_polldev_queue_work(dev); //重新把轮询输入设备对象的工作任务加入工作队列里,实现按间隔时间重复调用
    }
<think>嗯,用户的问题是关于在设备树中配置gpio-keys-polled,希望一个按键对应多个GPIO。我需要先理解现有的设备树代码结构。现有的代码有两个按键,reset和wifi,每个都对应一个GPIO。用户想实现一个按键同时检测多个GPIO的状态,比如同时按下两个GPIO才触发事件。 首先,我得回忆一下设备树中gpio-keys-polled的用法。通常每个子节点对应一个GPIO,但用户的需求是多个GPIO组合成一个按键事件。这时候可能需要用到中断或轮询的方式检测多个GPIO的状态,然后综合判断。 接下来,我需要考虑设备树是否支持这样的配置。标准的gpio-keys-polled驱动可能不支持直接在一个节点定义多个GPIO,因为通常每个按键节点对应一个GPIO。所以可能需要修改驱动或者在用户空间处理。 可能的解决方案有两种:修改内核驱动或在用户空间处理。修改驱动的话,需要创建一个新的设备驱动,能够处理多个GPIO的状态,比如检测它们是否同时被按下。这需要在内核模块注册多个GPIO的中断,并在中断处理函数中检查所有GPIO的状态,然后上报按键事件。 另一种方法是在用户空间编写脚本或程序,使用sysfs或libgpiod库监听多个GPIO的状态变化,当满足条件时触发相应的动作,比如执行脚本或发送按键事件。这种方法不需要修改内核,但实时性可能不如内核驱动。 还要考虑GPIO的状态,比如是否使用中断触发方式,以及去抖动处理。设备树中的poll-interval设置轮询间隔,如果使用轮询方式,可能需要调整这个值来及时检测多个GPIO的状态。 另外,用户可能需要这些GPIO的组合触发同一个按键事件,比如两个GPIO同时按下才认为是某个按键事件。这时候在驱动或用户空间程序中需要同时检测这两个GPIO的电平状态。 最后,需要给出具体的实现步骤,比如修改设备树添加多个GPIO,编写内核模块,或者用户空间脚本的示例。同时提醒用户注意GPIO的配置和冲突问题,确保多个GPIO使用不会互相干扰。</think>在设备树(Device Tree)中,若希望一个按键对应多个GPIO,需通过**组合逻辑**实现。以下是分步解决方案: --- ### 一、需求分析 目标:将多个GPIO的状态组合成一个按键事件(例如:同时检测两个GPIO的电平变化触发同一个按键) --- ### 二、实现方法 #### 方法1:通过内核驱动修改(推荐) 1. **修改设备树节点** 定义一个虚拟按键节点,并关联多个GPIO: ```dts combined_key { label = "combined_key"; linux,code = <KEY_F1>; // 自定义按键gpios = <&tlmm 25 GPIO_ACTIVE_LOW>, // GPIO 25 <&tlmm 17 GPIO_ACTIVE_LOW>; // GPIO 17 linux,input-type = <1>; // 按键类型 }; ``` 2. **自定义内核驱动** - 注册多个GPIO的中断处理函数 - 在中断中检查所有GPIO状态: ```c if (gpio_get_value(gpio1) == ACTIVE_STATE && gpio_get_value(gpio2) == ACTIVE_STATE) { input_report_key(input_dev, KEY_F1, 1); // 上报按键事件 } ``` #### 方法2:用户空间轮询(简单但实时性差) 1. **通过sysfs或libgpiod监听GPIO** ```bash # 监控GPIO状态 echo 25 > /sys/class/gpio/export echo 17 > /sys/class/gpio/export while true; do if [ $(cat /sys/class/gpio/gpio25/value) -eq 0 ] && [ $(cat /sys/class/gpio/gpio17/value) -eq 0 ]; then echo "Both GPIOs pressed" fi sleep 0.1 done ``` --- ### 三、关键注意事项 1. **GPIO冲突** 确保多个GPIO未被其他驱动占用(通过`gpio-reserved-ranges`声明)。 2. **去抖动(Debounce)** 在驱动中增加防抖逻辑,或通过`poll-interval`调整轮询间隔: ```dts poll-interval = <50>; // 缩短轮询间隔至50ms ``` 3. **按键事件上报** 组合逻辑需在内核或用户空间明确:AND(同时按下)或 OR(任一按下)。 --- ### 四、验证步骤 1. 加载设备树或驱动后,通过`evtest`工具监听输入事件: ```bash evtest /dev/input/eventX ``` 2. 同时操作两个GPIO,观察是否触发`KEY_F1`事件。 --- ### 五、扩展场景 - **三态逻辑**:例如GPIO1=高 + GPIO2=低时触发不同事件 - **超时检测**:在驱动中增加计时器,判断组合按键的持续时间 若有具体硬件型号或内核版本,可进一步提供适配代码。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值