上一篇博客实现了一个S3C2440的按键驱动 但是采用查询法实现,基本占尽了CPU资源,现在写一个改进型驱动,采用中断方式。
在上一篇基础上,我们要将按键对应的几个引脚设为中断引脚,多个引脚共享一个中断函数,然后利用传入的参数id来区分具体是哪个中断。整个代码基本工作流程为:当没有按键按下时,调用该驱动的应用程序处于休眠状态,当有按键按下时,进入到中断函数里去,看是哪个按键被按下。将该按键至于不定状态,然后启动为该按键服务的定时器,定时时间到了后进入定时器函数去判断,该按键是否仍然处于按下状态?如果按下,打印出相关键的信息,并唤醒队列。下面给出该驱动代码部分
#include <linux/miscdevice.h>
#include <linux/delay.h>
#include <asm/irq.h>
#include <mach/regs-gpio.h>
#include <mach/hardware.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/mm.h>
#include <linux/fs.h>
#include <linux/types.h>
#include <linux/delay.h>
#include <linux/moduleparam.h>
#include <linux/slab.h>
#include <linux/errno.h>
#include <linux/ioctl.h>
#include <linux/cdev.h>
#include <linux/string.h>
#include <linux/list.h>
#include <linux/pci.h>
#include <asm/uaccess.h>
#include <asm/atomic.h>
#include <asm/unistd.h>
#include <linux/poll.h>
#include <linux/irq.h>
#include <linux/interrupt.h>
/*#define definition*/
#define DEVICE_NAME "button_inter_drv" //设备名称 和文件系统里设备节点名称区分开
//#define BUTTON_MAJOR 0 //主设备号
#define KEY_TIMER_DELAY1 (HZ/50) //按下去抖20ms
#define KEY_TIMER_DELAY2 (HZ/10) //抬起去抖100ms
#define KEY_DOWN 0 //按键按下状态
#define KEY_UP 1 //按键抬起状态
#define KEY_UNCERTAIN 2 //按键不确定
static volatile int key_status[4]; //记录四个按键的状态
static struct timer_list key_timers[4]; //定义4个去抖定时器
static DECLARE_WAIT_QUEUE_HEAD(key_waitq); //定义并初始化等待队列
static volatile int ev_press = 0; //按键按下的标识
/*static unsigned long led_table[] =
{
S3C2410_GPF0,
S3C2410_GPF1,
S3C2410_GPF2,
S3C2410_GPF4,
};
static unsigned int led_cfg_table[] =
{
S3C2410_GPF0_INP,
S3C2410_GPF1_INP,
S3C2410_GPF2_INP,
S3C2410_GPF4_INP ,
};*/
struct key_irq_desc //组织硬件资源结构体
{
int irq; //中断号
int pin; //哪个引脚
int pin_setting; //该引脚配置成中断模式
char *name; //该中断名字,开始我们不知道这个起什么用 先写上
};
static struct key_irq_desc key_irqs[] = //根据上面的结构体我们来组织4个按键的具体配置
{
{IRQ_EINT0, S3C2410_GPF0, S3C2410_GPF0_EINT0, "key0"},
{IRQ_EINT1, S3C2410_GPF1, S3C2410_GPF1_EINT1, "key1"},
{IRQ_EINT2, S3C2410_GPF2, S3C2410_GPF2_EINT2, "key2"},
{IRQ_EINT4, S3C2410_GPF4, S3C2410_GPF4_EINT4, "key4"},
};
static irqreturn_t key_interrupt(int irq, void *dev_id, struct pt_regs *regs) //中断入口函数 第一个参数为中断号,第二个为中断id
//前面注册中断的传入
//共享中断的入口函数
{
int line = (int)dev_id; //将(void *)类型的共享中断id转化为int类型
//根据前面。open函数中初始化的id为0对于于GPF0的引脚
//或者说当GPF0按下时进入这个中断 line = 0;
if(key_status[line] == KEY_UP)
{
//设置当前状态为不确定
key_status[line] = KEY_UNCERTAIN;
//设置定时器并启动
key_timers[line].expires = jiffies + KEY_TIMER_DELAY1;
add_timer(&key_timers[line]); //启用定时器
}
return IRQ_RETVAL(IRQ_HANDLED);
}
static void key_timer(unsigned long arg) //定时器处理函数,当定时到就会调用该函数
{
int line = arg; //假设 line =5的定时器为key_irqs[0].pin服务
//line =6的定时器为key_irqs[1].pin服务
//line =7的定时器为key_irqs[2].pin服务
//line =8的定时器为key_irqs[3].pin服务
line -= 5;
int up = s3c2410_gpio_getpin(key_irqs[line].pin); //表示形参为0的定时器为key_irqs[0].pin服务
if(!up) //表示按下
{
if(key_status[line] == KEY_UNCERTAIN)//中断函数里一按下 按键属于不确定状态 并启动延时
{
//当前按键状态变为按下状态
key_status[line] = KEY_DOWN;
switch(line)
{
case 0:
printk("key0 put down");
break;
case 1:
printk("key1 put down");
break;
case 2:
printk("key2 put down");
break;
case 3:
printk("key3 put down");
break;
default:
printk("unknow key put down");
break;
}
//标识当前按键已按下 并唤醒等待队列
ev_press = 1;
wake_up_interruptible(&key_waitq); //按键按下 唤醒队列
}
//设置当前按键抬起去抖定时
key_timers[line].expires = jiffies + KEY_TIMER_DELAY2; //这个就是定时时间了
add_timer(&key_timers[line]); //我们需要这个定时器一直工作 每次进入定时函数需要重新加入定时列表
}
else //高电平状态
{
key_status[line] = KEY_UP;
}
}
//.open func
static int button_drv_open(struct inode *inode, struct file *filp)
{
int i;
int ret;
for(i = 0; i < 4; i++)
{
s3c2410_gpio_cfgpin(key_irqs[i].pin, key_irqs[i].pin_setting); //设置为中断触发
set_irq_type(key_irqs[i].irq, IRQ_TYPE_EDGE_FALLING); //中断方式为下降沿触发
ret = request_irq(key_irqs[i].irq, key_interrupt, IRQF_DISABLED, key_irqs[i].name, (void *)i);//最后一个参数为设备id
//申请中断,申请成功后返回0
if(ret) //失败
{
break;
}
key_status[i] = KEY_UP; //按键初始状态为抬起
key_timers[i].function = key_timer; //注册定时器处理函数它的形参就是就是.data即i
key_timers[i].data = i + 5; // 为了区分我们设定定时器的形参分别为5 6 7 8
init_timer(&key_timers[i]); //四个按键对应的4个定时器初始化
}
if(ret) //注册中断失败 将前面成功的注销
{
i--;
for(; i>=0; i--)
{
//释放已经注册的中断
disable_irq(key_irqs[i].irq);
free_irq(key_irqs[i].irq, (void *)i); //释放时参数包含中断号和中断id
}
return -EBUSY;
}
printk("button_drv_open\n");
/*配置GPIF0 1 2 4 为输入*/
return 0;
}
static int button_close(struct inode *inode, struct file *filp)
{
int i;
//释放4个定时器和中断
for(i = 0; i < 4; i++)
{
del_timer(&key_timers[i]); //删除定时器
disable_irq(key_irqs[i].irq); //禁止中断
free_irq(key_irqs[i].irq, (void *) i); //释放中断
}
return 0;
}
ssize_t button_read_drv(struct file *file, char __user *buf, size_t size, loff_t *ppos)
{
unsigned long ret;
//int i;
if(!ev_press)//0 表示没有产生按键按下
{
if(file->f_flags & O_NONBLOCK) //如果应用程序采用非阻塞方式读数据 则返回错误
{
return -EAGAIN; //返回重试的标识符
}
else //阻塞方式 当没有按键按下时让等待队列进入睡眠
{
wait_event_interruptible(key_waitq, ev_press); //当ev_press不成立(为0),挂起队列
}
}
//如果ev_press =1表示按键事件发生了 需要清按键事件
ev_press = 0;
ret = copy_to_user(buf, (void *)key_status, sizeof(key_status)); //只有按键按下了 才向用户传递数据
return ret? -EFAULT:sizeof(key_status);
}
static unsigned int button_poll(struct file *file, struct poll_table_struct *wait)
{
unsigned int mask = 0;
//添加等待队列到等待队列列表中(poll_table)
poll_wait(file, &key_waitq, wait); //将key_waitq加入等待队列列表
if(ev_press)
{
mask |= POLLIN | POLLRDNORM;//标识数据可以获得
printk("button_poll: mask= %d", mask);
}
return mask;
}
//define the file_operations
static struct file_operations button_drv_fops = {
.owner = THIS_MODULE,
.open = button_drv_open,
.read = button_read_drv,
.release = button_close,
.poll = button_poll,
};
static struct class *button_drv_class; //定义一个类 方便在/dev目录下建立一个节点
int major; //自动分配主设备号
int button_drv_init(void)
{
//int ret;
//ret = register_chrdev(FIRST_MAJOR, DEVICE_NAME, &myfirst_drv_fops);
major = register_chrdev(0, DEVICE_NAME, &button_drv_fops);
//if(ret < 0)//
if(major < 0)
{
printk("button_drv can not register major number!\n");
return major;
}
/*注册一个类 使得mdev可以在/dev目录下自动建立设备节点 不需要手动mknod dev c 224 0这样的操作*/
button_drv_class = class_create(THIS_MODULE, DEVICE_NAME);
if(IS_ERR(button_drv_class))
{
printk("error, fail to init button_drv_class");
return -1;
}
/*如果上面成功注册进mdev的话 下面自动在/dev目录下创建一个设备节点*/
device_create(button_drv_class, NULL, MKDEV(major, 0), NULL, "BUTTON_TEST"); //这个就是在/dev下创建的BUTTON_TEST 设备节点了 应用程序调用
printk("BUTTON_DRV initialized! \n");
return 0;
}
void button_drv_exit(void)
{
unregister_chrdev(major, DEVICE_NAME); //卸载驱动
device_destroy(button_drv_class, MKDEV(major, 0)); //删掉设备节点
class_destroy(button_drv_class); //注销类
printk("rm -rf BUTTON_DRV! \n");
}
module_init(button_drv_init);
module_exit(button_drv_exit);
MODULE_LICENSE("GPL"); //获得授权
因为采用的阻塞方式,下面测试该驱动的程序为:
#include <unistd.h>
#include <sys/ioctl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <pthread.h>
#include <errno.h>
int main(int argc, char **argv)
{
int fd;
int key_status[4];
int i;
int ret;
fd_set rds; //fd_set 是一组文件描述符的集合
//以阻塞方式打开设备,非阻塞方式时 flags = 0_NONBLOCK
fd = open("/dev/BUTTON_TEST", 0); //打开设备 并读写权限
if(fd < 0)
{
perror("open device failed");
exit(1);
}
while(1)
{
FD_ZERO(&rds); //在&rds中清除所有的文件描述符
FD_SET(fd, &rds); //将fd加入到&rds
ret = select(fd + 1, &rds, NULL, NULL, NULL);
if(ret < 0)
{
printf("Read Buttons Device Failed \n");
exit(1);
}
if(ret == 0)
{
printf("Read Buttons Device Timeout\n");
}
if(FD_ISSET(fd, &rds)) //检测rds中的fd是否有变化 既是否可读取键盘值
{
ret = read(fd, key_status, sizeof(key_status));
for(i = 0; i< 4; i++)
{
if(key_status[i] == 0)
{
printf("key%d is down!\n", i);
}
}
}
}
close(fd);
return 0;
}
实现该驱动的时候,很多资料是从网上找的现成的用,有几个具体函数现在还不是很懂具体用法,先去研究下,下一篇重点解释里面用到的一些关键函数。