15.linux按键驱动程序(二)
linux按键驱动程序
包含内容定时器延时去抖动,阻塞型设备驱动设计
一、定时器延时去抖
按键所用开关为机械弹性开关,当机械触点断开、闭合时,由于机械触点的弹性作用,开关不会马上稳定地接通或断开。因而在闭合及断开的瞬间总是伴有一连串的抖动的。按键去抖动的方法主要有两种,一种是硬件电路去抖动;另一种就是软件延时去抖动。而延时又一般分为了两种,一种是for循环等待,另一种是定时器延时。在操作系统中,由于效率方面的原因,一使用定时器。
1.1内核定时器
定时器的使用分为了四个步骤:
1.定义定时器的变量,就是timer_list结构。
2.要对结构进行初始化。Init_timer是系统自动运行的初始化函数,能初始化很大部分timer_list里面的成员。但是,超时函数是需要我们自己设置,就是function。
3.使用add_timer函数注册定时器。
4.mod_timer重启定时器。注意,定时器不是循环的,需要重复调用mod_timer函数。
Linux内核使用struct timer_list来描述一个定时器:
1 struct timer_list{ 2 struct list_head entry; 3 unsigned long expires; 4 void (*function)(unsigned long); 5 unsigned long data; 6 struct tvec_base *base; 7 };
其中expires是定时器延时的时间,function函数是定时器超时后要做的事情。
1.2使用内核定时器
二、阻塞型驱动程序设计
当一个设备无法立刻满足用户的读写请求时应当如何处理? 例如:调用read时,设备没有数据提供, 但以后可能会有;或者一个进程试图向设备写入数据,但是设备暂时没有准备好接收数据。当上述情况发生的时候,驱动程序应当(缺省地)阻塞进程,使它进入等待(睡眠)状态,直到请求可以得到满足。
2.1内核等待队列
在实现阻塞驱动的过程中,也需要有一个“候车室”来安排被阻塞的进程“休息”,当唤醒它们的条件成熟时,则可以从“候车室”中将这些进程唤醒。而这个“候车室”就是等待队列。
2.2队列描述
1、定义等待队列
wait_queue_head_t my_queue
2、初始化等待队列
init_waitqueue_head(&my_queue)
3、定义+初始化等待队列
DECLARE_WAIT_QUEUE_HEAD(my_queue)
4、进入等待队列,睡眠
4.1 wait_event(queue,condition)
当condition(布尔表达式)为真时,立即返回;否则让进程
进入TASK_UNINTERRUPTIBLE模式的睡眠,并挂在queue参数所指定的等待队列上。
4.2wait_event_interruptible(queue,condition)
当condition(布尔表达式)为真时,立即返回;否则让
进程进入TASK_INTERRUPTIBLE的睡眠,并挂在queue参数所指定的等待队列上。
4.3int wait_event_killable(queue, condition)
当condition(一个布尔表达式)为真时,立即返回;否则让进程进入TASK_KILLABLE的睡眠,并挂在queue参数所指定的等待队列上。
5、从等待队列中唤醒进程
5.1 wake_up(wait_queue_t *q)
从等待队列q中唤醒状态为TASK_UNINTERRUPTIBLE,TASK_INTERRUPTIBLE,TASK_KILLABLE 的所有进程。
5.2 wake_up_interruptible(wait_queue_t *q)
从等待队列q中唤醒状态为TASK_INTERRUPTIBLE 的进程
按键阻塞代码如下(也是关于按键所有的程序):
1 #include <linux/module.h> 2 #include <linux/init.h> 3 #include <linux/miscdevice.h> 4 #include <linux/interrupt.h> 5 #include <linux/io.h> 6 #include <linux/fs.h> 7 #include <linux/slab.h> 8 #include <linux/uaccess.h> 9 10 #include <linux/sched.h> 11 12 #define GPNCON 0x7f008830 13 #define GPNDAT 0x7f008834 14 15 MODULE_LICENSE("GPL"); 16 17 struct work_struct *work1; 18 unsigned int *gpio_dat; 19 struct timer_list key_timer; 20 21 unsigned int key_num = 0; 22 wait_queue_head_t key_q; 23 24 void work1_func(struct work_struct *work) 25 { 26 //启动定时器 100毫秒超时=HZ/10,HZ=1秒。jiffies是系统当前时间 27 mod_timer(&key_timer,jiffies + (HZ/10)); //启动定时器 28 } 29 30 void key_timer_func(unsigned long data) //定时器超时函数 31 { 32 unsigned int key_val; 33 key_val = readw(gpio_dat) &0x01; 34 if(key_val==0) 35 key_num = 1; 36 key_val = readw(gpio_dat) &0x02; 37 if(key_val==0) 38 key_num = 2; 39 wake_up(&key_q); 40 } 41 42 irqreturn_t key_int(int irp,void *dev_id) //中断处理函数 43 { 44 //3.提交下半部 45 schedule_work(work1); 46 return IRQ_HANDLED; 47 } 48 49 void key_hw_init(void) //硬件初始化 50 { 51 unsigned int *gpio_config; 52 unsigned short data; 53 gpio_config = ioremap(GPNCON,4); //动态映射虚拟地址 54 data = readw(gpio_config); 55 data &= ~0xfff; 56 data |= 0xaaa; 57 writew(data,gpio_config); 58 gpio_dat = ioremap(GPNDAT,4); //将数据寄存器地址转化为虚拟地址 59 } 60 61 int key_open(struct inode *node,struct file *filp) 62 { 63 return 0; 64 } 65 66 ssize_t key_read (struct file *filp, char __user *buf, size_t size, loff_t *pos) 67 { 68 wait_event(key_q,key_num); //进入睡眠 69 70 copy_to_user(buf,&key_num,4); 71 72 key_num = 0; 73 return 4; 74 } 75 76 struct file_operations key_fops = 77 { 78 .open = key_open, 79 .read = key_read, 80 }; 81 82 struct miscdevice key_miscdev = 83 { 84 .minor = 200, //次设备号 85 .name = "key", 86 .fops = &key_fops, 87 }; 88 //驱动程序初始化 89 static int button_init(void) 90 { 91 misc_register(&key_miscdev); //1注册混杂设备 92 //2按键硬件初始化 93 key_hw_init(); 94 request_irq(IRQ_EINT(0),key_int,IRQF_TRIGGER_FALLING,"key",0); //注册中断处理程序 95 request_irq(IRQ_EINT(1),key_int,IRQF_TRIGGER_FALLING,"key",0); //注册中断处理程序 96 97 //3创建工作1 98 work1 = kmalloc(sizeof(struct work_struct),GFP_KERNEL); 99 INIT_WORK(work1,work1_func); 100 101 //4初始化内核定时器 102 init_timer(&key_timer); 103 key_timer.function = key_timer_func; 104 //注册定时器 105 add_timer(&key_timer); 106 //初始化等待队列 107 init_waitqueue_head(&key_q); 108 return 0; 109 } 110 111 static void button_exit(void) 112 { 113 misc_deregister(&key_miscdev); //注销混杂设备 114 //注销中断处理程序 115 free_irq(IRQ_EINT(0),0); 116 free_irq(IRQ_EINT(1),0); 117 } 118 119 module_init(button_init); 120 module_exit(button_exit);
按键应用程序:
1 #include <stdio.h> 2 #include <stdlib.h> 3 4 int main() 5 { 6 int fd; 7 int key_num; 8 9 //1.打开设备 10 fd = open("/dev/6410key",0); 11 if(fd<0) 12 printf("open device fail!\n"); 13 14 //2.读取设备 15 read(fd,&key_num,4); 16 printf("key is %d\n",key_num); 17 18 //3.关闭设备 19 close(fd); 20 }
运行过程如下: