【北京迅为】《STM32MP157开发板嵌入式开发指南》- 第五十七章 Linux中断实验

devtools/2024/10/23 9:16:58/

iTOP-STM32MP157开发板采用ST推出的双核cortex-A7+单核cortex-M4异构处理器,既可用Linux、又可以用于STM32单片机开发。开发板采用核心板+底板结构,主频650M、1G内存、8G存储,核心板采用工业级板对板连接器,高可靠,牢固耐用,可满足高速信号环境下使用。共240PIN,CPU功能全部引出:底板扩展接口丰富底板板载4G接口(选配)、千兆以太网、WIFI蓝牙模块HDMI、CAN、RS485、LVDS接口、温湿度传感器(选配)光环境传感器、六轴传感器、2路USB OTG、3路串口,CAMERA接口、ADC电位器、SPDIF、SDIO接口等


第五十七章 Linux中断实验

本章导读

在 Linux 下的驱动实验中,中断是频繁使用的功能,Linux 内核提供了完善的中断框架,我们只需要使

用内核提供的函数,便可以方便的使用中断功能。本章我们就来学习一下如何在 Linux 中使用中断。

57.1章节讲解了Linux中断基础理论知识

57.2章节在STM32MP157开发板上以按键中断为例,进行实验。

本章内容对应视频讲解链接(在线观看):

中断基础概念  https://www.bilibili.com/video/BV1Vy4y1B7ta?p=34

设备树中的中断节点以及相关函数  https://www.bilibili.com/video/BV1Vy4y1B7ta?p=35

按键中断实验  https://www.bilibili.com/video/BV1Vy4y1B7ta?p=36

程序源码在网盘资料“iTOP-STM32MP157开发板网盘资料汇总\09_嵌入式Linux开发指南(iTOP-STM32MP157)手册配套资料\驱动程序例程\15-Linux中断实验”路径下。

57.1 Linux中断简介

57.1.1 Linux中断介绍

中断是指 CPU 在执行程序的过程中,出现了某些突发事件急待处理,CPU 必须暂停当前程序的执行,

转去处理突发事件,处理完毕后又返回原程序被中断的位置继续执行。由于中断的存在极大的提高了 CPU的运行效率,但是设备的中断会打断内核进程中的正常调度和运行,系统对更高吞吐率的追求势必要求中断服务程序尽量短小精悍。

举例来说,我现在正在厨房做饭,突然电话响了,然后我关火去接电话,接完电话在回去开火继续做饭,这个过程就是中断的一个过程。在这个看似简单的过程中,却涉及到了中断的几个过程,我们一起来看一下:

  • 电话铃声响了:中断请求
  • 我要去接电话:中断响应
  • 我关掉火:保护现场
  • 我接电话的过程:中断处理
  • 接完电话回到厨房开火:恢复现场
  • 继续做饭:中断返回
  • 如果我不接电话:中断屏蔽

为保证系统实时性,中断服务程序必须足够简短,但实际应用中某些时候发生中断时必须处理大量的

事物,这时候如果都在中断服务程序中完成,则会严重降低中断的实时性,基于这个原因,linux 系统提出了一个概念:把中断服务程序分为两部分:顶半部-底半部 。

顶半部(中断上文):完成尽可能少的比较急的功能,它往往只是简单的读取寄存器的中断状态,并清除中断标志后就进行“中断标记”(也就是把底半部处理程序挂到设备的底半部执行队列中)的工作。 顶半部的特点就是响应速度快。

底半部(中断下文):处理中断的剩余大部分任务,可以被新的中断打断。

57.1.2 中断相关函数

linux 中断有专门的中断子系统,其实现原理很复杂,但是驱动开发者不需要知道其实现的具体细节,

只需要知道如何应用该子系统提供的 API 函数来编写中断相关驱动代码即可。

1 获取中断号相关函数

编写驱动的时候需要用到中断号,每一个中断都有中断号,我们用到中断号,中断信息已经写到了设备树里面,因此可以通过 irq_of_parse_and_map 函数从 interupts 属性中提取到对应的设备号,函数原型如下表所示:

函数

unsigned int irq_of_parse_and_map(struct device_node *dev,int index)

dev

设备节点

index

索引号,interrupts 属性可能包含多条中断信息,通过 index 指定要获取的信息。

返回值

中断号

