正点原子Mini Linux—cortex-A7中断

一、cortex-A7中断系统

1、cortex-A7中断向量表

和stm32一样,cortex-A7也具有中断向量表,不过不同的是cortex-A7的中断向量表中只有8个中断,虽然看上去比stm32的中断少了很多,但是其中的IRQ中断包含了所有的外部中断,所以只要外部中断一触发,就会进入IRQ中断中判断中断类型。在这里插入图片描述

2、GIC中断控制器

①中断ID

在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逻辑分块

GIC架构分为了两个逻辑块:Distributor和CPU Interface,也就是分发器端和CPU接口端。
分发器端主要负责各个中断事件的分发问题,也就是中断事件应该分发到哪个CPU接口端上去。分发器收集所有的中断源,可以控制每个中断的优先级,它总是将优先级最高的中断事件发送到CPU接口段。
CPU接口段的作用就是连接分发器和CPU内核,作为其中的桥梁。
历程中的“core_ca7.h”中定义了GIC结构体,其中分发器端相关的的寄存器相对于GIC基地址偏移0x1000,CPU接口端相对于GIC基地址偏移0x2000。为了能精确访问两个逻辑块的寄存器,需要知道它们各自的基地址,所以就先需要获得GIC基地址,这就要用到CP15协处理器了。

③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接口端的寄存器基地址。

二、代码实现

1、start.s文件的编写

.s文件的主要内容这里不具体放出来了,里面的重要内容主要是中断向量表的实现,在复位中断函数中,需要关闭I,D Cache和MMU,设置IRQ,SYS,SVC模式下的sp指针。接下来最重要的是IRQ中断服务函数,内部最重要的部分是读取CPU接口端的GICC_IAR寄存器的值,确定中断ID(这步决定了中断处理函数的具体操作),将这个中断ID函数参数带入到C语言中的中断处理函数system_irqhandler中,在system_irqhandler中断服务函数执行完成后,重新回来执行下一步,需要将对应ID值写入GICC_EOIR寄存器。

2、bsp_int文件

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并执行对应的中断服务函数。

3、修改bsp_gpio文件

添加了描述中断触发类型的枚举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清零中断标志)。

4、编写最终按键中断驱动函数

在修改和编写好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灯闪烁的代码,按键驱动蜂鸣器直接通过中断实现,不再需要循环检测按键,代码更加高效。

猜你喜欢

转载自blog.csdn.net/Jayccccc/article/details/106348519