41 linux标准输入设备之矩阵键盘驱动的实现

来源:互联网 发布:思念诗词 知乎 编辑:程序博客网 时间:2024/06/10 18:59

准备知识点:
原子位操作 , linux输入设备的应用程序编程

在linux内核里用struct input_dev的一个对象来表示一个输入设备. 用一位二进制表示是否支持相应的功能, 多种功能需要多位, 用数组来表示所需的多位二进制数.

#include <linux/input.h>struct input_dev {    //这部分内容可"cat /proc/bus/input/device"来查看    const char *name; //输入设备名    const char *phys;     const char *uniq;    struct input_id id;  //设备id, 可指定厂家号,产品型号    ...    unsigned long evbit[BITS_TO_LONGS(EV_CNT)]; //表示此输入设备所支持的事件类型. 支持的事件在相应的位设1.    unsigned long keybit[BITS_TO_LONGS(KEY_CNT)]; //表示此输入设备所支持的键码。键码共有768个,需要768位来表示,支持的键码需在相应位设1.  查KEY_*    unsigned long relbit[BITS_TO_LONGS(REL_CNT)]; //表示支持的相对坐标的数据类型。如x, y, z轴的数据. 查 REL_*    unsigned long absbit[BITS_TO_LONGS(ABS_CNT)];//表示支持的绝对坐标的数据类型. 如x, y坐标. 查ABS_*     ...    unsigned int repeat_key;    struct timer_list timer; //此定时是用于重复提交键数据使用. 我们只需设置事件EV_REP就可以了, 内核里会设置此定时器    ...}

事件类型有:

//注意宏的值表示在第几位设1就是支持此事件#define EV_SYN          0x00  //同步,表示设备驱动的数据已更新. 所有的输入设备都需要此功能。所以这功能会在输入设备注册时自动在evbit[0]的第0位设1#define EV_KEY          0x01  //表示此输入设备有按键功能#define EV_REL          0x02  //相对坐标, 鼠标#define EV_ABS          0x03  //绝对坐标, 触摸屏#define EV_MSC          0x04    ...#define EV_REP          0x14  //支持按键重复提交。也就支持按着键不动时,自动按间隔时间提交按键数据.    ...#define EV_CNT          (EV_MAX+1) //事件总数,也就是共需多少位. BITS_TO_LONGS把位数转成需要多少个long数组的元素个数.

/////////////////////////////////////////////
在linux内核源码文档目录里的”input-programming.txt”提供一个最简单的按键的输入设备驱动范例.
总结步骤:

    (1)           struct input_dev *dev;         dev = input_allocate_device(); //动态分配空间,并部分成员初始化.    (2)         //初始化成员         dev->name = "mykeys";         dev->evbit[0] = BIT_MASK(EV_KEY); //设置支持的事件类型, BIT_MASK(EV_KEY)就是表示在第EV_KEY位设1.         dev->keybit[BIT_WORD(BTN_0)] = BIT_MASK(BTN_0); //设置支持的键码    (3)           //注册输入设备         input_register_device(dev);    (4)         //请求按键对应的IO口中断         request_irq(gpio_to_irq(KEY_IO), button_interrupt, 双边沿触发, "button", NULL))    (5)         //在按键的中断处理函数里,提交键码数据         input_report_key(dev, BTN_0, !gpio_get_value(KEY_IO)); //注意最后的一个参数值非0时表示键按下, 0时表示松手         input_sync(dev); //提交数据

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

把前面的矩阵键盘的驱动改造成标准的输入设备驱动:

