热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

输入设备驱动之按键设备驱动

     我的环境:      Fedora14内核版本

      我的环境:
      
Fedora14 内核版本,2.6.38.1


开发板:TQ2440

移植内核版本:2.6.30.4


2.6版本的内核中,驱动的开发逐渐发展成基于总线模型等一定结构的开发模式,采用了分层的设计思想,这样的变化使得驱动开发的工作量相对而言越来越少,但是也增加了我们阅读、分析源码的思想的难度。


Linux输入子系统就是一个基于分层模式的系统,其基本的层次分解如下图所示。





    在图中我们可以发现输入子系统主要包括三个部分设备驱动层(input driver)、核心层(input core)和输入事件驱动层。输入子系统的划分使得输入设备的驱动程序设计越来越简单,但是其中的思想采用我们学习的重点和难点。


Input子系统处理输入事务,任何输入设备的驱动程序都可以通过Input输入子系统提供的接口注册到内核,利用子系统提供的功能来与用户空间交互。输入设备一般包括键盘,鼠标,触摸屏等,在内核中都是以输入设备出现的。下面分析input输入子系统的结构,以及功能实现。

1. Input子系统是分层结构的,总共分为三层:硬件驱动层,子系统核心层,事件处理层。 

(1)、其中硬件驱动层负责操作具体的硬件设备,这层的代码是针对具体的驱动程序的,需要驱动程序的作者来编写。

(2)、子系统核心层是链接其他两个层之间的纽带与桥梁,向下提供驱动层的接口,向上提供事件处理层的接口。

(3)、事件处理层负责与用户程序打交道,将硬件驱动层传来的事件报告给用户程序。

2. 各层之间通信的基本单位就是事件,任何一个输入设备的动作都可以抽象成一种事件,如键盘的按下,触摸屏的按下,鼠标的移动等。事件有三种属性:类型(type),编码(code),值(value)Input子系统支持的所有事件都定义在input.h中,包括所有支持的类型,所属类型支持的编码等。事件传送的方向是硬件驱动层-->子系统核心-->事件处理层-->用户空间。


在驱动程序设计中,我们对于设备的驱动设计主要集中在设备驱动层的实现,但是这与之前的设备驱动开发存在较大的差别,主要是因为设备驱动不再是编写基本操作的实现过程,也就是不在是对struct file_operations 这个结构体对象的填充和实现。在输入设备驱动中的主要实现包括下面几个过程:

1、分配一个输入设备对象。并完成响应结构体元素的填充,主要包括支持的事件类型和事件代号等。

分配对象的函数:

struct input_dev *input_allocate_device(void);

释放对象函数:

void input_free_device(struct input_dev *dev);

设置支持的事件类型和事件代码:


通常采用set_bit函数实现:


设置支持的事件类型(支持按键事件)

        set_bit(EV_KEY, input_dev->evbit);


设置支持的事件代码(支持按键1)

      set_bit(KEY_1, input_dev->keybit);

2、完成输入设备对象的注册,将设备对象注册到输入子系统当中去,当然也有对应的释放函数。

注册设备到内核:

int input_register_device(struct input_dev *dev);


注销设备:

void input_unregister_device(struct input_dev *dev);

3、向核心层(input core)汇报事件的发生以及传输事件类型和事件代码等。这一部分通常是采用中断的方法实现,在中断中向上一层次(Input Core)传送发生事件的事件类型、事件代号以及事件对应的值等。但是上报的内容结构体都是基于一个固定结构体的

struct input_event,在用户空间也可以采用这个结构体实现对事件的访问。

 struct input_event {


       /*事件发生的时间*/

       struct timeval time;


       /*事件类型*/

       __u16 type;


       /*事件代号*/

       __u16 code;


       /*事件对应的值*/

       __s32 value;

};

基本的事件类型包括如下:

#define EV_SYN               0x00

/*常用的事件类型*/

#define EV_KEY               0x01

