【ARM中断系统】Cortex-A和Cortex-M中断系统异同

1.Cortex-M中断系统

以stm32中断系统为例,有以下几个关键点:

  • 中断向量表
  • NVIC(内嵌向量中断控制器)
  • 中断使能
  • 中断服务函数

1.1中断向量表

中断向量表是一个表,这个表里面存放的是中断向量。中断服务程序的入口地址或存放中断服务程序的首地址称为中断向量,因此中断向量表示一系列中断服务程序入口地址组成的表。 这些中断服务程序(函数)在中断向量表中的位置是由半导体厂商定好的,当某个中断被触发以后就会自动跳转到中断向量表中对应的中断服务程序(函数)入口地址处。

STM32-F3的中断向量如下表:

__Vectors   DCD __initial_sp ; Top of Stack,存放栈顶指针
            DCD Reset_Handler ; Reset Handler
            DCD NMI_Handler ; NMI Handler
            DCD HardFault_Handler ; Hard Fault Handler
            DCD MemManage_Handler ; MPU Fault Handler
            DCD BusFault_Handler ; Bus Fault Handler
            DCD UsageFault_Handler ; Usage Fault Handler
            DCD 0 ; Reserved
            DCD 0 ; Reserved
            DCD 0 ; Reserved
            DCD 0 ; Reserved
            DCD SVC_Handler ; SVCall Handler
            DCD DebugMon_Handler ; Debug Monitor Handler
            DCD 0 ; Reserved
            DCD PendSV_Handler ; PendSV Handler
            DCD SysTick_Handler ; SysTick Handler


            ; External Interrupts
            DCD WWDG_IRQHandler ; Window Watchdog
            DCD PVD_IRQHandler ; PVD through EXTI Line detect
            DCD TAMPER_IRQHandler ; Tamper
            DCD RTC_IRQHandler ; RTC
            DCD FLASH_IRQHandler ; Flash


            /* 省略掉其它代码 */
            DCD DMA2_Channel4_5_IRQHandler ; DMA2 Channel4 & l5
        
__Vectors_End

中断向量表都是连接到代码的最前面,比如一般ARM处理器都是从地址0X00000000开始执行指令的。 但是我们学习 STM32 的时候

代码是下载到 0X8000000 开始的存储区域中。因此中断向量表是存放到 0X8000000 地址处的,而不是 0X00000000, 为了解决这个问题, Cortex-M 架构引入了一个新的概念——中断向量表偏移,通过中断向量表偏移就可以将中断向量表存放到任意地址处,中断向量表偏移配置在函数 SystemInit 中完成,通过向 SCB_VTOR 寄存器写入新的中断向量表首地址即可,代码如下:

#define FLASH_BASE      ((uint32_t)0x08000000)#define VECT_TAB_OFFSET 0x0
void SystemInit (void)
{
    RCC->CR |= (uint32_t)0x00000001;

    /*省略其它代码 */

    ifdef VECT_TAB_SRAM
        SCB->VTOR = SRAM_BASE | VECT_TAB_OFFSET;     // 设置中断向量表偏移,将中断向量表设置到RAM中
    #else
        SCB->VTOR = FLASH_BASE | VECT_TAB_OFFSET;    // 将中断向量表设置到ROM中,就是地址0x08000000处
    #endif
}

1.2 NVIC(内嵌向量中断控制器)

Cortex-M内核的单片机的中断系统管理机构叫做NVIC,全称Nested Vectored Interrupt Controller。

1.3 中断使能

要是用某个外设的中断,需要先使能这个外设的中断,以PE2的IO为例:

NVIC_InitStructure.NVIC_IRQChannel = EXTI2_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x02; //抢占优先级 2,
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x02;        //子优先级 2
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;              //使能外部中断通道
NVIC_Init(&NVIC_InitStructure);

上述代码使能PE2对应的EXTI2中断。

1.4 中断服务函数

当中断发生以后中断服务函数就会被调用,以PE2的中断服务函数为例:

