S3C2440按键驱动之中断法

时间:2023-01-11 17:49:43

上一篇博客实现了一个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;

}

实现该驱动的时候,很多资料是从网上找的现成的用,有几个具体函数现在还不是很懂具体用法,先去研究下,下一篇重点解释里面用到的一些关键函数。