#define EV_REL                0x02

#define EV_ABS                0x03

#define EV_MSC               0x04

#define EV_SW                 0x05

#define EV_LED                0x11

#define EV_SND               0x12

#define EV_REP                0x14

#define EV_FF                   0x15

#define EV_PWR               0x16

#define EV_FF_STATUS          0x17

#define EV_MAX                     0x1f

#define EV_CNT               (EV_MAX+1)

支持的事件代号:

...

#define KEY_3                  4

#define KEY_4                  5

#define KEY_5                  6

#define KEY_6                  7

#define KEY_7                  8

#define KEY_8                  9

#define KEY_9                  10

#define KEY_0                  11

#define KEY_MINUS              12

#define KEY_EQUAL              13

#define KEY_BACKSPACE           14



主要的汇报函数如下:

/*汇报键值函数*/

void input_report_key(struct input_dev *dev, unsigned int code, int value);

/*汇报相对坐标值函数*/

void input_report_rel(struct input_dev *dev, unsigned int code, int value);

/*汇报绝对坐标值函数*/

void input_report_abs(struct input_dev *dev, unsigned int code, int value);


也可以采用更加一般的函数汇报,上面的三个函数是通过下面这个函数实现的。

void input_event(struct input_dev *dev, unsigned int type, unsigned int code, int value);

完成上面的三个部分,一个输入设备的驱动程序也就完成了。但是其中的具体实现还需要阅读相关的源码。特别是设备的操作是如何通过输入设备实现等基本的操作是我们应该去关注的,设备的具体操作函数实质上已经因为一些共性被设计成了通用的接口,在输入子系统内部已经实现。

TQ2440中的按键主要是采用外部中断的形式实现,所以我在实验过程中也采用了外部中断的模式直接对按键进行操作。