/* 外部中断 2 服务程序 */
void EXTI2_IRQHandler(void)
{
    /* 中断处理代码 */
}

当PE2引脚中断触发后,就会调用其对应的中断处理函数EXTI2_IRQHandler。

2.Cortex-A中断系统

2.1 Cortex-A7中断系统简介

CortexA以A7为例,Cortex-A7也有中断向量表,中断向量表也是在代码最前面。Cortex-A7内核有8个异常中断,如下表:

向量地址 中断类型 中断模式
0x00 复位中断(Reset) 特权模式(SVC)
0x04 未定义指令中断(Undefined Instruction) 未定义指令终止模式(Undef)
0x08 软中断(Software Interrupt,SWI) 特权模式(SVC)
0x0C 指令预取中止中断(Prefetch,Abort) 中止模式
0X10 数据访问中止中断(Data Abort) 中止模式
0X14 未使用(Not Used) 未使用
0X18 IRQ中断(IRQ Interrupt) 外部中断模式(IRQ)
0X1C FIQ中断(FIQ Interruot) 快速中断模式(FIQ)

1.复位中断(Reset):CPU复位以后就会进入复位中断,可以在复位中断服务函数里面做一些初始化工作,比如初始化SP指针、DDR等等。
2.未定义指令中断:如果指令不能识别的话就会产生此中断
3.软中断:由SWI指令引起的中断,Linux的系统调用会用SWI指令来引起软中断,通过软中断来陷入到内核空间
4.指令预取中止中断(Perfetch Abort):预取指令出错的时候会产生此中断
5.数据访问中止中断:访问数据出错的时候会产生此中断
6.IRQ中断:外部中断
7.FIQ中断:快速中断,如需快速处理的中断就可以使用此中断

中断向量表里面都是中断服务函数的入口地址,但是从上表中可以看出相比Cortex-M的中断向量少了很多,这个就是A架构和M架构的区别,对于Cortex-M来说,中断向量表可以列举出一款芯片所有的中断向量,包括芯片外设的所有中断。但是对于CortexA来说没有这么做,在上表中有一个IRQ中断,内核中CPU的所有外部中断都属于这个IRQ中断,当任意一个外部中断发生的时候都会触发IRQ中断。 在 IRQ 中断服务函数里面就可以读取指定的寄存器来判断发生的具体是什么中断,进而根据具体的中断做出相应的处理。外部中断和IRQ中断的关系如下图:

image

中断向量表模板:

.global _start                  /* 全局标号 */

_start:
    ldr pc,=Reset_Handler       /* 复位中断 */
    ldr pc,=Undfined_Handler    /* 未定义指令中断 */
    ldr pc,=SVC_Handler         /* SVC中断 */
    ldr pc,=PrefAbort_Handler   /* 预取终止中断*/
    ldr pc,=DataAbort_Handler   /* 数据终止中断*/
    ldr pc,=NotUsed_Handler     /* 未使用中断*/
    ldr pc,IRQ_Handler          /* IRQ中断*/
    ldr pc,FIQ_Handler          /* FIQ(快速中断)未定义中断*/

/* 复位中断 */
Reset_Handler:
    /*复位中断具体处理过程*/

/* 未定义指令中断 */
Undfined_Handler:
    ldr r0,=Undfined_Handler
    bx r0

/* SVC中断 */
SVC_Handler:
    ldr r0,=SVC_Handler
    bx r0

/* 预取终止中断 */
PrefAbort_Handler:
    ldr r0,=PrefAbort_Handler
    bx r0

/* 数据终止中断 */
DataAbort_Handler:
    ldr r0,=DataAbort_Handler
    bx r0

/* 数据终止中断 */
NotUsed_Handler:
    ldr r0,=NotUsed_Handler
    bx r0

/* IRQ中断 重点!*/
NotUsed_Handler:
    /*IRQ中断具体处理过程*/

/* FIQ中断 重点!*/
FIQ_Handler:
    ldr r0,=FIQ_Handler
    bx r0

_start是中断向量表,后面对应的都是中断服务函数,中断服务函数中需要编写的只有复位函数Reset_Handler和IRQ中断服务函数IRQ_Handler。

2.2 GIC控制器简介

2.2.1 GIC控制器总览

STM32(Cortex-M)的中断控制器叫做 NVIC, I.MX6U(Cortex-A)的中断控制器叫做 GIC。

GIC 是 ARM 公司给 Cortex-A/R 内核提供的一个中断控制器,类似 Cortex-M 内核中的NVIC。目前 GIC 有 4 个版本:V1~V4, V1 是最老的版本,已经被废弃了。 V2~V4 目前正在大量的使用。 GIC V2 是给 ARMv7-A 架构使用的,比如 Cortex-A7、 Cortex-A9、 Cortex-A15 等,V3 和 V4 是给 ARMv8-A/R 架构使用的,也就是 64 位芯片使用的。

ARM 针对 GIC V2 就开发出了 GIC400 这个中断控制器 IP 核。当 GIC 接收到外部中断信号以后就会报给 ARM 内核,但是ARM 内核只提供了四个信号给 GIC 来汇报中断情况: VFIQ、 VIRQ、 FIQ 和 IRQ,他们之间的关系如图:

image

VFIQ:虚拟快速FIQ(虚拟化)
VIRQ:虚拟外部IRQ (虚拟化)
FIQ:快速中断FIQ
IRQ:外部中断IRQ

GICV2 的逻辑图如图:

image

上图中左侧部分是中断源,中间部分是GIC控制器,最右侧就是中断控制器向处理器内核发送中断消息。重点看GIC部分:

1.SPI(Shared Peripheral Interrupt),共享中断,表示所有core的共享中断,所有的外部中断都属于SPI中断。比如按键中断、串口中断等等,这些中断所有的Core都可以处理,不限定也定Core。

2.PPI(Private Peripheral Interrupt),私有中断,GIC是支持多核心的,每个核心都有自己独有的中断。这些独有中断肯定要指定的核心处理,所有这些工作就叫私有中断。

3.SGI(Software-generated Interrupt),软件中断,由软件触发引起的中断,通过向寄存器GICD_SGIR写入数据来触发,系统会使用SGI中断完成多核之间的通信

2.2.2 中断ID

中断源有很多,为了区分这些不同的中断源肯定要给他们分配一个唯一 ID,这些 ID 就是中断 ID。每一个 CPU 最多支持 1020 个中断 ID,中断 ID 号为 ID0~ID1019。这 1020 个 ID 包含了 PPI、 SPI 和 SGI,那么这三类中断是如何分配这 1020 个中断 ID 的呢?这 1020 个 ID 分配如下:

ID0~ID15:这 16 个 ID 分配给 SGI。
ID16~ID31:这 16 个 ID 分配给 PPI。
ID32~ID1019:这 988 个 ID 分配给 SPI,像 GPIO 中断、串口中断等这些外部中断 ,至于具体到某个 ID 对应哪个中断那就由半导体厂商根据实际情况去定义了。

2.2.3 GIC逻辑分块

GIC 架构分为了两个逻辑块: Distributor 和 CPU Interface,也就是分发器端和 CPU 接口端。这两个逻辑块的含义如下:

Distributor(分发器端):此逻辑块负责处理各个中断事件的分发问题,也就是中断事件应该发送到哪个CPU Interface上去。分发器收集所有的中断源,可以控制每个中断的优先级,它总是将优先级最高的中断事件发送到CPU接口端。分发器端要做的主要工作如下:

1.全局中断使能控制
2.控制每一个中断的使能或者关闭
3.设置每个中断的优先级
4.设置每个中断的目标处理器列表
5.设置每个外部中断的触发模式:电平触发或边沿触发
6.设置每个中断属于组0还是组1

CPU Interface(CPU接口端):CPU接口端和CPU Core端相连接,因此每个CPU Core都可以在GIC中找到一个与之对应的CPU Interface。CPU接口端就是分发器和CPU Core之间的桥梁,CPU接口单主要工作:

1.使能或者关闭发送到CPU Core的中断请求信号
2.应答中断
3.通知中断处理完成
4.设置优先级掩码,通过掩码来设置那些中断不需要上报给CPU Core
5.定义抢占策略
6.当多个中断到来的时候,选择优先级最高的中断通知给CPU Core

一般访问GIC的分发端和CPU接口端的相关寄存器,是通过访问相对于GIC基地址+偏移地址的方式来访问,在GIC的结构体中会标明每一个寄存器的偏移地址,而GIC的基地址则存在CP15协处理器中。

2.2.4 CP15协处理器

CP15 协处理器一般用于存储系统管理, 但是在中断中也会使用到, CP15 协处理器一共有16 个 32 位寄存器。 CP15 协处理器的访问通过如下另个指令完成:

MRC:将CP15协处理器中的寄存器数据读到ARM寄存器中
MCR:将ARM寄存器的数据写入到CP15协处理器寄存器中

MRC 就是读 CP15 寄存器, MCR 就是写 CP15 寄存器, MCR 指令格式如下:

MCR {cond} p15, <opc1>, <Rt>, <CRn>, <CRm>, <opc2>

cond:指令执行的条件码,如果忽略的话就表示无条件执行。
opc1:协处理器要执行的操作码。
Rt: ARM 源寄存器,要写入到 CP15 寄存器的数据就保存在此寄存器中。
CRn: CP15 协处理器的目标寄存器。
CRm: 协处理器中附加的目标寄存器或者源操作数寄存器,如果不需要附加信息就将CRm 设置为 C0,否则结果不可预测。
opc2: 可选的协处理器特定操作码,当不需要的时候要设置为 0。

MRC 的指令格式和 MCR 一样,只不过在 MRC 指令中 Rt 就是目标寄存器,也就是从CP15 指定寄存器读出来的数据会保存在 Rt 中。而 CRn 就是源寄存器,也就是要读取的写处理器寄存器。

假如我们要将 CP15 中 C0 寄存器的值读取到 R0 寄存器中,那么就可以使用如下命令:

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

2.2.5 中断使能

中断使能包括两部分,一个是FIQ或者IRQ总中断使能,另一个就是ID0~ID1019这1020个中断源的使能

1.IRQ和FIQ总中断使能

IRQ和FIQ分别是外部中断和快速中断的总开关,寄存器CPSR的I=1禁止IRQ,当I=0使能IRQ;F=1禁止FIQ,F=0使能FIQ,以及以下开关中断指令:

指令 描述
cpsid i 禁止IRQ中断
cpsie i 使能IRQ中断
cpsied f 禁止FIQ中断
cpsie f 使能FIQ中断

2.ID0~ID1019中断使能和禁止

GIC寄存器GICD_ISENABLERn和GICD_ICENABLERn用来完成外部中断的使能和禁止,对于Cortex-A7内核来说中断ID只使用512个。 一个 bit 控制一个中断 ID 的使能,那么就需要 512/32=16 个 GICD_ISENABLER 寄存器来完成中断的使能。同理,也需要 16 个GICD_ICENABLER 寄存器来完成中断的禁止。其中 GICD_ISENABLER0 的 bit[15:0]对应ID15~0 的 SGI 中断, GICD_ISENABLER0 的 bit[31:16]对应 ID31~16 的 PPI 中断。剩下的GICD_ISENABLER1~GICD_ISENABLER15 就是控制 SPI 中断的。

2.2.6 中断优先级设置

1.优先级数配置

Cortex-A7 的中断优先级也可以分为抢占优先级和子优先级, GIC 控制器最多可以支持 256 个优先级,数字越小,优先级越高! Cortex-A7 选择了 32 个
优先级。在使用中断的时候需要初始化 GICC_PMR 寄存器,此寄存器用来决定使用几级优先级,

GICC_PMR寄存器:

image

GICC_PMR 寄存器只有低 8 位有效,这 8 位最多可以设置 256 个优先级,其他优先级数设置如表 17.1.6.1 所示:

image

2. 抢占优先级和子优先级位数设置

抢占优先级和子优先级各占多少位是由寄存器GICC_BPR来决定的,GICC_BPR寄存器结构如下:

GICC_BPR寄存器结构图:

image

寄存器 GICC_BPR 只有低 3 位有效,其值不同,抢占优先级和子优先级占用的位数也不同:

image

为了简单起见,一般将所有的中断优先级位都配置为抢占优先级,比如 I.MX6U 的优先级位数为 5(32 个优先级),所以可以设置 Binary point 为 2,表示 5 个优先级位全部为抢占优先级。

3.优先级设置

前面已经设置好了芯片一共有32个抢占优先级,数字越小优先级越高,具体要使用某个中断的时候就可以设置其优先级为0~31。

某个中断的ID配有一个优先级寄存器,所以一共有512个D_IPRIORITYR寄存器。

如果优先级个数为32的话,使用优先级个数为32的话,使用寄存器D_IPRIORITYR的bit7:4来设置优先级,也就是说实际的优先级要左移3位。比如要设置ID40中断的优先级为5,示例代码如下:

GICD_IPRIORITYR[40] = 5 << 3;

4.小结

优先级设置主要有三部分:

1.设置寄存器GICC_PMR,配置优先级个数,比如I.MX6U支持32级优先级
2.设置抢占优先级和子优先级位数,一般为了简单,会将所有的位数都设置为抢占优先级。
3.设置指定中断ID的优先级,也就是设置外设优先级

2.2.7 汇编代码

汇编代码start.S参考ixm6u的SDK包中core_ca7.h文件实现

.global _start                  /* 全局标号 */


_start:
    ldr pc,=Reset_Handler       /* 复位中断 */
    ldr pc,=Undfined_Handler    /* 未定义指令中断 */
    ldr pc,=SVC_Handler         /* SVC中断 */
    ldr pc,=PrefAbort_Handler   /* 预取终止中断*/
    ldr pc,=DataAbort_Handler   /* 数据终止中断*/
    ldr pc,=NotUsed_Handler     /* 未使用中断*/
    ldr pc,IRQ_Handler          /* IRQ中断*/
    ldr pc,FIQ_Handler          /* FIQ(快速中断)未定义中断*/


/* 复位中断 */
Reset_Handler:
    /*复位中断具体处理过程*/
    cpsid i      /*关闭全局中断*/
   
    /*关闭I,DCache和MMU,采取读-改-写*/
    mrc p15,0,r0,c1,c0,0    /*读取CP15的C1寄存器到R0中*/
    bic r0,r0,#(0x1 << 12)  /*清除C1的I位,关闭ICache*/
    bic r0,r0,#(0x1 << 2)   /*清除C1的A位,关闭对齐检查*/
    bic r0,r0,#0x2
    bic r0,r0,#(0x1 << 11)
    bic r0,r0,#0x1
    mcr p15,0,r0,c1,c0,0
   
#if 0
    /*汇编版本设置中断向量表偏移*/
    ldr r0,=0x87800000
   
    dsb
    isb
    mcr p15,0,r0,c12,c0,0
    dsb
    isb
#endif


    /*设置各个模式下的栈指针
    * 注意: IMX6UL 的堆栈是向下增长的!
    * 堆栈指针地址一定要是 4 字节地址对齐的!!!
    * DDR 范围:0X80000000~0X9FFFFFFF 或者 0X8FFFFFFF*/


    /*进入IRQ模式*/
    mrs r0,cpsr
    bic r0,r0,#0x1f
    orr r0,r0,#0x12
    msr cpsr,r0
    ldr sp,=0x806000000
   
    /*进入SYS模式*/
    mrs r0, cpsr
    bic r0, r0, #0x1f      /* 将 r0 的低 5 位清零,也就是 cpsr 的 M0~M4 */
    orr r0, r0, #0x1f      /* r0 或上 0x1f,表示使用 SYS 模式 */
    msr cpsr, r0           /* 将 r0 的数据写入到 cpsr 中 */
    ldr sp, =0x80400000    /* SYS 模式栈首地址为 0X80400000,大小为 2MB */
   
    /*进入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 中 */
    ldr sp, =0X80200000   /* SVC 模式栈首地址为 0X80200000,大小为 2MB */
   
    cpsie i               /*打开全局中断*/


