Cortex-A7的中断系统总结

跟STM32一样,Cortex-A7的中断系统也可以分为中断向量表中断控制器中断使能中断服务函数这四大块。

一、中断向量表

中断向量表在代码的最前面,Cortex-A7内核有8个异常中断,如下:

向量地址 中断类型 中断模式
0x00 复位中断 特权模式(SVC)
0X04 未定义指令中断 未定义指令中断模式(Undef)
0x08 软中断 特权模式(SVC)
0x0C 指令预取中止中断 中止模式
0x10 数据访问中止中断 中止模式
0x14 未使用 未使用
0x18 IRQ中断 外部中断模式(IRQ)
0x1C FIQ中断 快速中断模式(FIQ)

Cortex-A系列内核CPU的所有外部中断都属于IRQ中断,当这些外部中断被触发时,都会触发IRQ中断,而不同的外部中断拥有不同的中断ID,可以从指定的寄存器里面读取出来,根据这个中断ID,就可以在IRQ中断服务函数中执行跟这个中断ID对应的具体外设的中断服务函数。

二、中断控制器(GIC)

简介:

STM32(Cortex-M)的中断控制器叫做NVIC,而Cortex-A7的中断控制器时GIC。目前GIC共有V1-V4四个版本,其中V1版本已经废弃,V2-V4用得较多。GIC V2 是给 ARMv7-A 架构使用的,比如 Cortex-A7、Cortex-A9、Cortex-A15 等,V3 和 V4 是给 ARMv8-A/R 架构使用的,也就是 64 位芯片使用的。GIC400便是根据GIC V2开发的中断控制器IP核。GIC 中断控制器接收众多的外部中断,然后对其进行处理,最终将信号报给 ARM 内核。

中断ID:

每一个CPU最多支持1020个中断ID,中断ID分配如下:

ID号 分配
ID0-ID15 SGI(软件中断,由软件触发引起的中断,通过向寄存器GICD_SGIR 写入数据来触发,系统会使用 SGI 中断来完成多核之间的通信。)
ID16-ID31 PPI(私有中断,GIC支持多核,每个核有自己的中断)
ID32-ID1019 SPI(共享中断,所有核共享的中断)

逻辑分块

GIC架构分为Distributor(分发器端)和CPU interface(CPU接口端),分发器端负责处理中断事件的分发问题,也就是将中断事件发给哪个CPU interface上去。而CPU接口端是分发器端和CPU内核间的桥梁,每一个CPU内核都有一个CPU接口端。
分发器端的主要工作如下:

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

CPU接口端的工作内容:

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

分发器端与CPU接口端这么多的功能都是通过控制对应的寄存器来实现的,通过CP15协处理器可以获取GIC基地址,GIC基地址偏移0x1000便是分发器端的第一个寄存器地址,GIC基地址偏移0x2000便是CPU接口端的第一个寄存器地址。

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。
CP15 协处理器有 16 个 32 位寄存器,c0~c15,在使用 MRC 或者 MCR 指令访问这 16 个
寄存器的时候,指令中的 CRn、opc1、CRm 和 opc2 通过不同的搭配,其得到的寄存器含义是
不同的。具体含义得看数据手册。

三、中断使能及中断优先级设置

分为IRQ或者FIQ的总中断使能以及各个中断号(ID0-ID1019)的使能.

总中断使能和禁止:

指令 作用
cpsid i 禁止IRQ中断
cpsie i 使能IRQ中断
cpsid f 禁止FIQ中断
cpsie f 使能FIQ中断

ID0-ID1019的使能和禁止:

Cortex-A7内核只使用了512个中断ID,一个寄存器有32个bit位,因为给对应的位写0不起作用,写1才能生效,所以使能需要16个寄存器,禁止需要16个寄存器。分别是GIC 寄存器GICD_ISENABLERnGICD_ ICENABLERn,Rn从R0-R15。其中GICD_ISENABLER0的 bit[15:0]对应ID15-ID0 的 SGI 断,GICD_ISENABLER0 的 bit[31:16]对应 ID31-ID16 的 PPI 中断。剩下的GICD_ISENABLER1一直到GICD_ISENABLER15 就是控制 SPI 中断的。

中断优先级设置

1、配置优先级数

Cortex-A7的优先级分为抢占优先级和子优先级,Cortex-A7最多可以支持256个优先级,数字越小,优先级越高。配置优先级数的寄存器是GICC_PMR,只有低八位有效。设置参数如下:

bit7:0 优先级数
11111111 256个优先级
11111110 128个优先级
11111100 64个优先级
11111000 32个优先级
11110000 16个优先级

2、抢占优先级和子优先级设置

抢占优先级和子优先级设置,也就是将配置优先级数的寄存器GICC_PMR的低八位拿来分配抢占优先级和子优先级。优先比较抢占优先级,抢占优先级数字越小,优先级越高;抢占优先级数相同时比较子优先级,数字越小优先级越高。抢占优先级和子优先级各占多少位由GICC_BPR寄存器的低三位来决定,如下:

bit2:0 抢占优先级域 子优先级域 描述
0 [7:1] [0] 7级抢占优先级,1级子优先级
1 [7:2] [1:0] 6级抢占优先级,2级子优先级
2 [7:3] [2:0] 5级抢占优先级,3级子优先级
3 [7:4] [3:0] 4级抢占优先级,4级子优先级
4 [7:5] [4:0] 3级抢占优先级,5级子优先级
5 [7:6] [5:0] 2级抢占优先级,6级子优先级
6 [7:7] [6:0] 1级抢占优先级,7级子优先级
7 [7:0] 0级抢占优先级,8级子优先级

3、给指定外部中断配置优先级

Cortex-A7一共使用了512个中断ID,每一个中断ID都配有一个优先级寄存器,所以一共有512个名为GICD_IPRIORITYR[n]的优先级寄存器,n表示中断号。如果优先级个数为32的话,就使用GICD_IPRIORITYR[n]的bit[7:3]来设置优先级。例如要设置ID40中断优先级为7,设置如下:

GICD_IPRIORITYR[40] = 7<<3;

四、中断服务函数

Cortex-A7最常用的中断服务函数呢就是复位中断服务函数以及IRQ中断服务函数,将它们都写在启动文件中。

1、复位中断服务函数

大致可以分为
(1)、关闭IRQ中断,防止在复位过程中受到干扰
(2)、关闭I、D cache和MMU
(3)、设置处理器各模式下的sp栈指针
(4)、打开IRQ中断并跳转到main函数

/*复位中断服务函数 */
Reset_Handler:
    cpsid i           /*关闭IRQ中断,防止复位中断时受干扰 */
    /*关闭I、D cache和MMU 
    *修改SCTLR寄存器,采用的方式为读-改-写,以保证不改变其他不需要改变的位
    */
    MRC p15, 0, r0, c1, c0, 0    /*读取sctlr寄存器的数据到r0寄存器里 */
    bic r0,r0, #(1<<12)          /*关闭 I Cache */
    bic r0,r0, #(1<<11)          /*关闭分支预测 */
    bic r0,r0, #(1<<2)           /*关闭 D Cache */
    bic r0,r0, #(1<<1)           /*关闭对齐 */
    bic r0,r0, #(1<<0)           /*关闭MMU */
    MCR p15, 0, r0, c1, c0, 0   /*将r0寄存器的数据写到sctlr寄存器里 */

    /*设置处理器进入IRQ模式 */
    mrs r0,cpsr         /*读取状态寄存器cpsr的值到r0中 */
    bic r0,r0,#0x1f     /*BIC,位清零,r0=r0&(~0x1f) */
    orr r0,r0,#0x12     /*0x12为处理器的IRQ模式,r0=r0|(0x12) */
    msr cpsr,r0         /*将设置好的值写进cpsr中 */
    ldr sp, =0x80600000 /*设置IRQ模式下的sp */

    /*设置处理器进入SYS模式 */
    mrs r0,cpsr         /*读取状态寄存器cpsr的值到r0中 */
    bic r0,r0,#0x1f     /*BIC,位清零,r0=r0&(~0x1f) */
    orr r0,r0,#0x1f     /*0x1f为处理器的SYS模式,r0=r0|(0x1f) */
    msr cpsr,r0         /*将设置好的值写进cpsr中 */
    ldr sp, =0x80400000 /*设置SYS模式下的sp */

    /*设置处理器进入SVC模式 */
    mrs r0,cpsr         /*读取状态寄存器cpsr的值到r0中 */
    bic r0,r0,#0x1f     /*BIC,位清零,r0=r0&(~0x1f) */
    orr r0,r0,#0x13     /*0x13为处理器的SVC模式,r0=r0|(0x13) */
    msr cpsr,r0         /*将设置好的值写进cpsr中 */
    ldr sp, =0x80200000 /*设置SVC模式下的sp */

    cpsie i             /*打开IRQ中断 */

    /*跳转到C函数 */
    b main  /*跳转到C语言的main函数 */

