文章目录
一、中断的基础知识
1.什么是中断
中断是指 CPU 在执行程序的过程中,出现突发事件去处理,CPU 需要停止当前程序的执行,转去处理突发事件,处理完成之后再返回原程序部分。
2.硬件中断和软件中断
硬件中断一般指外设发出的中断请求以及内部硬件产生的中断(计算溢出,除数为 0,掉电等)
软件中断,典型的是中断处理程序的下半部操作.
3.中断优先级
系统根据中断事件的重要性和紧迫程度,将中断源分为若干个等级,优先级高的先执行。
二、按键的硬件连接
底板上有 5 个在一起的独立按键,以 HOME 和 BACK 为例,先来看一下原理图,如下图所示。
通过硬件连接可知,默认状态给中断的 IO 是高电平。所以可以判断后面对中断触发方式要为下降沿触发。
两个网络 UART_RING 和 SIM_DET。
对应的,对应的 GPIO 为“EXYNOS4_GPX1(1)”,中断号为“XEINT9
对应的 GPIO 为“EXYNOS4_GPX1(2)”中断号为“XEINT10”
可以看到将管脚配置为外部中断模式需要设置为 0xF。
最后再带大家看一个部分,在 Datasheet 的 56 章,找到 Debouncing Filter,这是4412 对于按键自动防抖的部分,如下图所示,可以看到有自带的防抖。
三、中断相关函数
1.request_irq
Linux 中的中断在使用前,都需要申请。中断申请函数是“request_irq”,在头文件“include/linux/interrupt.h”中,如下图所示。
中断申请函数 request_irq(unsigned int irq, irq_handler_t handler,
unsigned long flags,const char *name, void *dev)有下面几个参数。
参数 unsigned int irq:irq 是中断号
参数 irq_handler_t handler:handler 是向系统登记的处理函数
参数 unsigned long flags:irqflags 是触发标志位
参数 const char *name:devname 是中断名称,可以通过注册之后可以通过“cat/proc/interrupts”查看
参数 void *dev:dev_id 是设备
2. free_irq
和上面中断申请函数对应的就是中断释放函数 free_irq,卸载驱动的时候需要调用,如下图所示,也是在头文件“include/linux/interrupt.h”中。
中断释放函数 extern void free_irq(unsigned int, void *);的参数如下。
参数 1:irq 是中断号
参数 2:dev_id 是设备
3.irqreturn_t
产生中断之后,会调用中断处理函数 irqreturn_t,这个函数也是在头文件
“include/linux/interrupt.h”中如下图所示。
该函数为 extern irqreturn_t no_action(int cpl, void *dev_id);
中断函数类型为 irqreturn_t
参数 int cpl:中断号
参数 void *dev_id:设备
4.中断号
在初始化文件“drivers/gpio/gpio-exynos4.c”中,如下图所示
vim drivers/gpio/gpio-exynos4.c
选取的两个中断管脚都属于“GPX1”,所以如上图所示,中断编号的基础数值是IRQ_EINT(8),那么 GPX1CON[0]和 GPX1CON[1]则对应着中断号 IRQ_EINT(9)和IRQ_EINT(10)。
如果想调用其他外部中断也可以通过这种方式来查阅中断号,不过前提是这个中断没有被占用。
四、实验操作
外部中断的实际应用一般是集成在一些类似声卡、显卡、其他总线设备中,实际的应用也就是调用前面提到的头文件和函数,在驱动初始化的时候申请中断,然后针对具体的驱动写中断函数处理函数。例如声卡中调音量的按键,就是将按键部分的代码集成到声卡中,检测到按键就可以对应的调低调高音量。
这里注册一个简单的字符驱动,这样的话会便于大家的理解。要完成的功能就是按键中断产生之后打印数据。
1.注册设备
如下图所以,在平台文件“arch/arm/mach-exynos/mach-itop4412.c”中添加注册设备的代码。
vim arch/arm/mach-exynos/mach-itop4412.c
为了简单,可以也可以不定义宏变量,强制注册设备。(没有编写#ifdef)
2.添加设备调用的代码
还是在上一个文件里
3.menuconfig配置
然后打开 menuconfig 配置文件,将使用这两个中断的驱动卸载掉。
Device Drivers —>
Input device support —>
Keyboards —>
取消 GPIO Buttons —>
重新设置之后,将内核重新编译,并将生成的二进制 zImage 文件烧写到开发板中替换原来的内核。
4.编写驱动
1.驱动固定头文件
如下图所示,头文件部分,以后写 4412 的驱动可以将这些头文件一股脑的添加到代码前面。
/* 以后写驱动可以将头文件一股脑的加载到代码前面 */
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <mach/gpio.h>
#include <plat/gpio-cfg.h>
#include <linux/miscdevice.h>
#include <linux/platform_device.h>
#include <mach/regs-gpio.h>
#include <asm/io.h>
#include <linux/regulator/consumer.h>
#include <linux/delay.h>
/* 中断函数头文件 */
#include <linux/interrupt.h>
#include <linux/irq.h>
2.应用函数irq_probe
首先对中断 IO 进行检测,是否被占用了,处理方式一般就是申请 IO,看是否成功,申请成功之后就将 GPIO 配置为上拉模式,然后调用 gpio_free 将其释放。
其中用到了参数“IRQ_TYPE_EDGE_FALLING”,这个代表下降沿触发,这个宏定义在头文件“include/linux/irq.h”中,如下图所示。
3.Makefile
省略,和前面的都是一样的
4.itop4412_irq.c
/* 以后写驱动可以将头文件一股脑的加载到代码前面 */
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <mach/gpio.h>
#include <plat/gpio-cfg.h>
#include <linux/miscdevice.h>
#include <linux/platform_device.h>
#include <mach/regs-gpio.h>
#include <asm/io.h>
#include <linux/regulator/consumer.h>
#include <linux/delay.h>
/* 中断函数头文件 */
#include <linux/interrupt.h>
#include <linux/irq.h>
#define IRQ_DEBUG
#ifndef IRQ_DEBUG
#define DPRINTK(x...) printk("IRQ_CTL DEBUG:" x)
#else
#define DPRINTK(x...)
#endif
#define DRIVER_NAME "irq_test"
MODULE_LICENSE("Dual BSD/GPL");
/* 定义中断处理函数 */
static irqreturn_t eint9_interrupt(int irq,void *dev_id)
{
printk("%s(%d)\n",__FUNCTION__,__LINE__);
return IRQ_HANDLED;
}
static irqreturn_t eint10_interrupt(int irq,void *dev_id)
{
printk("%s(%d)\n",__FUNCTION__,__LINE__);
/*上面使用了一句比较特殊的代码“printk("%s(%d)\n", __FUNCTION__,__LINE__);
”这句代码在调试的过程中非常有用,就是打印当前所在的函数以及对应的行,
在后面测试的时候就可以看到其效果。*/
return IRQ_HANDLED;
}
static int irq_probe(struct platform_device *pdev)
{
int ret;
char *banner = "irq_test Initialize!\n";
printk(banner);
/* 第一个IO */
ret = gpio_request(EXYNOS4_GPX1(1),"EINT9");
if(ret)
{
printk("%s:request GPIO %d for EINT9 failed,ret = %d!\n",DRIVER_NAME,EXYNOS4_GPX1(1),ret);
return ret;
}
s3c_gpio_cfgpin(EXYNOS4_GPX1(1),S3C_GPIO_SFN(0xF));
s3c_gpio_setpull(EXYNOS4_GPX1(1),S3C_GPIO_PULL_UP);
gpio_free(EXYNOS4_GPX1(1));
/* 第二个IO*/
ret = gpio_request(EXYNOS4_GPX1(2),"EINT10");
if(ret)
{
printk("%s:request GPIO %d for EINT10 failed,ret = %d!\n",DRIVER_NAME,EXYNOS4_GPX1(2),ret);
return ret;
}
s3c_gpio_cfgpin(EXYNOS4_GPX1(2),S3C_GPIO_SFN(0xF));
s3c_gpio_setpull(EXYNOS4_GPX1(2),S3C_GPIO_PULL_UP);
gpio_free(EXYNOS4_GPX1(2));
/* 中断 */
ret = request_irq(IRQ_EINT(9),eint9_interrupt,IRQ_TYPE_EDGE_FALLING,"eint9",pdev);
if(ret < 0)
{
printk("Request IRQ %d failed,%d\n",IRQ_EINT(9),ret);
goto exit;
}
ret = request_irq(IRQ_EINT(10),eint10_interrupt,IRQ_TYPE_EDGE_FALLING,"eint10",pdev);
if(ret < 0)
{
printk("Request IRQ %d failed,%d\n",IRQ_EINT(10),ret);
goto exit;
}
return 0;
exit:
return ret;
}
static int irq_remove(struct platform_device *pdev)
{
free_irq(IRQ_EINT(9),pdev); // 释放中断
free_irq(IRQ_EINT(10),pdev); // 释放中断
return 0;
}
static int irq_suspend(struct platform_device *pdev,pm_message_t state)
{
DPRINTK("irq suspend:power off!\n");
return 0;
}
static int irq_resume(struct platform_device *pdev)
{
DPRINTK("irq resume:power on!\n");
return 0;
}
/* 驱动模块的入口函数和出口函数 */
static struct platform_driver irq_driver = {
.probe = irq_probe,
.remove = irq_remove,
.suspend = irq_suspend,
.resume = irq_resume,
.driver = {
.name = DRIVER_NAME,
.owner = THIS_MODULE,
},
};
static void __exit irq_test_exit(void)
{
platform_driver_unregister(&irq_driver); // 卸载驱动
}
static int __init irq_test_init(void)
{
return platform_driver_register(&irq_driver); // 注册驱动
}
module_init(irq_test_init);
module_exit(irq_test_exit);
5.编译
新建文件夹
mkdir irq_test
编译;
6.开发板运行
1.加载驱动
insmod itop4412_irq.ko
2.查看申请的中断
cat /proc/interrupts
3.测试
按几下HOME和BACK按键,会出现类似下面的打印信息
最后再使用命令“cat /proc/interrupts”查看申请的中断,如下图所示,已经检测到这两个中断分别触发了几次。