Linux嵌入式驱动开发14——中断的原理以及按键中断的实现(tasklet中断下文)

本文主要是介绍Linux嵌入式驱动开发14——中断的原理以及按键中断的实现(tasklet中断下文),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

中断

  • 全系列传送门
  • 中断基础
    • 什么是中断?
    • 中断上下文
    • Linux中断现在不可以嵌套
  • 设备书中的中断节点和相关函数
    • 设备树中的中断节点
    • 中断相关函数
      • 获取中断号相关函数
      • 申请中断函数
      • 中断处理函数
      • free_irq函数
  • 按键中断实验(gpio_to_irq函数获取中断号)
    • 配置设备树
    • 驱动编写
    • 错误排查
    • oops信息
    • 结果验证
    • 查看中断信息
  • 按键中断实验(interrupt-parent和interrupts属性信息获取中断号)
    • 配置设备树
    • 驱动编写
    • 结果验证
  • tasklet相关知识
    • 什么是tasklet
      • 怎么使用tasklet来设计中断下文
      • tasklet定义
    • tasklet相关函数
      • tasklet_schedule
      • tasklet_init函数
      • tasklet_kill函数
    • 使用tasklet设计中断步骤
  • tasklet驱动程序设计
    • driver.c源码
    • 实验结果

全系列传送门

Linux嵌入式驱动开发01——第一个驱动Hello World(附源码)

Linux嵌入式驱动开发02——驱动编译到内核

Linux嵌入式驱动开发03——杂项设备驱动(附源码)

Linux嵌入式驱动开发04——应用层和内核层数据传输

Linux嵌入式驱动开发05——物理地址到虚拟地址映射

Linux嵌入式驱动开发06——第一个相对完整的驱动实践编写

Linux嵌入式驱动开发07——GPIO驱动过程记录(飞凌开发板)

Linux嵌入式驱动开发08——字符设备(步步为营)

Linux嵌入式驱动开发09——平台总线详解及实战

Linux嵌入式驱动开发10——设备树开发详解

Linux嵌入式驱动开发11——平台总线模型修改为设备树实例

Linux嵌入式驱动开发12——pinctl和gpio子系统实践操作

Linux嵌入式驱动开发13——ioctl接口(gpio控制使用)

Linux嵌入式驱动开发14——中断的原理以及按键中断的实现(tasklet中断下文)

Linux嵌入式驱动开发15——等待队列和工作队列

Linux嵌入式驱动开发16——按键消抖实验(内核定时器)

Linux嵌入式驱动开发17——输入子系统

Linux嵌入式驱动开发18——I2C通信

中断基础

什么是中断?

在这里插入图片描述

中断上下文

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

Linux中断现在不可以嵌套

设备书中的中断节点和相关函数

设备树中的中断节点

在这里插入图片描述
设备树中断的参考绑定文档:

/Documentation/devicetree/bindings/arm/gic.txt

在这里插入图片描述
在这里插入图片描述
比如,在imx6qdl.dtsi文件,可以看到中断控制器的信息
在这里插入图片描述
这个是不需要我们进行配置的,只需要会调用就可以了

继续往下看,就可以看到子中断控制器,这个是gpio1的中断控制器

在这里插入图片描述
这里是gpio1的中断属性信息,这里我们可以看到是#interrupt-cells = <2>;,有两个cell,所以,有两个中断,一组gpio有32个引脚,那么这里就会分成两组,比如0-15就是中断号66,剩下的16-31中断号就是67

interrupts = <0 66 IRQ_TYPE_LEVEL_HIGH>,<0 67 IRQ_TYPE_LEVEL_HIGH>;

下面通过实际的代码了解一下中断的使用。

首先,在我们的imx6qdl-sabresd.dtsi文件中, 看到了这么一段代码,显然使用了中断。
在这里插入图片描述

代码中,使用了interrupt-parent和interrupts属性来描述中断,interrupt-parent属性值是gpio1,也就是描述了使用了哪一个中断控制器。

interrupts属性设置的是中断源,这里有两个cells,其中18就是具体的中断号,也就是我们对应的引脚号,后面的8就是我们用到的中断的类型。