2、IRQ中断服务函数

当有外部中断触发IRQ中断时,就会调用IRQ中断服务函数。IRQ中断服务函数主要可以分为以下几步:
(1)、在进入外部中断具体的服务函数前应该先保存现场
(2)、读取当前触发中断的外部中断ID
(3)、立马切换到SVC模式,以便响应随时可能出现的其他IRQ中断
(4)、执行中断号对应的具体外部中断服务函数
(5)、中断执行完毕,将中断号写入EOIR
(5)、恢复现场

/*IRQ中断 */
IRQ_Handler:
    push {lr}					/* 保存lr地址 */
	push {r0-r3, r12}			/* 保存r0-r3,r12寄存器 */

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

	mrc p15, 4, r1, c15, c0, 0 /* 从CP15的C0寄存器内的值到R1寄存器中
								* 参考文档ARM Cortex-A(armV7)编程手册V4.0.pdf P49
								* Cortex-A7 Technical ReferenceManua.pdf P68 P138
								*/							
	add r1, r1, #0X2000			/* GIC基地址加0X2000,也就是GIC的CPU接口端基地址 */
	ldr r0, [r1, #0XC]			/* GIC的CPU接口端基地址加0X0C就是GICC_IAR寄存器,
								 * GICC_IAR寄存器保存这当前发生中断的中断号,我们要根据
								 * 这个中断号来绝对调用哪个中断服务函数
								 */
	push {r0, r1}				/* 保存r0,r1 */
	
	cps #0x13					/* 进入SVC模式,允许其他中断再次进去 */
	
	push {lr}					/* 保存SVC模式的lr寄存器 */
	ldr r2, =system_irqhandler	/* 加载C语言中断处理函数到r2寄存器中*/
	blx r2						/* 运行C语言中断处理函数,带有一个参数,保存在R0寄存器中 */

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

	pop {r0}						
	msr spsr_cxsf, r0			/* 恢复spsr */

	pop {r0-r3, r12}			/* r0-r3,r12出栈 */
	pop {lr}					/* lr出栈 */
	subs pc, lr, #4				/* 将lr-4赋给pc */

3、具体的中断服务函数system_irqhandler需要在C语言中完成

在外部中断产生时,IRQ中断服务函数会调用system_irqhandler这个函数,并将读取到R0寄存器中的含有中断号的数据当作system_irqhandler函数的入口参数。
(1)、不同的中断号对应着不同的中断服务函数,且这些中断服务函数都是用户自己编写的,所以得有一个函数用来注册中断ID和与之对应的中断服务函数用。
(2)、创建一个数组用来存中断服务函数,下标用来表示中断号
(3)、这个数组的每一个元素存有对应中断服务函数的入口地址以及用户参数,所以得创建一个结构体
具体代码如下:

/*定义中断处理函数的形式,此处定义了一种名为system_irq_handler_t的数据类型,并定义了这种数据类型为指向某种函数的指针,且这种函数返回值类型为void,两个入口参数*/
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;

static unsigned int irqNesting; /*用于记录嵌套了多少个中断*/

/*中断处理函数表*/
static sys_irq_handle_t irqTable[NUMBER_OF_INT_VECTORS];

/*
 *@description		:初始化中断处理函数表函数
 *@param			:无
 *@return			:无
 */
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;
    }
}

/*
 *@description		    :注册中断处理函数
 *@param - irq		    :中断号
 *@param - handler	    :需要注册的与中断号对应的中断处理函数
 *@param - UserParam	:用户参数
 *@return			    :无
 */
void system_register_irqhandler(IRQn_Type irq, system_irq_handler_t handler, void *UserParam)
{
    irqTable[irq].irqHandler = handler;
    irqTable[irq].userParam = UserParam;
}

/*
 *@description		:中断初始化函数
 *@param			:无
 *@return			:无
 */
void int_init(void)
{
    GIC_Init();                 /*初始化GIC*/
    system_irqtable_init();     /*初始化中断处理函数表*/
    __set_VBAR(0X87800000);     /*中断向量偏移设置*/
}

/*
 *@description		:具体的中断处理函数,IRQ_Handler会调用此函数
 *@param gicciar	:保存中断号的32位寄存器
 *@return			:无
 */
void system_irqhandler(unsigned int gicciar)
{
    uint32_t intNum = gicciar & 0x3ff;      /*获取中断号*/
    /*检查中断ID是否正常*/
    if(intNum >= 160)
    {
        return;
    }
    irqNesting++;
    /*根据中断ID号读取中断处理函数,然后执行*/
    irqTable[intNum].irqHandler(intNum,irqTable[intNum].userParam);
    irqNesting--;
}

