您的位置:首页 > 其它

嵌入式学习-驱动开发-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;
}


菜鸟一枚,如有错误,多多指教。。。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: