• 8.输入子系统------键盘按键驱动程序


    • 由上一节的输入子系统的框架分析可知,其分三层:设备驱动层,核心层,事件驱动层

    我们在为某种设备的编写驱动层,只需要关心设备驱动层,即如何驱动设备并获得硬件数据(如按下的按键数据),然后调用核心层提供的接口,核心层就会自动把数据提交给事件处理层。在输入子系统中,事件驱动是标准的,适用于所有输入类的。我们的设备可以利用一个已经存在的,合适的输入事件驱动,通过输入核心,和用户应用程序接口。

    一、编写设备驱动层的流程

    1.分配一个input——dev结构体

    2.设置input_dev的成员

    3.注册input_dev 驱动设备

    4.硬件相关代码

      1)初始化定时器和中断

      2)写中断服务函数

      3)写定时器超时函数

      4)在出口函数中 释放中断函数,删除定时器,卸载释放驱动

    二、相关结构体及函数

    input_dev驱动设备结构体中常用成员及相关函数如下:(include/linux/Input.h)

     1 struct input_dev {      
     2 
     3        void *private;
     4        const char *name;  //设备名字
     5        const char *phys;  //文件路径,比如 input/buttons
     6        const char *uniq;   
     7        struct input_id id;
     8 
     9  
    10        unsigned long evbit[NBITS(EV_MAX)];  //表示支持哪事件,常用有以下几种事件(可以多选)
    11        //EV_SYN      同步事件,当使用input_event()函数后,就要使用这个上报个同步事件
    12        //EV_KEY       键盘事件  这些都是宏定义
    13        //EV_REL       (relative)相对坐标事件,比如鼠标
    14        //EV_ABS       (absolute)绝对坐标事件,比如摇杆、触摸屏感应
    15        //EV_MSC      其他事件,功能
    16        //EV_LED       LED灯事件
    17        //EV_SND      (sound)声音事件
    18 
    19        //EV_REP       重复键盘按键事件
    20   //(内部会定义一个定时器,若有键盘按键事件一直按下/松开,就重复定时,时间一到就上报事件)   
    21 
    22        //EV_FF         受力事件
    23        //EV_PWR      电源事件
    24        //EV_FF_STATUS  受力状态事件
    25 
    26        unsigned long keybit[NBITS(KEY_MAX)];   //存放支持的键盘按键值,即能产生哪些按键
    27                                     //键盘变量定义在:include/linux/input.h, 比如: KEY_L(按键L)
    28 
    29        unsigned long relbit[NBITS(REL_MAX)];    //存放支持的相对坐标值,如x,y,滚轮
    30        unsigned long absbit[NBITS(ABS_MAX)];   //存放支持的绝对坐标值
    31        unsigned long mscbit[NBITS(MSC_MAX)];   //存放支持的其它事件,也就是功能
    32        unsigned long ledbit[NBITS(LED_MAX)];    //存放支持的各种状态LED
    33        unsigned long sndbit[NBITS(SND_MAX)];    //存放支持的各种声音
    34        unsigned long ffbit[NBITS(FF_MAX)];       //存放支持的受力设备
    35        unsigned long swbit[NBITS(SW_MAX)];     //存放支持的开关功能

    2)函数如下:

     1 struct input_dev *input_allocate_device(void);  //向内核中申请一个input_dev设备,然后返回这个设备
     2   
     3 input_unregister_device(struct input_dev *dev);  //卸载/sys/class/input目录下的input_dev这个类设备, 一般在驱动出口函数写
     4  
     5 input_free_device(struct input_dev *dev);   //释放input_dev这个结构体, 一般在驱动出口函数写
     6  
     7  
     8 
     9 set_bit(nr,p);                  //设置某个结构体成员p里面的某位等于nr,支持这个功能
    10 /* 比如:
    11 set_bit(EV_KEY,buttons_dev->evbit);   //设置input_dev结构体buttons_dev->evbit支持EV_KEY
    12 set_bit(KEY_S,buttons_dev->keybit);  //设置input_dev结构体buttons_dev->keybit支持按键”S”
    13 */
    14 
    15 void input_event(struct input_dev *dev, unsigned int type, unsigned int code, int value);  //上报事件
    16  // input_dev *dev :要上报哪个input_dev驱动设备的事件
    17  // type : 要上报哪类事件, 比如按键事件,则填入: EV_KEY
    18  // code: 对应的事件里支持的哪个变量,比如按下按键L则填入: KEY_L
    19  //value:对应的变量里的数值,比如松开按键则填入1,松开按键则填入0
    input_sync(struct input_dev *dev); //同步事件通知

    为什么使用了input_event()上报事件函数,就要使用这个函数?

    因为input_event()函数只是个事件函数,所以需要这个input_sync()同步事件函数来通知系统,然后系统才会知道

    input_sync()代码如下

    static inline void input_sync(struct input_dev *dev)
    {
    input_event(dev, EV_SYN, SYN_REPORT, 0); //就是上报同步事件,告诉内核:input_event()事件执行完毕
    }

    三、编写设备驱动程序

      1 //参考:linux-2.6.22.6linux-2.6.22.6driversinputkeyboardGpio_keys.c
      2 //pre1.包含头文件
      3 #include <linux/module.h>
      4 #include <linux/version.h>
      5 
      6 #include <linux/init.h>
      7 #include <linux/fs.h>
      8 #include <linux/interrupt.h>
      9 #include <linux/irq.h>
     10 #include <linux/sched.h>
     11 #include <linux/pm.h>
     12 #include <linux/sysctl.h>
     13 #include <linux/proc_fs.h>
     14 #include <linux/delay.h>
     15 #include <linux/platform_device.h>
     16 #include <linux/input.h>
     17 #include <linux/irq.h>
     18 #include <asm/gpio.h>
     19 #include <asm/io.h>
     20 #include <asm/arch/regs-gpio.h>
     21 
     22 
     23 struct pin_desc{
     24     int irq;    //按键的外部中断标志位
     25     char *name;    //中断设备名称
     26     unsigned int pin;    //引脚
     27     unsigned int key_val;  //dev_id,对应键盘的 L ,  S,  空格,  enter  
     28 };
     29 
     30 /* 定义四个按键 */
     31 struct pin_desc pins_desc[4] = {
     32     {IRQ_EINT0,  "S2", S3C2410_GPF0,  KEY_L},
     33     {IRQ_EINT2,  "S3", S3C2410_GPF2,  KEY_S},
     34     {IRQ_EINT11, "S4", S3C2410_GPG3,  KEY_ENTER},
     35     {IRQ_EINT19, "S5", S3C2410_GPG11, KEY_LEFTSHIFT},
     36 };
     37 
     38 static struct pin_desc *irq_pd;             //指针irq_pd用来保存dev_id
     39 static struct timer_list buttons_timer;    //定时器结构体
     40 static struct input_dev *buttons_dev;    //定义一个input_dev结构体指针 
     41 
     42 /* 中断服务函数 */
     43 static irqreturn_t buttons_irq(int irq, void *dev_id)
     44 {
     45     /* 10ms后启动定时器 */
     46     irq_pd = (struct pin_desc *)dev_id;    //保存当前的dev_id
     47     mod_timer(&buttons_timer, jiffies+HZ/100);  //更新定时器10ms
     48     return IRQ_RETVAL(IRQ_HANDLED);
     49 }
     50 
     51 /* 定时器超时函数 */
     52 static void buttons_timer_function(unsigned long data)
     53 {
     54     //超时处理函数只需要将事件上报即可
     55     
     56     struct pin_desc * pindesc = irq_pd;
     57     unsigned int pinval;
     58     
     59     if (!pindesc)
     60         return;
     61     
     62     pinval = s3c2410_gpio_getpin(pindesc->pin);
     63 
     64     if (pinval)
     65     {
     66         /* 松开 :上报EV_KEY类型,button按键,0(没按下)*/
     67         input_event(buttons_dev, EV_KEY, pindesc->key_val, 0); 
     68         input_sync(buttons_dev); //上传同步事件,告诉系统有事件出现 
     69     }
     70     else
     71     {
     72         /* 按下:上报EV_KEY类型,button按键,1(按下) */
     73         input_event(buttons_dev, EV_KEY, pindesc->key_val, 1); 
     74         input_sync(buttons_dev);
     75     }
     76 }
     77 
     78 
     79 
     80 //pre2.写入口函数
     81 static int buttons_init(void)
     82 {
     83     int i;
     84     /* 1.分配一个input_dev结构体 */
     85     //向内核中申请一个input_dev设备,然后返回这个设备,此处省略判断返回值
     86     buttons_dev = input_allocate_device();
     87     
     88     /* 2.设置input_dev的成员 */
     89     /* 2.1先设置能产生哪一类事件 */
     90     set_bit(EV_KEY, buttons_dev->evbit); //此处表示能产生键盘事件
     91    set_bit(EV_REP, buttons_dev->evbit); //支持键盘重复按事件
     92     /* 2.2能产生这类操作里的哪些事件 eg: L,S,ENTER,LEFTSHIFT */
     93     set_bit(KEY_L, buttons_dev->keybit);       //#define KEY_L    38
     94     set_bit(KEY_S, buttons_dev->keybit);    //这些宏都在Input.h中定义
     95     set_bit(KEY_ENTER, buttons_dev->keybit); //支持按键回车
     96     set_bit(KEY_LEFTSHIFT, buttons_dev->keybit);
     97 
     98     /* 3.注册input_dev 驱动设备 */
     99     input_register_device(buttons_dev);
    100 
    101     /* 4.硬件相关代码 */
    102     init_timer(&buttons_timer);
    103     buttons_timer.function = buttons_timer_function;
    104     add_timer(&buttons_timer); 
    105     
    106     for (i = 0; i < 4; i++)
    107     {
    108         request_irq(pins_desc[i].irq, buttons_irq, IRQT_BOTHEDGE, pins_desc[i].name, &pins_desc[i]);
    109     }
    110 
    111     return 0;
    112 }
    113 
    114 //pre3.写出口函数
    115 static void buttons_exit(void)
    116 {
    117     int i;
    118     for (i = 0; i < 4; i++)
    119     {
    120         free_irq(pins_desc[i].irq, &pins_desc[i]); //清中断
    121     }
    122     del_timer(&buttons_timer);  //删除定时器
    123     input_unregister_device(buttons_dev);  //卸载设备
    124     input_free_device(buttons_dev);  //释放分配给input_dev设备的空间
    125 }
    126 
    127 
    128 
    129 
    130 //pre4.修饰,添加属性
    131 module_init(buttons_init);
    132 module_exit(buttons_exit);
    133 
    134 MODULE_LICENSE("GPL");

    四、测试

     1.挂载

    挂载键盘驱动后, 如下图,可以通过  ls -l /dev/event*   命令查看已挂载的设备节点:

     加载了驱动之后,多出事件event1,代表我们的按键驱动

     其中主设备号13,次设备号是65,

    在事件处理驱动的函数中,如Evdev.c中的evdev_connect函数中,

    for (minor = 0; minor < EVDEV_MINORS && evdev_table[minor]; minor++);

    ,其中event驱动本身的此设备号是从64开始的,如上图,内核启动时,会加载自带触摸屏驱动,所以我们的键盘驱动的次设备号=64+1

    2.运行

    测试运行有两种,一种是直接打开/dev/tyy1,第二种是使用exec命令

    方法1:

    cat /dev/tty1     //tty1:LCD终端,就会通过tty_io.c来访问键盘驱动,然后打印在tty1终端上

    方法2:

    exec 0</dev/tty1    //将/dev/tty1挂载到-sh进程描述符0下,此时的键盘驱动就会直接打印在tty1终端上

    3.调试:

    若测试不成功,板子又在QT下进行的:

    1)可以使用vi命令,在板子上打开记事本,按按键测试

    2)或者删除/etc/init.d/rcS 里面有关QT自启动的命令,然后重启

    若板子没在QT下进行,也无法测试成功:

    1)可以使用hexdump命令来调试代码

    详见NQian博主的文章

    (exec命令详解入口地址: http://www.cnblogs.com/lifexy/p/7553228.html)

    (hexdump命令调试代码详解地址:http://www.cnblogs.com/lifexy/p/7553550.html)

    参考:13.Linux键盘按键驱动 (详解)

  • 相关阅读:
    关于winform动态创建button等控件以及规定行列
    Winform调用系统计算器、记事本
    悲催的一晚
    C#winform从数据集导出Excel(带指定Excel样式) 转+修改
    C#一列数的规则如下: 1、1、2、3、5、8、13、21、34...... 求第100位数是多少, 用递归算法实现。
    C#任意输入一个数,判断这个数是否跟数组里的数相等
    构建一个web应用系统应该分为哪几个步骤
    ios XMPP GCDAsynSocket线程溢出挂掉程序
    Error Domain=NSCocoaErrorDomain Code=3000 "未找到应用程序的“apsenvironment”的权利字符串" UserInfo=0x22f6a0 {NSLocalizedDescription=未找到应用程序的“apsenvironment”的权利字符串
    ios推送php服务器端pem文件
  • 原文地址:https://www.cnblogs.com/y4247464/p/10128955.html
Copyright © 2020-2023  润新知