在上一节编写完成中断向量表后,这一节就开始配置具体的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
这一个中断确实是嵌入式当中非常核心的知识点,受限于篇幅所以,剩下的内容然后我们在下一节继续开始讲解