字符设备驱动程序开发之基于中断的按键驱动加去抖动

中断实现原理分析


实现程序如下所示:

#include <linux/module.h>

#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <linux/poll.h>
#include <linux/irq.h>
#include <asm/irq.h>
#include <linux/interrupt.h>
#include <asm/uaccess.h>
#include <mach/regs-gpio.h>
#include <mach/hardware.h>
#include <linux/platform_device.h>
#include <linux/cdev.h>
#include <linux/sched.h>
#include <linux/gpio.h>
#include <linux/device.h>


#define DEVICE_NAME "buttons"  //加载模式后,执行”cat /proc/devices”命令看到的设备名称 

/*定义主设备号*/
int button_major;

/*中断标识变量,配合上面的队列使用,中断服务程序会把它设置为1,read 函数会把它清零*/
static volatile int ev_press = 0;

/*开发板上按键的状态变量,注意这里是'0',对应的ASCII 码为30*/
static volatile char key_values[] = {'0', '0', '0', '0', '0', '0'};


/*定义按键三种状态*/
#define KEY_DOWN             0                   //按键按下                     
#define KEY_UP               1                   //按键抬起         
#define KEY_UNCERTAIN       2                   //按键不确定                     


/*定义按键的个数*/
#define KEY_COUNT           6


static volatile int key_status[KEY_COUNT];       //记录 6 个按键的状态 


/*定义 6 个按键去抖动定时器*/
static struct timer_list key_timers[KEY_COUNT];  


/*HZ表示1s内时钟的滴答数*/
#define KEY_TIMER_DELAY1   (HZ/50)        //按键按下去抖延时20 毫秒         
#define KEY_TIMER_DELAY2   (HZ/10)        //按键抬起去抖延时100 毫秒


/*定义并初始化等待队列*/
static DECLARE_WAIT_QUEUE_HEAD(button_waitq);


/*
*定义中断所用的结构体
*/
struct button_irq_desc {
 int irq;          //按键对应的中断号
 int pin;          //按键所对应的GPIO 端口
 int pin_setting; //按键对应的引脚描述
 int number;       //定义键值,以传递给应用层/用户态
 char *name; //每个按键的名称
};


/*
*结构体实体定义
*/
static struct button_irq_desc button_irqs[]={
 {IRQ_EINT8 , S3C2410_GPG(0)  , S3C2410_GPG0_EINT8 , 0, "KEY0"},
 {IRQ_EINT11, S3C2410_GPG(3)  , S3C2410_GPG0_EINT8 , 1, "KEY1"},
 {IRQ_EINT13, S3C2410_GPG(5)  , S3C2410_GPG0_EINT8 , 2, "KEY2"},
 {IRQ_EINT14, S3C2410_GPG(6)  , S3C2410_GPG0_EINT8 , 3, "KEY3"},
 {IRQ_EINT15, S3C2410_GPG(7)  , S3C2410_GPG0_EINT8 , 4, "KEY4"},
 {IRQ_EINT19, S3C2410_GPG(11) , S3C2410_GPG0_EINT8 , 5, "KEY5"},
};


/*创建类和设备,以使设备加载时自动创建设备节点*/
static struct class *button_class;
static struct device *button_class_dev;


/*
*按键驱动的中断服务程序
*/
static irqreturn_t buttons_interrupt(int irq, void *dev_id)
{
 /*获取当前按键资源的索引 */
  int key = (int)dev_id;
   
  if(key_status[key] == KEY_UP) { 


  /*设置当前按键的状态为不确定*/
  key_status[key] = KEY_UNCERTAIN; 
 
  /*设置当前按键按下去抖定时器的延时并启动定时器,延时时间到后启动定时器服务程序*/
  key_timers[key].expires = jiffies + KEY_TIMER_DELAY1; 
  add_timer(&key_timers[key]); 
  
 }
  
 return IRQ_RETVAL(IRQ_HANDLED);/*正确的中断*/
}


/*
*中断触发后启动延时定时器,进入定时器服务后处理按键的状态
*/
static void buttons_timer(unsigned long arg) {


   /*获取当前按键资源的索引 */
    int key = arg; 


   /*获取当前按键引脚上的电平值来判断按键是按下还是抬起 */
   int down = !s3c2410_gpio_getpin(button_irqs[key].pin); 


   /*状态改变,按键被按下,从这句可以看出,当按键没有被按下的时候,寄存器的值为1(上拉),当按
键被按下的时候,寄存器对应的值为0*/
   if (down != (key_values[button_irqs[key].number] & 1)) { // Changed
   
  /*如果key1 被按下,则key_value[0]就变为'1',对应的ASCII 码为31,其它按键类似*/
 key_values[button_irqs[key].number] = '0' + down;
  
 /*设置中断标志为1*/
 ev_press = 1; 


 /*唤醒等待队列*/
 wake_up_interruptible(&button_waitq); 


   }


   /*如果按下*/
   if (down = 1) {
     if(key_status[key] == KEY_UNCERTAIN) {


 //标识当前按键状态为按下 
 key_status[key] = KEY_DOWN; 
 
}


 /*设置当前按键抬起去抖定时器的延时并启动定时器 */
      key_timers[key].expires = jiffies + KEY_TIMER_DELAY2; 
      add_timer(&key_timers[key]);


  }
  else{ //按键抬起


//标识当前按键状态为抬起 
     key_status[key] = KEY_UP; 
//enable_irq(button_irqs[key].irq);


  }

}


