电子产品世界 » 论坛首页 » 嵌入式开发 » ARM » 迅为iTOP-4412实现中断驱动例程


共5条 1/1 1 跳转至

迅为iTOP-4412实现中断驱动例程

菜鸟
2016-02-22 17:55:59    评分

大家好,今天我们来学习一下 linux 中断处理驱动的编写,本节我们实现的功能是通过开发板上的按键来控制 led 发光二极管,在之前的章节我们学习了 led 驱动的编写,在掌握了 led 驱动的编写以后,如果要实现按键控制 led 的功能,大家可能会想到可以在 led 的驱动里面使用轮询的方式一直查询按键的状态,如果有按键按下就设置 led 的状态。通过这种方式可以实现按键控制 led 的功能, 但是通过这样的方式有一个缺点就是led 驱动会占用 cpu,这样 cpu 的利用率就大大降低了,所以我们可以通过中断的方式来实现。这样 cpu 就可以去做其他的事情了,当有按键中断触发的时候才会去设置 led

ARM 架构 linux 内核中,有 5 种常见的异常,其中中断异常是其一,Linux 内核将所有中断统一编号,使用一个 irq_desc 结构体来描述这些中断,里面记录了中断名称、中断状态、中断标记、并提供了中断的底层硬件访问函数(如:清除、屏蔽、使能中断) ,提供了这个中断的处理函数入口,通过它还可以调用用户注册的的中断处理函数。linux 内核的中断体系已经很完善了,驱动工程师需要做的就是调用 request_irq 函数向内核注册中断处理函数,下面我们来看看 request_irq 函数的定义:

static inline int __must_check

request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags,

const char *name, void *dev)

第一个参数 irq:中断号,与平台架构相关;

第二个参数 handler:用户中断处理函数;

第三个参数 flags:中断标记

第四个参数 devname:中断名字,可以通过 cat /proc/interrupts 查看;

第五个参数 dev_id:在 free_irq 中有用,也用做区分中断处理函数;

 

有注册就得对应着有注销,驱动的注销函数是 free_irq,其定义如下:

void free_irq(unsigned int irq, void *dev_id)

第一个参数 irq:中断号,与 request_irq 中的 irq 一致,用于定位 action 链表;

第二个参数 dev_id:用于在 action 链表中找到要卸载的表项;同一个中断的不同中断处理函数必须使用不通

的 dev_id 来区分,这就要求在注册中断共享时参数 dev_id 必须唯一。

下面我们来看一下中断按键的驱动,代码如下:

#include <linux/init.h>

#include <linux/module.h>

#include <linux/kernel.h>

#include <linux/fs.h>

#include <linux/interrupt.h>

#include <linux/irq.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>

#define IRQ_DEBUG

#ifdef IRQ_DEBUG

#define DPRINTK(x...) printk("IRQ_CTL DEBUG:" x)

#else

#define DPRINTK(x...)

#endif

#define DRIVER_NAME "irq_test"

static int led_gpios[] = {

EXYNOS4_GPL2(0),

EXYNOS4_GPK1(1),

};

#define LED_NUM ARRAY_SIZE(led_gpios)

#if 1

static irqreturn_t eint9_interrupt(int irq, void *dev_id) {

 

printk("%s(%d)\n", __FUNCTION__, __LINE__);

if(gpio_get_value(led_gpios[0]))

gpio_set_value(led_gpios[0], 0);

else

gpio_set_value(led_gpios[0], 1);

return IRQ_HANDLED;

}

static irqreturn_t eint10_interrupt(int irq, void *dev_id) {

printk("%s(%d)\n", __FUNCTION__, __LINE__);

if(gpio_get_value(led_gpios[1]))

gpio_set_value(led_gpios[1], 0);

else

gpio_set_value(led_gpios[1], 1);

return IRQ_HANDLED;

 

}

#endif

static int irq_probe(struct platform_device *pdev)

{

int ret, i;

char *banner = "irq_test Initialize\n";

printk(banner);

for(i=0; i<LED_NUM; i++)

{

ret = gpio_request(led_gpios[i], "LED");

if (ret) {

printk("%s: request GPIO %d for LED failed, ret = %d\n", DRIVER_NAME,

led_gpios[i], ret);

return ret;

}

s3c_gpio_cfgpin(led_gpios[i], S3C_GPIO_OUTPUT);

gpio_set_value(led_gpios[i], 0);

}

 

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));

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));

#if 1

ret = request_irq(IRQ_EINT(9), eint9_interrupt,

IRQ_TYPE_EDGE_FALLING /*IRQF_TRIGGER_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 /*IRQF_TRIGGER_FALLING*/, "eint10", pdev);

if (ret < 0) {

printk("Request IRQ %d failed, %d\n", IRQ_EINT(10), ret);

goto exit;

}

#endif

return 0;

exit:

return ret;

}

 

static int irq_remove (struct platform_device *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);

 

MODULE_LICENSE("Dual BSD/GPL");

首先我们来看下这个驱动的入口函数 irq_test_init,定义如下:

static int __init irq_test_init(void)

{

return platform_driver_register(&irq_driver);

}

该函数想内核注册一个 irq_driver 类型的设备,irq_driver 定义如下:

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,

},

};

从这个结构里我们可以看到驱动的探测函数是 irq_probe,注销函数是 irq_remove,休眠调用的函数是

irq_suspend, 唤醒调用的函数是 irq_resume, 驱动的名字是 DRIVER_NAME, DRIVER_NAME 是个宏定义,

如下:

#define DRIVER_NAME "irq_test"

 

接下来我们看看驱动的探测函数 irq_probe,这个函数也是这个驱动里面最主要的函数,这个函数一开始掉欧

勇 printk 打印一条信息,接着是一个 for 循环,初始化 led 的 GPIO,如下:

for(i=0; i<LED_NUM; i++)

{

ret = gpio_request(led_gpios[i], "LED");

if (ret) {

printk("%s: request GPIO %d for LED failed, ret = %d\n", DRIVER_NAME,

led_gpios[i], ret);

return ret;

}

s3c_gpio_cfgpin(led_gpios[i], S3C_GPIO_OUTPUT);

gpio_set_value(led_gpios[i], 0);

}

然后是初始化两个按键的 gpio,设置为中断模式,如下所示:

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));

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));

接着调用中断注册函数 request_irq 向内核注册中断处理函数,如下:

ret = request_irq(IRQ_EINT(9), eint9_interrupt,

IRQ_TYPE_EDGE_FALLING /*IRQF_TRIGGER_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 /*IRQF_TRIGGER_FALLING*/, "eint10", pdev);

if (ret < 0) {

printk("Request IRQ %d failed, %d\n", IRQ_EINT(10), ret);

goto exit;

}

上面的中断注册函数分别注册了两个中断处理函数 eint9_interrupt 和 eint10_interrupt。

eint9_interrupt 的定义如下:

static irqreturn_t eint9_interrupt(int irq, void *dev_id) {

printk("%s(%d)\n", __FUNCTION__, __LINE__);

if(gpio_get_value(led_gpios[0]))

gpio_set_value(led_gpios[0], 0);

else

gpio_set_value(led_gpios[0], 1);

return IRQ_HANDLED;

}

这个函数首先会打印一句信息,然后是获取 led 的状态,把状态取反。

eint10_interrupt 的定义如下:

static irqreturn_t eint10_interrupt(int irq, void *dev_id) {

printk("%s(%d)\n", __FUNCTION__, __LINE__);

if(gpio_get_value(led_gpios[1]))

gpio_set_value(led_gpios[1], 0);

else

gpio_set_value(led_gpios[1], 1);

return IRQ_HANDLED;

}

这个函数和 eint9_interrupt 的功能类似。

其他的函数就和前面讲的 led 驱动里面的函数基本一样了, 唯一的区别是我们在注销函数 irq_remove 里面使

用了 free_irq 函数来注销之前初测的中断处理函数。

把这个驱动放到内核的 driver/char 目录下面,如下图所示:

 

 

 

然后打开 driver/char 目录下面的 Makefile,添加“obj-y += itop4412_irq.o” ,如下图所示:

 

 

然后打开 arch/arm/mach-exynos/mack-itop4412.c 文件,找到“struct platform_device

s3c_device_buzzer_ctl” ,如下图所示:

 

 

然后在它的下面添加下面的信息:

struct platform_device s3c_device_irq_test = {

.name = "irq_test",

.id = -1,

};

如下图所示:

 

 

然后找到“&s3c_device_buzzer_ctl,”这一行,如下图所示:

 

 

 

在这一行的下面添加“&s3c_device_irq_test,” ,如下图所示:

 

 

 

然后保存并退出。因为本章实验使用到了 led 和按键,所以我们要把内核里面的 led 驱动和按键的驱动去掉,

在内核目录下使用 make menuconfig 命令打开内核配置界面,如下图所示:

 

 

进入到“Device Drivers” ,如下图所示:

 

 

 

然后进入到“Input device support”界面,如下图所示:

 

 

 

然后进入到“Keyboards”界面,如下图所示:

 

 

然后取消掉“GPIO Buttons” ,如下图所示:

 

 

然后返回到“Device Drivers”界面,如下图所示:

 

然后选择“Character devices” ,进入 Character devices 界面,如下图所示:

 

 

然后取消“Enable LEDS config” ,如下图所示:

 

 

 

 

然后保存并退出内核配置界面,使用 make 命令编译内核,如下图所示:

 

 

编译完成后,如下图所示:

 

 

然后把编译生成的 zImage 烧写到 iTOP-4412 开发板上,烧写完成后启动开发板,系统起来以后,我们可以

按开发板上的 BACK 或 HOME 按键,来看下运行结果。

当我们按下 BACK 按键时,可以看到 led 会点亮,在次按下,led 就会熄灭,同事串口会打印信息,如下图所

示:

 

 

 

我们按下 HOME 键,可以看到另外一个 led 会点亮,再次按下,led 就会熄灭,同事串口会打印信息,如下

图所示:

 

 

至此,linux 下中断驱动我们就已经完成了。




关键词: 4412开发板     嵌入式开发板     迅为开发板    

工程师
2016-02-22 21:44:28    评分
2楼
  楼主牛逼

菜鸟
2016-02-22 22:58:15    评分
3楼
linux系统也这么玩中断啊~~

助工
2016-02-23 09:54:16    评分
4楼
楼主棒

专家
2016-02-25 14:30:17    评分
5楼
为什么不编译成模块呢?重新编译一次内核需要时间多久?

共5条 1/1 1 跳转至

回复

匿名不能发帖!请先 [ 登陆 注册 ]