#include <linux/init.h>#include <linux/module.h>#include <linux/interrupt.h>#include <mach/gpio.h>#include <linux/gpio.h>#include <linux/timer.h>#include <linux/input.h>int rows[] = {GPIOA(7), GPIOA(8), GPIOA(9), GPIOA(10)};int cols[] = {GPIOA(20), GPIOA(21), GPIOC(4), GPIOC(7)};struct timer_list mytimer;//用一个数组记录哪个按键按下,当松手中断发生时,就用此数组里记录的按键作松手的按键char keys[4][4];int key_codes[4][4] = {    {KEY_L,     KEY_S,    KEY_SPACE, KEY_ENTER},    {KEY_A,     KEY_UP,   KEY_B,     KEY_ESC},    {KEY_LEFT,  KEY_DOWN, KEY_RIGHT, KEY_DOT},    {KEY_C,     KEY_D,    KEY_E,     KEY_F},}; //按键对应的键码表struct input_dev *dev;#define NAME   "mykeypad"void all_cols_output_level(int level){    int i;    for (i = 0; i < ARRAY_SIZE(cols); i++)        gpio_set_value(cols[i], level);}void timer_func(unsigned long data){    int r = data;    enable_irq(gpio_to_irq(rows[r]));}irqreturn_t irq_func(int irqno, void *arg){    int r = (int )arg; //r表示哪一行,由request_irq时指定    int i;    int stat = gpio_get_value(rows[r]);    disable_irq_nosync(irqno);/////检查列////    if (stat) //行线为高电平, 则是有按键松手    {        for (i = 0; i < ARRAY_SIZE(cols); i++)        {            if (keys[r][i]) //根据数据里存放的记录来判断是哪个按键松手            {//              printk("key[%d][%d] released\n", r, i);                input_report_key(dev, key_codes[r][i], 0); //松手的键码                keys[r][i] = 0;                break;            }        }    }    else //行线为低电平,则是有按键按下    {        for (i = 0; i < ARRAY_SIZE(cols); i++)        {            all_cols_output_level(1); //所有的列输出高电平            gpio_set_value(cols[i], 0); //逐一改变一列线的电平            if (!(gpio_get_value(rows[r])) && !(gpio_get_value(rows[r]))) //判断行线电平是否随着列线改变, 如改变则是按下            {                keys[r][i] = 1;//              printk("key[%d][%d] pressed\n", r, i);                input_report_key(dev, key_codes[r][i], 1); //按下的键码                break;            }        }        all_cols_output_level(0); //所有的列输出低电平    }    input_sync(dev); //提交键码数据//////////////    mytimer.data = r;    mod_timer(&mytimer, jiffies+HZ*50/1000); // 50ms后重新打开中断    return IRQ_HANDLED;}static int __init test_init(void){    int ret, i, j, k;    init_timer(&mytimer);       mytimer.function = timer_func;    //输入设备的初始化////////    dev = input_allocate_device();    dev->name = NAME;    set_bit(EV_KEY, dev->evbit); //设置支持的事件    set_bit(EV_REP, dev->evbit); //支持重复提交按键数据。即按下不动时,会自动定时重复提交按下的键码    for (i = 0; i < ARRAY_SIZE(key_codes); i++)    {        for (j = 0; j < ARRAY_SIZE(key_codes[0]); j++)            set_bit(key_codes[i][j], dev->keybit); //设置支持的键码    }    input_register_device(dev); //注册输入设备    ////////////////////////    //申请行线的中断    for (j = 0, k = 0; j < ARRAY_SIZE(rows); k++, j++)    {        ret = gpio_request(rows[j], NAME);        if (ret < 0)            goto err1;        ret = request_irq(gpio_to_irq(rows[k]), irq_func, IRQF_TRIGGER_FALLING|IRQF_TRIGGER_RISING, NAME, (void *)k);        if (ret < 0)            goto err1;    }    //把列全部输出低电平    for (i = 0; i < ARRAY_SIZE(cols); i++)    {        ret = gpio_request(cols[i], NAME);        if (ret < 0)            goto err0;        gpio_direction_output(cols[i], 0);    }    return 0;err1:    while (k-- > 0)        free_irq(gpio_to_irq(rows[k]), (void *)k);    while (j > 0)        gpio_free(rows[--j]);err0:    while (i > 0)        gpio_free(cols[--i]);    return ret;}static void __exit test_exit(void){    int i, j;    del_timer(&mytimer);    for (i = 0; i < ARRAY_SIZE(cols); i++)        gpio_free(cols[i]);    for (j = 0; j < ARRAY_SIZE(rows); j++)    {        gpio_free(rows[j]);        free_irq(gpio_to_irq(rows[j]), (void *)j);    }    input_unregister_device(dev);}module_init(test_init);module_exit(test_exit);MODULE_LICENSE("GPL");

在linux内核在输入设备驱动注册后,会自动生成设备驱动对应的设备文件,并提供了字符设备驱动接口供用户进程调用(输入设备驱动里无需再实现字符设备驱动). 可用”cat /proc/bus/input/devices”查出输入设备驱动对应的设备文件.
找出设备文件后,就可以用linux输入设备的应用程序编程的方法来获取键码数据。也可以移植qt应用程序,在qt程序里直接用keyPressEvent来捕捉键值.

  也可以把输入设备作shell的标准输入来测试, shell命令"exec 0</dev/tty1"(所有输入设备驱动提交的键码数据都会有一份提供到tty1对应的驱动里,所以可以从tty1里获取到输入设备驱动的键码).    执行命令后, 按下矩阵键盘的键,终端就可以出现相应的键值    
原创粉丝点击