功能

通过 irq_of_parse_and_map 函数从 interupts 属性中提取到对应的设备号

如果使用 GPIO 的话,可以使用 gpio_to_irq 函数来获取 gpio 对应的中断号,函数原型如下表所示 

函数

int gpio_to_irq(unsigned int gpio)

gpio

要获取的 GPIO 编号

返回值

GPIO 对应的中断号

功能

获取GPIO对应的中断号

2 申请中断函数

同GPIO一样,在Linux内核里面,如果我们要使用某个中断也是需要申请的,申请中断我们使用的函数是 request_irq。函数原型如下表所示:

函数

int request_irq( unsigned int irq,irq_handler_t handler,unsigned long flags,const char *name,void *dev)

irq

要申请中断的中断号

handler

中断处理函数,当中断发生以后就会执行此中断处理函数。

flags

中断标志

name

中断名字,设置以后可以在开发板/proc/interrupts 文件中看到对应的中断名字

dev

如果将 flags 设置为 IRQF_SHARED 的话, dev 用来区分不同的中断,一般情况下将 dev 设置为设备结构体,dev 会传递给中断处理函数 irq_handler_t 的第二个参数。

返回值

中断申请成功返回0,其他负值则中断申请失败,如果返回-EBUSY 的话表示中断已经被申请了。

中断标识可以在文件 include/linux/interrupt.h 里面查看所有的中断标志,这里我们介绍几个常用的中断标志,如下图所示: 

标志

功能

IRQF_SHARED

多个设备共享一个中断线,共享的所有中断都必须指定此标志。如果使用共享中断的话,request_irq 函数的 dev 参数就是唯一区分他们的标志。

IRQF_ONESHOT

单次中断,中断执行一次就结束。

IRQF_TRIGGER_NONE

无触发。

IRQF_TRIGGER_RISING

上升沿触发。

IRQF_TRIGGER_FALLING

下降沿触发。

IRQF_TRIGGER_HIGH

高电平触发。

IRQF_TRIGGER_LOW

低电平触发。

3 、free_irq 函数

中断使用完成以后就要通过 free_irq 函数释放掉相应的中断。如果中断不是共享的,那么 free_irq 会

删除中断处理函数并且禁止中断。free_irq 函数原型如下所示

函数

void free_irq(unsigned int irq,void *dev)

irq

要释放的中断

dev

如果中断设置为共享(IRQF_SHARED)的话,此参数用来区分具体的中断。共享中断只有在释放最后中断处理函数的时候才会被禁止掉。

返回值

功能

释放掉相应的中断

4、中断处理函数

使用 request_irq 函数申请中断的时候需要设置中断处理函数,中断处理函数函数如下表所示:

函数

irqreturn_t (*irq_handler_t) (int, void *)

第一个参数

要中断处理函数要相应的中断号

第二个参数

是一个指向 void 的指针,也就是个通用指针,需要与 request_irq 函数的 dev 参数保持一致。用于区分共享中断的不同设备,dev 也可以指向设备数据结构。

返回值

中断处理函数的返回值为 irqreturn_t 类型

irqreturn_t 类型定义如下所示: 

 enum irqreturn {IRQ_NONE = (0 << 0),IRQ_HANDLED = (1 << 0),IRQ_WAKE_THREAD = (1 << 1),};

可以看出 irqreturn_t 是个枚举类型,一共有三种返回值。一般中断服务函数返回值使用如下形式

return IRQ_RETVAL(IRQ_HANDLED)

5、中断使能和禁止函数

常用的中断使用和禁止函数如下所示:

void enable_irq(unsigned int irq)

void disable_irq(unsigned int irq)

enable_irq 和 disable_irq 用于使能和禁止指定的中断,irq 就是要禁止的中断号。disable_irq 函数要

等到当前正在执行的中断处理函数执行完才返回,因此使用者需要保证不会产生新的中断,并且确保所有

已经开始执行的中断处理程序已经全部退出。在这种情况下,可以使用另外一个中断禁止函数:

void disable_irq_nosync(unsigned int irq)

disable_irq_nosync 函数调用以后立即返回,不会等待当前中断处理程序执行完毕。

  

57.1.3 中断上文和中断下文

