7.自己写中断方式按键驱动程序(详解)

request_irq()和free_irq()分析完毕后,接下来开始编写上升沿中断的按键驱动

如下图,需要设置4个按键的EINT0, EINT2, EINT11, EINT19的模式为双边沿,且设置按键引脚为中断引脚

这里我们只需要使用request_irq函数就行了, 在request_irq函数里会初始chip->set_type(设置引脚和中断模式)

1.首先添加头文件

#include <linux/irq.h>         //要用到IRQ_EINT0和IRQT_RISING这些变量

2.在second_drv_open函数中,申请4个中断:

/* IRQ_EINT0:     中断号, 定义在 asm/arch/irqs.h,被linux/irq.h调用
buttons_irq :     中断服务函数,
IRQT_ BOTHEDGE:   双边沿中断, 定义在 asm/irq.h,被linux/irq.h调用
“S1”:             保存文件到/proc/interrupt/S1,
1:                dev_id,中断函数的参数, 被用来释放中断服务函数,中断时并会传入中断服务函数
*/

request_irq(IRQ_EINT0, buttons_irq,IRQT_BOTHEDGE, “S1”, 1);  
request_irq(IRQ_EINT2, buttons_irq,IRQT_ BOTHEDGE, “S2”, 1);
request_irq(IRQ_EINT11, buttons_irq,IRQT_ BOTHEDGE, “S3”, 1);
request_irq(IRQ_EINT19, buttons_irq,IRQT_ BOTHEDGE, “S4”, 1);

3.在file_oprations结构体中添加.release成员函数,用来释放中断

static struct file_operations second_drv_fops={
     .owner = THIS_MODULE,
     .open = second_drv_open,
     .read = second_drv_read,
     .release=second_drv_class,        //里面添加free_irq函数,来释放中断服务函数
};

然后写.release成员函数,释放中断:

int  second_drv_class(struct inode *inode, struct file  *file)
{
  free_irq(IRQ_EINT0,1);
  free_irq(IRQ_EINT2,1);
  free_irq(IRQ_EINT11,1);
  free_irq(IRQ_EINT19,1);

  return 0;

}

4.写action->handler中断服务函数,在第2小节里request_irq函数的中断服务函数是buttons_irq

static irqreturn_t  buttons_irq (int irq, void *dev_id)     //irq:中断号, void *:表示支持所有类型
{
  printk(“irq=%d\n”);
  return IRQ_HANDLED;
}

5.make后,然后放在开发板里insmod,并挂载好了buttons设备节点,如下图:

6.通过exec 5</dev/buttons   将/dev/buttons 设备节点挂载到-sh进程下描述符5:

如下图,使用ps查看-sh进程为801,然后ls -l /proc/801/fd 找到描述符5指向/dev/buttons

如下图,并申请中断,当有按键按下时,就进入中断服务函数buttons_irq()打印数据:

6.通过exec 5<&- 将描述符5卸载

会进入.release成员second_drv_class()函数释放中断,

然后cat /proc/interrupts会发现申请的中断已经注销掉了,在-sh进程fd文件里也没有文件描述符5

7.改进中断按键驱动程序

使用等待队列,让read函数没有中断时,进入休眠状态,降低CPU.

使用dev_id来获取不同按键的状态,是上升沿还是下降沿触发?

7.1接下来要用到以下几个函数:

s3c2410_gpio_getpin(unsigned int pin);     //获取引脚高低电平

pin: 引脚名称,例如:S3C2410_GPA0,定义在<asm/arch/regs-gpio.h>

队列3个函数(声明队列,唤醒队列,等待队列):

static DECLARE_WAIT_QUEUE_HEAD(qname);      

声明一个新的等待队列类型的中断

qname:就是中断名字,被用来后面的唤醒中断和等待中断

wake_up_interruptible(*qname);   

唤醒一个中断,会将这个中断重新添加到runqueue队列(将中断置为TASK_RUNNING状态)

qname:指向声明的等待队列类型中断名字

wait_event_interruptible(qname, condition);  

等待事件中断函数,用来将中断放回等待队列,

前提是condition要为0,然后将这个中断从runqueue队列中删除(将中断置为TASK_INTERRUPTIBLE状态),然后会在函数里一直for(; ;)判断condition为真才退出

注意:此时的中断属于僵尸进程(既不在等待队列,也不在运行队列),当需要这个进程时,需要使用wake_up_interruptible(*qname)来唤醒中断

qname: (wait queue):为声明的等待队列的中断名字

condition:状态,等于0时就是中断进入休眠, 1:退出休眠

7.2 驱动程序步骤

