嵌入式学习-驱动开发-lesson4-按键混杂设备驱动
2016-07-30 21:16
666 查看
一、按键驱动硬件操作实现
1)按键硬件初始化
按键的硬件初始化和在裸机编程时一样,就是将其设置为外部中断模式,但在这里需要注意虚拟地址与物理地址的转化。#define GPNCON 0x7f008830 //控制寄存器 //硬件初始化 void kw_init() { int *gpncon; int data; gpncon = ioremap(GPNCON,4); //地址转换 data = readw(gpncon); //只对后两位设置,不破坏其他位 data &= ~0b11; data |= 0b10; //设置为外部中断模式 writew(data,gpncon); //写入 }
2).实现中断功能
1中断类型与中断号
在裸机中用的是中断类型硬件号、序列号)如 EINT0、EINT1等。而在Linux中则使用的是中断号。中断类型与中断号对应之间的对应,在内核中,irqs.h 中则定义了相应的中断号
如上图所示,我们的硬件使用的是VIC0 EINT0这一中断,对应的中断号为IRQ_EINT(0)。
因此中断初始化函数如下所示:
/*中断初始化*/ request_irq(IRQ_EINT(0),key_handle,IRQF_TRIGGER_FALLING,"key",0); //IRQF_TRIGGER_FALLING 从高电平到低电平产生中断 下降沿
贴上代码:
/********************************************* *File name :key.c *Author :stone *Date :2016/07/29 *Function :1.按键混杂设备硬件初始化 + 按键中断处理 *********************************************/ #include <linux/init.h> #include <linux/module.h> #include <linux/miscdevice.h> #include <linux/interrupt.h> #include <linux/fs.h> #include <linux/io.h> #define GPNCON 0x7f008830 //控制寄存器 //硬件初始化 void kw_init() { int *gpncon; int data; gpncon = ioremap(GPNCON,4); //地址转换 data = readw(gpncon); //只对后两位设置,不破坏其他位 data &= ~0b11; data |= 0b10; //设置为外部中断模式 writew(data,gpncon); //写入 } irqreturn_t key_handle(int irq,void *dev_id) { /*1.检测是否发生按键中断*/ /*2.清除已经发生的按键中断*/ /*3.打印按键值*/ printk(KERN_WARNING"key down !\n"); return 0; } int key_open(struct inode *node ,struct file *filp) { return 0; } struct file_operations key_ops = { .open = key_open, }; struct miscdevice misc = { .minor = 200, /*次设备号*/ .name = "key", .fops = &key_ops, }; static int key_init() { /*注册混杂设备*/ misc_register(&misc); /*硬件初始化*/ kw_init(); /*中断初始化*/ request_irq(IRQ_EINT(0),key_handle,IRQF_TRIGGER_FALLING,"key",0); //IRQF_TRIGGER_FALLING 从高电平到低电平产生中断 下降沿 return 0; } static void key_exit() { /*注销混杂设备*/ misc_deregister(&misc); /*注销中断*/ free_irq(IRQ_EINT(0),0); } MODULE_LICENSE("GPL"); module_init(key_init); module_exit(key_exit);
二、中断分层设计
在Linux中,有慢速中断和快速中断之分,这一点在上一课linux中断处理流程中有提到。1).慢速中断
在中断过程中,允许别的中断的产生,但若产生的中断是同类型的中断,则忽略掉,这样就会会产生中断丢失现象。2).快速中断
在处理中断过程中,中断控制位IF被关掉,因此别的无法产生中断在中断处理程序中,会处理两种工作:
1.和硬件相关的 如:读取数据
2.一些比较无关的程序:如检测、处理
因此提出了中断分层的设计,将中断分为上半部 和 下半部。
上半部:当中断发生时,它进行相应地硬件读写,并登记该中断。一般是由中断处理程序充当上半部。
下半部:在系统空闲的时候对上半部登记的中断进行后续处理
3).工作队列
工作队列是一种将任务推后执行的形式,他把推后的任务交由一个内核线程去执行。这样下半部会在进程上下文执行,它允许重新调度甚至睡眠。 每个被推后的任务叫做“工作”,由这些工作组成的队列称为工作队列。把下半部形成工作挂到链表上去,内核线程会自动执行
Linux内核使用struct workqueue_struct来描述一个工作队列:
struct workqueue_struct { struct cpu_workqueue_struct *cpu_wq; struct list_head list; const char *name; /*workqueue name*/ int singlethread; int freezeable; /* Freeze threads during suspend */ int rt; };
Linux内核使用struct work_struct来描述一个工作项:
struct work_struct { atomic_long_t data; struct list_head entry; work_func_t func; }; typedef void (*work_func_t)(struct work_struct *work);
4)工作队列使用流程
创建工作队列create_workqueue
创建工作
INIT_WORK
提交工作(将工作挂载到工作队列上去)
queue_work
在大多数情况下, 驱动并不需要自己建立工作队列,只需定义工作, 然后将工作提交到内核已经定义好的工作队列keventd_wq中。
那么只需要两步:
1.创建工作
INIT_WORK
2.提交工作到默认队列
schedule_work
贴上代码:
/********************************************* *File name :key.c *Author :stone *Date :2016/07/29 *Function :1.按键混杂设备硬件初始化 + 按键中断处理 2.中断分层设计--工作队列 *********************************************/ #include <linux/init.h> #include <linux/module.h> #include <linux/miscdevice.h> #include <linux/interrupt.h> #include <linux/fs.h> #include <linux/io.h> #include <linux/slab.h> struct work_struct *work1; #define GPNCON 0x7f008830 //控制寄存器 //工作项 void work1_func(struct work_struct *work) { printk(KERN_WARNING"key down !!!!\n"); } //硬件初始化 void kw_init() { int *gpncon; int data; gpncon = ioremap(GPNCON,4); //地址转换 data = readw(gpncon); //只对后两位设置,不破坏其他位 data &= ~0b11; data |= 0b10; //设置为外部中断模式 writew(data,gpncon); //写入 } //中断处理函数 irqreturn_t key_handle(int irq,void *dev_id) { /*1.检测是否发生按键中断*/ /*2.清除已经发生的按键中断*/ /*3.打印按键值*/ //printk(KERN_WARNING"key down !\n"); /*提交工作到下半部*/ schedule_work(work1); return 0; } //open int key_open(struct inode *node ,struct file *filp) { return 0; } struct file_operations key_ops = { .open = key_open, }; struct miscdevice misc = { .minor = 200, /*次设备号*/ .name = "key", .fops = &key_ops, }; static int key_init() { /*注册混杂设备*/ misc_register(&misc); /*硬件初始化*/ kw_init(); /*中断初始化*/ request_irq(IRQ_EINT(0),key_handle,IRQF_TRIGGER_FALLING,"key",0); //IRQF_TRIGGER_FALLING 从高电平到低电平产生中断 下降沿 //创建工作 work1 = kmalloc (sizeof(struct work_struct),GFP_KERNEL); INIT_WORK(work1,work1_func); return 0; } static void key_exit() { /*注销混杂设备*/ misc_deregister(&misc); /*注销中断*/ free_irq(IRQ_EINT(0),0); } MODULE_LICENSE("GPL"); module_init(key_init); module_exit(key_exit);
三、按键定时器去抖
1).按键抖动
按键所用开关为机械弹性开关,当机械触点断开、闭合时,由于机械触点的弹性作用,开关不会马上稳定地接通或断开。因而在闭合及断开的瞬间总是伴随有一连串的抖动。按键去抖动的方法主要有二种,一种是硬件电路去抖动(貌似ok6410已经实现了硬件去抖^_^);另一种就是软件延时去抖。而延时又一般分为二种,一种是for循环等待,另一种是定时器延时。在操作系统中,由于效率方面的原因,一般不允许使用for循环来等待,只能使用定制器。
2).内核定时器
Linux内核使用struct timer_list来描述一个定时器:struct timer_list { struct list_head entry; unsigned long expires; void (*function)(unsigned long); //函数指针 unsigned long data; struct tvec_base *base; };
3)定时器使用流程
1.定义定时器变量struct timer_list key_timmer;
2.1初始化定时器
init_timer(&key_timmer);
2.2设置超时函数
key_timmer.function = key_timer_func;
3.向内核注册定时器
add_timer(&key_timmer);
4.启动定时器
mod_timer(&key_timmer,jiffies + HZ/10); /* 延时1S/10=100ms*/
贴上代码:
/********************************************* *File name :key.c *Author :stone *Date :2016/07/29 *Function :1.按键混杂设备硬件初始化 + 按键中断处理 2.中断分层设计--工作队列 3.按键定时器去抖 *********************************************/ #include <linux/init.h> #include <linux/module.h> #include <linux/miscdevice.h> #include <linux/interrupt.h> #include <linux/fs.h> #include <linux/io.h> #include <linux/slab.h> struct work_struct *work1; //工作队列 #define GPNCON 0x7f008830 //控制寄存器 #define GPNDAT 0x7f008834 //数据寄存器 unsigned int *gpiodata; //数据寄存器的虚拟地址 struct timer_list key_timmer; //定义定时器 //超时函数 void key_timer_func(unsigned long data) { unsigned int key_val; /*检测按键是否按下*/ key_val = readw(gpiodata)&0x01; if(key_val == 0) /*有效*/ printk(KERN_WARNING"key down !\n"); } //下半部工作项 void work1_func(struct work_struct *work) { /*启动定时器*/ mod_timer(&key_timmer,jiffies + HZ/10); /* 延时1S/10=100ms*/ //printk(KERN_WARNING"key down !!!!\n"); } //硬件初始化 void kw_init() { int *gpncon; int data; gpncon = ioremap(GPNCON,4); //地址转换 data = readw(gpncon); //只对后两位设置,不破坏其他位 data &= ~0b11; data |= 0b10; //设置为外部中断模式 writew(data,gpncon); //写入 gpiodata = ioremap(GPNDAT,4); } //中断处理函数 irqreturn_t key_handle(int irq,void *dev_id) { /*1.检测是否发生按键中断*/ /*2.清除已经发生的按键中断*/ /*3.打印按键值*/ //printk(KERN_WARNING"key down !\n"); /*提交工作到下半部*/ schedule_work(work1); return 0; } int key_open(struct inode *node ,struct file *filp) { return 0; } struct file_operations key_ops = { .open = key_open, }; struct miscdevice misc = { .minor = 200, /*次设备号*/ .name = "key", .fops = &key_ops, }; static int key_init() { /*注册混杂设备*/ misc_register(&misc); /*硬件初始化*/ kw_init(); /*中断初始化*/ request_irq(IRQ_EINT(0),key_handle,IRQF_TRIGGER_FALLING,"key",0); //创建工作 work1 = kmalloc (sizeof(struct work_struct),GFP_KERNEL); INIT_WORK(work1,work1_func); /*初始化定时器*/ init_timer(&key_timmer); /*设置超时函数*/ key_timmer.function = key_timer_func; /*向内核注册定时器*/ add_timer(&key_timmer); return 0; } static void key_exit() { /*注销混杂设备*/ misc_deregister(&misc); /*注销中断*/ free_irq(IRQ_EINT(0),0); } MODULE_LICENSE("GPL"); module_init(key_init); module_exit(key_exit);
四、多按键优化
在上面只是实现了一个按键的功能,为了加强理解,在本课中将实现两个按键/(ㄒoㄒ)/~~的中断处理。相对于单个按键,多个按键需要修改的地方有:
1.硬件方面
2.中断初始化
3.按键按下时对按键的检测是那个按键按下
同时,对按键进行了优化,添加了应用程序,当按键按下时,应用程序通过read函数,获取到那个按键被按下。
详细过程就不再说了,下面贴上代码:
key.c
/********************************************* *File name :key.c *Author :stone *Date :2016/07/29 *Function :1.按键混杂设备硬件初始化 + 按键中断处理 2.中断分层设计--工作队列 3.按键定时器去抖 4.多按键优化 *********************************************/ #include <linux/init.h> #include <linux/module.h> #include <linux/miscdevice.h> #include <linux/interrupt.h> #include <linux/fs.h> #include <linux/io.h> #include <linux/slab.h> #include <linux/uaccess.h> struct work_struct *work1; //工作队列 #define GPNCON 0x7f008830 //控制寄存器 #define GPNDAT 0x7f008834 //数据寄存器 unsigned int *gpiodata; //数据寄存器的虚拟地址 struct timer_list key_timmer; //定时器 unsigned int key_num; //超时函数 void key_timer_func(unsigned long data) { unsigned int key_val; /*检测按键是否按下*/ key_val = readw(gpiodata)&0x01; if(key_val == 0) //printk(KERN_WARNING"key1 down !\n"); key_num = 1; key_val = readw(gpiodata)&0x02; if(key_val == 0) /*有效*/ //printk(KERN_WARNING"key2 down !\n"); key_num = 2; } void work1_func(struct work_struct *work) { /*启动定时器*/ mod_timer(&key_timmer,jiffies + HZ/10); /* 延时1S/10=100ms*/ //printk(KERN_WARNING"key down !!!!\n"); } void kw_init() { int *gpncon; int data; gpncon = ioremap(GPNCON,4); data = readw(gpncon); //只对后两位设置,不破坏其他位 data &= ~0b1111; data |= 0b1010; writew(data,gpncon); gpiodata = ioremap(GPNDAT,4); } irqreturn_t key_handle(int irq,void *dev_id) { /*1.检测是否发生按键中断*/ /*2.清除已经发生的按键中断*/ /*3.打印按键值*/ //printk(KERN_WARNING"key down !\n"); /*提交工作到下半部*/ schedule_work(work1); return 0; } //open int key_open(struct inode *node ,struct file *filp) { return 0; } //read ssize_t key_read(struct file *filp,char __user *buf,size_t size,loff_t *pos) { copy_to_user(buf,&key_num,4); return 4; } struct file_operations key_ops = { .open = key_open, .read = key_read, }; struct miscdevice misc = { .minor = 200, /*次设备号*/ .name = "key", .fops = &key_ops, }; //入口函数 static int key_init() { /*注册混杂设备*/ misc_register(&misc); /*硬件初始化*/ kw_init(); /*中断初始化*/ request_irq(IRQ_EINT(0),key_handle,IRQF_TRIGGER_FALLING,"key",0); request_irq(IRQ_EINT(1),key_handle,IRQF_TRIGGER_FALLING,"key",0); //创建工作 work1 = kmalloc (sizeof(struct work_struct),GFP_KERNEL); INIT_WORK(work1,work1_func); /*初始化定时器*/ init_timer(&key_timmer); /*设置超时函数*/ key_timmer.function = key_timer_func; /*向内核注册定时器*/ add_timer(&key_timmer); return 0; } static void key_exit() { /*注销混杂设备*/ misc_deregister(&misc); /*注销中断*/ free_irq(IRQ_EINT(0),0); } MODULE_LICENSE("GPL"); module_init(key_init); module_exit(key_exit);
key_app.c
/********************************************* *File name :key_app.c *Author :stone *Date :2016/07/29 *Function :通过read函数,获取内核中是那个按键按下 *********************************************/ #include <stdio.h> #include <stdlib.h> int main() { int fd; int buf; fd = open("/dev/6410key0",0); read(fd,&buf,4); printf("num is %d\n",buf); close(fd); return 0; }
五、阻塞型驱动设计
1).阻塞必要性
当一个设备无法立刻满足用户的读写请求时应当如何处理?例如:调用read时,设备没有数据提供, 但以后可能会有;或者一个进程试图向设备写入数据,但是设备暂时没有准备好接收数据。当上述情况发生的时候,驱动程序应当(缺省地)阻塞进程,使它进入等待(睡眠)状态,直到请求可以得到满足。
2).内核等待队列
在实现阻塞驱动的过程中,也需要有一个“候车室”来安排被阻塞的进程“休息”,当唤醒它们的条件成熟时,则可以从“候车室”中将这些进程唤醒。而这个“候车室”就是等待队列。1、定义等待队列
wait_queue_head_t my_queue
2、初始化等待队列
init_waitqueue_head(&my_queue)
3、定义+初始化等待队列
DECLARE_WAIT_QUEUE_HEAD(my_queue)
4、进入等待队列,睡眠
wait_event(queue,condition) 当condition(布尔表达式)为真时,立即返回;否则让进程进入TASK_UNINTERRUPTIBLE(不可中断)模式的睡眠,并挂在queue参数所指定的等待队列上。 wait_event_interruptible(queue,condition) 当condition(布尔表达式)为真时,立即返回;否则让进程进入TASK_INTERRUPTIBLE(可中断)的睡眠,并挂在queue参数所指定的等待队列上。 int wait_event_killable(queue, condition) 当condition(一个布尔表达式)为真时,立即返回;否则让进程进入TASK_KILLABLE的睡眠,并挂在queue参数所指定的等待队列上。
5、从等待队列中唤醒进程
wake_up(wait_queue_t *q) 从等待队列q中唤醒状态为TASK_UNINTERRUPTIBLE, TASK_INTERRUPTIBLE,TASK_KILLABLE 的所有进程。 wake_up_interruptible(wait_queue_t *q) 从等待队列q中唤醒状态为TASK_INTERRUPTIBLE 的进程
贴上代码:
key.c
/********************************************* *File name :key.c *Author :stone *Date :2016/07/29 *Function :1.按键混杂设备硬件初始化 + 按键中断处理 2.中断分层设计--工作队列 3.按键定时器去抖 4.多按键优化 5.阻塞型驱动 *********************************************/ #include <linux/init.h> #include <linux/module.h> #include <linux/miscdevice.h> #include <linux/interrupt.h> #include <linux/fs.h> #include <linux/io.h> #include <linux/slab.h> #include <linux/uaccess.h> #include <linux/sched.h> struct work_struct *work1; //工作队列 #define GPNCON 0x7f008830 //控制寄存器 #define GPNDAT 0x7f008834 //数据寄存器 unsigned int *gpiodata; //数据寄存器的虚拟地址 struct timer_list key_timmer; //定时器 unsigned int key_num; /*定义等待队列*/ wait_queue_head_t key_q; //超时函数 void key_timer_func(unsigned long data) { unsigned int key_val; /*检测按键是否按下*/ key_val = readw(gpiodata)&0x01; if(key_val == 0) //printk(KERN_WARNING"key1 down !\n"); key_num = 1; key_val = readw(gpiodata)&0x02; if(key_val == 0) /*有效*/ //printk(KERN_WARNING"key2 down !\n"); key_num = 2; wake_up(&key_q);//唤醒进程 } void work1_func(struct work_struct *work) { /*启动定时器*/ mod_timer(&key_timmer,jiffies + HZ/10); /* 延时1S/10=100ms*/ //printk(KERN_WARNING"key down !!!!\n"); } void kw_init() { int *gpncon; int data; gpncon = ioremap(GPNCON,4); data = readw(gpncon); //只对后两位设置,不破坏其他位 data &= ~0b1111; data |= 0b1010; writew(data,gpncon); gpiodata = ioremap(GPNDAT,4); } irqreturn_t key_handle(int irq,void *dev_id) { /*1.检测是否发生按键中断*/ /*2.清除已经发生的按键中断*/ /*3.打印按键值*/ //printk(KERN_WARNING"key down !\n"); /*提交工作到下半部*/ schedule_work(work1); return 0; } int key_open(struct inode *node ,struct file *filp) { return 0; } ssize_t key_read(struct file *filp,char __user *buf,size_t size,loff_t *pos) { wait_event(key_q,key_num);//进入等待队列,睡眠 当key_num为true,即当按键按下时唤醒 copy_to_user(buf,&key_num,4); key_num = 0; return 4; } struct file_operations key_ops = { .open = key_open, .read = key_read, }; struct miscdevice misc = { .minor = 200, /*次设备号*/ .name = "key", .fops = &key_ops, }; static int key1_init() { /*注册混杂设备*/ misc_register(&misc); /*硬件初始化*/ kw_init(); /*中断初始化*/ request_irq(IRQ_EINT(0),key_handle,IRQF_TRIGGER_FALLING,"key",0); request_irq(IRQ_EINT(1),key_handle,IRQF_TRIGGER_FALLING,"key",0); //创建工作 work1 = kmalloc (sizeof(struct work_struct),GFP_KERNEL); INIT_WORK(work1,work1_func); /*初始化定时器*/ init_timer(&key_timmer); /*设置超时函数*/ key_timmer.function = key_timer_func; /*向内核注册定时器*/ add_timer(&key_timmer); /*初始化等待队列*/ init_waitqueue_head(&key_q); return 0; } static void key_exit() { /*注销混杂设备*/ misc_deregister(&misc); /*注销中断*/ free_irq(IRQ_EINT(0),0); } MODULE_LICENSE("GPL"); module_init(key1_init); module_exit(key_exit);
key_app.c
/********************************************* *File name :key_app.c *Author :stone *Date :2016/07/29 *Function :通过read函数,获取内核中是那个按键按下 *********************************************/ #include <stdio.h> #include <stdlib.h> int main() { int fd; int buf; fd = open("/dev/6410key0",0); read(fd,&buf,4); printf("num is %d\n",buf); close(fd); return 0; }
菜鸟一枚,如有错误,多多指教。。。
相关文章推荐
- 嵌入式学习-驱动开发-lesson3-混杂设备驱动模型与linux中断处理流程
- 嵌入式学习-驱动开发-lesson5-总线设备驱动模型及平台总线驱动
- 嵌入式学习-驱动开发-lesson1-字符设备驱动模型
- 嵌入式学习-驱动开发-lesson2-LED字符设备驱动
- 嵌入式学习-驱动开发-lesson7.1-网卡驱动架构分析驱动及CS8900流程分析
- 嵌入式学习-驱动开发-lesson7.2-DM9000驱动流程分析
- 嵌入式学习-驱动开发前奏-lesson2-内存管理与进程管理子系统
- 嵌入式学习-驱动开发前奏-lesson1-内核模块相关知识
- Linux嵌入式学习-烟雾传感器驱动-字符设备驱动-按键驱动
- 嵌入式学习-驱动开发-lesson6.3-UART驱动send和receive流程分析
- 嵌入式学习-驱动开发-lesson6.1-TTY驱动架构分析
- 嵌入式学习-驱动开发前奏-lesson4-驱动分类和硬件访问相关
- 嵌入式学习-驱动开发-lesson6.2-UART驱动初始化和open流程分析
- 嵌入式学习-驱动开发前奏-lesson3-linux内核链表
- 嵌入式Linux设备驱动开发之:按键驱动程序实例
- Linux设备驱动开发学习(3个)
- 字符设备驱动程序开发之基于中断的按键驱动加去抖动
- 嵌入式驱动开发的前期Linux 和 C学习(六)
- 嵌入式驱动开发的前期Linux 和C学习(二)
- linux 下块设备驱动开发学习笔记 2(sbull驱动分析)