baremetal GPIO中断
本文主要用来学习GPIO中断原理,不深入理解GIC控制器、CP15协处理器等大模块
Cortex-A7中断系统使用中断控制器为GIC-V2
GIC-V2控制器
GIC-V2的基本框图如下:

GIC最多支持8个处理器(processor0~ processor7)。不同处理器的GIC功能是相同的,只看其中一个即可。 GIC主要分为分发器(Distributor)和CPU接口(CPU interface/Virtual CPU interface)。
分发器:
分发器用于管理CPU所有中断源,确定每个中断的优先级,管理中断的屏蔽和中断抢占。最终将优先级最高的中断转发到一个或者多个CPU接口。CPU的中断源分为三类,如下:
SGIs(software generated interrupts):最大 16 个软件可产生的中断,中断号0-15,这类中断是软件写分发器的软件生成中断寄存器GICD_SGIR产生的,通常用于实现处理器之间的中断IPI
PPIs(private peripheral interrupts):中断号16-31;这类中断是每个处理器的私有组件产生的,例如定时器中断,每个处理器都有各自私有定时器,每个私有定时器使用相同的硬件中断号,只能给自己的处理器发中断。
SPIs(shared peripheral interrupts):SPI中断号32~1020,这类中断是所有处理器共享的外设中断,这类中断一般是边沿触发或电平触发的。
分发器提供了一些编程接口或者说是寄存器,可以通过分发器的编程接口实现如下操作。
- 全局的开启或关闭CPU的中断。
- 控制任意一个中断请求的开启和关闭。
- 设置每个中断请求的中断优先级。
- 指定中断发生时将中断请求发送到那些CPU(i.MX 6U是单核)。
- 设置每个”外部中断”的触发方式(边缘触发或者电平触发)。
CPU接口:
CPU接口为链接到GIC的处理器提供接口,与分发器类似它也提供了一些编程接口,可以通过CPU接口实现以下功能:
- 开启或关闭向处理器发送中断请求.。
- 确认中断(acknowledging an interrupt)。
- 指示中断处理的完成。
- 为处理器设置中断优先级掩码。
- 定义处理器的抢占策略
- 确定挂起的中断请求中优先级最高的中断请求。
CP15:
这里仅说明为啥需要CP15:
GIC接口寄存器讲解部分只给出了”偏移地址”,GIC的基地址保存在CP15协处理器中。 我们修改系统控制寄存器以及设置中断向量表地址都会用到CP15协处理器。
协处理器 CP15 中的 c0 寄存器是 ARM 处理器中的控制寄存器,用于配置和控制处理器的一些基本特性。这个寄存器通常被用来控制缓存、页表和系统控制等功能。具体的功能包括:
- 控制和配置缓存:c0 寄存器中的位可以用来控制处理器的 L1 缓存和 L2 缓存,例如启用或禁用缓存、配置缓存的大小、设置缓存的工作模式等。
- 系统控制:c0 寄存器中的部分位用来控制处理器的一些基本系统特性,例如设置处理器的工作模式(用户模式、特权模式等)、配置异常向量表的基地址、设置处理器的端序模式等。
- 控制页表和内存管理:c0 寄存器中的位也可以用来配置处理器的页表结构、启用或禁用 MMU(内存管理单元)以及设置内存访问权限等。
深入理解CP15可参考:
GPIO中断设置:
1、设置GPIO的中断触发方式,也就是GPIO_ICR1或者GPIO_ICR2寄存器。触发方式有低电平、高电平、上升沿和下降沿。
2、使能GPIO对应的中断,设置GPIO_IMR寄存器
3、处理完中断,需要清除中断标志位,也就是清除GPIO_ISR寄存器相应的bit位,GPIO_ISR寄存器是写1清零
4、使能GIC中断号,参考imx6ull的参考手册第三章,得到中断号为67+32 = 99,gpio1_16 - gpio1_31共用中断号99
名词缩写:
ICR:interrupt configuration register
IMR:interrupt mask register
ISR:interrupt status register
具体实现:
start.S
.global _start /* 全局标号 */ /* * 描述: _start函数,首先是中断向量表的创建 * 参考文档:ARM Cortex-A(armV7)编程手册V4.0.pdf P42,3 ARM Processor Modes and Registers(ARM处理器模型和寄存器) * ARM Cortex-A(armV7)编程手册V4.0.pdf P165 11.1.1 Exception priorities(异常) */ _start: ldr pc, =Reset_Handler /* 复位中断 */ ldr pc, =Undefined_Handler /* 未定义中断 */ ldr pc, =SVC_Handler /* SVC(Supervisor)中断 */ 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寄存器的bit12位(I位),关闭I Cache */ bic r0, r0, #(0x1 << 2) /* 清除C1寄存器的bit2(C位),关闭D Cache */ bic r0, r0, #0x2 /* 清除C1寄存器的bit1(A位),关闭对齐 */ bic r0, r0, #(0x1 << 11) /* 清除C1寄存器的bit11(Z位),关闭分支预测 */ bic r0, r0, #0x1 /* 清除C1寄存器的bit0(M位),关闭MMU */ mcr p15, 0, r0, c1, c0, 0 /* 将r0寄存器中的值写入到CP15的C1寄存器中 */ /* 设置各个模式下的栈指针, * 注意:IMX6UL的堆栈是向下增长的! * 堆栈指针地址一定要是4字节地址对齐的!!! * DDR范围:0X80000000~0X9FFFFFFF */ /* 进入IRQ模式 */ mrs r0, cpsr bic r0, r0, #0x1f /* 将r0寄存器中的低5位清零,也就是cpsr的M0~M4 */ orr r0, r0, #0x12 /* r0或上0x13,表示使用IRQ模式 */ msr cpsr, r0 /* 将r0 的数据写入到cpsr_c中 */ ldr sp, =0x80600000 /* 设置IRQ模式下的栈首地址为0X80600000,大小为2MB */ /* 进入SYS模式 */ mrs r0, cpsr bic r0, r0, #0x1f /* 将r0寄存器中的低5位清零,也就是cpsr的M0~M4 */ orr r0, r0, #0x1f /* r0或上0x13,表示使用SYS模式 */ msr cpsr, r0 /* 将r0 的数据写入到cpsr_c中 */ 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_c中 */ ldr sp, =0X80200000 /* 设置SVC模式下的栈首地址为0X80200000,大小为2MB */ cpsie i /* 打开全局中断 */ b main /* 跳转到main函数 */ /* 未定义中断 */ Undefined_Handler: ldr r0, =Undefined_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中断!重点!!!!! */ 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 */ /* FIQ中断 */ FIQ_Handler: ldr r0, =FIQ_Handler bx r0
bsp_int.c
.
/* * @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--; /* 中断执行完成,中断嵌套寄存器减一 */ }