具体的程序如下所示:驱动代码:



  1.  

  2.     #include
        #include
        #include
        #include
        #include
        #include
        #include
        #include
        #include
        #include
        #include
        #include
        #include
        #include

        #define DEVICE_NAME    "TQ2440_BUTTON"

        #define NUMBERS_BUTTONS        4

        static const char * input_name = "Tq2440_button";

        static struct input_dev *button_dev;

        /*中断集合*/
        static const int irqs[NUMBERS_BUTTONS]={
            IRQ_EINT0,
            IRQ_EINT1,
            IRQ_EINT2,
            IRQ_EINT4,
        };

        /*端口集合*/
        static const int gpios[NUMBERS_BUTTONS]={
            S3C2410_GPF0,
            S3C2410_GPF1,
            S3C2410_GPF2,
            S3C2410_GPF4,
        };
        /*事件代号集合*/
        static const int keys[NUMBERS_BUTTONS]={
            KEY_1,
            KEY_2,
            KEY_3,
            KEY_4,
        };

        /*中断处理程序*/

        static irqreturn_t button_interrupt(int irq,void * p)
        {
            int val = 0, i= 0;
            for(i = 0; i         {
                /*如果中断号正确*/
               
                if(irqs[i] == irq)
                {
                    /*读取端口的值*/
                   
                    val = s3c2410_gpio_getpin(gpios[i]);
                    /*汇报端口的值,实质上是将事件key[i]汇报*/
                   
                    input_report_key(button_dev,keys[i],val);
                    /*汇报同步,也就是完成汇报工作*/
                   
                    input_sync(button_dev);
                    break;
                }   
            }
            /*返回值*/
           
            return IRQ_RETVAL(IRQ_HANDLED);
        }

        /*设备初始化函数*/

        static int __init tq2440_button_init(void)
        {
            int err,i = 0;
            /*主要要先申请设备,然后申请中断*/
           
            /*申请输入设备*/
            button_dev = input_allocate_device();
           
            if(NULL == button_dev)
            {
                printk(KERN_ERR "not enough memory\n");
                err = - ENOMEM;
            }
           
            /*设置相应的事件处理类型,按键事件*/
            set_bit(EV_KEY,button_dev->evbit);

            /*申请中断*/
            for(i = 0; i         {
                /*申请中断*/
                request_irq(irqs[i],button_interrupt,IRQ_TYPE_EDGE_BOTH,
                    DEVICE_NAME,NULL);
                /*设置案件的代码值,即code值*/
                set_bit(keys[i],button_dev->keybit);
            }
               
            /*设备名,防止出现错误*/
            button_dev->name = input_name;   
           
            /*注册输入设备*/
            err = input_register_device(button_dev);
           
            if(err)
            {
                printk(KERN_ERR "failed to register device\n");
               
                goto err_free_dev;
            }
           
            printk("initialized\n");

            return 0;

        /*错误处理*/
        err_free_dev:
            /*注销设备*/
            input_unregister_device(button_dev);
            return err;
        }

        /*设备退出函数*/
        static void __exit tq2440_button_exit(void)
        {
            int i = 0;
            input_unregister_device(button_dev);

            /*释放中断号*/
            for(i = 0; i         {
                free_irq(irqs[i],NULL);
            }
        }

        /*加载和卸载*/
        module_init(tq2440_button_init);
        module_exit(tq2440_button_exit);

        /*LICENSE和作者信息*/
        MODULE_LICENSE("GPL");
        MODULE_AUTHOR("GP-");

    测试代码:

        #include
        #include
        #include
        #include
        #include
        #include
        #include

        /*事件结构体*/
        struct input_event buff;

        int main(int argc ,char **argv)
        {
            int fd;
            int count;
            /*打开设备文件*/
            fd = open("/dev/event0",O_RDWR);

            if(fd == -1)
            {
                printf("Open Failed!\n");

                exit(-1);
            }

            while(1)
            {
                /*读操作*/
                if(count = read(fd,&buff,sizeof(struct input_event))!=0)
                {
                    printf("type: %d\tcode: %d\t value: %d\n",buff.type,buff.code,buff.value);
                }
            }
           
            close(fd);
            exit(0);
        }


测试效果:





从效果上来看,代码基本上实现了按键的识别,但是该驱动程序的问题是按键并不能实现消抖操作。

这一年中最后的一天,希望自己明年更美好,坚持写博客,作总结。




推荐阅读
  • 本文介绍了九度OnlineJudge中的1002题目“Grading”的解决方法。该题目要求设计一个公平的评分过程,将每个考题分配给3个独立的专家,如果他们的评分不一致,则需要请一位裁判做出最终决定。文章详细描述了评分规则,并给出了解决该问题的程序。 ... [详细]
  • 本文主要解析了Open judge C16H问题中涉及到的Magical Balls的快速幂和逆元算法,并给出了问题的解析和解决方法。详细介绍了问题的背景和规则,并给出了相应的算法解析和实现步骤。通过本文的解析,读者可以更好地理解和解决Open judge C16H问题中的Magical Balls部分。 ... [详细]
  • Spring特性实现接口多类的动态调用详解
    本文详细介绍了如何使用Spring特性实现接口多类的动态调用。通过对Spring IoC容器的基础类BeanFactory和ApplicationContext的介绍,以及getBeansOfType方法的应用,解决了在实际工作中遇到的接口及多个实现类的问题。同时,文章还提到了SPI使用的不便之处,并介绍了借助ApplicationContext实现需求的方法。阅读本文,你将了解到Spring特性的实现原理和实际应用方式。 ... [详细]
  • 本文介绍了多因子选股模型在实际中的构建步骤,包括风险源分析、因子筛选和体系构建,并进行了模拟实证回测。在风险源分析中,从宏观、行业、公司和特殊因素四个角度分析了影响资产价格的因素。具体包括宏观经济运行和宏经济政策对证券市场的影响,以及行业类型、行业生命周期和行业政策对股票价格的影响。 ... [详细]
  • 拥抱Android Design Support Library新变化(导航视图、悬浮ActionBar)
    转载请注明明桑AndroidAndroid5.0Loollipop作为Android最重要的版本之一,为我们带来了全新的界面风格和设计语言。看起来很受欢迎࿰ ... [详细]
  • 个人学习使用:谨慎参考1Client类importcom.thoughtworks.gauge.Step;importcom.thoughtworks.gauge.T ... [详细]
  • 本文介绍了UVALive6575题目Odd and Even Zeroes的解法,使用了数位dp和找规律的方法。阶乘的定义和性质被介绍,并给出了一些例子。其中,部分阶乘的尾零个数为奇数,部分为偶数。 ... [详细]
  • CF:3D City Model(小思维)问题解析和代码实现
    本文通过解析CF:3D City Model问题,介绍了问题的背景和要求,并给出了相应的代码实现。该问题涉及到在一个矩形的网格上建造城市的情景,每个网格单元可以作为建筑的基础,建筑由多个立方体叠加而成。文章详细讲解了问题的解决思路,并给出了相应的代码实现供读者参考。 ... [详细]
  • HDFS2.x新特性
    一、集群间数据拷贝scp实现两个远程主机之间的文件复制scp-rhello.txtroothadoop103:useratguiguhello.txt推pushscp-rr ... [详细]
  • 成功安装Sabayon Linux在thinkpad X60上的经验分享
    本文分享了作者在国庆期间在thinkpad X60上成功安装Sabayon Linux的经验。通过修改CHOST和执行emerge命令,作者顺利完成了安装过程。Sabayon Linux是一个基于Gentoo Linux的发行版,可以将电脑快速转变为一个功能强大的系统。除了作为一个live DVD使用外,Sabayon Linux还可以被安装在硬盘上,方便用户使用。 ... [详细]
  • [大整数乘法] java代码实现
    本文介绍了使用java代码实现大整数乘法的过程,同时也涉及到大整数加法和大整数减法的计算方法。通过分治算法来提高计算效率,并对算法的时间复杂度进行了研究。详细代码实现请参考文章链接。 ... [详细]
  • Android源码深入理解JNI技术的概述和应用
    本文介绍了Android源码中的JNI技术,包括概述和应用。JNI是Java Native Interface的缩写,是一种技术,可以实现Java程序调用Native语言写的函数,以及Native程序调用Java层的函数。在Android平台上,JNI充当了连接Java世界和Native世界的桥梁。本文通过分析Android源码中的相关文件和位置,深入探讨了JNI技术在Android开发中的重要性和应用场景。 ... [详细]
  • 本文介绍了一个题目的解法,通过二分答案来解决问题,但困难在于如何进行检查。文章提供了一种逃逸方式,通过移动最慢的宿管来锁门时跑到更居中的位置,从而使所有合格的寝室都居中。文章还提到可以分开判断两边的情况,并使用前缀和的方式来求出在任意时刻能够到达宿管即将锁门的寝室的人数。最后,文章提到可以改成O(n)的直接枚举来解决问题。 ... [详细]
  • 3.223.28周学习总结中的贪心作业收获及困惑
    本文是对3.223.28周学习总结中的贪心作业进行总结,作者在解题过程中参考了他人的代码,但前提是要先理解题目并有解题思路。作者分享了自己在贪心作业中的收获,同时提到了一道让他困惑的题目,即input details部分引发的疑惑。 ... [详细]
  • 本文讨论了在openwrt-17.01版本中,mt7628设备上初始化启动时eth0的mac地址总是随机生成的问题。每次随机生成的eth0的mac地址都会写到/sys/class/net/eth0/address目录下,而openwrt-17.01原版的SDK会根据随机生成的eth0的mac地址再生成eth0.1、eth0.2等,生成后的mac地址会保存在/etc/config/network下。 ... [详细]
author-avatar
深厚科学修养的文艺青年
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有