关于触发类型,有更加规范的写法,通过宏定义已经被定义好了。在如图所示的文件目录下
在这里插入图片描述

#define IRQ_TYPE_NONE		0
#define IRQ_TYPE_EDGE_RISING	1
#define IRQ_TYPE_EDGE_FALLING	2
#define IRQ_TYPE_EDGE_BOTH	(IRQ_TYPE_EDGE_FALLING | IRQ_TYPE_EDGE_RISING)
#define IRQ_TYPE_LEVEL_HIGH	4
#define IRQ_TYPE_LEVEL_LOW	8

在这里插入图片描述
但是我们后面有相关的函数,可以直接获取到gpio的中断号,所以,使用gpio的话不需要步骤二也是可以的,但一般都写上。

中断相关函数

获取中断号相关函数

在这里插入图片描述
在这里插入图片描述

申请中断函数

在这里插入图片描述
在这里插入图片描述

#define IRQF_TRIGGER_NONE	0x00000000
#define IRQF_TRIGGER_RISING	0x00000001
#define IRQF_TRIGGER_FALLING	0x00000002
#define IRQF_TRIGGER_HIGH	0x00000004
#define IRQF_TRIGGER_LOW	0x00000008

在这里插入图片描述

中断处理函数

在这里插入图片描述
关于中断处理函数的返回值:中断程序的返回值是一个特殊类型—irqreturn_t。但是中断程序的返回值却只有两个—IRQ_NONE和IRQ_HANDLED。

IRQ_NONE means we didn’t handle it.中断程序接收到中断信号后发现这并不是注册时指定的中断原发出的中断信号.此时返回次值
IRQ_HANDLED means that we did have a valid interrupt and handled it.接收到了准确的中断信号,并且作了相应正确的处理
IRQ_RETVAL(x) selects on the two depending on x being non-zero (for handled)

在实际中,如图所示操作后
在这里插入图片描述
在编译过程中会出现警告
在这里插入图片描述

free_irq函数

在这里插入图片描述

按键中断实验(gpio_to_irq函数获取中断号)

配置设备树

