和stm32一样,cortex-A7也具有中断向量表,不过不同的是cortex-A7的中断向量表中只有8个中断,虽然看上去比stm32的中断少了很多,但是其中的IRQ中断包含了所有的外部中断,所以只要外部中断一触发,就会进入IRQ中断中判断中断类型。
在cortex-A7中,GIC中断控制器用于管理中断,可以使能或者关闭中断,设置中断优先级。GIC将众多的中断源分为三类:SPI(共享中断)、PPI(私有中断)、SGI(软件中断)。为了区别这么多的中断源,需要给它们分配一个唯一的ID,这就是中断ID。每一个CPU最多支持1020个中断,将ID0-15分配给SGI,ID16-31分配给PPI,剩下的ID都归于SPI。(这里I.MU6U总共使用了128个中断ID,加上前面PPI和SGI的32个ID,一共为160个)
GIC架构分为了两个逻辑块:Distributor和CPU Interface,也就是分发器端和CPU接口端。
分发器端主要负责各个中断事件的分发问题,也就是中断事件应该分发到哪个CPU接口端上去。分发器收集所有的中断源,可以控制每个中断的优先级,它总是将优先级最高的中断事件发送到CPU接口段。
CPU接口段的作用就是连接分发器和CPU内核,作为其中的桥梁。
历程中的“core_ca7.h”中定义了GIC结构体,其中分发器端相关的的寄存器相对于GIC基地址偏移0x1000,CPU接口端相对于GIC基地址偏移0x2000。为了能精确访问两个逻辑块的寄存器,需要知道它们各自的基地址,所以就先需要获得GIC基地址,这就要用到CP15协处理器了。
CP15协处理器一般用于存储系统管理,中断中也会用到,CP15一共有16个32位寄存器,其访问寄存器通过MRC(读)和MCR(写)指令。指令的格式如下:
cond:指令执行的条件码,如果忽略的话就表示无条件执行。
opc1:协处理器要执行的操作码。
Rt:ARM源寄存器,用于写入或者读出的数据寄存器。
CRn:CP15协处理器的目标寄存器。
CRm:协处理器中附加的目标寄存器或者源操作数寄存器,如果不需要附加信息就设置位C0,否则结果不可预测。
opc2:可选的协处理器特定操作码,当不需要的时候要设置为0.
指令中CRn、opc1、CRm和opc2通过不同的搭配,其得到的寄存器含义是不同的。
此实验中通过c1寄存器的SCTLR寄存器来关闭I,D Cache和MMU,通过c12寄存器的VBAR寄存器设置中断向量偏移,最后通过c15寄存器的CBAR寄存器获得GIC的基地址,这样就可以通过偏移获得分发器端和CPU接口端的寄存器基地址。
.s文件的主要内容这里不具体放出来了,里面的重要内容主要是中断向量表的实现,在复位中断函数中,需要关闭I,D Cache和MMU,设置IRQ,SYS,SVC模式下的sp指针。接下来最重要的是IRQ中断服务函数,内部最重要的部分是读取CPU接口端的GICC_IAR寄存器的值,确定中断ID(这步决定了中断处理函数的具体操作),将这个中断ID函数参数带入到C语言中的中断处理函数system_irqhandler中,在system_irqhandler中断服务函数执行完成后,重新回来执行下一步,需要将对应ID值写入GICC_EOIR寄存器。
bsp_int这个文件中的主要结构体和函数内容如下:
/* 定义中断处理函数 */
typedef void (*system_irq_handler_t)(unsigned int gicciar, void *param);
/* 中断处理函数结构体 */
typedef struct _sys_irq_handle
{
sys_irq_handler_t irqHandler; /* 中断处理函数 */
void *userParam; /* 中断处理函数的参数 */
}sys_irq_handle_t;
/* 中断初始化函数 */
void int_init(void);
/* 注册中断服务函数 */
void system_register_irqhandler(IRQn_type irq, system_irq_handler_t handler, void *userParam);
这里的第一句使用的了typedef定义了一个新的变量名system_irq_handler_t,这里代表着指向中断服务函数的函数指针类型。
下面的int_init函数中初始化了GIC和中断处理函数表,并且设置了中断向量偏移(如果start.s中设置过,这里可以不设置)。
system_register_irqhandler函数主要用于中具体注册中断处理函数(这里再bsp_exit中用于注册GPIO1_IO18的中断处理函数)。
IRQ中断发生时,会执行bsp_int.c文件中的system_irqhandler函数,会传递中断ID并执行对应的中断服务函数。
添加了描述中断触发类型的枚举gpio_interrupt_mode_t,并添加到了原来的gpio_pin_config_t结构体中。这样就能在总的GPIO初始化函数gpio_init中添加新编写的GPIO中断初始化函数gpio_intconfig实现函数的嵌套定义。除此之外,其他的时使能或禁止指定的IO中断函数gpio_enableint/gpio_disableint和清除中断标志位函数gpio_clearintflags。
/* 描述中断触发类型 */
typedef enum gpio_interrupt_mode
{
kGPIO_NoIntmode = 0U; /* 无触发 */
kGPIO_IntLowLevel = 1U; /* 低电平触发 */
kGPIO_IntHighLevel = 2U; /* 高电平触发 */
kGPIO_IntRisingEdge = 3U, /* 上升沿触发 */
kGPIO_IntFallingEdge = 4U, /* 下降沿触发 */
kGPIO_IntRisingOrFallingEdge = 5U, /* 上升沿和下降沿都触发 */
}gpio_interrupt_mode_t;
/* GPIO中断初始化函数 */
void gpio_intconfig(GPIO_Type *base, int pin, gpio_interrupt_mode_t pin_int_mode)
{
volatile uint32_t *icr;
uint32_t icrShift;
icrShift = pin;
base->EDGE_SEL &= ~(1 << pin); /* 清零中断引脚的EDGE_SEL位,不是双边沿触发*/
if(pin < 16) /* 低16位 */
{
icr = &(bass->ICR1); /* IO0~15为ICR1寄存器 */
icrShift -=16;
}
else
{
icr = &(bass->ICR2); /* IO16~31为ICR2寄存器 */
}
switch(pin_int_mode) /* 触发模式设置 */
{
case kGPIO_IntLowLevel:
*icr &= ~(3 << (2*icrShift));
break;
case kGPIO_IntHighLevel:
*icr &= ~(3 << (2*icrShift));
*icr |= (1 << (2*icrShift));
break;
case kGPIO_IntRisingEdge:
*icr &= ~(3 << (2*icrShift));
*icr |= (2 << (2*icrShift));
break;
case kGPIO_IntFallingEdge:
*icr |= (3 << (2*icrShift));
break;
case kGPIO_IntRisingOrFallingEdge:
base->EDGE_SEL |= (1 << pin);
break;
default:
break;
}
}
GPIO中断初始化函数gpio_intconfig中通过pin_int_mode设置中断方式寄存其ICR1和ICR2,并且在之前就禁止寄存器EDGE_SEL,从而先禁止双边沿触发方式。初始化GPIO输入或输出及中断方式后,就可以通过置位IMR寄存器来开启GPIO中断(GPIO中断结束后,要对ISR寄存器写1清零中断标志)。
在修改和编写好start.s,按键驱动文件和中断驱动文件后,就可以编写最后的按键中断驱动函数了,这段代码比较简单
/* 初始化外部中断,也就是GPIO1_IO18 */
void exit_init(void)
{
/* GPIO初始化及中断方式设置结构体 */
gpio_pin_config_t key_config;
/* 复用为GPIO1_IO18 */
IOMUXC_SetPinMux(IOMUXC_UART1_CTS_B_GPIO1_IO18, 0);
IOMUXC_SetPinConfig(IOMUXC_UART1_CTS_B_GPIO1_IO18, 0xF080);
/* GPIO初始化 */
key_config.direction = kGPIO_DigitalInput;
key_config.interruptMode = kGPIO_IntFallingEdge; /* 下降沿中断触发模式 */
gpio_init(GPIO1, 18, &key_config);
GIC_EnableIRQ(GPIO1_Combined_16_31_IRQn);
/* 注册中断服务函数 */
system_register_irqhandler(GPIO1_Combined_16_31_IRQn, (system_irq_handler_t)gpio1_io18_irqhandler, NULL);
/*使能中断*/
gpio_enableint(GPIO1, 18);
}
/*按键对应的中断处理函数*/
void gpio1_io18_irqhandler(unsigned int gicciar, void *param)
{
static unsigned char state = 0;
delay(10); /* 在实际的开发中,禁止在中断服务函数中调用延时函数 */
if(gpio_pin_read(GPIO1, 18) == 0)
{
state = !state;
beep_switch(state);
}
/* 清除中断标志位 */
gpio_clearintflags(GPIO1, 18);
}
最后的main函数实现非常简单,while循环中只需要添加led灯闪烁的代码,按键驱动蜂鸣器直接通过中断实现,不再需要循环检测按键,代码更加高效。