04.Linux中断实验
实验说明¶
使用正点原子开发板,用按键来测试中断服务
修改设备节点¶
在根节点“/”下创建 KEY 节点,节点名为“mini_key”,节点内容如下
mini_key {
compatible = "alientek,gpio_key";
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_key>;
key-gpio = <&gpio1 18 GPIO_ACTIVE_LOW>;/* KEY0 */
interrupt-parent = <&gpio1>;
interrupts = <18 IRQ_TYPE_EDGE_BOTH>; /* FALLING RISING */
status = "okay";
};
然后在&iomuxc中添加pinctrl引脚
函数API和流程¶
初始化¶
1、在初始化probe中,获得设备树节点信息,以下两种方式均可
struct device_node *node;
//1.这种方式需要传入的probe的参数pdev
node = pdev->dev.of_node;
//2.这种方式需要知道节点名称信息
node = of_find_node_by_path("/mini_key");
2、获得GPIO编号,也有两种方式,但是第二种未测试
int gpio;
//1.of_get_named_gpio 函数获取 GPIO 编号
//参数 1:设备树节点信息 2:设备树节点中描述引脚的名称 3:第几个引脚
gpio = of_get_named_gpio(node ,"key-gpio", 0);
enum of_gpio_flags flags;
//2.of_get_gpio_flags 函数获取 GPIO 编号
//参数 1:设备树节点信息 2:第几个引脚 3:配置的状态
//这种方式好像需要把gpio配置引脚的变量名称设置为gpios
gpio = of_get_gpio_flags(node, 0, &flags);
3、设置引脚为输入模式,同时获取gpio对应的中断号,获取中断号也有两种方式
//配置输入模式
//参数 1:GPIO编号
gpio_direction_input(gpio);
int irq;
//1.gpio_to_irq获取对应的中断号
//参数 1:GPIO编号
irq = gpio_to_irq(gpio);
//2.irq_of_parse_and_map获取对应的中断号
//参数 1:设备树节点信息 2:第几个中断配置
irq = irq_of_parse_and_map(node, 0);
4、获得设备描述符,可以传入设备描述信息,通过gpio_to_desc函数,但是此步不必要,相关功能有其他方法可实现,我看正点原子和讯为都没用,只有韦东山教程用了。
5、申请中断,使用request_irq申请中断,要提前定义好中断服务函数
int irq;
//中断服务函数
static irqreturn_t gpio_key_isr(int irq, void *dev_id)
{
return IRQ_RETVAL(IRQ_HANDLED);;
}
//参数 1:中断号 2:中断服务函数 3:中断中断标志,意为上升沿触发下降沿都触发
// 4:中断名字 5:中断触发时传入中断服务函数的参数
ret = request_irq(irq, gpio_key_isr, IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, "alitenk_gpio_key", &key);
5、然后就是注册字符设备的流程了,不细说
程序结束¶
在程序退出的时候需要将注册的中断注销,使用free_irq函数。
程序编写¶
先定义两个结构体,方便后面描述
struct key_dev{
struct class *class;
struct device *device;
struct device_node *nd;
struct gpio_desc *gpiod;
int gpio;
int irq;
int major;
int minor;
};
struct key_dev key; /* key设备 */
在按键初始化的时候注册中断
static int gpio_key_probe(struct platform_device *pdev)
{
int ret;
//1.获得设备树节点信息
struct device_node *node = pdev->dev.of_node;
//2.获得gpio编号
key.gpio = of_get_named_gpio(node ,"key-gpio", 0);
//3.获得中断号
gpio_direction_input(key.gpio);
key.irq = gpio_to_irq(key.gpio);
//4.设置gpio描述符
key.gpiod = gpio_to_desc(key.gpio);
//5.注册gpio中断
ret = request_irq(key.irq, gpio_key_isr, IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, "alitenk_gpio_key", &key);
/*
注册字符设备
*/
return 0;
}
中断服务函数
通过gpiod_get_value就可以获得设备引脚的按键值
static irqreturn_t gpio_key_isr(int irq, void *dev_id)
{
struct key_dev *gpio_key = dev_id;
int val;
val = gpiod_get_value(gpio_key->gpiod);
printk("key %d %d\n", gpio_key->gpio, g_key);
g_key = val;
return IRQ_RETVAL(IRQ_HANDLED);
}
实验测试文件如下
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
/*
* ./button_test /dev/100ask_button0
*
*/
int main(int argc, char **argv)
{
int fd;
int val;
int last_val;
/* 1. 判断参数 */
if (argc != 2)
{
printf("Usage: %s <dev>\n", argv[0]);
return -1;
}
/* 2. 打开文件 */
fd = open(argv[1], O_RDWR);
if (fd == -1)
{
printf("can not open file %s\n", argv[1]);
return -1;
}
while (1)
{
/* 3. 读文件 */
read(fd, &val, 4);
if(val != last_val)
{
printf("get button : 0x%x\n", val);
}
last_val = val;
}
close(fd);
return 0;
}
中断下半部¶
这部分程序在定时器后面
先在结构体中定义struct tasklet_struct tasklet。
struct key_dev{
struct class *class;
struct device *device;
struct device_node *nd;
struct gpio_desc *gpiod;
struct timer_list timer;
struct tasklet_struct tasklet;
int gpio;
int irq;
int major;
int minor;
};
然后在初始化程序中初始化tasklet。需要先定义好处理函数。
//tasklet处理函数
static void key_tasklet_func(unsigned long data)
{
/* data ==> gpio */
struct gpio_key *gpio_key = (struct gpio_key *)data;
int val;
val = gpiod_get_value(gpio_key->gpiod);
printk("key_tasklet_func key %d %d\n", gpio_key->gpio, val);
}
static int gpio_key_probe(struct platform_device *pdev)
{
/*注册按键中断*/
tasklet_init(&key.tasklet, key_tasklet_func, &key);
/*注册设备*/
return 0;
}
在中断服务函数中使用tasklet_schedule触发这个下半部
static irqreturn_t gpio_key_isr(int irq, void *dev_id)
{
struct key_dev *gpio_key = dev_id;
tasklet_schedule(&gpio_key->tasklet);
return IRQ_HANDLED;
}
中断使用内核线程¶
在初始化的时候,使用request_threaded_irq注册中断。
//参数1:中断号 2:上半部 3:线程函数 4:中断触发方式 5:名字 6:用户参数
ret = request_threaded_irq(key.irq, gpio_key_isr, gpio_key_thread_func, IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, "alitenk_gpio_key", &key);
注册时写好中断上半部和线程函数,重点是上半部返回IRQ_WAKE_THREAD触发线程函数。线程中再返回IRQ_HANDLED。
static irqreturn_t gpio_key_isr(int irq, void *dev_id)
{
struct key_dev *gpio_key = dev_id;
schedule_work(&gpio_key->work);
return IRQ_WAKE_THREAD;
}
static irqreturn_t gpio_key_thread_func(int irq, void *data)
{
struct key_dev *gpio_key = (struct key_dev *)data;
int val;
val = gpiod_get_value(gpio_key->gpiod);
printk("gpio_key_thread_func: the process is %s pid %d\n",current->comm, current->pid);
printk("gpio_key_thread_func key %d %d\n", gpio_key->gpio, val);
return IRQ_HANDLED;
}