上篇,学习GPIO输入功能的使用,本篇,来学习使用中断的方式来检测按键的按下。
文章目录- 1 Linux中断介绍
- 1.1 中断的上半部与下半部
- 1.2 下半部的3种实现方式
- 1.2.1 软中断
- 1.2.2 tasklet
- 1.2.3 工作队列
- 1.3 中断API函数
- 1.3.1 request_irq中断请求函数
- 1.3.2 free_irq中断释放函数
- 1.3.3 irq_handler_t中断处理函数
- 1.3.4 中断使能/禁用函数
- 1.3.5 获取中断号
- 2 软件编写
- 2.1 修改设备树文件
- 2.2 按键中断驱动程序
- 2.2.1 硬件初始化与中断配置
- 2.2.2 中断服务函数
- 2.2.3 定时器服务函数
- 2.2.4 按键读取函数
- 2.3 按键中断驱动程序
- 3 实验
- 4 总结
中断处理函数的执行,越快越好,但实际使用中,某些情况确实需要比较耗时是中断过程,为此,Linux内核将中断分为上半部和下半部两个处理部分:
- 上半部:中断处理函数,那些处理过程比较快,不会占用很长时间的处理就可以放在上半部完成
- 下半部:如果中断处理过程比较耗时,那么就将这些比较耗时的代码提出来,交给下半部去执行,这样中断处理函数就会快进快出
对于一个中断,如何划分出上下两部分呢?
- 对时间敏感,将其放在上半部
- 和硬件相关,将其放在上半部
- 要求不被其他中断打断,将其放在上半部
- 其他所有任务,考虑放在下半部
Linux内核使用softirq_action结构体表示软中断:
struct softirq_action
{
void (*action)(struct softirq_action *);
};
一共有 10 个软中断
enum
{
HI_SOFTIRQ = 0,
TIMER_SOFTIRQ,
NET_TX_SOFTIRQ,
NET_RX_SOFTIRQ,
BLOCK_SOFTIRQ,
BLOCK_IOPOLL_SOFTIRQ,
TASKLET_SOFTIRQ,
SCHED_SOFTIRQ,
HRTIMER_SOFTIRQ,
RCU_SOFTIRQ,
NR_SOFTIRQS
};
要使用软中断,必须先使用open_softirq函数注册对应的软中断处理函数:
void open_softirq(int nr, void (*action)(struct softirq_action *))
注册好软中断以后需要通过raise_softirq函数触发:
void raise_softirq(unsigned int nr)1.2.2 tasklet
Linux内核使用tasklet_struct结构体来表示tasklet:
struct tasklet_struct
{
struct tasklet_struct *next;
unsigned long state;
atomic_t count;
void (*func)(unsigned long);
unsigned long data;
};
要使用 tasklet,必须先定义一个tasklet,然后初始化:
void tasklet_init(struct tasklet_struct *t,
void (*func)(unsigned long),
unsigned long data);
在上半部(中断处理函数)中调用tasklet_schedule函数就能使tasklet在合适的时间运行:
void tasklet_schedule(struct tasklet_struct *t)1.2.3 工作队列
工作队列(work queue)是另外一种将中断的部分工作推后的一种方式,它可以实现一些tasklet不能实现的工作,比如工作队列机制可以睡眠。
Linux 内核使用work_struct结构体表示一个工作:
struct work_struct {
atomic_long_t data;
struct list_head entry;
work_func_t func;
};
这些工作组织成工作队列,工作队列使用workqueue_struct结构体表示。
在工作队列机制中,将推后的工作交给一个称之为工作者线程(worker thread)的内核线程去完成。
1.3 中断API函数 1.3.1 request_irq中断请求函数
int request_irq(unsigned int irq,
irq_handler_t handler,
unsigned long flags,
const char *name,
void *dev)
flags中断标志,有下面几种类型
| 中断标志 | 描述 |
|---|---|
| IRQF_SHARED | 多个设备共享一个中断线, 共享的所有中断都必须指定此标志 |
| IRQF_ONESHOT | 单次中断,中断执行一次就结束 |
| IRQF_TRIGGER_NONE | 无触发 |
| IRQF_TRIGGER_RISING | 上升沿触发 |
| IRQF_TRIGGER_FALLING | 下降沿触发 |
| IRQF_TRIGGER_HIGH | 高电平触发 |
| IRQF_TRIGGER_LOW | 低电平触发 |
void free_irq(unsigned int irq,
void *dev)
1.3.3 irq_handler_t中断处理函数
irqreturn_t (*irq_handler_t) (int, void *)
irqreturn_t枚举类型定义:
enum irqreturn {
IRQ_NONE = (0 << 0),
IRQ_HANDLED = (1 << 0),
IRQ_WAKE_THREAD = (1 << 1),
};
typedef enum irqreturn irqreturn_t;
1.3.4 中断使能/禁用函数
void enable_irq(unsigned int irq)
void disable_irq(unsigned int irq)
1.3.5 获取中断号
使用中断时,中断信息先写到了设备树里面,然后通过irq_of_parse_and_map函数从interupts属性中提取到对应的中断号
unsigned int irq_of_parse_and_map(struct device_node *dev,
int index)
2 软件编写
仍使用上篇按键实验中用到的两个按键:
为了理解简单,本次程序暂不实现中断的下半部逻辑,直接将整个中断处理过程都放到中断的上半部中处理。
2.1 修改设备树文件在上篇key实验代码的基础上,修改imx6ull-myboard.dts,主要是修改key子节点,添加中断,修改后内容如下:
key {
#address-cells = <1>;
#size-cells = <1>;
compatible = "myboard-irq-key";
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_key>;
key1-gpio = <&gpio5 1 GPIO_ACTIVE_HIGH>;
key2-gpio = <&gpio5 11 GPIO_ACTIVE_LOW>;
interrupt-parent = <&gpio5>;
interrupts = < 1 IRQ_TYPE_EDGE_BOTH
11 IRQ_TYPE_EDGE_BOTH >;
status = "okay";
};
2.2 按键中断驱动程序
2.2.1 硬件初始化与中断配置
static int keyio_init(void)
{
unsigned char i = 0;
int ret = 0;
imx6uirq.nd = of_find_node_by_path("/key");
if (imx6uirq.nd== NULL)
{
printk("key node not find!rn");
return -EINVAL;
}
imx6uirq.irqkeydesc[0].gpio = of_get_named_gpio(imx6uirq.nd ,"key1-gpio", 0);
imx6uirq.irqkeydesc[1].gpio = of_get_named_gpio(imx6uirq.nd ,"key2-gpio", 0);
if ((imx6uirq.irqkeydesc[0].gpio < 0)||(imx6uirq.irqkeydesc[1].gpio < 0))
{
printk("can't get keyrn");
return -EINVAL;
}
printk("key1_gpio=%d, key2_gpio=%drn", imx6uirq.irqkeydesc[0].gpio, imx6uirq.irqkeydesc[1].gpio);
for (i = 0; i < KEY_NUM; i++)
{
memset(imx6uirq.irqkeydesc[i].name, 0, sizeof(imx6uirq.irqkeydesc[i].name));
sprintf(imx6uirq.irqkeydesc[i].name, "key%d", i+1);
gpio_request(imx6uirq.irqkeydesc[i].gpio, imx6uirq.irqkeydesc[i].name);
gpio_direction_input(imx6uirq.irqkeydesc[i].gpio);
imx6uirq.irqkeydesc[i].irqnum = irq_of_parse_and_map(imx6uirq.nd, i);
printk("key%d:gpio=%d, irqnum=%drn",i+1,
imx6uirq.irqkeydesc[i].gpio,
imx6uirq.irqkeydesc[i].irqnum);
}
imx6uirq.irqkeydesc[0].handler = key1_handler;
imx6uirq.irqkeydesc[1].handler = key2_handler;
imx6uirq.irqkeydesc[0].value = KEY1VALUE;
imx6uirq.irqkeydesc[1].value = KEY2VALUE;
for (i = 0; i < KEY_NUM; i++)
{
ret = request_irq(imx6uirq.irqkeydesc[i].irqnum,
imx6uirq.irqkeydesc[i].handler,
IRQF_TRIGGER_FALLING|IRQF_TRIGGER_RISING,
imx6uirq.irqkeydesc[i].name,
&imx6uirq);
if(ret < 0)
{
printk("irq %d request failed!rn", imx6uirq.irqkeydesc[i].irqnum);
return -EFAULT;
}
}
init_timer(&imx6uirq.timer1);
imx6uirq.timer1.function = timer1_function;
init_timer(&imx6uirq.timer2);
imx6uirq.timer2.function = timer2_function;
return 0;
}
中断检测到按键按下后,为了消除按键抖动,这里使用定时器来进行按键消抖,因为本次实验用到两个按键,所以就先也使用两个定时器。
2.2.2 中断服务函数static irqreturn_t key1_handler(int irq, void *dev_id)
{
struct imx6uirq_dev *dev = (struct imx6uirq_dev *)dev_id;
dev->timer1.data = (volatile long)dev_id;
mod_timer(&dev->timer1, jiffies + msecs_to_jiffies(10));
return IRQ_RETVAL(IRQ_HANDLED);
}
中断函数检测到按键按下后,会开启一个10ms的定时器,用来按键消抖。
2.2.3 定时器服务函数定时器的10ms到达之后,会触发定时器服务函数,此时再次读取按键的值,若仍为按下,则是按键真的按下了,若10ms后又检测不到按键了,则说明是按键抖动导致的按键误触发。
void timer1_function(unsigned long arg)
{
unsigned char value;
struct irq_keydesc *keydesc;
struct imx6uirq_dev *dev = (struct imx6uirq_dev *)arg;
keydesc = &dev->irqkeydesc[0];
value = gpio_get_value(keydesc->gpio);
if(value == 1)
{
printk("get key1: highrn");
atomic_set(&dev->keyvalue, keydesc->value);
}
else
{
printk("key1 releasern");
atomic_set(&dev->keyvalue, 0x80 | keydesc->value);
atomic_set(&dev->releasekey, 1);
}
}
2.2.4 按键读取函数
static ssize_t imx6uirq_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{
int ret = 0;
unsigned char keyvalue = 0;
unsigned char releasekey = 0;
struct imx6uirq_dev *dev = (struct imx6uirq_dev *)filp->private_data;
keyvalue = atomic_read(&dev->keyvalue);
releasekey = atomic_read(&dev->releasekey);
if (releasekey)
{
//printk("releasekey!rn");
if (keyvalue & 0x80)
{
keyvalue &= ~0x80;
ret = copy_to_user(buf, &keyvalue, sizeof(keyvalue));
}
else
{
goto data_error;
}
atomic_set(&dev->releasekey, 0);
}
else
{
goto data_error;
}
return 0;
data_error:
return -EINVAL;
}
2.3 按键中断驱动程序
按键中断的应用程序,使用上篇的按键检测的应用程序即可
3 实验编译设备树与驱动文件(irqkey-BSp.ko),使用上篇的按键应用程序(key-App),按下按键,会打印get key,松开按键,会打印key release。
4 总结本篇主要介绍了Linux中断的使用方法,通过按键来进行中断实验测试,并使用Linux定时器进行按键去抖。