#if 0
    /* 使能 IRQ 中断 */
    mrs r0, cpsr /* 读取 cpsr 寄存器值到 r0 中 */
    bic r0, r0, #0x80 /* 将 r0 寄存器中 bit7 清零,也就是 CPSR 中
                       * 的 I 位清零,表示允许 IRQ 中断
                       */
    msr cpsr, r0 /* 将 r0 重新写入到 cpsr 中 */
#endif
    b main         /*跳转到main函数*/


/* 未定义指令中断 */
Undfined_Handler:
    ldr r0,=Undfined_Handler
    bx r0


/* SVC中断 */
SVC_Handler:
    ldr r0,=SVC_Handler
    bx r0


/* 预取终止中断 */
PrefAbort_Handler:
    ldr r0,=PrefAbort_Handler
    bx r0


/* 数据终止中断 */
DataAbort_Handler:
    ldr r0,=DataAbort_Handler
    bx r0


/* 数据终止中断 */
NotUsed_Handler:
    ldr r0,=NotUsed_Handler
    bx r0


/* IRQ中断 重点!*/
NotUsed_Handler:
    /*IRQ中断具体处理过程*/
    /*1.保存现场*/
    push {lr}           /*保存lr地址*/
    push {r0-r3,r12}   /*保存r0-r1,r12寄存器*/


    mrs r0,spsr        /* 读取 spsr 寄存器 */
    push {r0}          /* 保存 spsr 寄存器 */


    /*2.获取当前中断号,中断号保存在r0寄存器中*/
    mrc p15,4,r1,c15,c0,0   /* 将 CP15 的 C0 内的值到 R1 寄存器中*/


    add r1,r1,#0X2000     /* GIC 基地址加 0X2000, 得到 CPU 接口端基地址 */
    ldr r0,[r1,#0xC]      /* CPU 接口端基地址加 0X0C 就是 GICC_IAR 寄存器,
                           * GICC_IAR 保存着当前发生中断的中断号,我们要根据
                           * 这个中断号来绝对调用哪个中断服务函数
                           */

    push {r0,r1}          /* 保存 r0,r1 */


    cps #0x13             /*cps直接修改CPSR寄存器的M[4:0],进入 SVC 模式,允许其他中断再次进去*/


    push {lr}            /* 保存 SVC 模式的 lr 寄存器 */
   
    /*3.调用中断服务子函数system_irqhandler*/
    ldr r2,=system_irqhandler    /* 加载 C 语言中断处理函数到 r2 寄存器中*/
    blx r2               /* 运行 C 语言中断处理函数,带有一个参数 */


    pop {lr}            /* 执行完 C 语言中断服务函数, lr 出栈 */
    cps #0x12           /* 进入IRQ模式*/
    pop {r0,r1}


    /*4.向GICC_EOIR寄存器写入刚刚处理完成中断号,表示中断处理完成*/
    str r0,[r1,#0x10]   /* 中断执行完成,写 EOIR */


    /*5.恢复现场*/
    pop {r0}
    msr spsr_cxsf,r0    /* 恢复 spsr */


    pop {r0-r3,r12}     /* r0-r3,r12 出栈 */
    pop {lr}            /* lr 出栈 */


    /*6.pc指针返回曾经被中断打断的地方*/
    subs pc,lr,#4       /* 将 lr-4 赋给 pc,-4是因为ARM架构是三级流水线:取址,译址,执行,PC指向的
                            是正在取值的地址,所以pc也=当前执行指令地址+8 */


/* FIQ中断*/
FIQ_Handler:
    ldr r0,=FIQ_Handler
    bx r0

关于ARM的三级流水线:取址,译址和执行,以下面代码为例:

地址            指令              流水线
0X2000  MOV R1,R0     ; 执行
0X2004  MOV R2,R3     ; 译址
0X2008  MOV R4,R5     ;取址PC

当前正在执行的MOV R1,R0,但是CP里面保存的是0X2008处的指令,如果此时发生中断,中断发生的时候保存在lr里面的是PC的值,就是0X2008,当中断处理完成以后肯定需要回到被中断点接着执行,如果直接跳转到lr里面的保存的地址处(0X2008)开始运行,此时就有一个指令没有运行,就是0X2004处的MOV R2,R3,所以就需要将lr-4赋给PC指针,也就是pc=0X2004,从指令"MOV R2,R3"开始执行。

2.2.8.通用中断驱动文件编写

在上面汇编中中断服务函数IRQ_Handler中调用了C函数system_irqhandler来处理具体中断。但是C函数system_handler的具体内容还没有实现,所以需要实现system_Handler的具体内容。

不同的中断源对应不同的中断处理函数,以IMX6U有160个中断源为例,所以需要160个中断处理函数,可以将这些中断处理函数放到一个数组中,中断处理函数在数组中的标号就是其对应的中断号。 当中断发生以后函数 system_irqhandler 根据中断号从中断处理函数数组中找到对应的中断处理函数并执行即可。

通用驱动文件的编写需要借助imx6ul提供的SDK包中的core_ca7.h的10个API函数:

函数 描述
GIC_Init 初始化GIC
GIC_EnableIRQ 使能指定的外设中断
GIC_DisableIRQ 关闭指定的外设中断
GIC_AcknowledgeIRQ 返回中断号
GIC_DeactivateIRQ 无效化指定中断
GIC_GetRunningPriority 获取当前正在运行的中断优先级
GIC_SetPriorityGrouping 设置抢占优先级位数
GIC_GetPriorityGrouping 获取抢占优先级位数
GIC_SetPriority 设置指定中断的优先级
GIC_GetPriority 获取指定中断的优先级

bsp_int.h:

#ifndef _BSP_INT_H
#define _BSP_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

bsp_int.c:

#include "bsp_int.h"

/*中断嵌套计数器*/
static unsigned int irqNesting;

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

/*中断初始化函数*/
void int_init(void)
{
    GIC_Init();                 // 初始化GIC
    system_irqtable_init();     // 初始化中断表
    __set_VBAR((uint32_t)0x87800000);    // 中断向量表偏移
}


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


    // 先将所有的中断服务函数设置为默认中断服务函数
    for(i=0;i<NUMBER_OF_INT_VECTORS;i++)
    {
        system_register_irqhandler((IRQn_Type)i,
                                    default_irqhandler,
                                    NULL);
    }
}

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


/*C语言中断服务函数,irq汇编中断服务函数会调用此函数,此函数通过在中断服务列表中查找指定中断号所对应的中断处理函数并执行
* giccIar:中断号
*/
void system_irqhandler(unsigned int giccIar)
{
    uint32_t intNum = giccIar & 0x3FFUL;

    /*检查中断号是否符合要求*/
    if((intNum == 1020) || (intNum >= NUMBER_OF_INT_VECTORS))
    {
        return;
    }

    irqNesting++;      // 中断嵌套寄存器+1

    irqTable[intNum].irqHandler(intNum,irqTable[intNum].userParam);

    irqNesting--;      // 中断嵌套寄存器-1
}

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


    }
}











参考文章:

【正点原子】I.MX6U嵌入式Linux驱动开发指南V1.6.pdf - 第17章

posted @ 2023-11-25 13:54  Emma1111  阅读(609)  评论(0编辑  收藏  举报