Cortex-A7 内核有 8 个异常中断
跟 STM32 一样, Cortex-A7 也有中断向量表,中断向量表也是在代码的最前面。 CortexA7 内核有 8 个异常中断,这 8 个异常中断的中断向量表如下图
复位中断(Rest), CPU 复位以后就会进入复位中断, 我们可以在复位中断服务函数里面做一些初始化工作,比如初始化 SP 指针、DDR 等等。
未定义指令中断(Undefined Instruction),如果指令不能识别的话就会产生此中断。
软中断(Software Interrupt,SWI),由 SWI 指令引起的中断,Linux 的系统调用会用 SWI 指令来引起软中断,通过软中断来陷入到内核空间。
指令预取中止中断(Prefetch Abort),预取指令的出错的时候会产生此中断。
数据访问中止中断(Data Abort),访问数据出错的时候会产生此中断。
IRQ 中断(IRQ Interrupt),外部中断,前面已经说了,芯片内部的外设中断都会引起此中断的发生。
FIQ 中断(FIQ Interrupt),快速中断,如果需要快速处理中断的话就可以使用此中。
我们常用的就是复位中断和 IRQ 中断,Cortex-A 内核 CPU 的所有外部中断都属于IQR 中断。
Cortex-A 内核 CPU 的所有外部中断都属于这个 IQR 中断,当任意一个外部中断发生的时候都会触发 IRQ 中断。在 IRQ 中断服务函数里面就可以读取指定的寄存器来判断发生的具体是什么中断,进而根据具体的中断做出相应的处理。这些外部中断和 IQR 中断的关系如图 所示:
左侧的 Software0_IRQn~PMU_IRQ2_IRQ 这些都是 I.MX6U 的中断,他们都属于 IRQ 中断。当图 17.1.2.1 左侧这些中断中任意一个发生的时候 IRQ 中断都会被触发,所以我们需要在 IRQ 中断服务函数中判断究竟是左侧的哪个中断发生了,然后再做出具体的处
理。
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 位芯片使用的。 I.MX6U 是 Cortex-A 内核的,因此我们主要讲解 GIC V2。 GIC V2 最多支持 8 个核。 ARM 会根据 GIC 版本的不同研发出不同的 IP 核,那些半导体厂商直接购买对应的 IP 核即可,比如 ARM 针对 GIC V2 就开发出了 GIC400 这个中断控制器 IP 核。当 GIC 接收到外部中断信号以后就会报给 ARM 内核,但是ARM 内核只提供了四个信号给 GIC 来汇报中断情况: VFIQ、 VIRQ、 FIQ 和 IRQ,他们之间的关系如图 17.1.3.1 所示
VFIQ、VIRQ、FIQ、IRQ这些是什么呢
VFIQ:虚拟快速 FIQ。
VIRQ:虚拟快速 IRQ。
FIQ:快速中断 IRQ。
IRQ:外部中断 IRQ。
我们这里只谈 IRQ即是外部中断
下面了解一下GIC工作原理
左侧部分就是中断源,中间部分就是 GIC 控制器,最右侧就是中断控制器向处理器内核发送中断信息。中间 GIC 部分将众多的中断源分为分为三类:
①、SPI(Shared Peripheral Interrupt),共享中断,顾名思义,所有 Core 共享的中断,这个是最
常见的,那些外部中断都属于 SPI 中断(注意!不是 SPI 总线那个中断) 。比如按键中断、串口中断等等,这些中断所有的 Core 都可以处理,不限定特定 Core。
②、PPI(Private Peripheral Interrupt),私有中断,我们说了 GIC 是支持多核的,每个核肯定有自己独有的中断。 这些独有的中断肯定是要指定的核心处理, 因此这些中断就叫做私有中断。
③、SGI(Software-generated Interrupt),软件中断,由软件触发引起的中断,通过向寄存器GICD_SGIR 写入数据来触发,系统会使用 SGI 中断来完成多核之间的通信。
中断 ID
中断源有很多,为了区分这些不同的中断源肯定要给他们分配一个唯一 ID,这些 ID 就是中断 ID。每一个 CPU 最多支持 1020 个中断 ID,中断 ID 号为 ID0~ID1019。这 1020 个 ID 包含了 PPI、SPI 和 SGI.
ID0~ID15:这 16 个 ID 分配给 SGI。
ID16~ID31:这 16 个 ID 分配给 PPI。
ID32~ID1019:这 988 个 ID 分配给 SPI,像 GPIO 中断、串口中断等这些外部中断。
I.MX6U 的总共使用了 128 个中断 ID,加上前面属于 PPI 和 SGI 的 32 个 ID,I.MX6U 的中断源共有 128+32=160个。。
128 个中断 ID 对应的中断在《I.MX6ULL 参考手册》的“3.2 Cortex A7 interrupts”小节。
MCIMX6Y2C.h里的枚举类型 IRQn_Type,枚举出了 I.MX6U 的所有中断。
2.3 GID逻辑分块
GIC 架构分为了两个逻辑块: Distributor 和 CPU Interface, 也就是分发器端和 CPU 接口端。
例程“9_int”(原子LINUX),core_ca7.h 定义了 GIC 结构体。
结构体第 5 行是 GIC 的分发器端相关寄存器,
其相对于 GIC 基地址偏移为 0X1000,获取到GIC 基地址以后只需要加上 0X1000 即可访问 GIC 分发器端(Distributor)寄存器。
其相对于 GIC 基地址的偏移为 0X2000,获取到 GIC 基地址以后只需要加上 0X2000 即可访问 GIC 的 CPU 接口段寄存器。
2.3.1 Distributor( 分发器端)
此逻辑块负责处理各个中断事件的分发问题,也就是中断事件应该发送到哪个 CPU Interface 上去。分发器收集所有的中断源,可以控制每个中断的优先级, 它总是将优先级最高的中断事件发送到 CPU 接口端。 分发器端要做的主要工作如下:
①、全局中断使能控制。
②、控制每一个中断的使能或者关闭。
③、设置每个中断的优先级。
④、设置每个中断的目标处理器列表。
⑤、设置每个外部中断的触发模式:电平触发或边沿触发。
⑥、设置每个中断属于组 0 还是组 1。
2.3.2 CPU Interface(CPU 接口端)
CPU 接口端听名字就知道是和 CPU Core 相连接的,因此每个 CPU Core 都可以在 GIC 中找到一个与之对应的 CPU Interface。CPU 接口端就是分发器和 CPU Core 之间的桥梁,CPU 接口端主要工作如下:
①、使能或者关闭发送到 CPU Core 的中断请求信号。
②、应答中断。
③、通知中断处理完成。
④、设置优先级掩码,通过掩码来设置哪些中断不需要上报给 CPU Core。
⑤、定义抢占策略。
⑥、当多个中断到来的时候,选择优先级最高的中断通知给 CPU Core。
上面提到了要用到GIC的基地址
GIC 控制器的寄存器基地址在哪里呢?这个就需要用到 Cortex-A 的 CP15 协处
理器了。
关于 CP15 协处理器和其相关寄存器的详细内容请参考下面两份文档:
《ARM ArchitectureReference Manual ARMv7-A and ARMv7-R edition.pdf》第 1469 页“B3.17
Oranization of the CP15 registers in a VMSA implementation” 。
《Cortex-A7 Technical ReferenceManua.pdf》第 55 页“Capter 4 System Control” 。
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
IRQ 和 FIQ 总中断使能
寄存器 CPSR 的 I=1 禁止 IRQ,当 I=0 使能 IRQ;F=1 禁止 FIQ,F=0 使能 FIQ。
我们还有更简单的指令来完成 IRQ 或者 FIQ 的使能和禁止.
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 中断的。
优先级数
Cortex-A7 最多可以支持 256 个优先级,数字越小,优先级越高!I.MX6U 选择了 32 个优先级。在使用中断的时候需要初始化 GICC_PMR 寄存器,此寄存器用来决定使用几级优先级。
抢占优先级和子优先级各占多少位是由寄存器 GICC_BPR 来决定的, GICC_BPR 寄存器结
GICC_PMR 寄存器只有低 8 位有效,这 8 位最多可以设置 256 个优先级。
I.MX6U 支持 32 个优先级,所以 GICC_PMR 要设置为 0b11111000。
4.2 抢占优先级和子优先级位数设置
抢占优先级和子优先级各占多少位是由寄存器 GICC_BPR 来决定的。
寄存器GICC_BPR只有低3位有效, 其值不同, 抢占优先级和子优先级占用的位数也不同。
为了简单起见,一般将所有的中断优先级位都配置为抢占优先级.
下图是原子Linux core_ca7.h文件的一些常用函数
下面来使用gpio实现外部中断的一个例子:
中断初始化:
/* 中断嵌套计数器 */
static unsigned int irqNesting;
/* 中断服务函数表 */
static sys_irq_handle_t irqTable[NUMBER_OF_INT_VECTORS];
/*
* @description : 中断初始化函数
* @param : 无
* @return : 无
*/
void int_init(void)
{
GIC_Init(); /* 初始化GIC */
system_irqtable_init(); /* 初始化中断表 */
__set_VBAR((uint32_t)0x87800000); /* 中断向量表偏移,偏移到起始地址 */
}
/*
* @description : 初始化中断服务函数表
* @param : 无
* @return : 无
*/
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);
}
}
/*
* @description : 给指定的中断号注册中断服务函数
* @param - irq : 要注册的中断号
* @param - handler : 要注册的中断处理函数
* @param - usrParam : 中断服务处理函数参数
* @return : 无
*/
void system_register_irqhandler(IRQn_Type irq, system_irq_handler_t handler, void *userParam)
{
irqTable[irq].irqHandler = handler;
irqTable[irq].userParam = userParam;
}
/*
* @description : C语言中断服务函数,irq汇编中断服务函数会
调用此函数,此函数通过在中断服务列表中查
找指定中断号所对应的中断处理函数并执行。
* @param - giccIar : 中断号
* @return : 无
*/
void system_irqhandler(unsigned int giccIar)
{
uint32_t intNum = giccIar & 0x3FFUL;
/* 检查中断号是否符合要求 */
if ((intNum == 1023) || (intNum >= NUMBER_OF_INT_VECTORS))
{
return;
}
irqNesting++; /* 中断嵌套计数器加一 */
/* 根据传递进来的中断号,在irqTable中调用确定的中断服务函数*/
irqTable[intNum].irqHandler(intNum, irqTable[intNum].userParam);
irqNesting--; /* 中断执行完成,中断嵌套寄存器减一 */
}
/*
* @description : 默认中断服务函数
* @param - giccIar : 中断号
* @param - usrParam : 中断服务处理函数参数
* @return : 无
*/
void default_irqhandler(unsigned int giccIar, void *userParam)
{
while(1)
{
}
}
GPIO初始化:
/*
* @description : GPIO初始化。
* @param - base : 要初始化的GPIO组。
* @param - pin : 要初始化GPIO在组内的编号。
* @param - config : GPIO配置结构体。
* @return : 无
*/
void gpio_init(GPIO_Type *base, int pin, gpio_pin_config_t *config)
{
base->IMR &= ~(1U << pin);
if(config->direction == kGPIO_DigitalInput) /* GPIO作为输入 */
{
base->GDIR &= ~( 1 << pin);
}
else /* 输出 */
{
base->GDIR |= 1 << pin;
gpio_pinwrite(base,pin, config->outputLogic); /* 设置默认输出电平 */
}
gpio_intconfig(base, pin, config->interruptMode); /* 中断功能配置 */
}
/*
* @description : 读取指定GPIO的电平值 。
* @param - base : 要读取的GPIO组。
* @param - pin : 要读取的GPIO脚号。
* @return : 无
*/
int gpio_pinread(GPIO_Type *base, int pin)
{
return (((base->DR) >> pin) & 0x1);
}
/*
* @description : 指定GPIO输出高或者低电平 。
* @param - base : 要输出的的GPIO组。
* @param - pin : 要输出的GPIO脚号。
* @param - value : 要输出的电平,1 输出高电平, 0 输出低低电平
* @return : 无
*/
void gpio_pinwrite(GPIO_Type *base, int pin, int value)
{
if (value == 0U)
{
base->DR &= ~(1U << pin); /* 输出低电平 */
}
else
{
base->DR |= (1U << pin); /* 输出高电平 */
}
}
/*
* @description : 设置GPIO的中断配置功能
* @param - base : 要配置的IO所在的GPIO组。
* @param - pin : 要配置的GPIO脚号。
* @param - pinInterruptMode: 中断模式,参考枚举类型gpio_interrupt_mode_t
* @return : 无
*/
void gpio_intconfig(GPIO_Type* base, unsigned int pin, gpio_interrupt_mode_t pin_int_mode)
{
volatile uint32_t *icr;
uint32_t icrShift;
icrShift = pin;
base->EDGE_SEL &= ~(1U << pin);
if(pin < 16) /* 低16位 */
{
icr = &(base->ICR1);
}
else /* 高16位 */
{
icr = &(base->ICR2);
icrShift -= 16;
}
switch(pin_int_mode)
{
case(kGPIO_IntLowLevel):
*icr &= ~(3U << (2 * icrShift));
break;
case(kGPIO_IntHighLevel):
*icr = (*icr & (~(3U << (2 * icrShift)))) | (1U << (2 * icrShift));
break;
case(kGPIO_IntRisingEdge):
*icr = (*icr & (~(3U << (2 * icrShift)))) | (2U << (2 * icrShift));
break;
case(kGPIO_IntFallingEdge):
*icr |= (3U << (2 * icrShift));
break;
case(kGPIO_IntRisingOrFallingEdge):
base->EDGE_SEL |= (1U << pin);
break;
default:
break;
}
}
/*
* @description : 使能GPIO的中断功能
* @param - base : 要使能的IO所在的GPIO组。
* @param - pin : 要使能的GPIO在组内的编号。
* @return : 无
*/
void gpio_enableint(GPIO_Type* base, unsigned int pin)
{
base->IMR |= (1 << pin);
}
/*
* @description : 禁止GPIO的中断功能
* @param - base : 要禁止的IO所在的GPIO组。
* @param - pin : 要禁止的GPIO在组内的编号。
* @return : 无
*/
void gpio_disableint(GPIO_Type* base, unsigned int pin)
{
base->IMR &= ~(1 << pin);
}
/*
* @description : 清除中断标志位(写1清除)
* @param - base : 要清除的IO所在的GPIO组。
* @param - pin : 要清除的GPIO掩码。
* @return : 无
*/
void gpio_clearintflags(GPIO_Type* base, unsigned int pin)
{
base->ISR |= (1 << pin);
}
外部中断配置:
/*
* @description : 初始化外部中断
* @param : 无
* @return : 无
*/
void exit_init(void)
{
gpio_pin_config_t key_config;
/* 1、设置IO复用 */
IOMUXC_SetPinMux(IOMUXC_UART1_CTS_B_GPIO1_IO18,0); /* 复用为GPIO1_IO18 */
IOMUXC_SetPinConfig(IOMUXC_UART1_CTS_B_GPIO1_IO18,0xF080);
/* 2、初始化GPIO为中断模式 */
key_config.direction = kGPIO_DigitalInput;
key_config.interruptMode = kGPIO_IntFallingEdge;
key_config.outputLogic = 1;
gpio_init(GPIO1, 18, &key_config);
GIC_EnableIRQ(GPIO1_Combined_16_31_IRQn); /* 使能GIC中对应的中断 */
system_register_irqhandler(GPIO1_Combined_16_31_IRQn, (system_irq_handler_t)gpio1_io18_irqhandler, NULL); /* 注册中断服务函数 */
gpio_enableint(GPIO1, 18); /* 使能GPIO1_IO18的中断功能 */
}
/*
* @description : GPIO1_IO18最终的中断处理函数
* @param : 无
* @return : 无
*/
void gpio1_io18_irqhandler(void)
{
static unsigned char state = 0;
/*
*采用延时消抖,中断服务函数中禁止使用延时函数!因为中断服务需要
*快进快出!!这里为了演示所以采用了延时函数进行消抖,后面我们会讲解
*定时器中断消抖法!!!
*/
delay(10);
if(gpio_pinread(GPIO1, 18) == 0) /* 按键按下了 */
{
state = !state;
beep_switch(state);
}
gpio_clearintflags(GPIO1, 18); /* 清除中断标志位 */
}
感谢阅读,如有错误欢迎指正!