• 35648

    文章

  • 23

    评论

  • 20

    友链

  • 最近新加了很多技术文章,大家多来逛逛吧~~~~
  • 喜欢这个网站的朋友可以加一下QQ群,我们一起交流技术。

Linux驱动 - GPIO Buttons移植

欢迎来到阿八个人博客网站。本 阿八个人博客 网站提供最新的站长新闻,各种互联网资讯。 喜欢本站的朋友可以收藏本站,或者加QQ:我们大家一起来交流技术! URL链接:https://www.abboke.com/jsh/2019/0722/57270.html 1190000019827893

Linux驱动 - GPIO Buttons移植

这个设备驱动适用于,每个按键是连接到一个io口, 而且这个io口还有中断功能的

驱动移植

需要在linux内核配置里选上相关的配置。在内核源码目录下:

# make menuconfig
Device Drivers  --->
         Input device support  --->
             [*]   Keyboards  --->
                 <*>   GPIO Buttons

选择上后,再编内核,再使用新的内核镜像启动系统

使用新内核启动后,可以查看出设备驱动是否已选择上:

/sys/bus/platform/drivers/目录下应有”gpio-keys”目录

驱动源码分析

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

static struct platform_driver gpio_keys_device_driver = {
    .probe      = gpio_keys_probe,
    .remove     = __devexit_p(gpio_keys_remove),
    .driver     = {
        .name   = "gpio-keys", // 可匹配名为"gpio-keys"的平台设备
        .owner  = THIS_MODULE,
        .pm = &gpio_keys_pm_ops,
        .of_match_table = gpio_keys_of_match, //按这个成员来匹配平台设备也是可以的,要求设备的名字为"gpio-keys"
    }
};
//通过阅读平台驱动的probe函数,可得知我们写的平台设备应提供具本哪些硬件信息.
static int __devinit gpio_keys_probe(struct platform_device *pdev)
{
    const struct gpio_keys_platform_data *pdata = pdev->dev.platform_data; //这里可得知我们写的平台设备的platform_data成员应当提供gpio_keys_platform_data类型数据
    struct gpio_keys_drvdata *ddata;  //在设备驱动里对每个匹配上的设备都准备一个独立的数据
    struct device *dev = &pdev->dev;
    struct gpio_keys_platform_data alt_pdata;
    struct input_dev *input;
    int i, error;
    int wakeup = 0;
...
//对gpio_keys_drvdata对象的初始化
//输入设备对象的初始化
...
}
/////////////////////
//通过probe函数,可以确定我们写平台设备时只需通过platform_data成员提供平台驱动所需的信息,无需再提供resource. 
//再确定结构体gpio_keys_platform_data的每个成员的作用即可,如不清楚具体用途,可以在驱动代码里通过查看对成员值的访问推出用途.
//"include/linux/gpio_keys.h"
//每个struct gpio_key_button的对象表示一个按键的具体信息
struct gpio_keys_button {
    //此按键对应的键码
    unsigned int code;  /* input event code (KEY_*, SW_*) */
//此按键对应的一个io口
    int gpio;       /* -1 if this key does not support gpio */
//通过查看驱动代码,可得知表示是否按键按下是低电平,如是则设1.
    int active_low;
//就是申请io口,申请中断时使用的名字
    const char *desc;
//输入设备的事件类型,按键用EV_KEY
    unsigned int type;  /* input event type (EV_KEY, EV_SW, EV_ABS) */
//表示按键按下时是否唤醒系统, 这个需要io口硬件上有这功能
    int wakeup;     /* configure the button as a wake-up source */
//防抖动用,间隔多久时间
    int debounce_interval;  /* debounce ticks interval in msecs */
    ...
}; 
//gpio_keys_paltform_data对象表示一个输入设备, 一个输入设备可有多个按键
struct gpio_keys_platform_data {
    //多个按键需要用gpio_keys_button的变量数组才可以, buttons成员用于装数组首地址
    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 */
};

添加硬件信息

现用一个按键连接再板上,SIG脚接到PA20. 当键按下时,SIG脚为高电平。键松开时,SIG脚为低电平.

以下信息可以添加到arch/arm/mach-board/mach-soc.c。

//mypdev.c
#include <linux/init.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/gpio_keys.h>
#include <linux/input.h>
#include <mach/gpio.h>
struct gpio_keys_button btns[] = {
    {KEY_L, GPIOA(20), 0, "mygpio-keys", EV_KEY, 0, 100},
};
struct gpio_keys_platform_data pdata = {
    .buttons = btns,
    .nbuttons = ARRAY_SIZE(btns),
    .rep = 1,
    .name = "mygpio-keys",
};
struct platform_device mypdev = {
    .name = "gpio-keys", //与平台驱动的名字一致才会匹配上
    .id = -1,
    .dev = {
        .platform_data = &pdata,
    },
};
module_driver(mypdev, platform_device_register, platform_device_unregister);
MODULE_LICENSE("GPL");

GPIO_KEY使用

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

打开设备文件

Linux中的按键检测通过循环读取设备文件/dev/input/event[x](其中x可以为0,1,2…)设备文件获取按键事件,不同的平台设备文件可能会有差异, 如果不清楚对应的设备文件, 可以用下面的命令来查看:

1546502976401

打开设备代码如下:

/*1.key device*/

fd_key= open(KEY_DEVICE_FILE, O_RDONLY);

if(fd_key< 0) {

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

           returnfd_key;

}

按键事件一般由主线程循环获取按键事件,然后通过消息队列通知其他子线程,从而做出响应。

在Linux内核中,按键事件用input_event结构体描述,该结构体在头文件<linux/input.h>中定义,同时该文件还定义了有关按键事件的API函数接口、标准按键编码等。

input_event结构体定义如下:

struct input_event {  
    struct timeval time;  
    __u16 type;  
    __u16 code;  
    __s32 value;  
};  

<linux/input.h>还定义了常用标准按键编码:

#define KEY_RESERVED        0  
#define KEY_ESC         1  
#define KEY_1           2  
#define KEY_2           3  
#define KEY_3           4  
#define KEY_4           5  
#define KEY_5           6  
#define KEY_6           7  

在用户态,我们只需要循环读取设备文件/dev/input/eventx,就可以得到相应的键盘事件,代码如下:

#include <linux/input.h>  
#include <fcntl.h>  
#include <stdio.h>  
#include <stdint.h>  
  
#define  KEY_EVENT_DEV1_NAME    "/dev/input/event1"  
  
int sysKeyScan(void)  
{  
    int l_ret = -1;  
    int i = 0;  
      
    int key_fd  = 0;  
    struct input_event key_event  = {0};  
      
    key_fd = open(KEY_EVENT_DEV1_NAME, O_RDONLY);  
    if(key_fd <= 0)  
    {  
        printf("---open /dev/input/event1 device error!---\n");  
        return l_ret;  
    }  
      
    while(1)  
    {  
        l_ret = lseek(key_fd, 0, SEEK_SET);  
        l_ret = read(key_fd, &key_event, sizeof(key_event));  
          
        if(l_ret)  
        {  
            if(key_event.type == EV_KEY  
                && (key_event.value == 0 || key_event.value == 1))  
            {  
                printf("key %d %s\n", key_event.code, (key_event.value) ? "pressed" : "released");  
                  
                if(key_event.code == KEY_ESC)  
                {  
                    break;  
                }  
                  
            }  
        }  
  
    }  
      
    close(key_fd);  
      
    return l_ret;  
      
}  
  
int main(int arg, char *arc[])  
{  
    printf("---This is a key event test!---\n");  
      
    sysKeyScan();  
      
    return 0;  
} 

有时候,我们的的Linux内核也可以把不同的按键封装到不同的event中,例如数字键键盘事件通过event1通知用户态,而功能键通过event0通知用户态。此时我们可以使用poll函数来同时监测多个等待事件,若事件未发生,进程睡眠,放弃CPU控制权,直到有键盘事件发生,poll将唤醒睡眠的进程,并执行相应的操作。代码如下:

#include <linux/input.h>  
#include <fcntl.h>  
#include <poll.h>  
#include <stdio.h>  
#include <stdint.h>  
  
#define  KEY_EVENT_DEV0_NAME    "/dev/input/event0"  
#define  KEY_EVENT_DEV1_NAME    "/dev/input/event1"  
  
int sysKeyScan(void)  
{  
    int l_ret = -1;  
    int i = 0;  
  
    int key_fd[2]  = {0};  
    struct pollfd key_fds[2] = {0};  
    struct input_event key_event  = {0};  
  
    key_fd[0] = open(KEY_EVENT_DEV0_NAME, O_RDONLY);  
    if(key_fd[0] <= 0)  
    {  
        printf("---open /dev/input/event0 device error!---\n");  
        return l_ret;  
    }  
  
    key_fd[1] = open(KEY_EVENT_DEV1_NAME, O_RDONLY);  
    if(key_fd[1] <= 0)  
    {  
        printf("---open /dev/input/event1 device error!---\n");  
        return l_ret;  
    }  
  
    for(i = 0; i < 2; i++)  
    {  
        key_fds[i].fd = key_fd[i];  
        key_fds[i].events = POLLIN;  
    }  
  
    while(1)  
    {  
        l_ret = poll(key_fds, 2, -1);  
  
        for(i = 0; i < 2; i++)  
        {  
            l_ret = lseek(key_fd[i], 0, SEEK_SET);  
            l_ret = read(key_fd[i], &key_event, sizeof(key_event));  
  
            if(l_ret)  
            {  
                if(key_event.type == EV_KEY  
                    && (key_event.value == 0 || key_event.value == 1))  
                {  
  
                    printf("key value(%d) %s", key_event.code, key_event.value ? "press" : "release");  
  
                }  
            }  
        }  
    }  
  
    close(key_fd[0]);  
    close(key_fd[1]);  
  
    return l_ret;  
  
}  
  
int main(int argc, char *argv[])  
{  
    printf("---This is a key event test!---\n");  
  
    sysKeyScan();  
  
    return 0;  
}  

相关文章

暂住......别动,不想说点什么吗?
  • 全部评论(0
    还没有评论,快来抢沙发吧!