linux按键驱动的设计_linux设备驱动 按键播放音频驱动 - CSDN
  • 混杂设备驱动模型: 1. 混杂设备描述  在Linux系统中,存在一类字符设备,它们拥有相同的主设备号(10),单次设备号不同,我们称这类设备为混 杂设备(miscdevice).所有的混杂设备形成一个链表,对设备访问时内核...

    混杂设备驱动模型:

    1. 混杂设备描述

            在Linux系统中,存在一类字符设备,它们拥有相同的主设备号(10),单次设备号不同,我们称这类设备为混            杂设备(miscdevice).所有的混杂设备形成一个链表,对设备访问时内核根据次设备号查到相应的混杂设备。

             混杂设备也是字符设备!

         linux中使用struct miscdevice来描述一个混杂设备。

         

    2. 混杂驱动注册

        Linux中使用misc_register函数来注册一个混杂设备驱动。

        int  misc_register(struct miscdev *misc)

    3. 范例驱动分析

         3.1 初始化miscdevice(minor、name、fops)

         3.2 注册miscdevice (通过misc_register函数实现)

    这里安照上面的分析,先来搭建一个最简单只有一个open操作的混杂按键设备驱动模型,后边逐步深入分析逐步完善代码。

    key.c

    #include<linux/module.h>
    #include<linux/init.h>
    #inlcude<linux/miscdevice.h> /* for struct miscdevice*/
    
    int key_open(struct inode *node, struct file *filp)
    {
    	
    	
    	return 0;
    }
    
    struct file_operations key_fops = 
    {
    	.open = key_open,
    };
    
    struct miscdevice key_miscdev  //定义一个misdevice结构
    {
    	.minor = 200;
    	.name = "key";
    	.fops = &key_fops;//这里key_fops是一个struct file_operations结构
    };
    
    static int key_init()
    {
    	misc_register(&key_miscdev);//注册一个混杂设备驱动设备
    	
    	return 0;
    }
    
    static void key_exit()
    {
    	misc_deregister(&key_miscdev);//注销一个混杂设备驱动
    }
    
    
    module_init(key_init);
    module_exit(key_exit);

    2. Linux 中断处理流程分析

    下面先来分析写好按键驱动的一些准备工作!按键一般用中断的模式来处理,这里先分析linux中断处理程序:

    1. 裸机中断处理流程分析

        1.1 中断有一个统一的入口 irq:

        ......

        第一步: 保护现场(中断部分执行完毕后要恢复之前的状态继续执行)

        第二步: 跳转到hand_ini处执行中断程序

                    先事先注册中断程序,然后根据相应的中断找到对应的中断处理程序

        第三步:恢复现场,

    在Linux操作系统中,irq中断的统一入口其实也是这样的(entry-armv.S文件中)


    这里的irq_hander其实是一个宏定义:


    而arch_irq_hander_default这个宏是在entry-macro-multi.S这个文件中


    拿到中断号,然后设置相关寄存器并且调到asm_do_IRQ处理中断


    看看generic_handle_irq(irq)这个函数:


    然后函数又跳到这里了:


    最后调到了handle_irq这个结构中。

    这里总结一下上面函数跳转的分析过程:

    第一步:根据中断产生的统一入口进入中断处理程序,拿到产生中断源的中断号

    第二步:根据这个中断号irq找到irq_desc结构, 在这个irq结构中就会有一个action选项,在这个action结构中就是用户事先填写的中断处理程序handler,这里用一张图来说明:


    上面分析了那么多,其实就是为了说明在驱动中如果要用中断,驱动程序该干嘛?

    第一点:实现中断处理程序

    第二点:当我们的中断产生了,能够被linux操作系统调用到用户事先定义好的中断处理程序,还需要把中断处理程序               注册到Linux操作系统中来,简单的来说就是注册中断


    3. Linux 中断处理程序设计

        3.1 注册中断


    参数说明:

    unsigned int irq :中断号

    void(*handler)(int , void *):中断处理函数

    unsigned long flags:与中断管理有关的各种选项

    const char *devname:设备名

    void *dev_id:共享中断时使用

    在flags参数中, 可以选择一些与中断管理有关的选项,如:

    . IRQF_DISABLED(SA_INTERRUPT) 快速中断

    如果设置该位,表示是一个“快速”中断处理程序;如果没有设置该位,那么就是一个“慢速”中断处理程序。

    . IRQF_SHARED(SA_SHIRQ)  共享中断该位表明该中断号是多个设备共享的。

    快/慢速中断的主要区别在于:快速中断保证中断处理的原子性(不被打断),而慢速中断则不保证。换句话说,也就是“开启中断”标志位(处理器IF)在运行快速中断处理程序时是关闭的,因此在服务该中断时,不会被其他类型的中断打断;而调用慢速中断处理时,其他类型的中断仍可以得到服务。


        3.2 中断处理

    中断处理程序的特别之处是在中断上下文中运行的,它的行为为受到某些限制:

    1. 不能使用可能引起阻塞的函数

    2. 不能使用可能引起调度的函数

    处理流程:


        3.3 注销处理
    当设备不再需要使用中断时(通常在驱动卸载时),应当把它们注销,使用函数:

    void free_irq(unsigned int irq, void *dev_id)  // 参数dev_id 可以结和上面那张图来看,就是共享中断中的那个中断

    结和上面的分析在之前的代码基础上加入下面的部分:

    中断处理函数部分:



    下面来分析按键硬件部分的相关知识!硬件原理图以及相关GPIO设置

    这里先贴上OK6410开发板上的按键硬件原理图部分:

    这里KEYINT1是和GPN0相连,



    对应的CPU引脚是GPN组,下面查看下GPN引脚datasheet的相关部分

    由下面的图这里可以看到将GPNCON寄存器的最后两位设置为0b10(外部中断模式)


    GPN0对应的外部中断号查芯片手册可以看到为:XEINT0


    这里看看OK6410内核源码部分关于中断号的宏定义:

    这个在Irqs.h文件中:要与自己使用的硬件平台对应,我这里是OK6410


    这里对应的设备中断号为S3C_EINT(0)或者写出IRQ_EINT(0)都是一样的

    这个文件源码中还有一句#define S3C_IRQ_OFFSET(32)

    中断号偏移 其中前面的32个中断号是留给用户程序作为软中断来使用, 

    这里贴出在前面的基础上加的key.c的代码:

    #include <linux/module.h>
    #include <linux/init.h>
    #include <linux/miscdevice.h> /* for struct miscdevice*/
    #include <linux/interrupt.h>
    #include <linux/fs.h> /* for iormap */
    #include <linux/io.h>
    
    #define GPNCON 0x7F008830
    
    irqreturn_t key_int(int irq, void *dev_id)
    {
    	//1. 检测是否发生了按键中断 这里可以暂时不做,因为这里没有使用共享中断
    	
    	//2. 清除已经发生的按键中断 这个是指硬件内部处理,按键CPU内部不需要做处理
    	     //比如如果是网卡驱动 就要处理
    	
    	//3. 打印按键值
    	
    	printk(KERN_WARNING"key down!\n");
    	
    	return 0;
    }
    
    void key_hw_init(void) //按键硬件初始化部分
    {
    	unsigned int *gpio_config;
    	unsigned short data;
    	
    	//第一步:设置GPNCON寄存器设置GPIO为输入
    	gpio_config = ioremap(GPNCON, 4);//将物理地址转化为虚拟地址
    	data = readw(gpio_config);
    	data &= ~0b11; //先清零
    	data |= 0b10;  //后两位设置成0b10
    	writew(data, gpio_config);
    	printk(KERN_WARNING"init ...!\n");
    	//第二步: 按键中断部分相应处理 注册中断 注销等等
    }
    
    int key_open(struct inode *node, struct file *filp)
    {
    	printk(KERN_WARNING"open ...!\n");
    	
    	return 0;
    }
    
    struct file_operations key_fops = 
    {
    	.open = key_open,
    };
    
    struct miscdevice key_miscdev = //定义一个misdevice结构
    {
    	.minor = 200,
    	.name = "key",
    	.fops = &key_fops,//这里key_fops是一个struct file_operations结构
    };
    
    static int key_init(void)
    {
    	int err;
    	
    	misc_register(&key_miscdev);//注册一个混杂设备驱动设备
    	
    	//按键初始化 硬件初始化部分一般可一放在模块初始化部分或者open函数中 这里放在模块初始化部分
    	key_hw_init();
    	
    	//由高电平变为低电平产生中断 IRQF_TRIGGER_FALLING
    	
    	
    	if( (err = request_irq(S3C_EINT(0),key_int, IRQF_TRIGGER_FALLING, "key", 0)) < 0 )//注册中断处理程序 5个参数
    	{
    		 printk(KERN_WARNING"err = %d\n", err);
    		 goto irq_err;
    	}
    	
    	return 0;
    	
    irq_err:
    		misc_deregister(&key_miscdev);	
    	return -1;
    }
    
    static void key_exit(void)
    {
    	free_irq(S3C_EINT(0), 0);//注销中断 这里irqnumber参数暂时用一个变量来表示(中断号)
    	
    	misc_deregister(&key_miscdev);//注销一个混杂设备驱动
    	
    	printk(KERN_WARNING"key up!");
    }
    
    
    module_init(key_init);
    module_exit(key_exit);
    MODULE_LICENSE("GPL");
    MODULE_DESCRIPTION("key driver");

    这里贴一个代码编译后在开发板上运行,按下按键的效果截图:



    中断分层设计:

    1. 中断嵌套


    2. 中断分层方式

        2.1 软中断

        2.2 tasklet

        2.3 工作队列(使用更广泛)


    工作队列是一种将任务推后执行的形式,他把推后的任务交由一个内核线程去执行。这样下半部会在进程上下文执行,它允许重新调度甚至睡眠。每个被推后的任务叫做“工作”,由这些工作组成的队列称为工作队列



    这里应该是用struct  workqueue_struct:




    2.1. 从内核源码查看create_workqueue函数的用法:


    这是内核源码里面找到的这个函数用法示例,这里可以看到create_workqueue函数只有一个参数,参数为工作队列的名字,返回的为创建好的一个工作队列指针,下面第三个箭头所指向的部分就是这个指针的类型!

    用法示例:

    struct workqueue_struct *my_wq;//定义一个工作队列指针

    my_wq = create_workqueue("my_queue");


    2.2. 下面去内核源码中查找一下init_work这个函数的用法:


    两个参数:

    work :要初始化的工作work指针

    func  :工作要执行的函数

    用法示例:

    struct work_struct *work1;//定义一项工作

    void work1_func(struct work_struct *work)
    {
    printk(KERN_WARNING"this is work1>\n");
    }

    work1 = kmalloc(sizeof(struct work_struct), GFP_KERNEL);
    INIT_WORK(work1 , work1_func );


    2.3. queue_work函数用法示例:


    也是两个参数:

    一个是工作队列指针 struct workqueue_struct *wq

    一个是工作指针

    用法示例:

    queue_work(my_wq, work1);


    下面根据上面的分析这里贴出一个示例小程序:

    #include<linux/module.h>
    #include<linux/init.h>
    #include <linux/slab.h> /* for kmalloc */
    
    struct workqueue_struct *my_wq; //定义一个工作队列指针
    struct work_struct *work1; //定义一项工作
    struct work_struct *work2; //定义一项工作
    
    MODULE_LICENSE("GPL");
    
    void work1_func(struct work_struct *work)
    {
    	printk(KERN_WARNING"this is work1>\n");
    }
    
    void work2_func(struct work_struct *work)
    {
    	printk(KERN_WARNING"this is work2>\n");
    }
    
    int init_que(void)
    {
    	//1. 创建工作队列
    	my_wq = create_workqueue("my_queue");
    	
    	//2. 创建工作
    	//work1 = kmalloc(sizeof(struct work_struct), GFP_KERNEL);
    	  work1 = kmalloc(sizeof(struct work_struct),GFP_KERNEL);
    	INIT_WORK(work1 , work1_func );
    	
    	//3. 挂载(提交)提交工作
    	queue_work(my_wq, work1);
    	
    	//2. 创建工作
    	work2 = kmalloc(sizeof(struct work_struct), GFP_KERNEL);
    	INIT_WORK(work2 , work2_func );
    	
    	//3. 挂载(提交)提交工作
    	queue_work(my_wq, work2);
    	
    	
    	return 0;
    }
    
    void clean_que(void)
    {
    	
    }
    
    module_init(init_que);
    module_exit(clean_que);



    3. 使用工作队列实现分层

    在大多数情况下,驱动并不需要自己建立工作队列,只需定义工作,然后将工作提交到内核已经定义好的工作队列keventd_wq中。

    3.1 提交工作到默认队列

    schedule_work

    在上面的代码这样修改也是同样的效果:


    有了上面的基础,然后对之前的按键驱动进行改进!通过中断分层来实现按键驱动

    按键中断处理程序 硬件处理部分比较简单,中断上半部 硬件中断处理基本可以不做
    下半部 和硬件没有什么关系的部分,就是下面打印按键值部分 可以放到按键中断以外来处理,为系统节省更多的时间出来,避免应为中断程序处理部分耗时过长造成中断丢失!

    #include <linux/module.h>
    #include <linux/init.h>
    #include <linux/miscdevice.h> /* for struct miscdevice*/
    #include <linux/interrupt.h>
    #include <linux/fs.h> /* for iormap */
    #include <linux/io.h>
    #include <linux/slab.h> /* for kmalloc */
    
    #define GPNCON 0x7F008830
    
    struct work_struct *work1;//定义一项工作
    
    void work1_func(struct work_struct *work)
    {
    	printk(KERN_WARNING"key down!\n");
    }
    
    irqreturn_t key_int(int irq, void *dev_id)
    {
    	//1. 检测是否发生了按键中断 这里可以暂时不做,因为这里没有使用共享中断
    	
    	//2. 清除已经发生的按键中断 这个是指硬件内部处理,按键CPU内部不需要做处理
    		 
    	//3. 提交下半部
    	schedule_work(work1);
    	
    	return 0;
    }
    
    void key_hw_init(void) //按键硬件初始化部分
    {
    	unsigned int *gpio_config;
    	unsigned short data;
    	
    	//第一步:设置GPNCON寄存器设置GPIO为输入
    	gpio_config = ioremap(GPNCON, 4);//将物理地址转化为虚拟地址
    	data = readw(gpio_config);
    	data &= ~0b11; //先清零
    	data |= 0b10;  //后两位设置成0b10
    	writew(data, gpio_config);
    	printk(KERN_WARNING"init ...!\n");
    	//第二步: 按键中断部分相应处理 注册中断 注销等等
    }
    
    int key_open(struct inode *node, struct file *filp)
    {
    	printk(KERN_WARNING"open ...!\n");
    	
    	return 0;
    }
    
    struct file_operations key_fops = 
    {
    	.open = key_open,
    };
    
    struct miscdevice key_miscdev = //定义一个misdevice结构
    {
    	.minor = 200,
    	.name = "key",
    	.fops = &key_fops,//这里key_fops是一个struct file_operations结构
    };
    
    static int key_init(void)
    {
    	int err;
    	
    	misc_register(&key_miscdev);//注册一个混杂设备驱动设备
    	
    	//按键初始化 硬件初始化部分一般可一放在模块初始化部分或者open函数中 这里放在模块初始化部分
    	key_hw_init();
    	
    	work1 = kmalloc(sizeof(struct work_struct),GFP_KERNEL);
    	INIT_WORK(work1 , work1_func );
    	
    	//由高电平变为低电平产生中断 IRQF_TRIGGER_FALLING
    	if( (err = request_irq(S3C_EINT(0),key_int, IRQF_TRIGGER_FALLING, "key", 0)) < 0 )//注册中断处理程序 5个参数
    	{
    		 printk(KERN_WARNING"err = %d\n", err);
    		 goto irq_err;
    	}
    	
    	return 0;
    	
    irq_err:
    		misc_deregister(&key_miscdev);	
    	return -1;
    }
    
    static void key_exit(void)
    {
    	free_irq(S3C_EINT(0), 0);//注销中断 这里irqnumber参数暂时用一个变量来表示(中断号)
    	
    	misc_deregister(&key_miscdev);//注销一个混杂设备驱动
    	
    	printk(KERN_WARNING"key up!");
    }
    
    
    module_init(key_init);
    module_exit(key_exit);
    MODULE_LICENSE("GPL");
    MODULE_DESCRIPTION("key driver");
    编译并且insmod安装这个驱动模块,同样可以看到按键打印的效果!不过本质上驱动处理效率上提高了!当然这里只是一个很简单的例程!


    按键定时器去抖:

    按键所用开关为机械弹性开关,当机械触点断开、闭合时,由于机械触点的弹性作用,开关不会马上稳定接通或断开。因而在闭合及断开的瞬间总是伴随有一连串的抖动。

    按键去抖动的方法主要有两种,一种是硬件电路去抖动;另一种就是软件延时去抖。而延时一般由分为两种,一种是for循环等待,另一种是定时器延时,在操作系统中,由于效率方面的原因,一般不允许使用for循环来等待,只能使用定时器。

    内核定时器:


    上面两个重要的成员(红色部分)

    expires: 超时也就是定时多长时间

    function: 函数指针



    这之间的函数就不细说了,还是同样的方法不会就查看内核代码!上面的按键驱动实际上是不完善的,按一下会打印好几个按键按下的信息,这里利用上面介绍到的内核定时器知识优化上面的按键程序:

    #include <linux/module.h>
    #include <linux/init.h>
    #include <linux/miscdevice.h> /* for struct miscdevice*/
    #include <linux/interrupt.h>
    #include <linux/fs.h> /* for iormap */
    #include <linux/io.h>
    #include <linux/slab.h> /* for kmalloc */
    
    #define GPNCON  0x7F008830
    #define GPNDAT  0x7F008834
    
    unsigned int *gpio_data;
    
    struct work_struct *work1;//定义一项工作
    
    struct timer_list key_timer; //定义一个定时器key_timer
    
    void work1_func(struct work_struct *work)
    {
    	//启动定时器 jiffies是全局变量,用来表示当前系统时间 1S=1000个滴答数
    	mod_timer(&key_timer,jiffies + HZ/10); //设置100ms超时 1HZ=1S
    }
    
    void key_timer_func(unsigned long data)
    {
    	unsigned int key_val;
    	
    	key_val = readw(gpio_data)&0x01; //只读取最后一位
    	
    	if(key_val == 0)
    	{
    		printk(KERN_WARNING"OK6410 key0 down!\n");
    	}
    
    }
    
    irqreturn_t key_int(int irq, void *dev_id)
    {
    	//1. 检测是否发生了按键中断 这里可以暂时不做,因为这里没有使用共享中断
    	
    	//2. 清除已经发生的按键中断 这个是指硬件内部处理,按键CPU内部不需要做处理
    		 
    	//3. 提交下半部
    	schedule_work(work1);
    	
    	return 0;
    }
    
    void key_hw_init(void) //按键硬件初始化部分
    {
    	unsigned int *gpio_config;
    	unsigned short data;
    	
    	gpio_config = ioremap(GPNCON, 4);//将物理地址转化为虚拟地址
    	data = readw(gpio_config);
    	data &= ~0b11; //先清零
    	data |= 0b10;  //后两位设置成0b10
    	writew(data, gpio_config);
    	
    	gpio_data = ioremap(GPNDAT, 4);//将物理地址转化为虚拟地址
    	
    	printk(KERN_WARNING"init ...!\n");
    }
    
    int key_open(struct inode *node, struct file *filp)
    {
    	printk(KERN_WARNING"open ...!\n");
    	
    	return 0;
    }
    
    struct file_operations key_fops = 
    {
    	.open = key_open,
    };
    
    struct miscdevice key_miscdev = //定义一个misdevice结构
    {
    	.minor = 200,
    	.name = "key",
    	.fops = &key_fops,//这里key_fops是一个struct file_operations结构
    };
    
    static int key_init(void)
    {
    	int err;
    	
    	misc_register(&key_miscdev);//注册一个混杂设备驱动设备
    	
    	key_hw_init();
    	
    	work1 = kmalloc(sizeof(struct work_struct),GFP_KERNEL);
    	INIT_WORK(work1 , work1_func );
    	
    	//初始化定时器
    	init_timer(&key_timer);
    	key_timer.function = key_timer_func; //将定义的函数赋值给函数指针
    	
    	//注册定时器
    	add_timer(&key_timer);
    	
    	if( (err = request_irq(S3C_EINT(0),key_int, IRQF_TRIGGER_FALLING, "key", 0)) < 0 )
    	{
    		 printk(KERN_WARNING"err = %d\n", err);
    		 goto irq_err;
    	}
    	
    	return 0;
    	
    irq_err:
    		misc_deregister(&key_miscdev);	
    	return -1;
    }
    
    static void key_exit(void)
    {
    	free_irq(S3C_EINT(0), 0);//注销中断 这里irqnumber参数暂时用一个变量来表示(中断号)
    	
    	misc_deregister(&key_miscdev);//注销一个混杂设备驱动
    	
    	printk(KERN_WARNING"key up!");
    }
    
    module_init(key_init);
    module_exit(key_exit);
    MODULE_LICENSE("GPL");
    MODULE_DESCRIPTION("key driver");

    编译运行可以看到:按一下按键 只打印一个OK6410 key0 down!


    在上面的基础上继续优化,实现多按键驱动这里增加key5按键!(结合上边的原理图部分)

    #include <linux/module.h>
    #include <linux/init.h>
    #include <linux/miscdevice.h> /* for struct miscdevice*/
    #include <linux/interrupt.h>
    #include <linux/fs.h> /* for iormap */
    #include <linux/io.h>
    #include <linux/slab.h> /* for kmalloc */
    
    #define GPNCON  0x7F008830
    #define GPNDAT  0x7F008834
    
    unsigned int *gpio_data;
    
    struct work_struct *work1;//定义一项工作
    
    struct timer_list key_timer; //定义一个定时器key_timer
    
    void work1_func(struct work_struct *work)
    {
    	//启动定时器 jiffies是全局变量,用来表示当前系统时间 1S=1000个滴答数
    	mod_timer(&key_timer,jiffies + HZ/10); //设置100ms超时 1HZ=1S
    }
    
    void key_timer_func(unsigned long data)
    {
    	unsigned int key_val;
    	
    	key_val = readw(gpio_data)&0x01; //只读取最后一位
    	
    	if(key_val == 0)
    	{
    		printk(KERN_WARNING"OK6410 key0 down!\n");
    	}
    	
    	key_val = readw(gpio_data)&0x20; //只读取最后一位
    	
    	if(key_val == 0)
    	{
    		printk(KERN_WARNING"OK6410 key5 down!\n");
    	}
    
    }
    
    irqreturn_t key_int(int irq, void *dev_id)
    {
    	//1. 检测是否发生了按键中断 这里可以暂时不做,因为这里没有使用共享中断
    	
    	//2. 清除已经发生的按键中断 这个是指硬件内部处理,按键CPU内部不需要做处理
    		 
    	//3. 提交下半部
    	schedule_work(work1);
    	
    	return 0;
    }
    
    void key_hw_init(void) //按键硬件初始化部分
    {
    	unsigned int *gpio_config;
    	unsigned short data;
    	
    	gpio_config = ioremap(GPNCON, 4);//将物理地址转化为虚拟地址
    	data = readw(gpio_config);
    	data &= ~0b110000000011; //先清零
    	data |= 0b100000000010;  //后两位设置成0b10
    	writew(data, gpio_config);
    	
    	gpio_data = ioremap(GPNDAT, 4);//将物理地址转化为虚拟地址
    	
    	printk(KERN_WARNING"init ...!\n");
    }
    
    int key_open(struct inode *node, struct file *filp)
    {
    	printk(KERN_WARNING"open ...!\n");
    	
    	return 0;
    }
    
    struct file_operations key_fops = 
    {
    	.open = key_open,
    };
    
    struct miscdevice key_miscdev = //定义一个misdevice结构
    {
    	.minor = 200,
    	.name = "key",
    	.fops = &key_fops,//这里key_fops是一个struct file_operations结构
    };
    
    static int key_init(void)
    {
    	int err;
    	
    	misc_register(&key_miscdev);//注册一个混杂设备驱动设备
    	
    	key_hw_init();
    	
    	work1 = kmalloc(sizeof(struct work_struct),GFP_KERNEL);
    	INIT_WORK(work1 , work1_func );
    	
    	//初始化定时器
    	init_timer(&key_timer);
    	key_timer.function = key_timer_func; //将定义的函数赋值给函数指针
    	
    	//注册定时器
    	add_timer(&key_timer);
    	
    	if( (err = request_irq(S3C_EINT(0),key_int, IRQF_TRIGGER_FALLING, "key", 0)) < 0 )
    	{
    		 printk(KERN_WARNING"err = %d\n", err);
    		 goto irq_err;
    	}
    	if( (err = request_irq(S3C_EINT(5),key_int, IRQF_TRIGGER_FALLING, "key", 0)) < 0 )
    	{
    		 printk(KERN_WARNING"err = %d\n", err);
    		 goto irq_err;
    	}
    
    	
    	return 0;
    	
    irq_err:
    		misc_deregister(&key_miscdev);	
    	return -1;
    }
    
    static void key_exit(void)
    {
    	free_irq(S3C_EINT(0), 0);//注销中断 这里irqnumber参数暂时用一个变量来表示(中断号)
    	
    	misc_deregister(&key_miscdev);//注销一个混杂设备驱动
    	
    	printk(KERN_WARNING"key up!");
    }
    
    module_init(key_init);
    module_exit(key_exit);
    MODULE_LICENSE("GPL");
    MODULE_DESCRIPTION("key driver");

    运行效果:



    阻塞型驱动设计:


    阻塞的必要性:

    1. 当一个设备无法立即满足用户的读写请求时应当如何处理?例如: 调用read时,设备没有数据提供,但以后可能会有:或者一个进程试图向设备写入数据,但是设备暂时没有准备好接受数据。当上述情况发生的时候,驱动程序应当阻塞进程,当它进入等待(睡眠)状态,直到请求可以得到满足。

    2. 在实现阻塞型驱动的过程中,也需要有一个“候车室”来安排被阻塞的进程“休息”,当唤醒它们的条件成熟时,则可以从“候车室”中将这些进程唤醒。而这个“候车室”就是等待队列。






    这里结合阻塞型驱动的知识点继续优化程序代码!这里顺便写个应用测试程序来测试按键驱动!

    key.c代码

    #include <linux/module.h>
    #include <linux/init.h>
    #include <linux/miscdevice.h> /* for struct miscdevice*/
    #include <linux/interrupt.h>
    #include <linux/fs.h> /* for iormap */
    #include <linux/io.h>
    #include <linux/slab.h> /* for kmalloc */
    #include<linux/uaccess.h> /* for copy_to_usr */
    #include <linux/sched.h>
    
    #define GPNCON  0x7F008830
    #define GPNDAT  0x7F008834
    
    unsigned int *gpio_data;
    
    struct work_struct *work1;//定义一项工作
    
    struct timer_list key_timer; //定义一个定时器key_timer
    
    unsigned int key_num = 0;
    
    wait_queue_head_t key_q; //定义一个等待队列
    
    void work1_func(struct work_struct *work)
    {
    	//启动定时器 jiffies是全局变量,用来表示当前系统时间 1S=1000个滴答数
    	mod_timer(&key_timer,jiffies + HZ/10); //设置100ms超时 1HZ=1S
    }
    
    void key_timer_func(unsigned long data)
    {
    	unsigned int key_val;
    	
    	key_val = readw(gpio_data)&0x01; //只读取最后一位
    	if(key_val == 0)
    	{
    		//printk(KERN_WARNING"OK6410 key0 down!\n");
    		key_num = 1;
    	}
    	
    	key_val = readw(gpio_data)&0x20; //只读取最后一位
    	if(key_val == 0)
    	{
    		//printk(KERN_WARNING"OK6410 key5 down!\n");
    		key_num = 6;
    	}
    	
    	wake_up(&key_q);
    }
    
    irqreturn_t key_int(int irq, void *dev_id)
    {
    	//1. 检测是否发生了按键中断 这里可以暂时不做,因为这里没有使用共享中断
    	
    	//2. 清除已经发生的按键中断 这个是指硬件内部处理,按键CPU内部不需要做处理
    		 
    	//3. 提交下半部
    	schedule_work(work1);
    	
    	//return 0;
    	return IRQ_HANDLED;
    }
    
    void key_hw_init(void) //按键硬件初始化部分
    {
    	unsigned int *gpio_config;
    	unsigned short data;
    	
    	gpio_config = ioremap(GPNCON, 4);//将物理地址转化为虚拟地址
    	data = readw(gpio_config);
    	data &= ~0b110000000011; //先清零
    	data |= 0b100000000010;  //后两位设置成0b10
    	writew(data, gpio_config);
    	
    	gpio_data = ioremap(GPNDAT, 4);//将物理地址转化为虚拟地址
    	
    	printk(KERN_WARNING"init ...!\n");
    }
    
    int key_open(struct inode *node, struct file *filp)
    {
    	printk(KERN_WARNING"open ...!\n");
    	
    	return 0;
    }
    
    ssize_t key_read(struct file *filp, char __user *buf, size_t size, loff_t *pos)
    {
    	wait_event(key_q,key_num);//休眠 没有按下为0
    	
    	//将key_value值返回给用户空间
    	printk(KERN_WARNING"in kernel :key num is %d\n",key_num);
    	copy_to_user(buf, &key_num, 4); //buf为用户空间传过来的地址
    	
    	key_num = 0;
    	
    	return 4;
    }
    
    struct file_operations key_fops = 
    {
    	.open = key_open,
    	.read = key_read,
    };
    
    struct miscdevice key_miscdev = //定义一个misdevice结构
    {
    	.minor = 200,
    	.name = "6410key",
    	.fops = &key_fops,//这里key_fops是一个struct file_operations结构
    };
    
    static int key_init11(void)
    {
    	int err;
    	
    	misc_register(&key_miscdev);//注册一个混杂设备驱动设备
    	
    	if( (err = request_irq(S3C_EINT(0),key_int, IRQF_TRIGGER_FALLING, "6410key", 0)) < 0 )
    	{
    		 printk(KERN_WARNING"err = %d\n", err);
    		 goto irq_err;
    	}
    	if( (err = request_irq(S3C_EINT(5),key_int, IRQF_TRIGGER_FALLING, "6410key", 0)) < 0 )
    	{
    		 printk(KERN_WARNING"err = %d\n", err);
    		 goto irq_err;
    	}
    
    	key_hw_init();
    	
    	work1 = kmalloc(sizeof(struct work_struct),GFP_KERNEL);
    	INIT_WORK(work1 , work1_func );
    	
    	//初始化定时器
    	init_timer(&key_timer);
    	key_timer.function = key_timer_func; //将定义的函数赋值给函数指针
    	
    	//注册定时器
    	add_timer(&key_timer);
    	
    	//初始化一个等待队列
    	init_waitqueue_head(&key_q);
    	
    	return 0;
    	
    irq_err:
    		misc_deregister(&key_miscdev);	
    	return -1;
    }
    
    static void key_exit(void)
    {
    	free_irq(S3C_EINT(0), 0);//注销中断 这里irqnumber参数暂时用一个变量来表示(中断号)
    	free_irq(S3C_EINT(5), 0);//注销中断 这里irqnumber参数暂时用一个变量来表示(中断号)
    	
    	misc_deregister(&key_miscdev);//注销一个混杂设备驱动
    	
    	printk(KERN_WARNING"key up!");
    }
    
    module_init(key_init11);
    module_exit(key_exit);
    MODULE_LICENSE("GPL");
    MODULE_DESCRIPTION("key driver");

    key_app.c

    #include<stdio.h>
    #include<stdlib.h>
    #include<unistd.h>
    #include<sys/types.h>
    #include<sys/stat.h>
    #include<fcntl.h>
    
    int main(void)
    {
    	int fd;
    	int key_num;
    	int ret;
    	
    	//1. 打开设备
    	fd = open("/dev/ok6410key", 0);
    	if(fd < 0)
    	{
    		printf("open key_device fail!\n");
    	}
    	
    
    	//2. 读取设备
    	ret = read(fd, &key_num, 4);
    	if(ret == -1)
    	{
    		printf("read fail\n");
    	}
    	printf("key is %d\n", key_num);
    
    	//3. 关闭设备
    	close(fd);
    	
    	return 0;
    }

    Makefile

    obj-m := key.o
    KDIR := /home/kernel/linux-ok6410
    all:
    	make -C $(KDIR) M=$(PWD) modules CROSS_COMPILE=arm-linux- ARCH=arm
    clean:
    	rm -f *.ko *.o *.mod.o *.mod.c *.symvers *.bak *.order
    

    编译:


    同步到开发上,安装驱动模块 insmod key.ko

    然后mknod /dev/ok6410key  c   10  200 

    这一行的命令作用是产生设备结点供应用程序访问 ,ok6410key为设备名字 c表示这个是字符设备 混杂设备也是字符设备 10 是混杂字符设备的统一设备号 200是在驱动程序中定义的次设备号.

    运行应用程序按下按键效果截图:


    终于搞定了!大笑(历时两天半)

    展开全文
  • 本文转载自边缘之火《有限状态机的嵌入式Linux按键驱动设计(转载)》 原文链接: http://www.eccn.com/design_2010052509381340.htm 秦国栋 (广西大学 电气学院,南宁 530004) 0 引言 一般的...
    
    
    原文链接:  http://www.eccn.com/design_2010052509381340.htm


    秦国栋

    (广西大学 电气学院,南宁 530004)

    0  引言

    一般的按键驱动程序通常非常简单。在程序 中一旦检测到按键输入口为低电平时,就采用软件延时10 ms后再次检测按键输入口。如果仍然是低电平则表示有按键按下,便转入执行按键处理程序;否则,当按键输入口为高电平,就会放弃本次按键的检测,重新开始 一次按键检测过程。这种方式不仅由于采用了软件延时而使得MCU的效率降低,同时也不容易同系统中其他功能模块协调工作,且系统的实时性也差。本文把单个 按键作为一个简单的系统,根据状态机的原理对其动作的操作和确认的过程进行分析,并用状态图表示出来,然后根据状态图编写出按键接口程序。   

    1  基于状态机的简单按键驱动设计

    在一个嵌入式系统中,按键的操作是随机的。为了提高CPU的工作效率,在设计按键驱动的时候,利用 S3C2440的外部中断来实现对按键的处理。很明显,系统的输入信号与按键连接的I/O口电平,“1”表示按键处于开放状态,“0”表示按键处于闭合状 态。而系统的输出信号则表示检测和确认到一次按键的闭合操作,用“1”表示。

    图1给出了一个简单按键状态机的状态转换图。

    有限状态机的嵌入式Linux按键驱动设计 - 边缘之火 - 边缘之火

    在图中,将1次按键完整的操作分解为3个状态。其中,状态0为按键的初始状态,当按 键输入为“1”时,表示按键处于开放,输出“0”(I/0),下一状态仍为状态0;当按键输入为“0”时,表示按键闭合,但输出还是“0”(没有经过消 抖,不能确认按键真正按下),下一状态进入状态1。

    状态1为按键闭合确认状态,它表示在10 ms前按键为闭合的,因此当再次检测到按键输入为“0”时,可以确认按键被按下了(经过10 ms的消抖);输出“1”则表示确认按键闭合(0/1),下一状态进入状态2。而当再次检测到按键的输入为“1”时,表示按键可能处在抖动干扰;输出为 “0”(I/0),下一状态返回到状态0。这样,利用状态1,实现了按键的消抖处理。状态2为等待按键释放状态,因为只有等按键释放后,一次完整的按键操 作过程才算完成。

    对图1的分析可知,在一次按键操作的整个过程中,按键的状态是从状态0→状态1→状态2,最后返回到状态0的,并且在整 个过程中,按键的输出信号仅在状态1时给出了唯一的一次确认按键闭合的信号“1”,其他状态均输出“0”。因此,图1状态机所表示的按键系统,不仅克服了 按键抖动的问题,同时也确保在一次按键的整个过程中,系统只输出一次按键闭合信号(“1”)。

    2  具有连发功能的按键驱动设计

    上 面介绍的是最简单的情况,不管按键被按下的时间保持多长,在这个按键的整个过程中都只给出了一次确认的输出。但是有些场合为了方便使用者,根据使用者按按 键的时间多少来确定是否按键“连发”。例如,在设置时钟时,按按键的时间较短时,设置加1;按按键时间较长时,设置加10,这时就需要根据按按键的时间长 短来确定具体输出。图2是将按键驱动设计为具有连发功能状态机的状态转换图。

    有限状态机的嵌入式Linux按键驱动设计 - 边缘之火 - 边缘之火

    当按键按下后1 s内释放了,系统输出为1;当按键按下后1 s没有释放,那么以后每隔0.5 s,输出为2,直到按键释放为止。如果系统输出1,应用程序将变量加1;如果系统输出2,应用程序将变量加10。这样按键驱动就有了处理连发按键的功能 了。

    3  程序设计

    由于篇幅所限,下面只给出按键驱动的关键程序,按键中断处理程序和时间处理函数:

    有限状态机的嵌入式Linux按键驱动设计 - 边缘之火 - 边缘之火
    有限状态机的嵌入式Linux按键驱动设计 - 边缘之火 - 边缘之火

    这里的定时函数使用了Linux的内核定时器。使用内核定时器可以方便地实现每个状 态的特定定时时间,并且安全释放CPU,提高CPU的效率。程序的基本思路是,首先按键被按下进入按键中断服务程序 buttons_interrupt(),在中断服务程序里确定按键状态是否为初始态。如果是,则进行kbd_timer初始化且使按键状态转为消抖状 态。当kbd_timer定时到以后,按键检测按键状态是否仍处于按下时转换状态为按键确定状态,如果不是则恢复初始态。当定时器1 s到达后,判断按键是否仍是按下。如是则转换为连发状态,否则恢复初始态。当0.5 s到达后,重新判断按键是否仍是按下。如是,则继续为连发状态,输出值加10;如果按键抬起,则恢复初始态。

    4  实验结果

    该 驱动程序经过gcc-arm-liunx-3.4.4编译,并在Micro2440SDK开发板上运行(开发板上的系统版本为linux2.6.13), 运行结果如图3所示。

    有限状态机的嵌入式Linux按键驱动设计 - 边缘之火 - 边缘之火

    从运行结果可以看出,如果按下按键并在1 s抬起,输出值每次只加1;如果按下按键超过1 s,系统的输出值每隔0.5s将加10。说明本驱动运行正常,且具有了连发功能。

    结  语

    本 文主要分析了按键有限状态机的工作过程,并利用Liunx内核定时器实现了状态机的状态转换时间间隔,最后给出了基于有限状态机的具有连发功能Linux 驱动编写代码,实现了具有连发功能的按键驱动,为基于有限状态机的按键驱动提供了一种解决思路。

    展开全文
  • 一、按键驱动 1、对按键驱动添加设备信息 linux-stable-3.10.46对按键的驱动定义在gpio_keys.c (drivers\input\keyboard)文件中,在led驱动分析中,我们知道,只有平台设备和平台驱动两者的name名字一致才可以注册...

    一、按键驱动

    1、对按键驱动添加设备信息

    linux-stable-3.10.46对按键的驱动定义在gpio_keys.c (drivers\input\keyboard)文件中,在led驱动分析中,我们知道,只有平台设备和平台驱动两者的name名字一致才可以注册成功一个驱动。这里,内核代码中没有对按键平台信息的定义,因此我们需要给他补充完整。

    首先将按键驱动编译到内核:

    Device Drivers --->
            Input device support --->
                    [*]  Keyboards --->
                            <*>  GPIO Buttons

    为了简单起见,我们就在mach-Louis210.c(arch\arm\mach-Louis210)下定义设备信息,其中struct gpio_keys_button定义在gpio_keys.h (include\linux)文件中,其定义为:

    struct gpio_keys_button {
          /* Configurationparameters */
          unsigned int code;    按键对应的编码,其定义在Input.h
          int gpio;             设置按键对应引脚
          int active_low;       设置按键按下是否是低电平(低电平有效 1)
          const char *desc;     给按键起个名字,相当于led4 led5 led6等
          unsigned int type;        设备按键类型, 默认类型是EV_KEY
          int wakeup;               设置是否设置为唤醒源
          int debounce_interval;    消抖间隔时间,单位毫秒,默认为5毫秒
          bool can_disable;     设置是否共享中断
          int value;            绝对坐标值
          unsigned int irq;     irq中断号的设定
    };

    结构体介绍完毕,那么就仿照Mach-Louis210.c在mach-Louis210.c定义设备信息如下:

    /* gpio keys (add by Louis) */
    
    设置按键的GPIO信息
    static struct gpio_keys_button buttons[] = {   
    	[0] = {
    		.code = KEY_UP,          按键对应的键码
    		.gpio = S5PV210_GPH0(0), 按键对应的IO口
    		.active_low = 1,         通过查看驱动代码,可得知表示是否按键按下是低电平,如是则设1
    		.desc = "KEY_UP",        申请io口,申请中断时使用的名字
    		.type = EV_KEY,          输入设备的事件类型,按键用EV_KEY
    		.debounce_interval = 50, 防抖动用,间隔多久时间
    	},
    	[1] = {
    		.code = KEY_DOWN,
    		.gpio = S5PV210_GPH0(1),
    		.active_low = 1,
    		.desc = "KEY_DOWN",
    		.type = EV_KEY,
    		.debounce_interval = 50,
    	},
    	[2] = {
    		.code = KEY_LEFT,
    		.gpio = S5PV210_GPH0(2),
    		.active_low = 1,
    		.desc = "KEY_LEFT",
    		.type = EV_KEY,
    		.debounce_interval = 50,
    	},
    	[3] = {
    		.code = KEY_RIGHT,
    		.gpio = S5PV210_GPH0(3),
    		.active_low = 1,
    		.desc = "KEY_RIGHT",
    		.type = EV_KEY,
    		.debounce_interval = 50,
    	},
    	[4] = {
    		.code = KEY_ENTER,
    		.gpio = S5PV210_GPH0(4),
    		.active_low = 1,
    		.desc = "KEY_ENTER",
    		.type = EV_KEY,
    		.debounce_interval = 50,
    	},
    	[5] = {
    		.code = KEY_BACK,
    		.gpio = S5PV210_GPH0(5),
    		.active_low = 1,
    		.desc = "KEY_BACK",
    		.type = EV_KEY,
    		.debounce_interval = 50,
    	},
    	[6] = {
    		.code = KEY_MENU,
    		.gpio = S5PV210_GPH2(6),
    		.active_low = 1,
    		.desc = "KEY_MENU",
    		.type = EV_KEY,
    		.debounce_interval = 50,
    	},
    	[7] = {
    		.code = KEY_POWER,
    		.gpio = S5PV210_GPH2(7),
    		.active_low = 1,
    		.desc = "KEY_POWER",
    		.type = EV_KEY,
    		.debounce_interval = 50,
    	},
    };
    
    设置按键的设备平台数据
    static struct gpio_keys_platform_data Louis210_keys_pdata = {
    	.buttons = buttons,     对应的按键的GPIO信息
    	.nbuttons = ARRAY_SIZE(buttons),    获取要定义的按键个数
    	.rep = 1,   //同一次按键是否重复上报
    };
     
    static struct platform_device Louis210_keys = {
    	.name = "gpio-keys",
    	.dev = {
    		.platform_data =  &Louis210_keys_pdata,
    	},
    	.id = -1,
    };
    
    添加平台数据至平台设备数组 
    static struct platform_device *Louis210_devices[] __initdata = {
    	&Louis210_keys,
    };

          平台设备信息就设置完毕,

    2、对按键驱动的测试

    通过查看按键设备信息可以看到添加的按键驱动信息,

    在/proc/bus/input目录下执行 cat  devices可以看到:

    [root@Louis210: gpio-keys]# cd /proc/bus/input/
    [root@Louis210: input]# cat devices
    I: Bus=0019 Vendor=0001 Product=0001 Version=0100
    N: Name="gpio-keys"
    P: Phys=gpio-keys/input0
    S: Sysfs=/devices/platform/gpio-keys/input/input2
    U: Uniq=
    H: Handlers=kbd event2 
    B: PROP=0
    B: EV=100003
    B: KEY=40000800 101680 0 0 10000000

    这里列出了按键驱动的详细信息,第一个I是

    struct input_id {
          __u16 bustype;
          __u16 vendor;
          __u16 product;
          __u16 version;
    
    }列出的信息

    下面有名字,设备文件存在的路径,等等信息。

    可以通过按键动作进行测试。

    当有按键按下时,我们可以通过读取/dev/input/event2文件来查看上报事件,此时我们以16进制查看类型读取显示该文件,使用hexdump命令:

    [root@Louis210: input]# hexdump  /dev/input/event2
    0000000 4cb8 386d c2ff 000c 0001 0069 0001 0000
    0000010 4cb8 386d c2ff 000c 0000 0000 0000 0000
    0000020 4cb8 386d 2078 000f 0001 0069 0000 0000
    0000030 4cb8 386d 2078 000f 0000 0000 0000 0000
    
    0000040 4cbc 386d 99d6 000d 0001 0067 0001 0000
    0000050 4cbc 386d 99d6 000d 0000 0000 0000 0000
    0000060 4cbd 386d 032e 0001 0001 0067 0000 0000
    0000070 4cbd 386d 032e 0001 0000 0000 0000 0000
    
    0000080 4cc0 386d b0e1 0002 0001 006c 0001 0000
    0000090 4cc0 386d b0e1 0002 0000 0000 0000 0000
    00000a0 4cc0 386d 5c7b 0005 0001 006c 0000 0000
    00000b0 4cc0 386d 5c7b 0005 0000 0000 0000 0000

    本代码设置三个按键,当我按下三个按键的时候,会上报三组数据,就以第一组数据为例,当我按下按键的时候(保持按下这个动作,不松开按键),会打印出两组数据:

    [root@Louis210: input]# hexdump  /dev/input/event2
    0000000 4cb8 386d c2ff 000c 0001 0069 0001 0000
    0000010 4cb8 386d c2ff 000c 0000 0000 0000 0000

    其中0001(倒数第4列)代表事件类型,因为类型为EV_KEY所以为1
    其中0069(倒数第3列)代表按键的编码 设置按键功能为 KEY_LEFT  KEY_RIGHT  KEY_END则编码为0x69 0x6A 0x6B
    其中0001(倒数第2列)代表按键值,当按键按下的时候按键值为1

    测试按键程序key.c

    #include<stdint.h>
    #include<string.h>
    #include<fcntl.h>
    #include<unistd.h>
    #include<stdio.h>
    #include<linux/input.h>
    
    int main(void)
    {
        int fd;
        int flag=0;
        struct input_event ev_key;
        fd=open("/dev/input/event2", O_RDWR);   读写方式打开
        
        if(fd < 0)
        {
            perror("open device buttons");
            close(fd);
            
            return -1;
        }
    
        while(1)
        {
            read(fd,&ev_key,sizeof(struct input_event));
            
            if(flag!=ev_key.type)    这里是判断是否有上报按键事件的发生。
                printf("type:%d,code:%d,value:%d,sec:%d,nsec:%d\n",
                ev_key.type,ev_key.code,ev_key.value,ev_key.time.tv_sec,ev_key.time.tv_usec);
                
            flag=ev_key.type;
        }
    
        close(fd);
        return 0;
    }
    

    read的返回值是一个

    struct input_event {
          struct timeval time;    系统开机时间
          __u16 type;
          __u16 code;
          __s32 value;
    };
    
    struct timeval {
          __kernel_time_t          tv_sec;          秒 /* seconds */
          __kernel_suseconds_t     tv_usec;         微妙 /*microseconds */
    };
    
    可以看出来,这里是按键按下时候的系统时间(第一个参数单位是秒,第二个参数单位是微秒)
    后面的参数为事件驱动类型(对应的是ev_key)
    后面两个是按键的编码和按键的值(这里的按键值是松开是0或者按键按键其值为1)。

    3、对按键驱动分析

    分析按键驱动之前,我觉得有必要先学习一下有关输入系统的介绍:

    Input子系统分为三层,从下至上分别是设备驱动层核心层以及事件处理层

    输入设备主要的工作过程都是动作产生(按键,触屏……)-->产生中断-->读取数值(键值,坐标……)-->将数值传递给应用程序。一个大致的工作流程就是,input device向上层报告-->input core接收报告,并根据在注册input device时建立好的连接选择哪一类handler来处理事件-->通过handler将数据存放在相应的dev(evdev,mousedev…)实例的缓冲区中,等待应用程序来读取。这三层中的输入核心层和事件处理层都是内核已经完成了的,因此需要我们完成的只有设备驱动层。

    下面网上有张图来解释输入子系统的框架:

    通过上面的图可以看出来,input有像按键等的输入设备event;触摸屏等的输入设备ts;遥控杆等输入设备js;鼠标等输入设备mouse和键盘等不会在/dev/input下产生节点,而是作为ttyn终端(不包括串口终端)的输入。

    通过上面的介绍,结合具体的函数或结构体来解释就是设备驱动层为具体用户设备驱动,输入设备由struct input_dev 结构表示,并由input_register_deviceinput_unregister_device来注册和卸载;input_hander事件处理层主要和用户空间交互,接收用户空间下发的file_operation操作命令,生成/dev/input/xx设备节点供用户空间进行file_operations操作; input_core层负责管理系统中的input_dev 设备 和input_hander事件处理,并起到承上启下作用,负责输入设备和input_hander之间信息传输,框架图如下:

    大概介绍到这里,下面来分析代码:

    按键的驱动定义在gpio_keys.c(drivers\input\keyboard)文件中,老规矩首先看到这个宏:

    late_initcall(gpio_keys_init);
    module_exit(gpio_keys_exit);

    这里有个宏late_initcall,这个宏的跟之前遇见的subsys_initcall的功能是一样的,只不过其优先级不同,具体的他们全部定义在Init.h (include\linux)中:

    #define early_initcall(fn)             __define_initcall(fn,early)
    #define pure_initcall(fn)              __define_initcall(fn,0)
    #define core_initcall(fn)              __define_initcall(fn,1)
    #define core_initcall_sync(fn)         __define_initcall(fn,1s)
    #define postcore_initcall(fn)          __define_initcall(fn,2)
    #define postcore_initcall_sync(fn)     __define_initcall(fn,2s)
    #define arch_initcall(fn)              __define_initcall(fn,3)
    #define arch_initcall_sync(fn)         __define_initcall(fn,3s)
    #define subsys_initcall(fn)            __define_initcall(fn, 4)
    #define subsys_initcall_sync(fn)       __define_initcall(fn,4s)
    #define fs_initcall(fn)                __define_initcall(fn,5)
    #define fs_initcall_sync(fn)           __define_initcall(fn,5s)
    #define rootfs_initcall(fn)            __define_initcall(fn,rootfs)
    #define device_initcall(fn)            __define_initcall(fn, 6)
    #define device_initcall_sync(fn)       __define_initcall(fn,6s)
    #define late_initcall(fn)              __define_initcall(fn, 7)
    #define late_initcall_sync(fn)         __define_initcall(fn,7s)
    #define module_init(x)                 __initcall(x);
    #define __initcall(fn)                 device_initcall(fn)

    通过上面的宏可以看出他们的后面的参数不同,系数越小优先级越大,可以看出:
    优先级由大到小为:subsys_initcall>module_init > late_initcall

    接下来就来看gpio_keys_init gpio_keys_exit

    static int __init gpio_keys_init(void)
    {
          return platform_driver_register(&gpio_keys_device_driver);
    }
    
    static void __exit gpio_keys_exit(void)
    {
          platform_driver_unregister(&gpio_keys_device_driver);
    }

    他们分别调用了平台驱动注册和注销函数,先看平台驱动注册函数的参数结构体:

    static struct platform_driver gpio_keys_device_driver = {
    	.probe		= gpio_keys_probe,
    	.remove		= gpio_keys_remove,
    	.driver		= {
    		.name	= "gpio-keys",
    		.owner	= THIS_MODULE,
    		.pm	= &gpio_keys_pm_ops,
    		.of_match_table = of_match_ptr(gpio_keys_of_match),
    	}
    };

    从这个结构体看,跟led的格式就一样的,显示比较name是否都是"gpio-keys"平台设备跟平台驱动的名字比较搭配的话,在注册的时候就会去执行gpio_keys_probe函数,在注销的时候就会执行gpio_keys_remove的函数,往下看就是对应驱动的名字了;然后gpio_keys_pm_ops,这个由宏

    static SIMPLE_DEV_PM_OPS(gpio_keys_pm_ops, gpio_keys_suspend,gpio_keys_resume);

    生成的,该宏定义在Pm.h (include\linux),根据该宏的定义,最终会调用gpio_keys_remove和gpio_keys_suspend函数控制电源的进入工作状态或者低功耗状态。

    下一个定义了设备和驱动匹配函数,匹配方法可以用两个字方法:如果driver中定义了of_device_id,则通过driver中的of_device_id和device中的device_node内容进行匹配判断,匹配工作由of_match_node来完成,该函数会遍历of_device_id列表,查找是否有成员与device_node相匹配,具体由matches的name,type和compatioble来进行对比,如果找到则返回相应的表项,否则返回null.如果没有定义of_device_id,device_node或不能找到对应的匹配项,则通过第二种方式platform_device_id来进行对比匹配,通过platform_match_id来完成。

    接下来详细说明上面提到的几个函数。

    先看探测函数gpio_keys_probe

    static int gpio_keys_probe(struct platform_device *pdev)
    {
    	struct device *dev = &pdev->dev;
    	const struct gpio_keys_platform_data *pdata = dev_get_platdata(dev);    
    获取平台设备信息,即得到Louis210_keys_pdata的数据信息。
    	struct gpio_keys_drvdata *ddata;    驱动信息的数据结构体,
    	struct input_dev *input;    定义了一个输入设备,
    	int i, error;
    	int wakeup = 0;
    
    	if (!pdata) {
    		pdata = gpio_keys_get_devtree_pdata(dev);
    		if (IS_ERR(pdata))
    			return PTR_ERR(pdata);
    	}
    
    	ddata = kzalloc(sizeof(struct gpio_keys_drvdata) +      分配且清空数据空间
    			pdata->nbuttons * sizeof(struct gpio_button_data),
    			GFP_KERNEL);
    	input = input_allocate_device();        分配一个input设备
    	if (!ddata || !input) {
    		dev_err(dev, "failed to allocate state\n");
    		error = -ENOMEM;
    		goto fail1;
    	}
    
    	ddata->pdata = pdata;
    	ddata->input = input;
    	mutex_init(&ddata->disable_lock);  
     为ddata初始化互斥锁(互斥体),mutex的使用场合跟信号量基本相同,一般用户那些进程之间竞争,
     且占用时间较长的场合,当占用时间较短是,一般使用互旋锁。
    
    	platform_set_drvdata(pdev, ddata);  将驱动数据保存到驱动平台数据中,后期将会使用保存的函数
    	input_set_drvdata(input, ddata);    将驱动数据保存到输入设备中中,后期将会使用保存的函数
    
    	input->name = pdata->name ? : pdev->name;
    	input->phys = "gpio-keys/input0";
    	input->dev.parent = &pdev->dev;
    	input->open = gpio_keys_open;       在挂起或者唤醒的时候会调用open和close函数
    	input->close = gpio_keys_close;
    
    	input->id.bustype = BUS_HOST;
    	input->id.vendor = 0x0001;
    	input->id.product = 0x0001;
    	input->id.version = 0x0100;
    
    	/* Enable auto repeat feature of Linux input subsystem */
    	if (pdata->rep)     给按键设置可重复多次按下的特性
    		__set_bit(EV_REP, input->evbit);
    
        循环获取每个按键的信息,并通过gpio_keys_setup_key函数为每个按键初始化引脚,滤波消抖,
        申请外部中断,申请定时器中断平且设定中断定时器服务函数
    	for (i = 0; i < pdata->nbuttons; i++) { 
    		const struct gpio_keys_button *button = &pdata->buttons[i];
    		struct gpio_button_data *bdata = &ddata->data[i];
    
    		error = gpio_keys_setup_key(pdev, input, bdata, button);
    		if (error)
    			goto fail2;
    
    		if (button->wakeup)
    			wakeup = 1;
    	}
    
    	error = sysfs_create_group(&pdev->dev.kobj, &gpio_keys_attr_group);     
    在kobj目录下创建一个属性集合,并显示集合中的属性文件。如果文件已存在,会报错。以函数为例,
    这里将会在gpio-keys/目录下创建一个属性文件gpio_keys_attr_group,gpio_keys_attr_group最终会调用:
    	if (error) {
    		dev_err(dev, "Unable to export keys/switches, error: %d\n",
    			error);
    		goto fail2;
    	}
    
    	error = input_register_device(input);   向内核注册一个input设备
    	if (error) {
    		dev_err(dev, "Unable to register input device, error: %d\n",
    			error);
    		goto fail3;
    	}
    
    	device_init_wakeup(&pdev->dev, wakeup);     电源管理有关
    
    	return 0;
    
     fail3:
    	sysfs_remove_group(&pdev->dev.kobj, &gpio_keys_attr_group);     删除之前创建的属性文件
     fail2:
    	while (--i >= 0)
    		gpio_remove_key(&ddata->data[i]);       释放掉申请的引脚,取消申请的队列等等
    
    	platform_set_drvdata(pdev, NULL);
     fail1:
    	input_free_device(input);
    	kfree(ddata);
    	/* If we have no platform data, we allocated pdata dynamically. */
    	if (!dev_get_platdata(&pdev->dev))
    		kfree(pdata);
    
    	return error;
    }
    
    structgpio_keys_drvdata {
          const struct gpio_keys_platform_data *pdata;      定义的平台信息,包括引脚等的信息
          struct input_dev *input;                          分配了一个输入设备
          struct mutex disable_lock;                        分配一个互斥锁
          struct gpio_button_data data[0];                  分配一个按键数据结构体
    };
    
    电源管理有关
    staticinline int device_init_wakeup(struct device *dev, bool val)
    {
    
        device_set_wakeup_capable(dev, val);    设置设备能不能被唤醒
        device_set_wakeup_enable(dev, val);     设置设备使不使用唤醒;
        return 0;
    
    }

    probe函数做完了整个驱动要做的事情,现在总结一下probe都做了些什么事:
    1、首先获取平台设备信息:dev_get_platdata(dev)或者gpio_keys_get_devtree_pdata(dev)
    2、为定义的设备信息申请一块内存:kzalloc
    3、申请分配一个输入设备: input_allocate_device
    4、为该输入设备设置属性:input->······
    5、初始化按键相关的引脚、中断、及其有关的定时器信息:gpio_keys_setup_key
    6、为该设备创建一个属性集合:sysfs_create_group
    7、正式申请为输入设备:input_register_device

    刚才有看到在probe函数中,调用了好多自定义的一些函数,接下来逐个分析一下这些函数的实现。
    先看一下设置按键的函数gpio_keys_setup_key

    static int gpio_keys_setup_key(struct platform_device *pdev,
    				struct input_dev *input,
    				struct gpio_button_data *bdata,
    				const struct gpio_keys_button *button)
    {
    	const char *desc = button->desc ? button->desc : "gpio_keys";
    	struct device *dev = &pdev->dev;
    	irq_handler_t isr;
    	unsigned long irqflags;
    	int irq, error;
    
    	bdata->input = input;
    	bdata->button = button;
    	spin_lock_init(&bdata->lock);
        
    /**************************************************************************
        初始化自旋锁,自旋锁适用于临界区不是很大的情况(临界区的执行时间比较短)
    总结自旋锁使用流程:
        1、首先定义一个自旋锁:
            spinlock_t lock
        2、初始化自旋锁:
           spin_lock_init(lock)
        3、获取自旋锁
          (1)spin_trylock(lock)//假如获得锁返回真,否则返回假,返回假货不会原地打转的等着获得锁
          (2)spin_lock(lock)//假如获得锁返回真,否则返回假,返回假货将会原地打转的等着获得锁
        4、释放掉自旋锁
           spin_unlock(lock);
    **************************************************************************/
       
    	if (gpio_is_valid(button->gpio)) {  //测试端口是否合法
    
    		error = gpio_request_one(button->gpio, GPIOF_IN, desc); //申请IO口,并设置为输入模式
    		if (error < 0) {
    			dev_err(dev, "Failed to request GPIO %d, error %d\n",
    				button->gpio, error);
    			return error;
    		}
    
    		if (button->debounce_interval) {
    			error = gpio_set_debounce(button->gpio,
    					button->debounce_interval * 1000);  按键消抖处理
    			/* use timer if gpiolib doesn't provide debounce */
    			if (error < 0)
    				bdata->timer_debounce =
    						button->debounce_interval;
    		}
    
    		irq = gpio_to_irq(button->gpio);    申请GPIO中断,设置为外部中断,同时获取中断号
    		if (irq < 0) {
    			error = irq;
    			dev_err(dev,
    				"Unable to get irq number for GPIO %d, error %d\n",
    				button->gpio, error);
    			goto fail;
    		}
    		bdata->irq = irq;   赋值中断号
    
    		INIT_WORK(&bdata->work, gpio_keys_gpio_work_func); 
            
    /**************************************************************************
            创建工作,关联工作函数 将函数gpio_keys_gpio_work_func假如工作队列,让中断的第二阶段
    去执行该函数。实际上&bdata->work是一个描述工作队列的结构体,变量定义为struct work_struct work
            
    总结工作队列过程:
        1、首先定义一个工作结构体:
            struct work_struct work
        2、将定义的工作加入内核工作队列并且绑定放入队列的函数:
            INIT_WORK(work, work_func)  或者  INIT_DELAYED_WORK(work, work_func)
        3、当需要执行工作函数的时:
            (1)对应的INIT_WORK  执行schedule_work(work)会马上调用work_func函数
            (2) 对应的NIT_DELAYED_WORK 执行schedule_delayed_work(time,work)会在time时间后执行work_func
        4、取消工作队列中的工作:
            (1)对应的INIT_WORK   cancel_work_sync(work)
            (2)对应的NIT_DELAYED_WORK
                cancel_delayed_work_sync//取消延时工作并且等待其完成工作
                cancel_delayed_work(work)//取消延时工作
    **************************************************************************/
            
    		setup_timer(&bdata->timer,
    			    gpio_keys_gpio_timer, (unsigned long)bdata);
                    
    /**************************************************************************
    总结定时器的使用方法:
        1、定义一个定时器timer_listmytimer
        2、初始化定时器并赋值成员setup_timer(mytimer, timer_func, data);
        3、增加定时器add_timer(mytimer)
        4、该修改定时器的定时时间expire  mod_timer(mytimer,expire)
        5、取消定时器,有两个可选择
            1. del_timer(mytimer) 直接删除定时器
            2. del_timer_sync(mytimer)等待本次定时器处理完毕再取消(不适用中断上下文)
    		isr = gpio_keys_gpio_isr;   //设置外部中断服务函数,并配置中断处理标志为上升沿触发和下降沿触发
    		irqflags = IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING;
            设定中断处理的属性,也就是中断触发方式处理方式等等,假如设置IRQF_SHARED表明多个设备共享
    一个中断,此时会用到dev_id;当设置IRQF_DISABLED时候表明,中断为快速中断
    **************************************************************************/
    
    	} else {
    		if (!button->irq) {
    			dev_err(dev, "No IRQ specified\n");
    			return -EINVAL;
    		}
    		bdata->irq = button->irq;
    
    		if (button->type && button->type != EV_KEY) {
    			dev_err(dev, "Only EV_KEY allowed for IRQ buttons.\n");
    			return -EINVAL;
    		}
    
    		bdata->timer_debounce = button->debounce_interval;
    		setup_timer(&bdata->timer,
    			    gpio_keys_irq_timer, (unsigned long)bdata);
    
    		isr = gpio_keys_irq_isr;
    		irqflags = 0;
    	}
    
    	input_set_capability(input, button->type ?: EV_KEY, button->code);
        设定该按键具有什么能力,以本例来说,button1 button2 button3具有按键Left Right End的能力
    
    	/*
    	 * If platform has specified that the button can be disabled,
    	 * we don't want it to share the interrupt line.
    	 */
    	if (!button->can_disable)
    		irqflags |= IRQF_SHARED;
            
    /**************************************************************************
    设置是否共享中断,这里涉及到中断共享机制:
    多个中断共享一个中断线必须用IRQF_SHARED做标记,以IRQF_SHARED作为标记的中断假如要向内核申请成功
    一个中断需要两个条件之一:
    该中断还没有被申请
    该中断虽然被申请了,但是已经申请的中断也有IRQF_SHARED做标记
    当发生共享中断时,所用挂载到此中断的中断服务函数都会得到响应(遍历所有该中断线上的中断),所以说,
    当该中断设备为共享中断的时候,中断服务函数首先需要判断是否是自己的dev_id假如不是自己的dev_id那么
    返回IRQ_NOTE(表明不是本中断),假如检测到时本中断的话就会执行中断里面的函数,最后返回IRQ_HANDLED。
    **************************************************************************/
    
    	error = request_any_context_irq(bdata->irq, isr, irqflags, desc, bdata);
        
    /**************************************************************************
    申请一个中断线,其中bdata->irq为要申请的中断线(中断号);isr为中断服务函数;irqflags中断的触发
    方式或者处理方式;desc要申请中断的描述符;bdata为dev_id,区分共享中断线线而设定的。
    
    申请外部中断的流程:
        1、申请某个引脚为外部中断 intirq = gpio_to_irq(button->gpio);
        2、设备外部中断函数irq_handler_tisr = gpio_keys_gpio_isr;
        3、设置中断发出类型 unsignedlong flags =?
        4、描述该中断的一个assic字符串的名字  constchar *name=?
        5、设备dev-id 是一个空函数指针  void*dev_id =?    
    **************************************************************************/
    
    	if (error < 0) {
    		dev_err(dev, "Unable to claim irq %d; error %d\n",
    			bdata->irq, error);
    		goto fail;
    	}
    
    	return 0;
    
    fail:
    	if (gpio_is_valid(button->gpio))
    		gpio_free(button->gpio);
    
    	return error;
    }
    

    总结设置按键的函数gpio_keys_setup_key作用:
           该函数主要是申请外部中断,设定中断服务函数,由于在消抖的时候会用到定时器,这个函数还初始化了定时器。并且绑定了定时器中断服务函数。

    接下来看一下按键服务函数gpio_keys_gpio_isr

    static irqreturn_t gpio_keys_gpio_isr(int irq, void *dev_id)
    {
    /*************************************************************************
    首先看中断服务函数的返回值:
    IRQ_NONE ,还没有发生中断
    IRQ_HANDLED     中断已经处理完毕
    IRQ_WAKE_THREAD中断需要唤醒处理线程
    **************************************************************************/
    	struct gpio_button_data *bdata = dev_id;
    
    	BUG_ON(irq != bdata->irq);  代码调试用的
    
    	if (bdata->button->wakeup)  根据wakeup保存非睡眠状态
    		pm_stay_awake(bdata->input->dev.parent);
    
    /*************************************************************************        
    设置定时器定时时间,定时器时间到后就会执行定时器服务函数
    (在按键设置函数gpio_keys_setup_key中已经设置定时器服务函数为gpio_keys_gpio_timer)
    **************************************************************************/
    
    	if (bdata->timer_debounce)
    		mod_timer(&bdata->timer, jiffies + msecs_to_jiffies(bdata->timer_debounce));
            else
    		schedule_work(&bdata->work);    执行中断底部的队列部分
    
    	return IRQ_HANDLED;
    }
    
    static void gpio_keys_gpio_timer(unsigned long_data)
    {
        structgpio_button_data *bdata = (struct gpio_button_data *)_data;
        schedule_work(&bdata->work);      执行中断底部的队列部分;
    }
    

    对于按键中断服务函数gpio_keys_gpio_isr可以看出来,中断函数确实是分为中断顶半部(top half)和中断底半部(bottom half),对于按键来说,中断顶半部就是消抖(消抖还用到了定时间,所以说需要的执行时间很短),中断的底半部就是用来执行工作队列里面的工作了,接下来看一下队列的工作都做了什么:

    static void gpio_keys_gpio_work_func(struct work_struct *work)
    {
    	struct gpio_button_data *bdata = container_of(work, struct gpio_button_data, work);
    
    	gpio_keys_gpio_report_event(bdata);
    
    	if (bdata->button->wakeup)  //电源管理相关
    		pm_relax(bdata->input->dev.parent);
    }

    可以看到队列工作就是为了调用时间上报函数gpio_keys_gpio_report_event

    static void gpio_keys_gpio_report_event(struct gpio_button_data *bdata)
    {
    	const struct gpio_keys_button *button = bdata->button;
    	struct input_dev *input = bdata->input;
    	unsigned int type = button->type ?: EV_KEY;
    	int state = (gpio_get_value_cansleep(button->gpio) ? 1 : 0) ^ button->active_low;
        
    /*************************************************************************
    gpio_get_value_cansleep是获取按键的值,其gpio_get_value区别是,有些芯片的引脚与
    cpu是依靠一些总线连接的比如iic总线,那么这些引脚就有可能产生休眠,因此要用
    gpio_get_value_cansleep来获取按键的值,获取后异或button->active_low(之前设定的
    是1),那么当有按键按下时,gpio_get_value_cansleep得到的是低电平也就是0,所以0^1
    =1,这里也是再说名,当按键按下时,获取的state应该是1。
    **************************************************************************/
    
    	if (type == EV_ABS) {   这个类型判断是不是EV_ABS(绝对坐标)事件,本次是EV_KEY事件
    		if (state)
    			input_event(input, type, button->code, button->value);
    	} else {
    		input_event(input, type, button->code, !!state);
    	}
        
    	input_sync(input);  上报事件上报完成
    }

    到这里上报事件就完工了,回想一下,整个输入子系统的流程可以总结为两部:
    1、首先定义申请注册一个输入设备。
    2、申请按键为外部中断,当按键按下时,在中断服务函数中处理,并上报按键事件。

    展开全文
  • 一般的按键驱动程序通常非常简单。在程序 中一旦检测到按键输入口为低电平时,就采用软件延时10 ms后再次检测按键输入口。如果仍然是低电平则表示有按键按下,便转入执行按键处理程序;否则,当按键输入口为
    原文链接:  http://www.eccn.com/design_2010052509381340.htm


    秦国栋

    (广西大学 电气学院,南宁 530004)

    0  引言

    一般的按键驱动程序通常非常简单。在程序 中一旦检测到按键输入口为低电平时,就采用软件延时10 ms后再次检测按键输入口。如果仍然是低电平则表示有按键按下,便转入执行按键处理程序;否则,当按键输入口为高电平,就会放弃本次按键的检测,重新开始 一次按键检测过程。这种方式不仅由于采用了软件延时而使得MCU的效率降低,同时也不容易同系统中其他功能模块协调工作,且系统的实时性也差。本文把单个 按键作为一个简单的系统,根据状态机的原理对其动作的操作和确认的过程进行分析,并用状态图表示出来,然后根据状态图编写出按键接口程序。   

    1  基于状态机的简单按键驱动设计

    在一个嵌入式系统中,按键的操作是随机的。为了提高CPU的工作效率,在设计按键驱动的时候,利用 S3C2440的外部中断来实现对按键的处理。很明显,系统的输入信号与按键连接的I/O口电平,“1”表示按键处于开放状态,“0”表示按键处于闭合状 态。而系统的输出信号则表示检测和确认到一次按键的闭合操作,用“1”表示。

    图1给出了一个简单按键状态机的状态转换图。

    有限状态机的嵌入式Linux按键驱动设计 - 边缘之火 - 边缘之火

    在图中,将1次按键完整的操作分解为3个状态。其中,状态0为按键的初始状态,当按 键输入为“1”时,表示按键处于开放,输出“0”(I/0),下一状态仍为状态0;当按键输入为“0”时,表示按键闭合,但输出还是“0”(没有经过消 抖,不能确认按键真正按下),下一状态进入状态1。

    状态1为按键闭合确认状态,它表示在10 ms前按键为闭合的,因此当再次检测到按键输入为“0”时,可以确认按键被按下了(经过10 ms的消抖);输出“1”则表示确认按键闭合(0/1),下一状态进入状态2。而当再次检测到按键的输入为“1”时,表示按键可能处在抖动干扰;输出为 “0”(I/0),下一状态返回到状态0。这样,利用状态1,实现了按键的消抖处理。状态2为等待按键释放状态,因为只有等按键释放后,一次完整的按键操 作过程才算完成。

    对图1的分析可知,在一次按键操作的整个过程中,按键的状态是从状态0→状态1→状态2,最后返回到状态0的,并且在整 个过程中,按键的输出信号仅在状态1时给出了唯一的一次确认按键闭合的信号“1”,其他状态均输出“0”。因此,图1状态机所表示的按键系统,不仅克服了 按键抖动的问题,同时也确保在一次按键的整个过程中,系统只输出一次按键闭合信号(“1”)。

    2  具有连发功能的按键驱动设计

    上 面介绍的是最简单的情况,不管按键被按下的时间保持多长,在这个按键的整个过程中都只给出了一次确认的输出。但是有些场合为了方便使用者,根据使用者按按 键的时间多少来确定是否按键“连发”。例如,在设置时钟时,按按键的时间较短时,设置加1;按按键时间较长时,设置加10,这时就需要根据按按键的时间长 短来确定具体输出。图2是将按键驱动设计为具有连发功能状态机的状态转换图。

    有限状态机的嵌入式Linux按键驱动设计 - 边缘之火 - 边缘之火

    当按键按下后1 s内释放了,系统输出为1;当按键按下后1 s没有释放,那么以后每隔0.5 s,输出为2,直到按键释放为止。如果系统输出1,应用程序将变量加1;如果系统输出2,应用程序将变量加10。这样按键驱动就有了处理连发按键的功能 了。

    3  程序设计

    由于篇幅所限,下面只给出按键驱动的关键程序,按键中断处理程序和时间处理函数:

    有限状态机的嵌入式Linux按键驱动设计 - 边缘之火 - 边缘之火
    有限状态机的嵌入式Linux按键驱动设计 - 边缘之火 - 边缘之火

    这里的定时函数使用了Linux的内核定时器。使用内核定时器可以方便地实现每个状 态的特定定时时间,并且安全释放CPU,提高CPU的效率。程序的基本思路是,首先按键被按下进入按键中断服务程序 buttons_interrupt(),在中断服务程序里确定按键状态是否为初始态。如果是,则进行kbd_timer初始化且使按键状态转为消抖状 态。当kbd_timer定时到以后,按键检测按键状态是否仍处于按下时转换状态为按键确定状态,如果不是则恢复初始态。当定时器1 s到达后,判断按键是否仍是按下。如是则转换为连发状态,否则恢复初始态。当0.5 s到达后,重新判断按键是否仍是按下。如是,则继续为连发状态,输出值加10;如果按键抬起,则恢复初始态。

    4  实验结果

    该 驱动程序经过gcc-arm-liunx-3.4.4编译,并在Micro2440SDK开发板上运行(开发板上的系统版本为linux2.6.13), 运行结果如图3所示。

    有限状态机的嵌入式Linux按键驱动设计 - 边缘之火 - 边缘之火

    从运行结果可以看出,如果按下按键并在1 s抬起,输出值每次只加1;如果按下按键超过1 s,系统的输出值每隔0.5s将加10。说明本驱动运行正常,且具有了连发功能。

    结  语

    本 文主要分析了按键有限状态机的工作过程,并利用Liunx内核定时器实现了状态机的状态转换时间间隔,最后给出了基于有限状态机的具有连发功能Linux 驱动编写代码,实现了具有连发功能的按键驱动,为基于有限状态机的按键驱动提供了一种解决思路。
    展开全文
  • 0.0上一个按键驱动使用查询方式,占用cpu为99%,根本不实用,因此使用中断方式按键驱动。 0.1驱动功能:记录按键按下次数并发往用户端。读取按键状态时,如果按键未按下则休眠进程,按键按下则进入中断服务函数,在...
  • 本章将介绍Linux输入子系统的驱动开发。Linux的输入子系统不仅支持鼠标、键盘等常规输入设备,而且还支持蜂鸣器、触摸屏等设备。本章将对Linux输入子系统进行详细的分析。 输入子系统大致实现方法:  底层驱动层...
  • 二、按键驱动 1、对按键驱动添加设备信息 linux-3.14.28对按键的驱动定义在Gpio_keys.c (drivers\input\keyboard) 文件中,在led驱动分析中,我们知道,只有平台设备和平台驱动两者的name名字一致才
  • 这一节在nanopi上实现按键驱动,和LED驱动一样,通用的按键驱动linux内核中已经实现好,我们只需要按照要求写好设备树即可,不用我们自己实现按键驱动。这一节中首先修改设备树并测试按键驱动,然后分析drivers/...
  • ADC按键驱动 Adc键盘原理图如下,将串联电阻之间分别用按键引出来与地相连,当按键按下时端电压会发生改变。基本思想是在ADC驱动基础上,对采样电压进行判断,检测是哪一个按键按下。 1. ADC驱动分析 在init...
  • 使用定时器可以防止因为按键抖动产生的多次  #include #include #include #include #include #include #include #include #include #include #include #include #include #define BUT_NAME ...
  • 二、按键驱动 1、对按键驱动添加设备信息 linux-3.14.28对按键的驱动定义在Gpio_keys.c (drivers\input\keyboard) 文件中,在led驱动分析中,我们知道,只有平台设备和平台驱动两者的name名字一致才可以注册成功一...
  • 在上一节中,我们讲解了如何自动创建设备节点,实现一个中断方式的按键驱动。虽然中断式的驱动,效率是蛮高的,但是大家有没有发现,应用程序的死循环里的读函数是一直在读的;在实际的应用场所里,有没有那么一种...
  • 1.复习裸机按键操作驱动 #define GPGCON (volatile unsigned long *)0x56000060 /* * K1,K2,K3,K4对应GPG0、GPG3、GPG5、GPG6 */ #define GPG0_int (0x2<<(0*2)) #define GPG3_int (0x2<<(3*2)) #...
  • linux中的按键驱动

    2018-12-03 15:39:51
    linux字符驱动之查询按键       linux字符驱动之中断按键         linux字符驱动之poll机制按键驱动       linux字符驱动之异步通知按键驱动
  • 本课程讲解Linux驱动程序开发基本知识,程序架构,字符设备编程,杂项设备编程,具体硬件模块驱动开发。
  • linux按键驱动

    2017-05-10 20:24:41
    LINUX 按键驱动
  • 按键驱动程序抖动问题会造成多次中断发生,实则可能是一次按下或释放的操作。本驱动程序就是在按键驱动程序(中断方式)的基础之上,用定时器来去抖动。 当一次按键按下的时候,可能产生多个脉冲,我们可以等到...
  • 本博实时更新《Linux设备驱动开发详解(第3版)》(即《Linux设备驱动开发详解:基于最新的Linux 4.0内核》)的最新进展。 目前已经完成稿件。 2015年8月9日,china-pub开始上线预售: ... 2015年8月20日,各路朋友报喜...
  • button按键驱动,相对于前面的LED驱动来说。增加了中断服务程序以及等待队列等新知识点。 先上学习的驱动代码。 /********************************************************************************* * ...
1 2 3 4 5 ... 20
收藏数 19,589
精华内容 7,835
关键字:

linux按键驱动的设计