把原设备树文件中的按键相关的代码注释掉,然后写一个我们测试用的设备树节点
在这里插入图片描述
因为我们这个是gpio的中断,所以interrupt-parent和interrupts属性信息可以不写,直接在驱动函数中用相关的of函数获取中断编号就可以了。

	test_key {compatible = "keys";						// 设置compatible属性pinctrl-names = "default";					// 配置pinctrl子系统pinctrl-0 = <&pinctrl_gpio_keys>;gpios = <&gpio3 29 GPIO_ACTIVE_LOW>;		// 描述管脚};

配置完之后,编译

make dtbs

启动并且烧录设备树到我们的开发板,可以看到我们自己定义的节点,信息都是匹配的。

在这里插入图片描述

驱动编写

这里继续复用之前实验的代码,在原有的代码基础上进行修改,整体的思路就是

  1. of_find_node_by_path查找指定路径的节点
  2. of_get_named_gpio获取设备树编号
  3. gpio_direction_input方向设置为输入模式
  4. gpio_to_irq获取中断号
  5. request_irq请求中断,带入中断的处理函数作为参数

首先说一下,这个driver驱动代码是有问题的,但是正好做一个报错的方案解决

#include <linux/init.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/of.h>
#include <linux/of_address.h>#include <linux/gpio.h>
#include <linux/of_gpio.h>
#include <linux/interrupt.h>#include <linux/irq.h>
#include <linux/irqreturn.h>struct device_node  *test_device_node;int gpio_name;                                                                  // gpio编号
int irq;                                                                        // 中断号irq_handler_t test_key_handle(int irq, void *args)                              // 中断处理函数
{printk("test_key_handle ok!!!\n");return IRQ_HANDLED;                                                         // 中断程序的返回值只有两个IRQ_NONE和IRQ_HANDLED。
}/*probe函数*/
int beep_probe(struct platform_device *pdev){int ret = 0;printk("beep_probe ok!!!\n");/*间接获取设备节点信息*//********查找指定路径的节点***********/test_device_node = of_find_node_by_path("/test_key");               // 节点名字叫做test_keyif(test_device_node == NULL) {printk("of_find_node_by_path error!!!\n");return -1;}else {printk("of_find_node_by_path ok!!!\n");printk("test_device_node name is %s\n", test_device_node->name);}gpio_name = of_get_named_gpio(test_device_node, "gpios", 0);         //gpios是设备树节点里的索引值gpios = <&gpio3 29 GPIO_ACTIVE_LOW>;if(gpio_name < 0) {printk("of_get_named_gpio error!!!\n");return -1;}else{printk("of_get_named_gpio ok!!!\n");}gpio_direction_input(gpio_name);                                    // 因为是模拟按键,方向设置成输入模式irq = gpio_to_irq(gpio_name);                                       // 获取中断号,参数是gpio编号printk("irq is %d\n", irq);/* request_irq* 第一个参数:中断号,* 第二个参数:中断处理函数,* 第三个参数:中断标志(边沿触发方式),* 第四个函数:中断名字,* 第五个参数:设备结构体,传给中断处理函数irq_hander_t的第二个参数*/ret = request_irq(irq, test_key_handle, IRQF_TRIGGER_RISING, "test_key", NULL);  if(ret < 0) {printk("request_irq failed!!!\n");return -1;}else{printk("request_irq successful!!!\n");}return 0;
}int beep_remove(struct platform_device *pdev){printk("beep_remove ok!!!\n");return 0;
}const struct of_device_id of_match_table_test[] = {{.compatible = "keys"},{}                                              // 不写会提示警告
};struct platform_driver beep_device = {.probe = beep_probe,                            //  这个probe函数其实和  device_driver中的是一样的功能,但是一般是使用device_driver中的那个.remove = beep_remove,                          //  卸载平台设备驱动的时候会调用这个函数,但是device_driver下面也有,具体调用的是谁这个就得分析了.driver = {.owner = THIS_MODULE,      .of_match_table = of_match_table_test,      // 最优先匹配of_match_table其次是id_table最后是name},                                              //   内置的device_driver 结构体
};static int beep_driver_init(void)
{int ret = 0;printk("beep_driver_init ok!!!\n");             // 在内核中无法使用c语言库,所以不用printfret = platform_driver_register(&beep_device);if(ret < 0){printk("platform_driver_register error!!!\n");return ret;}else{printk("platform_driver_register ok!!!\n");}return 0;
}static void beep_driver_exit(void)
{printk("beep_driver_exit bye!!!\n");free_irq(irq, NULL);platform_driver_unregister(&beep_device);
}module_init(beep_driver_init);
module_exit(beep_driver_exit);MODULE_LICENSE("GPL");              //声明模块拥有开源许可

错误排查

上面的driver.c代码编译之后,发送到开发板,然后运行,发现开发板给我们报错。

那么来查看一下这个错误
在这里插入图片描述
这么一大串的错误,应该怎么办呢?

oops信息

其实错误信息打印的还是比较明确的

//无法处理内核页面请求的虚拟地址
[   66.286951] Unable to handle kernel NULL pointer dereference at virtual address 00000000
[   66.295109] pgd = a966c000
[   66.297849] [00000000] *pgd=38b1d831, *pte=00000000, *ppte=00000000
//内部错误oops
[   66.304246] Internal error: Oops: 17 [#1] PREEMPT SMP ARM
//表示内部错误发生在driver.ko驱动模块里
[   66.309659] Modules linked in: driver(O+)
[   66.313736] CPU: 2 PID: 848 Comm: insmod Tainted: G           O    4.1.15 #1
[   66.320799] Hardware name: Freescale i.MX6 Quad/DualLite (Device Tree)
[   66.327343] task: a8e604c0 ti: a924c000 task.ti: a924c000
//PC值:程序运行成功的最后一次地址,位于first_drv_open()函数里,偏移值0x4,该函数总大小0x34
[   66.332765] PC is at strcmp+0x4/0x34
//LR值
[   66.336371] LR is at kset_find_obj+0x3c/0xa4/*发生错误时的各个寄存器值*/
[   66.340662] pc : [<802b6184>]    lr : [<802b0c30>]    psr: a0070013
[   66.340662] sp : a924ddf8  ip : 00000000  fp : 00000124
[   66.352156] r10: 5640699c  r9 : 00000001  r8 : 00000000
[   66.357396] r7 : a803b688  r6 : 00000000  r5 : a803b680  r4 : a8148000
[   66.363939] r3 : 00000043  r2 : 0000013f  r1 : 00000000  r0 : a8144dc1
[   66.370484] Flags: NzCv  IRQs on  FIQs on  Mode SVC_32  ISA ARM  Segment user
[   66.377633] Control: 10c53c7d  Table: 3966c04a  DAC: 00000015
//发生错误时,进程名称为insmod
[   66.383394] Process insmod (pid: 848, stack limit = 0xa924c210)
//栈信息
[   66.389329] Stack: (0xa924ddf8 to 0xa924e000)
[   66.393703] dde0:                                                       7f000564 80be2860
[   66.401904] de00: a9bf9440 7f000158 00000000 8038a570 7f000564 8038a5f4 80be2860 80be2860
[   66.410105] de20: a9bf9440 7f00017c 80be2860 80009718 abc7cac0 800e4224 00000002 a92d6500
[   66.418305] de40: 8040003e 00000001 00010000 800b1eb8 00000002 8040003e abc6d300 abc7cac0
[   66.426505] de60: 80bdbafc 80bd8430 a8001f00 80bdbafc 00000124 800e59fc 7f0005a8 00000001
[   66.434706] de80: 7f0005a8 00000001 a9bf9c00 7f0005a8 a9bf9680 808229e0 7f0005a8 a9bf9680
[   66.442894] dea0: a924df58 00000001 a9bf9688 80095154 7f0005b4 00007fff 80092194 00000000
[   66.451075] dec0: 00000000 7f0005f0 00000000 7f0006f0 c0e648a4 7f0005b4 00000000 80830a34
[   66.459257] dee0: c0e63000 000018f4 00001924 00000000 00000010 00000000 00000000 00000000
[   66.467438] df00: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
[   66.475619] df20: 00000000 00000000 00000000 00000000 00000010 00000000 00000003 00027bf4
[   66.483800] df40: 0000017b 8000f604 a924c000 00000000 00000000 800955b8 c0e63000 000018f4
[   66.491981] df60: c0e64584 c0e644b0 c0e63fb0 0000070c 0000098c 00000000 00000000 00000000
[   66.500163] df80: 00000014 00000015 0000000d 0000000a 00000008 00000000 0028c008 00000000
[   66.508345] dfa0: 00000000 8000f480 0028c008 00000000 00000003 00027bf4 00000000 00000000
[   66.516527] dfc0: 0028c008 00000000 00000000 0000017b 00000000 00000002 76fca000 00000000
[   66.524708] dfe0: 7eb00c18 7eb00c08 0001f2e0 76f08e90 60070010 00000003 00000000 00000000
//回溯信息
[   66.532898] [<802b6184>] (strcmp) from [<802b0c30>] (kset_find_obj+0x3c/0xa4)
[   66.540045] [<802b0c30>] (kset_find_obj) from [<8038a570>] (driver_find+0x14/0x30)
[   66.547620] [<8038a570>] (driver_find) from [<8038a5f4>] (driver_register+0x68/0xf8)
[   66.555373] [<8038a5f4>] (driver_register) from [<7f00017c>] (beep_driver_init+0x24/0x54 [driver])
[   66.564344] [<7f00017c>] (beep_driver_init [driver]) from [<80009718>] (do_one_initcall+0x8c/0x1d4)
[   66.573405] [<80009718>] (do_one_initcall) from [<808229e0>] (do_init_module+0x5c/0x1a8)
[   66.581510] [<808229e0>] (do_init_module) from [<80095154>] (load_module+0x1ba8/0x1e50)
[   66.589523] [<80095154>] (load_module) from [<800955b8>] (SyS_finit_module+0x80/0x90)
[   66.597364] [<800955b8>] (SyS_finit_module) from [<8000f480>] (ret_fast_syscall+0x0/0x3c)
[   66.605549] Code: e5e32001 1afffffb e12fff1e e4d03001 (e4d12001)
[   66.611683] ---[ end trace 44e08424ce7c760b ]---Message from syslogd@imx6qsab[   66.616394] note: insmod[848] exited with preempt_count 1
resd at Wed Feb 24 11:05:14 2021 ...Segmentation fault

通过回溯信息,我们得到以下的信息:

SyS_finit_module -> load_module -> do_init_module -> do_one_initcall -> beep_driver_init -> driver_register -> driver_find -> kset_find_obj -> strcmp

所以我们的错误是出现在了驱动这块

回到我们的代码中,通过对驱动设置相关的模块检查,发现是代码的编写过程中,少了两部分的代码

结果验证

补齐两行相关的代码之后,我们在进行模块的安装
在这里插入图片描述
正常加载模块后,操作我们的按键,就可以发现,我们预设的信息正常打印,试验成功。
在这里插入图片描述

查看中断信息

最后再验证一下中断的信息,是否正确,前面我们已经打印出来了irq is 132

对应到

cat /proc/interrupts

在这里插入图片描述
看到了test_key,这个是因为这段代码中的命名
在这里插入图片描述
继续查看中断信息

cd /proc/irq

可以看到很多的中断号
在这里插入图片描述
我们查找这次测试的中断号132,打开
在这里插入图片描述

cat spurious

在这里插入图片描述
可以看到一个count 11这个值,这个值对应的是我们触发了多少次的中断,我们刚才正好是按键了11次

按键中断实验(interrupt-parent和interrupts属性信息获取中断号)

配置设备树

跟上面的一样,需要在设备树里添加信息,这里主要就是两行代码的添加。
在这里插入图片描述

	test_key {compatible = "keys";						// 设置compatible属性pinctrl-names = "default";					// 配置pinctrl子系统pinctrl-0 = <&pinctrl_gpio_keys>;gpios = <&gpio3 29 GPIO_ACTIVE_LOW>;		// 描述管脚interrupt-parent = <&gpio3>;interrupts = <29 IRQ_TYPE_EDGE_BOTH>;};

烧录到开发板,可以看到有了中断信息。
在这里插入图片描述

驱动编写

这里只是修改了中断号的获取方式,所以步骤与之前基本一致

  1. of_find_node_by_path查找指定路径的节点
  2. of_get_named_gpio获取设备树编号
  3. gpio_direction_input方向设置为输入模式
  4. irq_of_parse_and_map获取中断号
  5. request_irq请求中断,带入中断的处理函数作为参数
#include <linux/init.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/of.h>
#include <linux/of_address.h>#include <linux/gpio.h>
#include <linux/of_gpio.h>
#include <linux/interrupt.h>
#include <linux/of_irq.h>
#include <linux/irqreturn.h>struct device_node  *test_device_node;int gpio_name;                                                                  // gpio编号
int irq;                                                                        // 中断号irq_handler_t test_key_handle(int irq, void *args)                              // 中断处理函数
{printk("test_key_handle ok!!!\n");return IRQ_HANDLED;                                                         // 中断程序的返回值只有两个IRQ_NONE和IRQ_HANDLED。
}/*probe函数*/
int beep_probe(struct platform_device *pdev){int ret = 0;printk("beep_probe ok!!!\n");/*间接获取设备节点信息*//********查找指定路径的节点***********/test_device_node = of_find_node_by_path("/test_key");               // 节点名字叫做test_keyif(test_device_node == NULL) {printk("of_find_node_by_path error!!!\n");return -1;}else {printk("of_find_node_by_path ok!!!\n");printk("test_device_node name is %s\n", test_device_node->name);}gpio_name = of_get_named_gpio(test_device_node, "gpios", 0);         //gpios是设备树节点里的索引值gpios = <&gpio3 29 GPIO_ACTIVE_LOW>;if(gpio_name < 0) {printk("of_get_named_gpio error!!!\n");return -1;}else{printk("of_get_named_gpio ok!!!\n");}gpio_direction_input(gpio_name);                                    // 因为是模拟按键,方向设置成输入模式// irq = gpio_to_irq(gpio_name);                                       // 通过gpio函数获取中断号,参数是gpio编号/* irq_of_parse_and_map,通过设备树中interrupts获取中断号* 第一个参数:设备树节点* 第二个参数:索引值,这里只有一个,所以是0* */irq = irq_of_parse_and_map(test_device_node, 0);printk("irq is %d\n", irq);/* request_irq* 第一个参数:中断号,* 第二个参数:中断处理函数,* 第三个参数:中断标志(边沿触发方式),* 第四个函数:中断名字,* 第五个参数:设备结构体,传给中断处理函数irq_hander_t的第二个参数*/ret = request_irq(irq, test_key_handle, IRQF_TRIGGER_RISING, "test_key", NULL);  if(ret < 0) {printk("request_irq failed!!!\n");return -1;}else{printk("request_irq successful!!!\n");}return 0;
}const struct platform_device_id beep_id_table = {.name = "keys",
};int beep_remove(struct platform_device *pdev){printk("beep_remove ok!!!\n");return 0;
}const struct of_device_id of_match_table_test[] = {{.compatible = "keys"},{}                                              // 不写会提示警告
};struct platform_driver beep_device = {.probe = beep_probe,                            //  这个probe函数其实和  device_driver中的是一样的功能,但是一般是使用device_driver中的那个.remove = beep_remove,                          //  卸载平台设备驱动的时候会调用这个函数,但是device_driver下面也有,具体调用的是谁这个就得分析了.driver = {.owner = THIS_MODULE,      .name  = "keys",.of_match_table = of_match_table_test,      // 最优先匹配of_match_table其次是id_table最后是name},                                              //   内置的device_driver 结构体.id_table = &beep_id_table,
};static int beep_driver_init(void)
{int ret = 0;printk("beep_driver_init ok!!!\n");             // 在内核中无法使用c语言库,所以不用printfret = platform_driver_register(&beep_device);if(ret < 0){printk("platform_driver_register error!!!\n");return ret;}else{printk("platform_driver_register ok!!!\n");}return 0;
}static void beep_driver_exit(void)
{printk("beep_driver_exit bye!!!\n");free_irq(irq, NULL);platform_driver_unregister(&beep_device);
}module_init(beep_driver_init);
module_exit(beep_driver_exit);MODULE_LICENSE("GPL");              //声明模块拥有开源许可

结果验证

在这里插入图片描述
按键之后也可以顺利进入中断。
在这里插入图片描述

tasklet相关知识

什么是tasklet

在这里插入图片描述

怎么使用tasklet来设计中断下文

在这里插入图片描述
在这里插入图片描述

tasklet定义

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

tasklet相关函数

tasklet_schedule

在这里插入图片描述

tasklet_init函数

在这里插入图片描述

tasklet_kill函数

在这里插入图片描述

使用tasklet设计中断步骤

在这里插入图片描述

tasklet驱动程序设计

driver.c源码

遵循上面的步骤,添加相应的代码就可以了

#include <linux/init.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/of.h>
#include <linux/of_address.h>#include <linux/gpio.h>
#include <linux/of_gpio.h>
#include <linux/interrupt.h>
#include <linux/of_irq.h>
#include <linux/irqreturn.h>struct device_node  *test_device_node;struct tasklet_struct test_key_tasklet_struct;int gpio_name;                                                                  // gpio编号
int irq;                                                                        // 中断号/*打印一百次,模拟一个较长实践的任务*/
void test(unsigned long data)
{int i = 100;while(i--){printk("test_key is %d\n",i);}
}/*中断上文*/
irq_handler_t test_key_handle(int irq, void *args)                              // 中断处理函数
{printk("start\n");/*启动中断下文*/tasklet_schedule(&test_key_tasklet_struct);printk("tasklet_schedule end!!!\n");return IRQ_HANDLED;                                                         // 中断程序的返回值只有两个IRQ_NONE和IRQ_HANDLED。
}/*probe函数*/
int beep_probe(struct platform_device *pdev){int ret = 0;printk("beep_probe ok!!!\n");/*间接获取设备节点信息*//********查找指定路径的节点***********/test_device_node = of_find_node_by_path("/test_key");               // 节点名字叫做test_keyif(test_device_node == NULL) {printk("of_find_node_by_path error!!!\n");return -1;}else {printk("of_find_node_by_path ok!!!\n");printk("test_device_node name is %s\n", test_device_node->name);}gpio_name = of_get_named_gpio(test_device_node, "gpios", 0);         //gpios是设备树节点里的索引值gpios = <&gpio3 29 GPIO_ACTIVE_LOW>;if(gpio_name < 0) {printk("of_get_named_gpio error!!!\n");return -1;}else{printk("of_get_named_gpio ok!!!\n");}gpio_direction_input(gpio_name);                                    // 因为是模拟按键,方向设置成输入模式// irq = gpio_to_irq(gpio_name);                                       // 通过gpio函数获取中断号,参数是gpio编号/* irq_of_parse_and_map,通过设备树中interrupts获取中断号* 第一个参数:设备树节点* 第二个参数:索引值,这里只有一个,所以是0* */irq = irq_of_parse_and_map(test_device_node, 0);printk("irq is %d\n", irq);/* request_irq申请中断* 第一个参数:中断号,* 第二个参数:中断处理函数,* 第三个参数:中断标志(边沿触发方式),* 第四个函数:中断名字,* 第五个参数:设备结构体,传给中断处理函数irq_hander_t的第二个参数*/ret = request_irq(irq, test_key_handle, IRQF_TRIGGER_RISING, "test_key", NULL);  if(ret < 0) {printk("request_irq failed!!!\n");return -1;}else{printk("request_irq successful!!!\n");}/* tasklet_init初始化* 第一个参数:tasklet_struct结构体* 第二个参数:中断下文执行的函数* 第三个参数:传递给中断下文的参数*/tasklet_init(&test_key_tasklet_struct, test, 0);return 0;
}const struct platform_device_id beep_id_table = {.name = "keys",
};int beep_remove(struct platform_device *pdev){printk("beep_remove ok!!!\n");return 0;
}const struct of_device_id of_match_table_test[] = {{.compatible = "keys"},{}                                              // 不写会提示警告
};struct platform_driver beep_device = {.probe = beep_probe,                            //  这个probe函数其实和  device_driver中的是一样的功能,但是一般是使用device_driver中的那个.remove = beep_remove,                          //  卸载平台设备驱动的时候会调用这个函数,但是device_driver下面也有,具体调用的是谁这个就得分析了.driver = {.owner = THIS_MODULE,      .name  = "keys",.of_match_table = of_match_table_test,      // 最优先匹配of_match_table其次是id_table最后是name},                                              //   内置的device_driver 结构体.id_table = &beep_id_table,
};static int beep_driver_init(void)
{int ret = 0;printk("beep_driver_init ok!!!\n");             // 在内核中无法使用c语言库,所以不用printfret = platform_driver_register(&beep_device);if(ret < 0){printk("platform_driver_register error!!!\n");return ret;}else{printk("platform_driver_register ok!!!\n");}return 0;
}static void beep_driver_exit(void)
{printk("beep_driver_exit bye!!!\n");free_irq(irq, NULL);tasklet_kill(&test_key_tasklet_struct);platform_driver_unregister(&beep_device);
}module_init(beep_driver_init);
module_exit(beep_driver_exit);MODULE_LICENSE("GPL");              //声明模块拥有开源许可

实验结果

在这里插入图片描述
我们也可以传入参数,只需要把我们之前设置的i=100,修改成如下所示代码
在这里插入图片描述
在这里插入图片描述
可以得到正确的实验结果,循环计数了一百次

在这里插入图片描述
到这里可能会有一个问题,就是为什么我们在test_key_handle里面打印的开始和结束的信息,会一起显示
在这里插入图片描述
这是因为tasklet_schedule绑定的函数不会立即执行,而是等我们的中断结束后的不确定时间,再开始执行我们的中断下文函数。

这篇关于Linux嵌入式驱动开发14——中断的原理以及按键中断的实现(tasklet中断下文)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/784331

相关文章

SpringBoot集成Milvus实现数据增删改查功能

《SpringBoot集成Milvus实现数据增删改查功能》milvus支持的语言比较多,支持python,Java,Go,node等开发语言,本文主要介绍如何使用Java语言,采用springboo... 目录1、Milvus基本概念2、添加maven依赖3、配置yml文件4、创建MilvusClient

JS+HTML实现在线图片水印添加工具

《JS+HTML实现在线图片水印添加工具》在社交媒体和内容创作日益频繁的今天,如何保护原创内容、展示品牌身份成了一个不得不面对的问题,本文将实现一个完全基于HTML+CSS构建的现代化图片水印在线工具... 目录概述功能亮点使用方法技术解析延伸思考运行效果项目源码下载总结概述在社交媒体和内容创作日益频繁的

kali linux 无法登录root的问题及解决方法

《kalilinux无法登录root的问题及解决方法》:本文主要介绍kalilinux无法登录root的问题及解决方法,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,... 目录kali linux 无法登录root1、问题描述1.1、本地登录root1.2、ssh远程登录root2、

使用Python开发一个带EPUB转换功能的Markdown编辑器

《使用Python开发一个带EPUB转换功能的Markdown编辑器》Markdown因其简单易用和强大的格式支持,成为了写作者、开发者及内容创作者的首选格式,本文将通过Python开发一个Markd... 目录应用概览代码结构与核心组件1. 初始化与布局 (__init__)2. 工具栏 (setup_t

openCV中KNN算法的实现

《openCV中KNN算法的实现》KNN算法是一种简单且常用的分类算法,本文主要介绍了openCV中KNN算法的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的... 目录KNN算法流程使用OpenCV实现KNNOpenCV 是一个开源的跨平台计算机视觉库,它提供了各

OpenCV图像形态学的实现

《OpenCV图像形态学的实现》本文主要介绍了OpenCV图像形态学的实现,包括腐蚀、膨胀、开运算、闭运算、梯度运算、顶帽运算和黑帽运算,文中通过示例代码介绍的非常详细,需要的朋友们下面随着小编来一起... 目录一、图像形态学简介二、腐蚀(Erosion)1. 原理2. OpenCV 实现三、膨胀China编程(

通过Spring层面进行事务回滚的实现

《通过Spring层面进行事务回滚的实现》本文主要介绍了通过Spring层面进行事务回滚的实现,包括声明式事务和编程式事务,具有一定的参考价值,感兴趣的可以了解一下... 目录声明式事务回滚:1. 基础注解配置2. 指定回滚异常类型3. ​不回滚特殊场景编程式事务回滚:1. ​使用 TransactionT

Android实现打开本地pdf文件的两种方式

《Android实现打开本地pdf文件的两种方式》在现代应用中,PDF格式因其跨平台、稳定性好、展示内容一致等特点,在Android平台上,如何高效地打开本地PDF文件,不仅关系到用户体验,也直接影响... 目录一、项目概述二、相关知识2.1 PDF文件基本概述2.2 android 文件访问与存储权限2.

使用Python实现全能手机虚拟键盘的示例代码

《使用Python实现全能手机虚拟键盘的示例代码》在数字化办公时代,你是否遇到过这样的场景:会议室投影电脑突然键盘失灵、躺在沙发上想远程控制书房电脑、或者需要给长辈远程协助操作?今天我要分享的Pyth... 目录一、项目概述:不止于键盘的远程控制方案1.1 创新价值1.2 技术栈全景二、需求实现步骤一、需求

Spring Shell 命令行实现交互式Shell应用开发

《SpringShell命令行实现交互式Shell应用开发》本文主要介绍了SpringShell命令行实现交互式Shell应用开发,能够帮助开发者快速构建功能丰富的命令行应用程序,具有一定的参考价... 目录引言一、Spring Shell概述二、创建命令类三、命令参数处理四、命令分组与帮助系统五、自定义S