中断的存在可以极大的提高CPU的运行效率,但是中断会打断内核进程中的正常调度和运行,所以为保证系统实时性,中断服务程序必须足够简短,但实际应用中某些时候发生中断时必须处理大量的事物,这时候如果都在中断服务程序中完成,则会严重降低中断的实时性,基于这个原因,linux 系统提出了一个概念:把中断服务程序分为两部分:中断上文和中断下文。

有些资料中也将顶半部和底半部称为上半部和下半部,都是一个意思。Linux 内核将中断分为顶半部和

底半部的主要目的就是实现中断处理函数的快进快出,那些对时间敏感、执行速度快的操作可以放到中断

处理函数中,也就是顶半部。剩下的所有工作都可以放到底半部去执行,至于哪些代码要在顶半部完成,哪些代码要在底半部完成,并没有严格的要求,要根据实际情况来判断,下面有一些参考点:

① 如果要处理的内容不希望被其他中断打断,那么可以放到上半部。

② 如果要处理的任务对时间敏感,可以放到上半部。

③ 如果要处理的任务与硬件有关,可以放到上半部

④ 除了上述三点以外的其他任务,优先考虑放到下半部。

中断上文:完成尽可能少却比较急的任务,中断上文的特点就是响应速度快。中断下文:处理中断剩余的大量比较耗时间的任务,而且可以被新的中断打断。

举例来说,我现在正在厨房做饭,突然电话响了,然后我关火去接电话,快递员打电话让我下楼去拿快递,接完电话叫我女朋友去下楼拿快递,然后我在回去开火继续做饭,这个过程就是中断上下文。

分析例子:快递员打电话让我下去拿快递,这个事情很紧急,所以要快速处理,这个就是要在中断上文中完成。但是下楼拿快递这个过程非常耗时间,所以叫女朋友去拿快递,这个就是中断下文。下楼拿快递很耗时间,如果我不叫女朋友去帮我拿而是自己拿,等我拿完饭回来我锅里的菜是不是就凉了呀,同理,如果你在中断里面做很耗时间的时间,系统就会崩溃。如果女朋友在去拿快递的过程中,突然口渴了,要去超市买水,所以,中断下半部分是可以被中断打断的。

总之,中断上文越快越好,中断下文可以做比较耗时间的事情,但是不能死循环。Linux中断可以嵌套吗?以前是可以,现在不可以。

57.1.4 设备树中的中断节点