(1)定义引脚描述结构体数组,每个结构体都保存按键引脚和初始状态,然后在中断服务函数中通过s3c2410_gpio_getpin()来获取按键是松开还是按下(因为中断是双边沿触发),并保存在key_val里(它会在.read函数发送给用户层)

 /*
  *引脚描述结构体
  */
 struct pin_desc{
   unsigned int  pin;
   unsigned int  pin_status;
};
 /*
  *key初始状态(没有按下): 0x01,0x02,0x03,0x04
  *key状态(按下):                       0x81,0x82,0x83,0x84
  */
struct pin_desc  pins_desc[4]={
        {S3C2410_GPF0,0x01 }, 
        {S3C2410_GPF2, 0x02 },
        {S3C2410_GPG3, 0x03 },
        {S3C2410_GPG11,0x04},} ;

 (2)声明等待队列类型的中断button_wait:

static DECLARE_WAIT_QUEUE_HEAD(button_ wait); //声明等待队列类型的中断

(3)定义全局变量even _press,用于中断事件标志:

static volatile int even _press = 0;

(4)在.read函数里,将even _press置0放入等待事件中断函数中,判断even _press为真,才发送数据:

even_press = 0;                                

wait_event_interruptible(button_ wait, even _press);   //当even _press为真,表示有按键按下,退出等待队列  

copy_to_user(buf, &key_val, 1);       //even _press为真,有数据了,发送给用户层      

(5)在中断服务函数里,发生中断时, 就将even _press置1,并唤醒中断button_wait(.read函数里就会发送数据给用户层):

even _press = 0; wake_up_interruptible(&button_wait); //唤醒中断

7.3 更改测试程序second_interrupt_text.c

最终修改如下:

#include <sys/types.h>    //调用sys目录下types.h文件
#include <sys/stat.h>      //stat.h获取文件属性
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
/*secondtext            while一直获取按键信息   */
int main(int argc,char **argv)
{
 int fd,ret;
 unsigned int val=0;               
 fd=open("/dev/buttons",O_RDWR);       
 if(fd<0)
     {printf("can't open!!!\n");
     return -1;}
 while(1)
 {
     ret=read(fd,&val,1);    //读取一个值,(当在等待队列时,本进程就会进入休眠状态)
     if(ret<0)
     {
     printf("read err!\n");    
     continue;
     } 
  printf("key_val=0X%x\r\n",val);
}
 return 0;
}

8.运行结果

insmod second_interrupt.ko           //挂载驱动设备

./second_interrupt_text &             //后台运行测试程序 

创建了4个中断,如下图:

当没有按键按下时,这个进程就处于静止状态staitc,如下图所示:

在等待队列(休眠状态)下,该进程占用了CPU0%资源,如下图所示:

当有按键按下时,便打印数据,如下图所示:

下节继续改进按键程序—使用poll机制

本节驱动代码如下:

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <linux/irq.h>  
#include <asm/irq.h>
#include <asm/arch/regs-gpio.h>
#include <asm/hardware.h>
#include <asm/uaccess.h>
#include <asm/io.h>


static struct class *seconddrv_class;                 
static struct class_device   *seconddrv_class_devs; 

 
/*    声明等待队列类型中断 button_wait      */
static DECLARE_WAIT_QUEUE_HEAD(button_wait);

 
 /*
  * 定义中断事件标志
  * 0:进入等待队列        1:退出等待队列
  */
static int even_press=0;                          

 /*
  * 定义全局变量key_val,保存key状态
  */
static int key_val=0;                          

 /*
  *引脚描述结构体
  */
 struct pin_desc{
   unsigned int  pin;
   unsigned int  pin_status;
};

 
 /*
  *key初始状态(没有按下): 0x01,0x02,0x03,0x04
  *key状态(按下):   0x81,0x82,0x83,0x84
  */
struct pin_desc  pins_desc[4]={
                   {S3C2410_GPF0,0x01 },
                   {S3C2410_GPF2, 0x02 },
                   {S3C2410_GPG3, 0x03 },
                   {S3C2410_GPG11,0x04},} ;


int  second_drv_class(struct inode *inode, struct file  *file)  //卸载中断
{
  free_irq(IRQ_EINT0,&pins_desc[0]);
  free_irq(IRQ_EINT2,&pins_desc[1]);
  free_irq(IRQ_EINT11,&pins_desc[2]);
  free_irq(IRQ_EINT19,&pins_desc[3]);
  return 0;
}


/*   确定是上升沿还是下降沿  */
static irqreturn_t  buttons_irq (int irq, void *dev_id)       //中断服务函数
{
      struct pin_desc *pindesc=(struct pin_desc *)dev_id;     //获取引脚描述结构体
      unsigned int  pin_val=0;                                               
      pin_val=s3c2410_gpio_getpin(pindesc->pin);

        if(pin_val)
        {
                   /*没有按下 (下降沿),清除0x80*/                 
                   key_val=pindesc->pin_status&0xef;
         }
         else
         {
                   /*按下(上升沿),加上0x80*/
                   key_val=pindesc->pin_status|0x80;
         }             

            even_press=1;               //退出等待队列
            wake_up_interruptible(&button_wait);      //唤醒 中断
                          
         return IRQ_HANDLED;
}

