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

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

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

#include 

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 
#include 
#include 
#include 
#include 
#include 
#include 

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(所有输入设备驱动提交的键码数据都会有一份提供到tty1对应的驱动里,所以可以从tty1里获取到输入设备驱动的键码).
    执行命令后, 按下矩阵键盘的键,终端就可以出现相应的键值    

你可能感兴趣的:(OrangePi,H3,Linux设备驱动开发,linux,kernel,input-dev,输入设备驱动)