17_GPIO外部中断的使用

介绍使用GPIO上升下降沿触发外部中断

Posted by Jerry Chen on August 9, 2019

了解内容

Linux中查看系统中断的统计信息:

1
cat /proc/interrupts

接下来我们来实现BUTTON1、BUTTON2中断。在原理图中分别对应GPX1_1(home键)、GPX1_2(back键),相应中断号的宏分别是IRQ_EINT(9)IRQ_EINT(10)。下图来自4412用户手册“2.5 Pin Description”章节。

开始

由于内核中已经申请过home、back按键的中断,首先需要从内核中取消“KEYBOARD_GPIO”,路径是“Device Drivers”–>“Input device support”–>“Keyboards”–>“GPIO Buttons”。取消后,编译内核镜像进行烧录会发现cat /proc/interrupts中已经没有BUTTON1、BUTTON2中断了。

紧接着添加平台文件mach-itop4412.c中设备信息,模仿LED平台设备信息添加即可,为了简单实验这里就不用#if宏了,也就是始终编译而不用改Kconfig:

上述完成后,重新编译内核镜像再次烧录。接下来完成keyirq驱动即可。

一些函数

  1. 注册中断函数request_irq,在include/linux/interrupt.h中有定义

    1
    
    int request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags, const char *name, void *dev)
    
    传参 描述
    irq 申请的硬件中断号,比如IRQ_EINT(9)宏、IRQ_EINT(10)宏,可以在arch/arm/mach-s3c24xx/include/mach/irqs.h中找到定义。
    handler 中断服务函数。
    flags 中断触发条件,可同时使用多个,在include/linux/interrupt.h中有定义:
    IRQF_TRIGGER_RISING :上升沿触发
    IRQF_TRIGGER_FALLING :下降沿触发
    IRQF_TRIGGER_HIGH:高电平触发
    IRQF_TRIGGER_LOW:低电平触发(实测,如果始终是低电平会一直触发)
    name 中断名,会出现在cat /proc/interrupts
    dev 填写设备结构体,或者NULL。也就是probe函数的传参,有没有发现probe函数是driver中的,但是probe传参是device中的结构体。
    返回值 0表示成功;
    -INVAL表示中断号无效或处理函数指针为NULL;
    -EBUSY表示中断已经被占用且不能共享。
  2. 声明一个中断服务程序,其中dev_id就是request_irq传参中的dev,是一样的

    1
    2
    3
    4
    5
    
    static irqreturn_t my_interrupt(int irq,void *dev_id)
    {
    	printk("receive a interrupt!\n");
    	return IRQ_HANDLED;
    }
    
  3. 释放中断函数free_irq

    1
    
    free_irq(unsigned int irq, void *dev_id);
    

例程

驱动例程

keyirq_demo.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
#include <linux/init.h>
#include <linux/module.h>
#include <linux/platform_device.h>
//中断头文件
#include <linux/irq.h>
#include <linux/interrupt.h>

MODULE_LICENSE("Dual BSD/GPL");
MODULE_AUTHOR("jerry");

#define DRV_NAME "keyirq"

static irqreturn_t eint9_handler(int irq,void *dev_id)
{
	printk("receive a interrupt eint9!\n");
	return IRQ_HANDLED;
}
static irqreturn_t eint10_handler(int irq,void *dev_id)
{
	printk("receive a interrupt eint10!\n");
	return IRQ_HANDLED;
}

static int demo_probe(struct platform_device *pdv){
	int ret1,ret2;
	
	/*申请中断资源*/
	ret1 = request_irq(IRQ_EINT(9),eint9_handler,IRQF_TRIGGER_FALLING,"eint9",pdv);
	ret2 = request_irq(IRQ_EINT(10),eint10_handler,IRQF_TRIGGER_FALLING,"eint10",pdv);
	if((ret1 < 0)||(ret2 < 0)){
		printk(KERN_EMERG "irq_request failed!\n");
		return 1;
	}
	
	printk(KERN_EMERG "demo_probe ok!\n");
	return 0;
}

static int demo_remove(struct platform_device *pdv){
	free_irq(IRQ_EINT(9),pdv); /*释放中断资源*/
	free_irq(IRQ_EINT(10),pdv); /*释放中断资源*/
	
	printk(KERN_EMERG "demo_remove ok!\n");
	return 0;
}

static void demo_shutdown(struct platform_device *pdv){
	printk(KERN_EMERG "demo_shutdown ok!\n");
}
static int demo_suspend(struct platform_device *pdv, pm_message_t state){
	printk(KERN_EMERG "demo_suspend ok!\n");
	return 0;
}
static int demo_resume(struct platform_device *pdv){
	printk(KERN_EMERG "demo_resume ok!\n");
	return 0;
}

static struct platform_driver platform_drv_demo = {
	.probe	= demo_probe,
	.remove	= demo_remove,
	.shutdown	= demo_shutdown,
	.suspend	= demo_suspend,
	.resume	= demo_resume,
	.driver	= {
		.name	= DRV_NAME,
		.owner	= THIS_MODULE,
	},
};

static int demo_init(void){
	platform_driver_register(&platform_drv_demo);
	return 0;
}
static void demo_exit(void){
	platform_driver_unregister(&platform_drv_demo);
}

module_init(demo_init);
module_exit(demo_exit);
测试结果

EINT9和EINT10在原理图上连接的是按键home和按键back。

编写简单Makefile:参考这里

编译生成模块后,拷贝到开发板中:

  • 首先加载模块后正常执行probe函数;

  • 查看cat /proc/interrupts中断信息

    可以看到已经生成了中断”eint9”和”eint10”,中断均产生0次。

  • 交替按下3次home键和2次back键,控制台会打印如下内核信息:

    然后再次查看中断信息,eint9中断产生了3次,eint10中断产生了2次。