• Linux驱动之一个简单的输入子系统程序编写


    的在Linux驱动之输入子系统简析已经分析过了输入子系统的构成,它是由设备层、核心层、事件层共同组成的。其中核心层提供一些设备层与事件层公用的函数,比如说注册函数、反注册函数、事件到来的处理函数等等;事件层其实在Linux内核里面已经帮我们写好了很多有关的事件;而设备层就跟我们新添加到输入系统的具体设备相关了。这里以JZ2440开发板上的4个按键作为输入子系统的按键:它定义的功能分别为:KEY_L、KEY_S、KEY_ENTER、KEY_LEFTSHIFT。这几个值是在includelinuxinput.h中被定义的。接下来就是编写程序:

    直接贴出源程序:

    #include <linux/module.h>
    #include <linux/kernel.h>
    #include <linux/fs.h>
    #include <linux/init.h>
    #include <asm/io.h>        //含有iomap函数iounmap函数
    #include <asm/uaccess.h>//含有copy_from_user函数
    #include <linux/device.h>//含有类相关的处理函数
    #include <asm/arch/regs-gpio.h>//含有S3C2410_GPF0等相关的
    #include <linux/irq.h>    //含有IRQ_HANDLEDIRQ_TYPE_EDGE_RISING
    #include <asm-arm/irq.h>   //含有IRQT_BOTHEDGE触发类型
    #include <linux/interrupt.h> //含有request_irq、free_irq函数
    #include <linux/poll.h>
    #include <asm-generic/errno-base.h>  //含有各种错误返回值
    #include <linux/input.h>                    //含有输入子系统相关的类型
    //#include <asm-armarch-s3c2410irqs.h>
    
    struct pin_desc 
    {
        char * name;          //名称
        unsigned int pin;     //管脚定义
        unsigned int irq;     //中断号
        unsigned int key_val; //按键值
    };
    
    static struct pin_desc  pins_desc[4] = //初始化四个按键
    {
        {"S2",S3C2410_GPF0,IRQ_EINT0,KEY_L},
        {"S3",S3C2410_GPF2,IRQ_EINT2,KEY_S},
        {"S4",S3C2410_GPG3,IRQ_EINT11,KEY_ENTER},
        {"S5",S3C2410_GPG11,IRQ_EINT19,KEY_LEFTSHIFT}
    };
    
    static struct pin_desc *pin_des=NULL;
    
    
    static struct timer_list inputbuttons_timer;//新建一个定时器
    
    static struct input_dev *buttons_input;     //新建一个输入子系统的设备层结构
    
    /*
     *利用dev_id的值为pins_desc来判断是哪一个按键被按下或松开
     *中断处理程序主要是将发生中断的按键记录下来,然后修改定时器的定时时间为10ms
     */
    static irqreturn_t buttons_irq(int irq, void *dev_id)
    {
        pin_des = (struct pin_desc *)dev_id;            //取得哪个按键被按下的状态
        mod_timer(&inputbuttons_timer, jiffies+HZ/100);//10ms之后调用定时器处理函数
        
        return IRQ_HANDLED;
    }
    
    /*
     *定时器的处理程序,主要根据按下的按键,通过input_event函数上传事件
     */
    static void inputbuttons_timer_timeout(unsigned long a)
    {
        unsigned int pin_val;
    
        if(pin_des==NULL)
            return;
        else
        {
            
            pin_val = s3c2410_gpio_getpin(pin_des->pin);
    
            /*0松开,1按下*/
            if(pin_val) //按键松开
            {
                       input_event(buttons_input,EV_KEY, pin_des->key_val, 0);
                    //input_sync(buttons_input);//上传同步事件,似乎没什么作用
            }
            else
            {
                      input_event(buttons_input,EV_KEY, pin_des->key_val, 1);
                    //input_sync(buttons_input);//上传同步事件
            }
        }
    }
    
    /*
     *模块入口函数
      1、分配buttons_input结构体并初始化
      2、注册buttons_input结构,一旦注册会产生一个/dev/event1设备节点文件
      3、初始化一个定时器
      4、申请4个中断
     */
    static int seven_drv_init(void)
    {
        unsigned char i;
        int ret;
        /*1、分配一个buttons_input结构体*/
        buttons_input = input_allocate_device();
        if (!buttons_input)
            return -ENOMEM;
    
        /*2、设置输入事件类型*/
        set_bit(EV_KEY, buttons_input->evbit);
        set_bit(EV_REP, buttons_input->evbit);//重复事件类型
        
        /*3、输入事件类型的哪一种按键*/
        set_bit(KEY_L, buttons_input->keybit);
        set_bit(KEY_S, buttons_input->keybit);
        set_bit(KEY_ENTER, buttons_input->keybit);
        set_bit(KEY_LEFTSHIFT, buttons_input->keybit);
        
        /*4、注册它*/
        input_register_device(buttons_input);//注册设备驱动
    
        /*5、硬件相关操作*/
        /*增加一个定时器用于处理按键抖动*/
        init_timer(&inputbuttons_timer);
        inputbuttons_timer.expires = 0;
    //    buttons_timer->data = (unsigned long) cs;
        inputbuttons_timer.function = inputbuttons_timer_timeout;
        add_timer(&inputbuttons_timer);
        
        /*申请中断*/
        for(i=0;i<4;i++)
        {
            ret = request_irq(pins_desc[i].irq, buttons_irq, IRQT_BOTHEDGE, pins_desc[i].name, (void * )&pins_desc[i]);
            if(ret)
            {
                printk("open failed %d
    ",i);
                return -(i+1);
            }
        }    
        
        return 0;
    }
    
    
    /*
     *模块出口函数
      1、反注册buttons_input
      2、释放buttons_input结构所占内存
      3、删除定时器
      4、释放4个中断申请
     */
    static void seven_drv_exit(void)
    {
        unsigned char i;
    
        input_unregister_device(buttons_input);
        input_free_device(buttons_input);
        del_timer(&inputbuttons_timer);
    
        for(i=0;i<4;i++)
        {
            free_irq(pins_desc[i].irq, (void * )&pins_desc[i]);
        }    
    }
    
    module_init(seven_drv_init);
    module_exit(seven_drv_exit);
    
    MODULE_LICENSE("GPL");

    a、先看seven_drv_init函数,因为它负责对设备层进行初始,并且注册它,将它与事件层联系起来。看到这个函数:

    1、分配一个buttons_input结构体

    /*1、分配一个buttons_input结构体*/
        buttons_input = input_allocate_device();
        if (!buttons_input)
            return -ENOMEM;

    2、设置输入事件类型

    set_bit(EV_KEY, buttons_input->evbit);
    set_bit(EV_REP, buttons_input->evbit);//重复事件类型

    事件类型位于includelinuxinput.h中

    /*
     * Event types
     */
    #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

    3、设置输入事件类型的哪一种按键

    /*3、输入事件类型的哪一种按键*/
        set_bit(KEY_L, buttons_input->keybit);
        set_bit(KEY_S, buttons_input->keybit);
        set_bit(KEY_ENTER, buttons_input->keybit);
        set_bit(KEY_LEFTSHIFT, buttons_input->keybit);

    按键码同样定义在includelinuxinput.h中,截取其中一小部分:

    #define KEY_ENTER        28//enter的按键码
    #define KEY_S            31//S的按键码
    #define KEY_L            38//L的按键码
    #define KEY_LEFTSHIFT    42//leftshift的按键码

    4、注册它buttons_input结构体

    注册的功能其实就是将当前的设备与事件层的结构进行匹配,这里会匹配到evdev_handler,它位于driversinputevdev.c,匹配后会产生/dev/event1设备节点文件。

    /*4、注册它*/
    input_register_device(buttons_input);//注册设备驱动

    b、接着看到inputbuttons_timer_timeout函数,最终的按键的按键值是通过它上传的。

    /*
     *定时器的处理程序,主要根据按下的按键,通过input_event函数上传事件
     */
    static void inputbuttons_timer_timeout(unsigned long a)
    {
        unsigned int pin_val;
    
        if(pin_des==NULL)
            return;
        else
        {
            
            pin_val = s3c2410_gpio_getpin(pin_des->pin);
    
            /*0松开,1按下*/
            if(pin_val) //按键松开
            {
                       input_event(buttons_input,EV_KEY, pin_des->key_val, 0);
                    //input_sync(buttons_input);//上传同步事件,似乎没什么作用
            }
            else
            {
                      input_event(buttons_input,EV_KEY, pin_des->key_val, 1);
                    //input_sync(buttons_input);//上传同步事件
            }
        }
    }

    c、接着编写测试程序,源码如下:

    #include <sys/types.h>
    #include <sys/stat.h>
    #include <fcntl.h>
    #include <stdio.h>
    #include <poll.h>
    #include <signal.h>
    
    static int fd;
    
    int main(int argc, char **argv)
    {
        char* filename="/dev/event1";
       int oflags,ret=0;
       unsigned char key_val[16];
        
        fd = open(filename, O_RDWR);//|O_NONBLOCK);//打开dev/firstdrv设备文件,阻塞方式打开
        if (fd < 0)//小于0说明没有成功
        {
            printf("error, can't open %s
    ", filename);
            return 0;
        }
        
        if(argc !=1)
        {
            printf("Usage : %s ",argv[0]);
         return 0;
        }
        
      while(1)
      {
           ret = read(fd, key_val, 16);//读取的个数必须大于16字节
           printf("ret = %d,code: %02d value:%d
    ",ret,key_val[10],key_val[12]);
      }
        
       return 0;
    }

    可以看到这个测试程序是以阻塞方式打开的。read函数读的个数必须大于16个字节,看到driversinputevdev.c下的evdev_read函数,最后是它是将event这个结构发送给应用程序的。

    static ssize_t evdev_read(struct file *file, char __user *buffer, size_t count, loff_t *ppos)
    {
        struct evdev_client *client = file->private_data;
        struct evdev *evdev = client->evdev;
        int retval;
    
        if (count < evdev_event_size())
            return -EINVAL;
    
        if (client->head == client->tail && evdev->exist && (file->f_flags & O_NONBLOCK))//如果是非阻塞方式打开的文件,并且现在缓存中不存在数据,直接返回
            return -EAGAIN;
    
        retval = wait_event_interruptible(evdev->wait,
            client->head != client->tail || !evdev->exist);//如果以阻塞方式打开文件的话,将当前进程挂起,等待数据过来后,被唤醒
        if (retval)
            return retval;
    
        if (!evdev->exist)
            return -ENODEV;
    
        while (client->head != client->tail && retval + evdev_event_size() <= count) {//循环将数据发送给应用层
    
            struct input_event *event = (struct input_event *) client->buffer + client->tail;
    
            if (evdev_event_to_user(buffer + retval, event))//将数据发送给应用程序
                return -EFAULT;
    
            client->tail = (client->tail + 1) & (EVDEV_BUFFER_SIZE - 1);
            retval += evdev_event_size();
        }
    
        return retval;
    }

    再找到event的类型定义,它位于includelinuxinput.h文件中,其中时间占了8字节、类型2字节、按键码2字节、按键值4字节刚好16字节。

    struct input_event {
        struct timeval time;//时间
        __u16 type;         //类型
        __u16 code;            //按键码
        __s32 value;        //按键值
    };

    运行测试程序测试得到如下的图,分别按下四个按键,产生如下的按键值,一次按键会产生两个键值。所以会有8个按键值。其中code代码按键码:与前面设置的按键码一样;value代表按键值:1表示按下,0表示松开。

  • 相关阅读:
    Git 9. 远程仓库
    Git 8. 删除文件
    Git 7. 撤销修改
    # 并发编程 -进程理论-进程的方法
    socket 上传 -- 异常处理--UDP协议 --自定义socket #29
    socket(套接字)
    面向过程补充 网络编程 #27
    ATM
    选课系统 -- # 25 -26
    面向对象高级 1.反射 2.元类 # 24
  • 原文地址:https://www.cnblogs.com/andyfly/p/9512291.html
Copyright © 2020-2023  润新知