static int second_drv_open(struct inode *inode, struct file  *file)
{
   request_irq(IRQ_EINT0,buttons_irq,IRQT_BOTHEDGE,"S1",&pins_desc[0]);   request_irq(IRQ_EINT2, buttons_irq,IRQT_BOTHEDGE, "S2", &pins_desc[1]);
   request_irq(IRQ_EINT11, buttons_irq,IRQT_BOTHEDGE, "S3", &pins_desc[2]);
   request_irq(IRQ_EINT19, buttons_irq,IRQT_BOTHEDGE, "S4", &pins_desc[3]);

   return 0;
}

 
static int second_drv_read(struct file *file, const char __user *buf, size_t count, loff_t * ppos)
{/*将中断 进入等待队列(休眠状态)*/
         wait_event_interruptible(button_wait, even_press);            

        /*有按键按下,退出等待队列,上传key_val 给用户层*/
         if(copy_to_user(buf,&key_val,sizeof(key_val)))
         return EFAULT;
     even_press=0; //数据发完后,立马设为休眠状态,避免误操作
         return 0;
}
 

static struct file_operations second_drv_fops={
         .owner = THIS_MODULE,
         .open = second_drv_open,
         .read = second_drv_read,
         .release=second_drv_class,    //里面添加free_irq函数,来释放中断服务函数
};

 
volatile int second_major;
static int second_drv_init(void)
{
   second_major=register_chrdev(0,"second_drv",&second_drv_fops);  //创建驱动
   seconddrv_class=class_create(THIS_MODULE,"second_dev");    //创建类名
   seconddrv_class_devs=class_device_create(seconddrv_class, NULL, MKDEV(second_major,0), NULL,"buttons");
   return 0;
}

static int second_drv_exit(void)
{
 unregister_chrdev(second_major,"second_drv");            //卸载驱动
 class_device_unregister(seconddrv_class_devs);          //卸载类设备        
 class_destroy(seconddrv_class);                      //卸载类
 return 0;
}

module_init(second_drv_init);
module_exit(second_drv_exit);
MODULE_LICENSE("GPL v2");

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏LEo的网络日志

05 Jun 2018 shell技巧分享(五)

1263
来自专栏编程一生

linux内核中听过就能记住的概念

1212
来自专栏CSDN技术头条

一组 Redis 实际应用中的异常场景及其根因分析和解决方案

在上一场 Chat《基于 Redis 的分布式缓存实现方案及可靠性加固策略》中,我已经较为全面的介绍了 Redis 的原理和分布式缓存方案。如果只是从“会用”的...

2123
来自专栏JetpropelledSnake

Python入门之logging日志模块以及多进程日志

本篇文章主要对 python logging 的介绍加深理解。更主要是 讨论在多进程环境下如何使用logging 来输出日志, 如何安全地切分日志文件。 1. ...

6587
来自专栏Python爬虫与数据挖掘

如何在虚拟机上安装centos7.4系统—靠谱的centos7.4系统安装教程

前几天给大家分享了在虚拟机上安装Centos6.7系统的教程,感兴趣的童鞋们可以点击进去查看。今天小编给大家分享在虚拟机上安装Centos7.4...

1032
来自专栏余林丰

外观模式

 外观模式又称为门面模式,为子系统中的一组接口提供一个一致的界面,此模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。我们还是用通俗的语言来解释这句话...

1738
来自专栏Golang语言社区

【Go 语言社区】linux 下Go服务器部署(也同样适合C++等)

作为服务器开发人员,GO语言内网测试数据及调试肯定很简单,如果你在Windows下开发 直接编成EXE执行就可以,然后部署外网在打包成linux的执行文件(交...

3248
来自专栏崔庆才的专栏

一看就懂,Python 日志模块详解及应用

Windows网络操作系统都设计有各种各样的日志文件,如应用程序日志,安全日志、系统日志、Scheduler服务日志、FTP日志、WWW日志、DNS服务器日志等...

863
来自专栏aoho求索

基于可靠消息方案的分布式事务(四):接入Lottor服务

在上一篇文章中,通过Lottor Sample介绍了快速体验分布式事务Lottor。本文将会介绍如何将微服务中的生产方和消费方服务接入Lottor。

1911
来自专栏邹立巍的专栏

Linux 的进程间通信:消息队列

Linux 环境提供了 XSI 和 POSIX 两套消息队列,本文将帮助您掌握以下内容:如何使用 XSI 消息队列,如何使用 POSIX 消息队列,它们的底层实...

5470

扫码关注云+社区