/*
 *@description		:默认的中断处理函数
 *@param gicciar	:保存中断号的32位寄存器
 *@param UserParam	:用户参数
 *@return			:无
 */
/**/
void default_irqhandler(unsigned int gicciar, void *UserParam)
{
    while(1)
    {
        ;
    }
}

4、使用一个IO作为外部中断的过程

(1)、初始化IO复用,将对应IO复用为你需要的功能
(2)、配置该IO的电气属性
(3)、初始化该IO输入或者输出,以及默认输出电平和中断触发的模式
(4)、编写中断服务函数
(5)、GIC控制器使能该中断ID号的中断功能
(6)、注册中断ID及其中断服务函数
(7)、gpio使能对应IO口的中断功能

五、定时器中断(EPIT(enhanced periodic interrupt timer))

简介:

I.MX6ULL的定时器有以下五个特点:
1、I.MX6ULL有两个EPIT定时器,每个定时器有五个32位的寄存器,分别是EPIT配置寄存器(EPITxCR)、比较事件清零用的状态寄存器(EPITx_SR)、计数寄存器(EPITx_CNR)、预加载寄存器(EPITx_LR)、比较寄存器(EPITx_CMPR),x等于1或者2。
2、有三个时钟源可选,分别为ipg_clk、ipg_clk_32k 和 ipg_clk_highfreq,有一个12位分频器
3、可编程使之在低功耗和调试模式下运行。
4、当计数寄存器的值与比较寄存器的值相等时产生中断
5、当计数寄存器的值减到0后,可以重新写入预加载寄存器里的值,也可以从0XFFFFFFFF开始减

1、EPIT的两种工作模式

(1)、set-and-forget模式

描述:该模式下计数寄存器的初始值和计数寄存器减到0后,都会将预加载寄存器中的值写入计数寄存器里
设置:EPITx_CR(x=1,2)寄存器的RLD位置1

(2)、free-running模式

描述:该模式计数寄存器减到0后,不会将预加载寄存器中的值写入计数寄存器里,而是写入0XFFFFFFFF
设置:EPITx_CR(x=1,2)寄存器的RLD位清零

2、EPIT的五个寄存器

(1)、EPITx_CR
此寄存器是EPIT配置寄存器。

  • CLKSRC(bit25:24):这两位是用来配置时钟源的,0(关闭时钟源),1(选择ipg_clk时钟源),2(选择ipg_clk_highfreq参考时钟),3(选择ipg_clk_32k时钟源);通常使用ipg_clk这个66MHz的时钟源。
  • PRESCALAR(bit15:4):这12位是用来给EPIT 时钟源分频的,可设置的范围 0~4095,分别对应 1~4096 分频。
  • RLD(bit3):工作模式设定位,0(free-running),1(set-and-forget)。
  • OCIEN(bit2):比较中断使能位,0(关闭比较中断),1(使能比较中断)。
  • ENMOD(bit1):设置计数器初始值,为 0 时计数器初始值等于上次关闭 EPIT 定时器以后
    计数器里面的值,为 1 的时候来源于预加载寄存器。
  • EN(bit0):EPIT使能位,0(关闭EPIT),1(使能EPIT)。

(2)、EPITx_SR
该寄存器只有一个位(bit0)有效,为中断状态标志位,写1清零;当bit0为1时表示有中断发生,为0时表示无中断事件发生。

(3)、EPITx_LR
预加载寄存器,当使用自动重装初值的功能时,每当计数器减到0后,就会去读取预加载寄存器里的值作为初值。

(4)、EPITx_CMPR
比较寄存器,当比较中断使能时,一旦计数寄存器的值和比较寄存器相等,就会产生中断。

(5)、EPITx_CNR
计数寄存器,没经过一个时钟周期,该寄存器的值就会减1。

3、EPIT定时器中断在I.MX6ULL上的实现步骤

(1)、配置EPITx_CR寄存器
(2)、配置EPITx_LR寄存器
(3)、配置EPITx_CMPR寄存器
(4)、使用GIC_EnableIRQ()函数使能EPIT1对应的中断号
(5)、使用system_register_irqhandler()函数注册EPIT1的中断服务函数
(6)、配置EPITx_CR寄存器的bit0,使能EPIT中断
(7)、另外还得写中断服务函数

你可能感兴趣的:(I.MX6ULL学习笔记)