如果一个设备需要用到中断功能,开发人员就需要在设备树中配置好中断属性信息,因为设备树是用来描述硬件信息的,然后Linux内核通过设备树配置的中断属性来配置中断功能。对于中断控制器而言,设 备树绑定信息参考文档Documentation/devicetree/bindings/arm/gic.txt。打开 stm32mp151.dtsi文件,其中的 intc 节点就是 STM32MP157的中断控制器节点,节点内容如下所示:

          intc: interrupt-controller@a0021000 {compatible = "arm,cortex-a7-gic";#interrupt-cells = <3>;interrupt-controller;reg = <0xa0021000 0x1000>,<0xa0022000 0x2000>;};

compatible 属性值为“arm,cortex-a7-gic”在 Linux 内核源码中搜索“arm,cortex-a7-gic”即可找到 GIC 中断控制器驱动文件。

#interrupt-cells 和#address-cells、#size-cells 一样。表示此中断控制器下设备的 cells 大小,对于设备而言,会使用 interrupts 属性描述中断信息,#interrupt-cells 描述了 interrupts 属性的 cells 大小,也就是一条信息有几个 cells。每个 cells 都是 32 位整型值,对于 ARM 处理的 GIC 来说,一共有 3 个cells,这三个 cells 的含义如下:

  • 第一个 cells:中断类型,0 表示 SPI 中断,1 表示 PPI 中断。

第二个 cells:中断号,对于 SPI 中断来说中断号的范围为 32~287(256),对于 PPI 中断来说中断号的范围为 16~31

  • 第三个 cells:标志,bit[3:0]表示中断触发类型,为 1 的时候表示上升沿触发,为 2 的时候表示下降

沿触发,为 4 的时候表示高电平触发,为 8 的时候表示低电平触发。bit[15:8]为 PPI 中断的 CPU 掩码。

interrupt-controller 节点为空,表示当前节点是中断控制器。

对于 gpio 来说,gpio 节点也可以作为中断控制器,比如stm32mp151.dtsi文件中的 uart4节点内容如下所示:

  rcc: rcc@50000000 {compatible = "st,stm32mp1-rcc-secure", "st,stm32mp1-rcc", "syscon";reg = <0x50000000 0x1000>;#clock-cells = <1>;#reset-cells = <1>;interrupts = <GIC_SPI 5 IRQ_TYPE_LEVEL_HIGH>;clock-names = "hse", "hsi", "csi", "lse", "lsi";clocks = <&scmi0_clk CK_SCMI0_HSE>,<&scmi0_clk CK_SCMI0_HSI>,<&scmi0_clk CK_SCMI0_CSI>,<&scmi0_clk CK_SCMI0_LSE>,<&scmi0_clk CK_SCMI0_LSI>;};

 interrupts 描述中断中断源的信息,第一个表示中断类型,为 GIC_SPI,也就是共享中断。第二个表示中断号为 5关于中断号的确定可以根据iTOP-STM32MP157开发板\iTOP-STM32MP157开发板光盘资料\20220223\01_开发板硬件资料\官方数据手册\STM32MP157参考手册.pdf的21.1 NVIC interrupts小节来确定,第三个表示中断触发类型是高电平触发。

简单总结下与中断有关的设备树属性信息

  • #interrupt-cells,指定中断源的信息 cells 个数。 
  • interrupt-controller,表示当前节点为中断控制器 
  • interrupts,指定中断号,触发方式等。 
  • interrupt-parent,指定父中断,也就是中断控制器 

中断实际上是非常复杂的,但是作为开发人员,我们只需要关心怎么在设备树中指定中断,怎么在代码中获得中断就可以。其他的事情,比如设备树中的中断控制器,这些都是由原厂的BSP工程师帮我们写好了,我们不需要来修改他。除非将来你有机会去原厂工作,否则我们不会从头开始写一个设备树文件的,分工是非常明确的。我们需要关注的点是怎么在设备树里面描述一个外设的中断节点,我们来看一个例子。

这个例子是用按键给大家举例的,比如说你现在要接一个外设按键,通过中断来获取中断的键值,首先在设备树里面写节点。

key
{compatible = "key";gpios = <&gpioi 2 GPIO_ACTIVE_HIGH>;interrupt-parent = <&gpioi>;interrupts = <2 IRQ_TYPE_EDGE_BOTH>;status = "okay";}

在这个例子中,我们先使用pinctrl和gpio子系统把这个引脚设置为了gpio功能,因为我们在使用中断的时候需要把引脚设置成输入。然后使用interrupt-parent和interrupts属性来描述中断。interrupt-parent的属性值是gpioi,也就是他的要使用gpioi这个中断控制器,为什么是gpioi呢,因为我们的引脚使用的是gpioi里面的io2,所以我们使用的是gpioi这个中断控制器。interrupts属性设置的是中断源

例子中的第一个 cells 的 2表示 GPIOi 组的 2号 IO。IRQ_TYPE_EDGE_BOTH表示上升沿和下降沿都有效。

IRQ_TYPE_EDGE_BOTH 定义在文件 include/linux/irq.h 中,定义如下 

所以我们在设备树里面配置中断的时候只需要两个步骤即可,第一个步骤是把管脚设置为gpio功能。第二个步骤是使用interrupt-parent和interrupts属性来描述中断。

57.2 实验程序编写

我们以STM32MP157开发板为例,写一个按键中断,按一下按键就让在终端上打印一句话,这个程序是非常简单的,因为他没有涉及到中断下文的编写,只有中断上文。

57.2.1 修改设备树文件

我们修改设备树文件stm32mp15xx-itop.dtsi,路径在源码目录/home/topeet/work/linux-5.4.31/arch/arm/boot/dts目录下。我们要使用开发板底板上的音量BACK键进行试验,另外两个用户按键和摄像头的DCMI接口引脚重复了,所以只有屏蔽了DCMI节点才能使用,我们在这里将原来所有的按键节点注释掉(这里使用的if判断),如下图所示:

 

然后在根节点下,将之前的添加的test节点修改为以下内容。

        test: test{

                compatible = "keys";

                gpios = <&gpioi 2 GPIO_ACTIVE_HIGH>;

                status = "okay";

       };

 

注释掉以前修改的内容,如下图所示 

 

然后编译源码,编译源码请参考本手册51.3编译设备树文件章节。

编译完设备树,然后重新烧写Linux镜像,接下来我们来检查你编译的设备树文件有没有被加载到系统里面,也就是说查看你添加的节点有没有,如下图所示:

 

从上图可以看到我们添加的节点,我们查看下节点“test”,如下图所示: 

 

从上图可以看出“compatible”属性值是keys。

57.2.2 编写驱动代码

程序源码在网盘资料“iTOP-STM32MP157开发板网盘资料汇总\09_嵌入式Linux开发指南(iTOP-STM32MP157)手册配套资料\驱动程序例程\15-Linux中断实验\001”路径下。

我们在Ubuntu的/home/nfs/19/01目录下新建driver.c,将上次编译driver.c的Makefile文件新建的driver.c同级目录下,修改Makefile为:

完整的驱动代码如下面所示:

/** @Author:topeet* @Description: 使用irq_of_parse_and_map函数来获取中断号*/
#include <linux/init.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_irq.h>#include <linux/gpio.h>
#include <linux/of_gpio.h>
#include <linux/interrupt.h>
//定义结构体表示我们的节点
struct device_node *test_device_node;
struct property *test_node_property;
//要申请的中断号
int irq;
// GPIO 编号
int gpio_nu;/*** @description: 中断处理函数test_key* @param {int} irq :要申请的中断号* @param {void} *args :* @return {*}IRQ_HANDLED*/
irqreturn_t test_key(int irq, void *args)
{printk("test_key \n");return IRQ_RETVAL(IRQ_HANDLED);
}
/***************************************************************************************** @brief led_probe : 与设备信息层(设备树)匹配成功后自动执行此函数,* @param inode : 文件索引* @param file  : 文件* @return 成功返回 0           ****************************************************************************************/
int led_probe(struct platform_device *pdev)
{int ret = 0;// 打印匹配成功进入probe函数printk("led_probe\n");test_device_node = of_find_node_by_path("/test");if (test_device_node == NULL){//查找节点失败则打印信息printk("of_find_node_by_path is error \n");return -1;}gpio_nu = of_get_named_gpio(test_device_node, "gpios", 0);if (gpio_nu < 0){printk("of_get_namd_gpio is error \n");return -1;}//设置GPIO为输入模式gpio_direction_input(gpio_nu);//获取GPIO对应的中断号irq = gpio_to_irq(gpio_nu);//irq = irq_of_parse_and_map(test_device_node, 0);printk("irq is %d \n", irq);/*申请中断,irq:中断号名字  test_key:中断处理函数IRQF_TRIGGER_RISING:中断标志,意为上升沿触发"test_key":中断的名字*/ret = request_irq(irq, test_key, IRQF_TRIGGER_RISING, "test_key", NULL);if (ret < 0){printk("request_irq is error \n");return -1;}return 0;
}int led_remove(struct platform_device *pdev)
{printk("led_remove\n");return 0;
}
const struct platform_device_id led_idtable = {.name = "keys",
};
const struct of_device_id of_match_table_test[] = {{.compatible = "keys"},{},
};
struct platform_driver led_driver = {//3. 在led_driver结构体中完成了led_probe和led_remove.probe = led_probe,.remove = led_remove,.driver = {.owner = THIS_MODULE,.name = "led_test",.of_match_table = of_match_table_test},//4 .id_table的优先级要比driver.name的优先级要高,优先与.id_table进行匹配.id_table = &led_idtable};/*** @description: 模块初始化函数* @param {*}* @return {*}*/
static int led_driver_init(void)
{//1.我们看驱动文件要从init函数开始看int ret = 0;//2.在init函数里面注册了platform_driverret = platform_driver_register(&led_driver);if (ret < 0){printk("platform_driver_register error \n");}printk("platform_driver_register ok \n");return 0;
}/*** @description: 模块卸载函数* @param {*}* @return {*}*/
static void led_driver_exit(void)
{free_irq(irq, NULL);platform_driver_unregister(&led_driver);printk("goodbye! \n");
}
module_init(led_driver_init);
module_exit(led_driver_exit);MODULE_LICENSE("GPL");

将driver.c编译为driver.ko 文件,如下图所示:

57.2.3 运行测试

我们进入共享文件夹,加载驱动模块,如下图所示:

 

那么我们怎么查看是否申请中断成功了呢?我们可以进入到开发板的/proc/interrupts目录查看,如下图所示:

cat /proc/interrupts | grep test_key

 

我们按一下底板上的BACK,可以看到控制台打印信息,如下图所示: 

 

我们可以看中断发生了几次,我们可以进入到开发板的/proc/irq/43/spurious 目录下,如下图所示:

cat /proc/irq/84/spurious

 

从上图可知,中断发生了13次,我们可以数一下test_key打印的次数是十三次,说明发生一次中断,会打印一次test_key。

57.2.4 优化方案

上面的例子我们是使用函数gpio_to_irq来获取中断号的,接下来我们通过在设备树文件里面使用属性“interrupt-parent”和“interrupts”来获取中断号,我们修改设备树文件如下图所示:

    test: test{compatible = "key";gpios = <&gpioi 2 GPIO_ACTIVE_HIGH>;interrupt-parent = <&gpioi>;interrupts = <2 IRQ_TYPE_EDGE_BOTH>;status = "okay";};

然后编译设备树,编译设备树请参考本手册51.3编译设备树章节。

我们检查一下有没有我们添加的节点,如下图所示,出现添加的设备节点test。

 

接下来我们改一下驱动driver.c,完整代码如下所示:

程序源码在网盘资料iTOP-STM32MP157开发板网盘资料汇总\09_嵌入式Linux开发指南(iTOP-STM32MP157)手册配套资料\驱动程序例程\15-Linux中断实验\002”路径下。

 

/** @Author:topeet* @Description: 使用gpio_to_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/of_irq.h>#include <linux/gpio.h>
#include <linux/of_gpio.h>
#include <linux/interrupt.h>
//定义结构体表示我们的节点
struct device_node *test_device_node;
struct property *test_node_property;
//要申请的中断号
int irq;
// GPIO 编号
int gpio_nu;/*** @description: 中断处理函数test_key* @param {int} irq :要申请的中断号* @param {void} *args :* @return {*}IRQ_HANDLED*/
irqreturn_t test_key(int irq, void *args)
{printk("test_key \n");return IRQ_HANDLED;
}
/***************************************************************************************** @brief led_probe : 与设备信息层(设备树)匹配成功后自动执行此函数,* @param inode : 文件索引* @param file  : 文件* @return 成功返回 0           ****************************************************************************************/
int led_probe(struct platform_device *pdev)
{int ret = 0;// 打印匹配成功进入probe函数printk("led_probe\n");test_device_node = of_find_node_by_path("/test");if (test_device_node == NULL){//查找节点失败则打印信息printk("of_find_node_by_path is error \n");return -1;}gpio_nu = of_get_named_gpio(test_device_node, "gpios", 0);if (gpio_nu < 0){printk("of_get_namd_gpio is error \n");return -1;}//设置GPIO为输入模式gpio_direction_input(gpio_nu);//获取GPIO对应的中断号// irq = gpio_to_irq(gpio_nu);irq =irq_of_parse_and_map(test_device_node,0);printk("irq is %d \n", irq);/*申请中断,irq:中断号名字  test_key:中断处理函数IRQF_TRIGGER_RISING:中断标志,意为上升沿触发"test_key":中断的名字*/ret = request_irq(irq, test_key, IRQF_TRIGGER_RISING, "test_key", NULL);if (ret < 0){printk("request_irq is error \n");return -1;}return 0;
}
int led_remove(struct platform_device *pdev)
{printk("led_remove\n");return 0;
}
const struct platform_device_id led_idtable = {.name = "keys",
};
const struct of_device_id of_match_table_test[] = {{.compatible = "keys"},{},
};
struct platform_driver led_driver = {//3. 在led_driver结构体中完成了led_probe和led_remove.probe = led_probe,.remove = led_remove,.driver = {.owner = THIS_MODULE,.name = "led_test",.of_match_table = of_match_table_test},//4 .id_table的优先级要比driver.name的优先级要高,优先与.id_table进行匹配.id_table = &led_idtable};
/*** @description: 模块初始化函数* @param {*}* @return {*}*/
static int led_driver_init(void)
{//1.我们看驱动文件要从init函数开始看int ret = 0;//2.在init函数里面注册了platform_driverret = platform_driver_register(&led_driver);if (ret < 0){printk("platform_driver_register error \n");}printk("platform_driver_register ok \n");return 0;
}
/*** @description: 模块卸载函数* @param {*}* @return {*}*/
static void led_driver_exit(void)
{free_irq(irq, NULL);platform_driver_unregister(&led_driver);printk("gooodbye! \n");
}
module_init(led_driver_init);
module_exit(led_driver_exit);MODULE_LICENSE("GPL");

编译驱动代码为驱动模块,如下图所示:

编译成功加载驱动,可以成功获得irq号,如下图所示: 

我们按一下底板上的音量BACK,可以看到控制台打印信息,如下图所示: 

我们可以使用以下命令来可以看中断发生了几次,如下图所示:

cat /proc/irq/84/spurious

 

从上图可知,中断发生了8次,我们可以数一下test_key打印的次数是8次,说明发生一次中断,会打印一次test_key。 


http://www.ppmy.cn/devtools/128123.html

相关文章

windows下Qt的安装方法

Qt Creator是个人非常喜欢的一款开发工具&#xff0c;喜欢用其来开发C和CPC平台项目&#xff0c;当然也可以用其来开发Android和Auto平台项目&#xff0c;但其现在采用离线安装&#xff0c;限于网络问题&#xff0c;安装速度非常慢。 现在介绍一种可以完成快速的安装方法。 下…

leetcode hot100【LeetCode 24. 两两交换链表中的节点】java实现

LeetCode 24. 两两交换链表中的节点 题目描述 给定一个链表&#xff0c;两两交换其中相邻的节点&#xff0c;并返回交换后的链表。 你不能只是单纯地改变节点内部的值&#xff0c;而是需要实际的进行节点交换。 示例 1&#xff1a; 输入&#xff1a;head [1,2,3,4] 输出&a…

MQTT远程串口、远程TCP透传工具

工具说明 下载地址&#xff1a;远程串口工具 – ARMFUN V0.1.1 增加mqtt clientid可设置 *以前的clientid是随机数&#xff0c;现在注意防止clientid重复导致无法连接 V0.1: 1&#xff0c;TCP可以选择服务器端模式或者客户端模式&#xff0c;只能选择一个&#xff0c;注意…

QT日志库:log4Qt及Qt自带日志库使用

介绍 Log4Qt是使用Trolltech Qt Framework的Apache Software Foundation Log4j包的C 端口。它旨在供开源和商业Qt项目使用。所以 Log4Qt 是Apache Log4J 的Qt移植版&#xff0c;所以看Log4J的资料应该是最直接有效的(因为 Log4Qt的直接资料太少了)。 Log4Qt主要是用来记录日志(…

[Vue3核心语法] setup语法糖

一、setup 概述 setup是Vue3中一个新的配置项&#xff0c;值是一个函数&#xff0c;它是 Composition API “表演的舞台”&#xff0c;组件中所用到的&#xff1a;数据、方法、计算属性、监视......等等&#xff0c;均配置在setup中。 特点&#xff1a; setup函数返回的对象中…

【C++篇】栈的层叠与队列的流动:在 STL 的节奏中聆听算法的静谧旋律

文章目录 C 栈与队列详解&#xff1a;基础与进阶应用前言第一章&#xff1a;栈的介绍与使用1.1 栈的介绍1.2 栈的使用1.2.1 最小栈1.2.2 示例与输出 1.3 栈的模拟实现 第二章&#xff1a;队列的介绍与使用2.1 队列的介绍2.2 队列的使用2.2.1 示例与输出 2.3 队列的模拟实现2.3.…

C#与Sqlite数据库

1&#xff0c;一般的访问方式。 1.1&#xff0c;连接语句。 //sqlite 连接,支持相对位置&#xff0c;也支持绝对位置 Data Source../../Database/cater.db// 连接数据库&#xff0c;FailIfMissingfalse时若文件不存在会自动创建 string connStr "DataSourcetest.db;Vers…

Pr 视频效果:视频限制器

视频效果/颜色校正/视频限制器 Color Correction/Video Limiter 视频限制器 Video Limiter效果可用于确保视频信号符合广播和电视标准。它可以防止视频的亮度和色彩超出指定的范围&#xff0c;避免在播放时出现过曝、过暗或色彩失真等问题。 ◆ ◆ ◆ 效果选项说明 此效果也称…