/*
*在应用程序执行open("/dev/buttons",…)时会调用到此函数,在这里,它的作用主要是注册6 个按键的中断。
*/
static int button_open(struct inode *inode, struct file *file)
{
 int i;
 int err = 0;


 for (i = 0; i < KEY_COUNT ; i++) {


    /*设备6个IO口为中断触发方式*/
    s3c2410_gpio_cfgpin(button_irqs[i].pin, button_irqs[i].pin_setting);


/*设置中断下降沿为有效触发*/ 
set_irq_type(button_irqs[i].irq, IRQ_TYPE_EDGE_FALLING); 


    /*申请中断,类型为快速中断,中断服务时屏蔽所有外部中断*/  
    err = request_irq(button_irqs[i].irq, buttons_interrupt, IRQF_DISABLED,
                        button_irqs[i].name, (void *)i);
  
  if(err)  break;


    /*初始化6个按键的状态为抬起*/ 
    key_status[i] = KEY_UP; 


   /*初始化延时定时器*/
setup_timer(&key_timers[i], buttons_timer, i); 
   
  }
 
 if (err) { /*如果出错,释放已经注册的中断,并返回*/
  i--;
  for (; i >= 0; i--) {


   disable_irq(button_irqs[i].irq);
   free_irq(button_irqs[i].irq, (void *)i);
  }
  
  return -EBUSY;


  }
 /*注册成功,则中断队列标记为1,表示可以通过read 读取*/ 
 //ev_press = 1;


 /*正常返回*/
 return 0;
}


/*
*此函数对应应用程序的系统调用close(fd)函数,在此,它的主要作用是当关闭设备时释放6 个按键的中断*
*处理函数
*/
static int button_close(struct inode *inode, struct file *file)
{
 int i;
 for (i = 0; i < sizeof(button_irqs)/sizeof(button_irqs[0]); i++) {
 
   /*删除定时器*/
  del_timer(&key_timers[i]);
  
  /*释放中断号,并注销中断处理函数*/
  disable_irq(button_irqs[i].irq);
  free_irq(button_irqs[i].irq, (void *)&button_irqs[i]);
 }
 return 0;
}


/*
*对应应用程序的read(fd,…)函数,主要用来向用户空间传递键值
*/
static int button_read(struct file *filp, char __user *buff, size_t count, loff_t *offp)
{
 unsigned long err;


 if (!ev_press) {


  /*当中断标识为0 时,并且该设备是以非阻塞方式打开时,返回*/
  if (filp->f_flags & O_NONBLOCK)
   return -EAGAIN;


  /*当中断标识为0 时,并且该设备是以阻塞方式打开时,进入休眠状态,等待被唤醒*/
  else   
   wait_event_interruptible(button_waitq, ev_press);
 }


 /*否则表示按键按下,则把中断标识清零*/
 ev_press = 0;


 /*一组键值被传递到用户空间*/
 err = copy_to_user(buff, (const void *)key_values, min(sizeof(key_values), count));
 return err ? -EFAULT : min(sizeof(key_values), count);
}

static unsigned int button_poll( struct file *file, struct poll_table_struct *wait)
{
 unsigned int mask = 0;


 /*把调用poll 或者select 的进程挂入队列,以便被驱动程序唤醒*/
 poll_wait(file, &button_waitq, wait);
 if (ev_press)
  mask |= POLLIN | POLLRDNORM;
 return mask;
}

/* 
*字符操作结构体
*/
static struct file_operations buttons_fops = {
    .owner   =   THIS_MODULE,    /* 这是一个宏,指向编译模块时自动创建的__this_module变量 */
    .open    =   button_open,
    .release =   button_close, 
    .read    =   button_read,
    .poll    =   button_poll,
};


/*
 * 模块加载函数
 */
static int __init button_init(void)
{
    /* 注册字符设备驱动程序*/
    button_major = register_chrdev(0, DEVICE_NAME, &buttons_fops);
   
    if (button_major < 0) {
      printk(DEVICE_NAME " can't register major number\n");
      return button_major;
    }


    button_class = class_create(THIS_MODULE, DEVICE_NAME); //创建类
     /* 创建设备/dev/buttons */
    button_class_dev = device_create(button_class, NULL, MKDEV(button_major, 0), NULL, DEVICE_NAME);


    printk(DEVICE_NAME " initialized\n");
    return 0;
}


/*
 * 模块卸载函数 
 */
static void __exit button_exit(void)
{
     /*删除设备*/ 
    device_unregister(button_class_dev);   


/*删除类,顺序不能颠倒*/
    class_destroy(button_class);          


/* 卸载驱动程序 */
    unregister_chrdev(button_major, DEVICE_NAME);
}


/* 这两行指定驱动程序的初始化函数和卸载函数 */
module_init(button_init);
module_exit(button_exit);


/* 描述驱动程序的一些信息,不是必须的 */
MODULE_AUTHOR("DreamCatcher");             // 驱动程序的作者
MODULE_DESCRIPTION("MINI2440 BUTTON Driver");   // 一些描述信息

MODULE_LICENSE("GPL"); //版权信息

测试程序还是用之前的测试程序,测试有如下结果,并且不会有抖动现象出现:

root@MINI2440:/# insmod mini2440_buttons.ko
buttons initialized
root@MINI2440:/# cd /opt
root@MINI2440:/opt# ./buttons_test&
root@MINI2440:/opt# key 1 is down
key 1 is up
key 2 is down
key 2 is up
key 3 is down
key 3 is up
key 6 is down
key 6 is up
key 4 is down
key 4 is up
key 5 is down
key 5 is up

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值