IMX6UL开发板的GPIO中断实验(二)

        在上一节编写完成中断向量表后,这一节就开始配置具体的IRQ的中断服务函数

        在嵌入式系统或微控制器的编程中,特别是在启动代码(如启动文件或复位中断服务例程)中,处理全局中断(也称为全局中断使能或中断屏蔽位)的方式对于系统的稳定性和可靠性至关重要
        首先在复位中断函数中,关闭全局中断

	Reset_Handler:



		cpsid i   /*关闭全局中断 */


		MRC p15, 0, r0, c1, c0, 0

		bic	r0 , r0, #(0x1 << 12)	/*关闭Icache*/

		bic r0 , r0 ,#(0x1 << 11)	/*关闭分支预测 */

		bic r0 , r0 ,#(0x1 << 2)	/*关闭Dcache */

		bic r0 , r0 ,#( 1 <<  1)	/*关闭对齐 */

		bic r0 , r0 ,#( 1 <<  0)	/*关闭MMU */

		MCR p15, 0 , r0, c1, c0, 0 

        然后再在进入main函数之前,再次打开全局中断

/* 进入SVC模式 */

	mrs r0, cpsr

	bic r0, r0, #0x1f 	/* 将r0寄存器中的低5位清零,也就是cpsr的M0~M4 	*/

	orr r0, r0, #0x13 	/* r0或上0x13,表示使用SVC模式					*/

	msr cpsr, r0		/* 将r0 的数据写入到cpsr_c中 					*/

	ldr sp,=0X80200000	/* 设置用户模式下的栈首地址为0X80200000,大小为2MB */


	cpsie i


	b main				/* 跳转到main函数 										*/

        首先,需要了解什么是全局中断,,全局中断是指通过控制一个特定的使能位(或者寄存器)来影响系统对所有中断请求的响应能力。当全局中断被关闭时,系系统将不会响应任何中断请求,即使这些请求被我们配置或者使能。然而如果全局中断被打开时,系统能够正常地响应和处理中断请求
        复位中断是系统启动时首先执行的中断服务例程,用于进行系统的初始化。这个时候关闭全局中断,在复位中断服务函数执行期间,系统正在进行关键的初始化操作,如时钟配置、内存初始化等。如果此时允许中断,中断服务例程可能会与初始化代码产生冲突,导致初始化失败或系统状态不一致。
        然后要在程序进入主函数之前重新打开,全局中断函数

        在之后,出发的所有中断都是在IRQ的中断服务函数中执行,所以现在就要开始编写IRQ中断服务函数,

IRQ_Handler:
	push, {lr}  /*保存lr的地址 */

	push, {r0-r3, r12}

        首先lr指令(Link Register,叫做链接寄存器),push在英文当中也是推、压的意思,所以可以理解位将lr的值压入堆栈中。在我们ARM架构中,lr寄存器用于存储函数调用或中断发生前的返回地址。当从中断处理程序中返回时,这个地址会被用来继续执行被中断的代码。保存lr是为了在中断处理完成后,程序能够正确地返回到原来的执行点
        然后下一条,这条指令将多个寄存器的值压入堆栈中,具体是r0到r3以及r12。这些寄存器在ARM架构中常用于存储函数的参数、返回值和局部变量。可能唯一需要注意的就是这里里面ARM寄存器应该是从r0-r15但是这里为什么只存入了r0-r3和r12,因为其余的寄存器都是它自动将其保存在了栈中,而没有保存的,我们在上面才将其用手动的方式保存


        然后这里先读取CPSR的寄存器,然后同样将他的值存入到R0中,SPSR一个特殊的寄存器,用于在发生异常(如中断)时保存当前程序状态寄存器(CPSR, Current Program Status Register)的内容。CPSR 包含了当前处理器的状态信息,如条件码(如N、Z、C、V标志)、中断禁止位、模式控制位等。
        因此,mrs r0, spsr 这条指令的目的是在发生异常处理之前(或之后,具体取决于上下文),将当前保存的处理器状态信息(即之前CPSR的内容)读取到 r0 寄存器中,以便后续可以对其进行检查或修改。
        同样堆栈常用于通常用于保存临时数据、寄存器或者控制程序的执行流程,这里将之前通过MRS的指令从SPSR读取的值压入当前活动的堆栈中在ARM中通常与堆栈指针(SP, Stack Pointer)寄存器相关联。在执行 push {r0} 指令时,SP寄存器的值会自动减少(向下增长),以在堆栈上为新数据腾出空间,然后 r0 寄存器的值被写入到这个新分配的堆栈位置。
        所以新增的两端代码则表示,将从SPSR寄存器中读取的处理器状态信息保存到 r0 寄存器中,然后将这个信息压入堆栈。用于保存异常发生前的处理器状态,以便在异常处理完成后能够恢复执行。

IRQ_Handler:

	
	push {lr}  /*保存lr的地址 */

	push {r0-r3, r12}

	mrs  r0, spsr

	push {r0}

        接下来我们要读取下面的,CP15的CBAR寄存器,还是需要了解什么是CBAR,点击下面的蓝色链接跳转

        CBAR寄存器(Coprocessor Barrier Address Register),它是协处理器(Coprocessor)的一个特殊寄存器,用于存储中断控制器(GIC,Generic Interrupt Controller)寄存器组的物理地址(或基地址)

        GIC寄存器组偏移0x1000-0x10FFF是分发器,0x2000-0x3FFF对应GIC组中的CPU接口端,知道了这些那有什么作用呢,意味着我们就可以访问GIC控制器了
        其实总结回来就是,CPU需要通过CBAR寄存器来获取GIC(或类似的中断控制器)的基地址。这个基地址是访问GIC寄存器组所有其他寄存器(如分发器、CPU接口端等)的起点,在进行中断处理时,CPU会首先从CBAR寄存器中读取GIC的基地址。
        然后,CPU会根据这个基地址加上适当的偏移量来访问GIC寄存器组中的其他寄存器,目前大概流程

1.  就是当一个中断发生时,CPU会跳转到中断向量表,并执行相应的中断服务例程(ISR)
2.  在ISR中,CPU首先会从CBAR寄存器中读取GIC的基地址。
3.  接着,CPU会从GICC_IAR寄存器中读取中断ID,以确定是哪个中断源触发了中断。
4.  处理完中断后,CPU会将中断ID写入GICC_EOIR寄存器,以标记该中断已被处理

        将CP15的C0的值加载到r1寄存器当中

	mrc p15, 4 ,r1 ,c15, ,c0 , 0

	add r1, r1, #0x2000

	ldr r0, [r1 ,#0XC]

        博主也是从头开始学的,刚开始到这个地方非常混乱,其实现在理解了,首先写入立即数0x2000,相当于就是如上图0X2000-0X3FFF在这个范围中的都是与CPU interface也就是接口控制器有关的寄存器,然后下图的CPU Interface register summary,中文名字也就是叫做,CPU接口寄存器汇总
        这里我们需要使用的是GICC_IAR寄存器,那么只需要在原本开始的0X2000的基础上完成一定的地址偏移,就可以访问到此寄存器,上面的代码的意思就是0x2000的基础上偏移0xC到GICC_IAR

        那么现在我们就需要理解为什么需要使用GICC_IAR这个寄存器,可以看一下他下面的purpose也就是目的,作用,翻译为  处理器读取这个寄存器来获得被信号中断的中断ID。这
Read作为中断的确认。

        相当于除了可以读取,中断ID以外,还可以读取CPU对于多核的系统来说,但是因为iwom的开发板仍然是单核的,但其实不管怎样,最重要的还是目前的中断ID,按照上图的0-9位可以读取中断ID,就可以知道知道触发了IRQ的具体是哪一个中断,,那么在之后我们也可以根据这个中断ID,去执行对应的中断处理函数
        我们读取中断ID的目的,就是为了得到对应的中断处理函数,所以我们选择的还是此寄存器

        那么最后将r0和r1的值压入堆栈,其实最后就相当于,mrc p15, 4 ,r1 ,c15, ,c0 , 0对应CBAR寄存器用于存储基地址,也就是最后CPU接口的基地址就被存储在这个寄存器,也就是最后的r0,然后在r0的基础上偏移0xC,这个值被存入了r1,最后通r1的值可以访问CPU寄存器中的GICC_IAR

	push {lr}  /*保存lr的地址 */

	push {r0-r3, r12}


	mrs  r0, spsr

	push {r0}


	mrc p15, 4 ,r1 ,c15, ,c0 , 0


	add r1, r1, #0x2000

	ldr r0, [r1 ,#0XC]


	push {r0,r1}

        在ARM,架构中用于改变当前处理器的状态,特别是与中断和异常相关的状态,相当于这里就直接将模式切换为,SVC模式
        CPS指令用于改变当前处理器的状态,SVC对应的#0x13

	add r1, r1, #0x2000

	ldr r0, [r1 ,#0XC]

	push {r0,r1}

	cps #0x13

        然后使用push{lr}重新保存或者备份lr寄存器的值,这个原因也是非常的长哈,我这里引用一下别人的帖子,

        arm 7中模式下除user与sys模式外,其余5中模式各自有各自的单独的sp(r13),lr(r14),spsr。上电启动后,系统自动进入svc模式,此时设置sp栈是设置在svc模式下的栈。

        当发生中断进入irq模式后,由于irq模式有单独的sp,故可以直接用ldr sp, #XXX设置irq模式下的栈,此栈不会与svc模式下设置的栈混淆,而在irq模式下进行的各种函数调用等需要

        使用到栈的情形下,都是使用之前设置好的栈空间,因此当从svc模式进入irq模式下保护现场时,不存在需要对sp进行保护的说法。但需要对lr值进行保存备份。因为在进入irq后,irq下的lr寄存器保存的是svc模式下当时PC的值,即被中断的指令的下一条指令的地址+4(arm三级流水线时),lr中保存的该地址可用于中断程序处理完后,返回到被中断的指令处继续运行(sub lr, lr , #4, 经过此指令执行后lr中保存的是断点的下一条指令的地址)。而在irq模式下可能存在函数调用等情况,此时函数调用返回也需要lr寄存器来保存函数返回的地址,因此,lr之前保存svc下被中断的指令的下一条指令地址可能在irq模式下覆盖(irq模式下sp不存在被覆盖的情况),因此在现场保护程序中需要保存lr的值,可通过stm sp!, {r0~r12, lr}将r0一直到r12以及lr压进栈中保存,防止irq模式下有函数调用需要保存断点返回记录时,lr被覆盖,丢失中断返回的断点地址。

        system_irqhandler是我们之后会定义的C语言中断处理函数,目前我们将函数加载到地址r2,因为在前面r1和r0都被我们使用用来存储其他值了
        刚开始的时候我并不是很理解这种写法,我以为是直接将函数储存在了寄存器中,后面查了资料发现,在ARM架构中,函数本身并不是直接存储在寄存器中的。寄存器(如R2)通常用于存储地址(即内存中的位置)或较小的数据值。当你看到类似ldr r2, =system_irqhandler的指令时,这实际上是在告诉汇编器将system_irqhandler这个标签(或函数名)对应的地址加载到R2寄存器中。这里的=符号是汇编器指令的一部分,用于指示这是一个立即数加载,但实际上是加载一个地址(即函数的入口点)
           BLX R2 指令的作用就是基于 R2 寄存器中存储的地址执行跳转。具体来说,它会将 R2 寄存器中的值(即 system_irqhandler 函数的地址)加载到程序计数器(PC)中,从而改变程序的控制流,使其开始执行 system_irqhandler 函数中的代码。

	push{lr}

	ldr r2, =system_irqhandler

	blx r2

        在C语言中断处理函数执行完毕后,通过POP指令将之前保存的SVC模式的LR值从堆栈中弹出,并存储回LR寄存器。这是为了恢复SVC模式的调用上下文,以便能够正确地返回到SVC模式中的下一个指令。在SVC模式下完成了C语言中断处理函数的调用,然后在执行完成过后,就需要返回IRQ模式
         使用POP是从堆栈中弹出之前保存的其他上下文信息,也就是保存现场,在ARM架构中,不同的处理器模式(如用户模式、系统模式、SVC模式、IRQ模式等)有不同的权限和寄存器集。当从一种模式切换到另一种模式时,当前模式的上下文(包括寄存器值、程序计数器、程序状态寄存器等)需要被保存,以便在返回时能够恢复到之前的状态。这是为了保护当前模式的执行环境不被破坏。特别是在中断处理中,当IRQ(或FIQ)中断发生时,处理器会自动切换到IRQ(或FIQ)模式,并保存当前模式的上下文到堆栈中。这是为了确保中断服务例程(ISR)能够在一个干净、一致的环境中执行,而不会受到被中断代码的影响。当中断服务例程执行完毕后,它会恢复之前保存的上下文,并返回到被中断的代码处继续执行。   

	push{lr}

	ldr r2, =system_irqhandler

	blx r2



	pop {lr}

	cps #0x12

	pop {r0, r1}

	str r0, [r1, #0x10]

        处理完成具体的中断函数后,需要将对应的中断ID值写入到一个名字叫做GICC_EOIR的寄存器里面,大意就是当处理完GICC后得到了某个中断ID后,必须在寄存器里面写入一次

        这里的0-9位正好就对应的是,前面GICC_IAR写入的寄存器的中断ID号,所以到时候清楚中断ID号也是在这里相对应的
        GICC_EOIR 寄存器写入刚刚处理完成的中断号,当一个中断处理完成以后必
须向 GICC_EOIR 寄存器写入其中断号表示中断处理完成。

        然后下一步就是回复IRQ的现场,第一段将r0从栈中弹出,然后恢复异常处理前的处理器状态。也就是恢复spsr,然后这条指令从栈中连续弹出多个值,分别恢复到R0、R1、R2、R3和R12寄存器中。这通常用于恢复在异常处理之前保存在栈中的一组通用寄存器的值。在ARM架构中,异常处理通常需要保存一些寄存器的值,以便在异常处理完成后能够恢复到异常发生前的执行状态。最后,这条指令从栈中弹出一个值到LR(Link Register)寄存器中。LR寄存器通常用于存储子程序返回地址或异常返回地址。在异常处理中,异常发生时的返回地址会被保存在栈中,并在异常处理完成后使用这条指令恢复,以便能够返回到异常发生前的执行点继续执行。

	pop {r0}
	msr spsr_cxsf, r0

	pop {r0-r3, r12}

	pop {lr}

        在中断处理结束过后,后就要重新返回到曾经被中断打断的地方运行,这里为什么要将
lr-4 然后赋给 pc 呢?而不是直接将 lr 赋值给 pc?ARM 的指令是三级流水线:取指、译指、执
行,pc 指向的是正在取值的地址,这就是很多书上说的 pc=当前执行指令地址+8。比如下面代
码示例:

0X2000 MOV R1, R0 ;执行
0X2004 MOV R2, R3 ;译指
0X2008 MOV R4, R5 ;取值 PC

        上面示例代码中,左侧一列是地址,中间是指令,最右边是流水线。当前正在执行 0X2000
地址处的指令“MOV R1, R0”,但是 PC 里面已经保存了 0X2008 地址处的指令“MOV R4, R5”。
假设此时发生了中断,中断发生的时候保存在 lr 中的是 pc 的值,也就是地址 0X2008。当中断
处理完成以后肯定需要回到被中断点接着执行,如果直接跳转到 lr 里面保存的地址处(0X2008)
开始运行,那么就有一个指令没有执行,那就是地址 0X2004 处的指令“MOV R2, R3”,显然这
是一个很严重的错误!所以就需要将 lr-4 赋值给 pc,也就是 pc=0X2004,从指令“MOV R2,
R3”开始执行
        所以在代码中如下编写

	pop {r0-r3, r12}

	pop {lr}

	subs pc, lr ,#4

        编写完.S的启动文件后,现在就开始准备开始编写具体的中断回调函数了,首先现在文件夹下方添加与中断相关的.C和.H文件,

        然后我们需要移植一个库文件文件名字叫做core_ca7.h,这个文件是移植好了的,所以只需要把他丢到imx6ul的文件夹中就可以了

        然后在imx6ul下的imx6ul.h头文件中引用这个文件,

#ifndef __IMX6UL_H
#define __IMX6UL_H

#include "cc.h"
#include "fsl_common.h"
#include "fsl_iomuxc.h"
#include "MCIMX6Y2.h"
#include "core_ca7.h"


#endif

        然后去编写中断驱动头文件和.C文件

// init.c文件

#include "int.h"

void int_init(void)
{
    GIC_Init();

    __set_VBAR(0X87800000);
}
//init.h文件

#ifndef _BSP_INT_H
#define _BSP_INT_H

#include "imx6ul.h"

void int_init(void);

typedef void(*system_irq_handler_t)(unsigned int giccIar, void *param); /*中断处理函数形式*/

typedef struct _sys_irq_handler
{
    system_irq_handler_t irqHandler;    /*中断处理函数*/
    void *userParam;                    /*中断处理函数的参数*/
}sys_irq_handle_t;

#endif                                                                                        

        首先,先分析一下.c文件,在我们的移植的文件当中有,已经对GIC有了初始化,所以不需要我们再去进行复杂的初始化,只需要调用它的函数就可以了,这也是为什么要移植这个库的原因,
如下图就是它的初始化函数,然后.c里面的第二段代码是如果没有在启动文件中设置中断向量偏移,那么就在这里重新设置一下中断向量表的偏移,偏移到起始地址,也是调用了库中的一个函数

/* 

 * GIC初始化

 * 为了简单使用GIC的group0

 */

FORCEDINLINE __STATIC_INLINE void GIC_Init(void)

{

  uint32_t i;

  uint32_t irqRegs;

  GIC_Type *gic = (GIC_Type *)(__get_CBAR() & 0xFFFF0000UL);



  irqRegs = (gic->D_TYPER & 0x1FUL) + 1;



  /* On POR, all SPI is in group 0, level-sensitive and using 1-N model */



  /* Disable all PPI, SGI and SPI */

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

    gic->D_ICENABLER[i] = 0xFFFFFFFFUL;



  /* Make all interrupts have higher priority */

  gic->C_PMR = (0xFFUL << (8 - __GIC_PRIO_BITS)) & 0xFFUL;



  /* No subpriority, all priority level allows preemption */

  gic->C_BPR = 7 - __GIC_PRIO_BITS;



  /* Enable group0 distribution */

  gic->D_CTLR = 1UL;



  /* Enable group0 signaling */

  gic->C_CTLR = 1UL;

}

         然后就是.h文件,首先先申明一下int_init()这个初始化函数,然后定义了一个函数指针类型和一个结构体,首先typedef void(*system_irq_handler_t)(unsigned int giccIar, void *param);定义了一个函数指针类型的新名称,即是system_irq_handler_t,函数指针的主要用途是存储函数的地址,并通过这个指针来间接调用那个函数,该函数接受两个参数返回void
        然后再下方构建了一个结构体,结构体当中包含了两个成员,irqHandler这是一个函数指针,类型为 system_irq_handler_t,它是一个函数指针类型,指向上面的中断处理函数,userParam,这是一个 void* 类型的成员,可以存储任何类型的数据的地址,用作中断处理函数的参数。
        括号后买你的sys_irq_handle_t是一个类型的别名,通过typedef定义的,它将结构体类型 struct _sys_irq_handle 命名为 sys_irq_handle_t。通过这种方式,你可以用更简单的名字来引用该结构体,而不必每次都写出完整的 struct _sys_irq_handle
        也就是sys_irq_handle_t 是通过 typedef 定义的别名,等价于 struct _sys_irq_handle。这意味着在后续代码中,可以使用 sys_irq_handle_t 来声明结构体变量,而不必写 struct _sys_irq_handle

static sys_irq_handle_t irqTable[NUMBER_OF_INT_VECTORS];

//*中断初始化函数*/
void int_init()
{
    GIC_Init();

    __set_VBAR(0x87800000);

}

        然后接下来,在int.c使用数组创建一个中断服务函数表,如上图,其中的NUMBER_OF_INT_VECTORS,是core_ca7.h文件当中已经定义好的160个中断号.
        static这个关键字表示irqTable数组的作用域仅限于定义它的文件内,不会被其他文件访问。这有助于封装和保护中断服务例程表,防止外部代码意外修改。
     sys_irq_handle_t ,  这个类型定义确保了irqTable数组中的每个元素都是一个指向中断服务例程的函数指针。
    irqTable[],这是中断服务例程表的名称,它是一个数组,用于存储指向各个中断服务例程的函数指针。每个数组元素对应一个特定的中断向量,即一个特定的中断类型。
    当中断发生时,CPU会查找中断号(或中断向量号),然后在中断服务例程表中找到对应的函数指针,并跳转到该函数执行中断服务例程。

void system_irqtable_init(void)
{
    unsigned int i = 0;

    for( i = 0; i < NUMBER_OF_INT_VECTORS; i++)
    {
        irqTable[i].irqHandler = default_irqhandler;
        irqTable[i].userParam  = NULL;
    }
}

        刚开始中断服务表中没有数据,是空的。所以我们需要对其里面的每一个,首先一共有160个这个中断信号,每个中断信号都需要一个中断服务函数,所以创建一个for循环,能够让每个都可以被初始化,然后再编写这个中断信号要执行的中断服务函数,

/*默认中断服务函数*/
void default_irqhandler(unsigned int giccIar, void *userParam)
{
    while(1)
    {

    }
}

         irqTable[i].irqHandler = default_irqhandler;这行代码将 irqTable 数组中索引为 i 的元素的 irqHandler 成员设置为 default_irqhandler 函数的地址。这意味着,如果某个中断发生时没有为它指定特定的中断服务例程,那么系统将调用 default_irqhandler 来处理该中断。
        irqTable[i].userParam  = NULL;这行代码将 irqTable 数组中索引为 i 的元素的 userParam 成员设置为 NULL。这表示默认情况下,没有用户定义的参数与中断服务例程相关联。如果 default_irqhandler 或其他中断服务例程需要使用用户参数,它们需要检查这个参数是否为 NULL,并相应地处理它。

void system_register_irqhandler(IRQn_Type irq, system_irq_handler_t handler, void *userParam)
{
    irqTable[irq].irqHandler = handler;
    irqTable[irq].userParam =  userParam;
}

        我们每次要开启一个中断,那就必须注册一个中断函数,通过中断号 irq 作为索引,找到 irqTable 数组中对应的元素。irqTable 是一个全局或静态的数组,其每个元素都包含了一个指向中断服务例程的函数指针(irqHandler)和一个指向用户参数的指针(userParam
        注册中断服务例程,将传入的中断服务例程 handler 赋值给 irqTable[irq].irqHandler。这样,当该中断号对应的中断发生时,系统就知道应该调用哪个函数来处理这个中断了。
        将传入的用户参数 userParam 赋值给 irqTable[irq].userParam。这个用户参数可以在中断服务例程中被访问和使用,以提供额外的上下文信息或数据给中断服务例程。

void system_irqhandler(unsigned int giccIar)
{
    uint32_t intNum = giccIar & 0x3ff;
    
    /*检查中断ID是否正常*/
    if( (intNum == 1023) || (intNum >= NUMBER_OF_INT_VECTORS))
    {
        return;
    }
    /*根据中断ID号,读取中断处理函数执行*/
    irqTable[intNum].irqHandler(intNum, irqTable[intNum].userParam);
}

        system_irqhandler,这是中断处理函数的名称,当GIC识别到一个中断并准备好将其分发给CPU时,该函数会被调用。unsigned int giccIar,这是GIC中断接受寄存器(Interrupt Acknowledge Register, IAR)的当前值。GIC使用该寄存器来通知CPU哪个中断被触发。
        第二段,这行代码通过位与操作(&)提取了giccIar的低10位(即0x3ff,二进制为0000 0011 1111 1111),这些位包含了中断编号(Interrupt Number)。GIC使用这些位来唯一标识一个中断。
        if判断的话,这段代码检查中断编号intNum是否有效。1023(二进制为1111 1111 11)是一个特殊的值,可能表示无效或保留的中断编号。NUMBER_OF_INT_VECTORS是一个宏或常量,定义了系统中可用的中断向量的总数。如果intNum等于1023或大于或等于NUMBER_OF_INT_VECTORS,这意味着中断编号无效,函数将直接返回,不执行任何中断处理。
        如果不满足if的判断条件,那么则执行下面的处理函数irqTable[intNum].irqHandler是对应中断编号intNum的中断处理函数的指针。intNum作为第一个参数传递给中断处理函数,以便处理函数知道是哪个中断被触发。irqTable[intNum].userParam是第二个参数,它允许每个中断处理函数有一个自定义的参数。

        

#include "int.h"

static unsigned int irqNesting;

/*中断服务函数表*/
static sys_irq_handle_t irqTable[NUMBER_OF_INT_VECTORS];

//*中断初始化函数*/
void int_init()
{
    GIC_Init();
    system_irqtable_init();
    __set_VBAR(0x87800000);

}

/*初始化中断服务函数表*/
void system_irqtable_init(void)
{
    unsigned int i = 0;
    irqNesting = 0; 

    for( i = 0; i < NUMBER_OF_INT_VECTORS; i++)
    {
        irqTable[i].irqHandler = default_irqhandler;
        irqTable[i].userParam  = NULL;
    }
}

/*指定中断号注册中断服务函数*/
void system_register_irqhandler(IRQn_Type irq, system_irq_handler_t handler, void *userParam)
{
    irqTable[irq].irqHandler = handler;
    irqTable[irq].userParam =  userParam;
}

void system_irqhandler(unsigned int giccIar)
{
    uint32_t intNum = giccIar & 0x3ff;
    
    /*检查中断ID是否正常*/
    if( (intNum == 1023) || (intNum >= NUMBER_OF_INT_VECTORS))
    {
        return;
    }
    static unsigned int irqNesting ++;

    /*根据中断ID号,读取中断处理函数执行*/
    irqTable[intNum].irqHandler(intNum, irqTable[intNum].userParam);

    static unsigned int irqNesting --;
}

/*默认中断服务函数*/
void default_irqhandler(unsigned int giccIar, void *userParam)
{
    while(1)
    {

    }
}





        总体int.c的文件如下,然后在最前面加入了一个变量static unsigned int irqNesting;我们将他放在了检查中断ID的下面,这个作用是关于中断向量的嵌套,在执行前加1,然后在执行完成后减1,然后将我们所编写的函数在.h文件当中申明一下,总的init.h如下

#ifndef _INT_H
#define _INT_H
#include "imx6ul.h"

/*中断服务函数形式*/
typedef void (*system_irq_handler_t) (unsigned int giccIar, void *param);

 
/* 中断服务函数结构体*/
typedef struct _sys_irq_handle
{
    system_irq_handler_t irqHandler; /* 中断服务函数 */
    void *userParam;                 /* 中断服务函数参数 */
} sys_irq_handle_t;

void int_init(void);
void system_irqtable_init(void);
void system_register_irqhandler(IRQn_Type irq, system_irq_handler_t handler, void *userParam);
void system_irqhandler(unsigned int giccIar);
void default_irqhandler(unsigned int giccIar, void *userParam); 

#endif 

        这一个中断确实是嵌入式当中非常核心的知识点,受限于篇幅所以,剩下的内容然后我们在下一节继续开始讲解

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值