Skip to content

LV050-imx6ull中断实例

一、硬件连接

我们用到的就是ALPHA开发板上的按键,电路原理图如下:

image-20230910131613053

按键 KEY0 是连接到 I.MX6U 的 UART1_CTS 这个 IO 上的 ,我们查一下这个IO是哪个,这里其实我没看懂,但是根据教程描述,这个引脚应该是这个:

image-20260115185049870

这个引脚的默认功能是UART1_CTS_B,但是可以复用为GPIO1_IO18,这里就相当于接在了GPIO1_IO18上边。

二、IMX6ULL 的GPIO中断寄存器简介

1. GPIO interrupt configuration register1(GPIOx_ICR1)

GPIO 中断配置寄存器 1,用来配置 GPIO 中断 1~15 的触发类型。

image-20260115185130510

位域读写描述
[2n+1:2n]ICRnR/W用来设置 GPIO 中断的触发类型, 00:低电平触发; 01:高电平触发; 10:上升沿触发; 11:下降沿触发

ICR0~ICR15 对应 GPIO interrupt 0 ~ 15。

2. GPIO interrupt configuration register2(GPIOx_ICR2)

GPIO 中断配置寄存器 2,用来配置 GPIO 中断 16 ~ 31 的触发类型。

image-20260115185150686

与 GPIOx_ICR1 类似, ICR15 ~ ICR31 对应 GPIO interrupt 16 ~ 31。

3. GPIO interrupt mask register (GPIOx_IMR)

GPIO 中断屏蔽寄存器,用来屏蔽或使能某个 GPIO 中断。

image-20260115185206485

位域名称读写描述
[n]IMRR/W每一位对应一个 GPIO 中断, 0:中断被屏蔽 1:中断使能,未被屏蔽

4. GPIO interrupt status register (GPIOx_ISR)

GPIO 中断状态寄存器,表示某个 GPIO 中断是否发生了。

image-20260115185219327

位域名称读写描述
[n]ISRR/W每一位对应一个 GPIO 中断,跟 GPIO_IMR 无关,就是说即使屏 蔽了某个中断,还是可以在本寄存器中观察它的状态。 读: 0:中断未发生; 1:中断已发生。 写:某位写入 1 时,清零该位。

5. GPIO edge select register (GPIOx_EDGE_SEL)

GPIO 中断边沿选择寄存器,它可以用来覆盖 GPIOx_ICR1/2 中的配置值。

image-20260115185233131

每一位对应一个 GPIO 中断,一旦设置了 GPIO_EDGE_SEL[n]时, GPIO 会忽略ICR [n]设置, GPIO interrupt n 的触发类型就是双边沿触发。

三、中断程序编程示例

1. 总体编程流程

我使用的开发板IMX6ULL 有 1 个按键,本节程序将设置它的中断处理函数,实现GPIO的外部中断。整体的编程流程是:

① 在中断向量中,保存现场,调用处理函数,恢复现场;

② 初始化:为 KEY0 设置处理函数;初使化 GPIO 模块、初始化 GIC;

③ 准备好一切之后,使能中断。

2. 如何确认中断号

我们要知道发生了什么中断,需要确认中断发生得到时候的中断号,那么怎么确认中断号?我们前边知道了按键接在了GPIO1_IO18上边,我们可以查看《I.MX6UL参考手册》的3.2 Cortex A7 interrupts一节,找到这个GPIO管脚对应的中断号:

image-20260115185421500

可以看到GPIO1的0 -15管脚使用的是66,16 - 31使用的是67,这里只是IRQ的编号,对应到 GIC 的 SPI中断号需要在此编号基础上加上 32,所以这里的按键中断号实际为99(67+32)。这个其实在我们之前移植的SDK包里边就有定义,我们打开 MCIMX6Y2.h 文件,有如下内容:

c
  GPIO1_INT7_IRQn              = 90,               /**< INT7 interrupt request. */
  GPIO1_INT6_IRQn              = 91,               /**< INT6 interrupt request. */
  GPIO1_INT5_IRQn              = 92,               /**< INT5 interrupt request. */
  GPIO1_INT4_IRQn              = 93,               /**< INT4 interrupt request. */
  GPIO1_INT3_IRQn              = 94,               /**< INT3 interrupt request. */
  GPIO1_INT2_IRQn              = 95,               /**< INT2 interrupt request. */
  GPIO1_INT1_IRQn              = 96,               /**< INT1 interrupt request. */
  GPIO1_INT0_IRQn              = 97,               /**< INT0 interrupt request. */
  GPIO1_Combined_0_15_IRQn     = 98,               /**< Combined interrupt indication for GPIO1 signals 0 - 15. */
  GPIO1_Combined_16_31_IRQn    = 99,               /**< Combined interrupt indication for GPIO1 signals 16 - 31. */
  GPIO2_Combined_0_15_IRQn     = 100,              /**< Combined interrupt indication for GPIO2 signals 0 - 15. */
  GPIO2_Combined_16_31_IRQn    = 101,              /**< Combined interrupt indication for GPIO2 signals 16 - 31. */
  GPIO3_Combined_0_15_IRQn     = 102,              /**< Combined interrupt indication for GPIO3 signals 0 - 15. */
  GPIO3_Combined_16_31_IRQn    = 103,              /**< Combined interrupt indication for GPIO3 signals 16 - 31. */
  GPIO4_Combined_0_15_IRQn     = 104,              /**< Combined interrupt indication for GPIO4 signals 0 - 15. */
  GPIO4_Combined_16_31_IRQn    = 105,              /**< Combined interrupt indication for GPIO4 signals 16 - 31. */
  GPIO5_Combined_0_15_IRQn     = 106,              /**< Combined interrupt indication for GPIO5 signals 0 - 15. */
  GPIO5_Combined_16_31_IRQn    = 107,              /**< Combined interrupt indication for GPIO5 signals 16 - 31. */

这里其实已经为我们定义好了中断号的枚举类型。上边我们知道多个GPIO引脚都会产生这个中断号,所以我们需要注意:当发生 GIC 99 号中断时,表示发生了 GPIO1 中 interrupt 0 ~15,然后需要进一步细分出是 GPIO1 里的哪一个中断。

3. GIC 控制器基地址的获取方法

直接查数据手册 《i.MX 6ULL Applications ProcessorReference Manual 》的Table 2-1. System memory map,可以知道 gic 的基地址是 0xA0000,如下图:

image-20260116091751592

对于 GIC 基地址,还可以通过 CP15 查询,下面指令将 GIC 的基地址读到 r0 寄存器:

assembly
mrc p15, 4, r0, c15, c0, 0

四、软件设计

1. 使用NXP官方SDK实现

1.1 移植 SDK 包中断相关文件

将 SDK 包中的文件 core_ca7.h 拷贝到工程中的“imx6ul”文件夹中,并需要进行修改进行修改。主要留下和 GIC 相关的内容,我们重点是需要core_ca7.h 中的 10 个 API 函数,这 10 个函数如表所示:

函数描述
GIC_Init初始化 GIC。
GIC_EnableIRQ使能指定的外设中断。
GIC_DisableIRQ关闭指定的外设中断。
GIC_AcknowledgeIRQ返回中断号。
GIC_DeactivateIRQ无效化指定中断。
GIC_GetRunningPriority获取当前正在运行的中断优先级。
GIC_SetPriorityGrouping设置抢占优先级位数。
GIC_GetPriorityGrouping获取抢占优先级位数。
GIC_SetPriority设置指定中断的优先级。
GIC_GetPriority获取指定中断的优先级。

移植好 core_ca7.h 以后,修改文件 imx6ul.h,在里面加上如下一行代码:

c
#include "core_ca7.h"

1.2 重新编写 start.S

完整的可以看这里:07_EXIT/01_nxp_sdk_exit/project/start.S,这里只写一部分重要的内容:

assembly
/* 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 */

IRQ 中断服务函数主要的工作就是区分当前发生的什么中断,也就是获取发生中断的中断号。然后针对不同的外部中断做出不同的处理。

1.2.1 保存现场
assembly
	push {lr}					/* 保存lr地址 */
	push {r0-r3, r12}			/* 保存r0-r3,r12寄存器 */

	mrs r0, spsr				/* 读取spsr寄存器 */
	push {r0}					/* 保存spsr寄存器 */
1.2.2 获取当前中断号
assembly
	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寄存器保存这当前发生中断的中断号,我们要根据
								 * 这个中断号来绝对调用哪个中断服务函数
								 */

经过这一步,中断号被保存到了 r0 寄存器中。

1.2.3 调用中断处理函数
assembly
	push {r0, r1}				/* 保存r0,r1 */
	
	cps #0x13					/* 进入SVC模式,允许其他中断再次进去 */
	
	push {lr}					/* 保存SVC模式的lr寄存器 */
	ldr r2, =system_irqhandler	/* 加载C语言中断处理函数到r2寄存器中*/
	blx r2						/* 运行C语言中断处理函数,带有一个参数,保存在R0寄存器中 */

这里先讲R0和R1中的数据入栈,随后调用了函数 system_irqhandler,函数 system_irqhandler 是一个 C 语言函数,此函数有一个参数,这个参数就是中断号,所以我们需要传递一个参数。汇编中调用 C 函数如何实现参数传递呢?根据 ATPCS(ARM-Thumb Procedure Call Standard)定义的函数参数传递规则,在汇编调用 C 函数的时候建议形参不要超过 4 个, 形参可以由 r0~r3 这四个寄存器来传递,如果形参大于 4 个, 那么大于 4 个的部分要使用堆栈进行传递。 所以给 r0 寄存器写入中断号就可以了函数 system_irqhandler 的参数传递,前边已经向 r0 寄存器写入了中断号了。中断的真正处理过程其实是在函数 system_irqhandler 中完成,稍后需要编写函数 system_irqhandler。

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

最后向 GICC_EOIR 寄存器写入刚刚处理完成的中断号, 当一个中断处理完成以后必须向 GICC_EOIR 寄存器写入其中断号表示中断处理完成。

1.2.5 恢复现场
assembly
	pop {r0}						
	msr spsr_cxsf, r0			/* 恢复spsr */

	pop {r0-r3, r12}			/* r0-r3,r12出栈 */
	pop {lr}					/* lr出栈 */
1.2.6 跳回原来的地方运行
assembly
	subs pc, lr, #4				/* 将lr-4赋给pc */

中断处理完成以后就要重新返回到曾经被中断打断的地方运行,这里为什么要将lr-4 然后赋给 pc 呢?而不是直接将 lr 赋值给 pc? ARM 的指令是三级流水线:取指、译指、执行, pc 指向的是正在取值的地址,这就是很多书上说的 pc=当前执行指令地址+8。比如下面代码示例:

assembly
0X2000 MOV R1, R0 ;执行
0X2004 MOV R2, R3 ;译指
0X2008 MOV R4, R5 ;取值 PC

上面示例代码中,左侧一列是地址,中间是指令,最右边是流水线。当前正在执行 0X2000地址处的指令“MOV R1, R0”,但是 PC 里面已经保存了 0X2008 地址处的指令“MOV R4, R5”。假设此时发生了中断,中断发生的时候保存在 lr 中的是 pc 的值,也就是地址 0X2008。当中断处理完成以后肯定需要回到被中断点接着执行,如果直接跳转到 lr 里面保存的地址处(0X2008)开始运行,那么就有一个指令没有执行,那就是地址 0X2004 处的指令“MOV R2, R3”,显然这是一个很严重的错误!所以就需要将 lr-4 赋值给 pc,也就是 pc=0X2004,从指令“MOV R2,R3”开始执行。

1.3 通用中断驱动文件

在 start.S 文件中我们在中断服务函数 IRQ_Handler 中调用了 C 函数 system_irqhandler 来处理具体的中断。此函数有一个参数,参数是中断号,但是函数 system_irqhandler 的具体内容还没有实现,所以需要实现函数 system_irqhandler 的具体内容。不同的中断源对应不同的中断处理函数, I.MX6U 有 160 个中断源,所以需要 160 个中断处理函数,我们可以将这些中断处理函数放到一个数组里面,中断处理函数在数组中的标号就是其对应的中断号。当中断发生以后函数 system_irqhandler 根据中断号从中断处理函数数组中找到对应的中断处理函数并执行即可。

在 bsp 目录下新建名为“int”的文件夹,在 bsp/int 文件夹里面创建 bsp_int.c 和 bsp_int.h 这两个文件。 07_EXIT/01_nxp_sdk_exit/bsp/int

image-20260116092607500

1.4 GPIO驱动文件

前面的实验中我们只是使用到了 GPIO 最基本的输入输出功能,本章我们需要使用GPIO 的中断功能。所以需要修改文件 GPIO 的驱动文件bsp_gpio.c 和 bsp_gpio.h,加上中断相关函数。07_EXIT/01_nxp_sdk_exit/bsp/gpio

1.5 按键中断驱动文件

本实验的目的是以中断的方式编写 KEY 按键驱动,当按下 KEY 以后触发 GPIO 中断,然后在中断服务函数里面控制蜂鸣器的开关。所以接下来是要编写按键 KEY 对应的UART1_CTS 这个 IO 的中断驱动,在 bsp 文件夹里面新建名为“exit”的文件夹,然后在 bsp/exit里面新建 bsp_exit.c 和 bsp_exit.h 两个文件。 可以看这里:07_EXIT/01_nxp_sdk_exit/bsp/exit

1.6 完整代码

可以看这里:07_EXIT · sumumm/imx6ull-bare-demo

2. 自己写一个?

上面是要用到NXP提供的SDK的,为了加深理解,这里我们其实可以自己试下实现GIC的一些控制操作,这里参考的是韦东山裸机开发教程的相关章节。这里我没有调试,直接参考了下教程,学习一下思路而已。

2.1 GIC初始化

gic.c文件中gic_init 函数实现了如下功能:

① 通过 CP15 获取 GIC 的基地址;

② 读取 GICD_TYPER 寄存器获得中断的数目 ;

③ 往 GICD_ ICENABLERn 寄存器写入 0xFFFFFFFF 禁用所有的 SGI, PPI 和SPI;

④ 通过 GICC_PMR 设置优先级等级,设置为 0xF8;

⑤ 将 GICC_BPR 设置为 2,这允许各个优先级进行抢占;

⑥ 最后使能 group0 的 distributor 和 CPU interface。

c
void gic_init(void)
{
	u32 i, irq_num;

	GIC_Type *gic = get_gic_base();

	/* the maximum number of interrupt IDs that the GIC supports */
	irq_num = (gic->D_TYPER & 0x1F) + 1;

	/* On POR, all SPI is in group 0, level-sensitive and using 1-N model */
	
	/* Disable all PPI, SGI and SPI */
	for (i = 0; i < irq_num; i++)
	  gic->D_ICENABLER[i] = 0xFFFFFFFFUL;

	/* The priority mask level for the CPU interface. If the priority of an 
	 * interrupt is higher than the value indicated by this field, 
	 * the interface signals the interrupt to the processor.
	 */
	gic->C_PMR = (0xFFUL << (8 - 5)) & 0xFFUL;
	
	/* No subpriority, all priority level allows preemption */
	gic->C_BPR = 7 - 5;
	
	/* Enables the forwarding of pending interrupts from the Distributor to the CPU interfaces.
	 * Enable group0 distribution
	 */
	gic->D_CTLR = 1UL;
	
	/* Enables the signaling of interrupts by the CPU interface to the connected processor
	 * Enable group0 signaling 
	 */
	gic->C_CTLR = 1UL;
}

2.2 中断异常处理汇编部分

start.S 中对于中断的处理,概括如下:

  • 在异常向量表偏移为 0x18 的地方使用“ ldr pc, =IRQ_Handler”跳转;
  • IRQ_Handler 标号的处理可以简单分为:保存现场,执行 C 函数,恢复现场:

在 IRQ_Handler 标号,处理器处于中断模式,“ lr_irq - 4”就是被中断的、尚未执行的指令的地址,我们将 r0-r12 和“ lr-4”都保存在栈上。然后调用C 函数 handle_irq_c 来处理中断。C 函数返回来后,执行ldmia sp!, {r0-r12, pc}^,这条指令做的事情可多了:

  • 把保存在栈上的值恢复到 r0-r12,把之前保存的“ lr_irq - 4”恢复到PC。
  • 把 SPSR 中保存的被中断状态的 CPSR,恢复到 CPSR(指令后的“ ^”号表示这个操作) 。
assembly
	
.text
.global  _start, _vector_table
_start:
_vector_table:
	ldr 	pc, =Reset_Handler			 /* Reset				   */
	ldr 	pc, =Undefined_Handler		 /* Undefined instructions */
	ldr 	pc, =SVC_Handler			 /* Supervisor Call 	   */
	b halt//ldr 	pc, =PrefAbort_Handler		 /* Prefetch abort		   */
	b halt//ldr 	pc, =DataAbort_Handler		 /* Data abort			   */
	.word	0							 /* RESERVED			   */
	ldr 	pc, =IRQ_Handler			 /* IRQ interrupt		   */
	b halt//ldr 	pc, =FIQ_Handler			 /* FIQ interrupt		   */

.align 2
Undefined_Handler:
	/* 执行到这里之前:
	 * 1. lr_und保存有被中断模式中的下一条即将执行的指令的地址
	 * 2. SPSR_und保存有被中断模式的CPSR
	 * 3. CPSR中的M4-M0被设置为11011, 进入到und模式
	 * 4. 跳到0x4的地方执行程序 
	 */

	/* 在und异常处理函数中有可能会修改r0-r12, 所以先保存 */
	/* lr是异常处理完后的返回地址, 也要保存 */
	stmdb sp!, {r0-r12, lr}  
	
	/* 保存现场 */
	/* 处理und异常 */
	mrs r0, cpsr
	ldr r1, =und_string
	bl printException
	
	/* 恢复现场 */
	ldmia sp!, {r0-r12, pc}^  /* ^会把spsr的值恢复到cpsr里 */
	
und_string:
	.string "undefined instruction exception"

.align 2
SVC_Handler:
	/* 执行到这里之前:
	 * 1. lr_svc保存有被中断模式中的下一条即将执行的指令的地址
	 * 2. SPSR_svc保存有被中断模式的CPSR
	 * 3. CPSR中的M4-M0被设置为10011, 进入到svc模式
	 * 4. 跳到0x08的地方执行程序 
	 */

	/* 保存现场 */
	/* 在swi异常处理函数中有可能会修改r0-r12, 所以先保存 */
	/* lr是异常处理完后的返回地址, 也要保存 */
	stmdb sp!, {r0-r12, lr}  

	mov r4, lr
	
	/* 处理swi异常 */
	mrs r0, cpsr
	ldr r1, =swi_string
	bl printException

	sub r0, r4, #4
	bl printSWIVal
	
	/* 恢复现场 */
	ldmia sp!, {r0-r12, pc}^  /* ^会把spsr的值恢复到cpsr里 */
	
swi_string:
	.string "swi exception"

.align 2
IRQ_Handler:
	/* 执行到这里之前:
	 * 1. lr_irq保存有被中断模式中的下一条即将执行的指令的地址
	 * 2. SPSR_irq保存有被中断模式的CPSR
	 * 3. CPSR中的M4-M0被设置为10010, 进入到irq模式
	 * 4. 跳到0x18的地方执行程序 
	 */

	/* 保存现场 */
	/* 在irq异常处理函数中有可能会修改r0-r12, 所以先保存 */
	/* lr-4是异常处理完后的返回地址, 也要保存 */
	sub lr, lr, #4
	stmdb sp!, {r0-r12, lr}  
	
	/* 处理irq异常 */
	bl handle_irq_c
	
	/* 恢复现场 */
	ldmia sp!, {r0-r12, pc}^  /* ^会把spsr_irq的值恢复到cpsr里 */	


.align 2
Reset_Handler:
	/* Reset SCTlr Settings */
	mrc 	p15, 0, r0, c1, c0, 0	  /* read SCTRL, Read CP15 System Control register		*/
	bic 	r0,  r0, #(0x1 << 13)	  /* Clear V bit 13 to use normal exception vectors  	*/
	bic 	r0,  r0, #(0x1 << 12)	  /* Clear I bit 12 to disable I Cache					*/
	bic 	r0,  r0, #(0x1 <<  2)	  /* Clear C bit  2 to disable D Cache					*/
	bic 	r0,  r0, #(0x1 << 2)	  /* Clear A bit  1 to disable strict alignment 		*/
	bic 	r0,  r0, #(0x1 << 11)	  /* Clear Z bit 11 to disable branch prediction		*/
	bic 	r0,  r0, #0x1			  /* Clear M bit  0 to disable MMU						*/
	mcr 	p15, 0, r0, c1, c0, 0	  /* write SCTRL, Write to CP15 System Control register	*/

    cps     #0x1B                /* Enter undef mode                */
    ldr     sp, =0x80300000     /* Set up undef mode stack      */

    cps     #0x12                /* Enter irq mode                */
    ldr     sp, =0x80400000     /* Set up irq mode stack      */

    cps     #0x13                /* Enter Supervisor mode         */
    ldr     sp, =0x80200000     /* Set up Supervisor Mode stack  */

	ldr r0, =_vector_table
	mcr p15, 0, r0, c12, c0, 0  /* set VBAR, Vector Base Address Register*/
	//mrc p15, 0, r0, c12, c0, 0  //read VBAR

	bl clean_bss

	bl system_init
	cpsie	i					 /* Unmask interrupts			  */

	bl main

halt:
	b  halt


clean_bss:
	/* 清除BSS段 */
	ldr r1, =__bss_start
	ldr r2, =__bss_end
	mov r3, #0
clean:
	cmp r1, r2
	strlt r3, [r1]
	add r1, r1, #4
	blt clean
	
	mov pc, lr

注意

(1)执行 Reset_Handler 时, CPU 处于 IRQ 模式,用的是 IRQ 模式下的栈,需要先在 Reset_Handler 里设置好 IRQ 模式的栈,这样在中断模式里才可以使用栈,才能调用 C 函数。

(2)在 Reset_Handler 里调用“ cpsie i”打开中断,这是把 CPSR 中的 I 位清零。

(3)在 Reset_Handler 里使用如下两条指令设置异常向量的基地址,。

assembly
ldr r0, =_vector_table
mcr p15, 0, r0, c12, c0, 0 /* set VBAR, Vector Base Address Register*/

2.3 中断异常处理 C 函数部分

gic.c文件中handle_irq_c 函数功能简述如下:

① 获取到 gic 的基地址;

② 读取 GICC_IAR 获得中断号;

③ 根据中断号调用对应中断号的 irq_handler 函数,该函数是用户通过request_irq 注册的中断处理函数;

④ 然后往 GICC_EOIR 写入中断号清除掉中断。

c
void handle_irq_c(void)
{
	int nr;

	GIC_Type *gic = get_gic_base();
	/* The processor reads GICC_IAR to obtain the interrupt ID of the
	 * signaled interrupt. This read acts as an acknowledge for the interrupt
	 */
	nr = gic->C_IAR;
	printf("irq %d is happened\r\n", nr);
	
	irq_table[nr].irq_handler(nr, irq_table[nr].param);

	/* write GICC_EOIR inform the CPU interface that it has completed 
	 * the processing of the specified interrupt 
	 */
	gic->C_EOIR = nr;
}

谁调用 reqeust_irq 设置了 irq_table[nr].irq_handler?后面会有说明。

2.4 GPIO 中断初始化和注册中断处理程序

先看看main.c中初始化函数 key_irq_init,功能如下(以 KEY1 为例):

① 对于 KEY1,对应的引脚是 GPIO5_01,通过 EDGE_SEL 设置成双边沿触发;

② 设置 IMR 使能中断;

③ 为了防止误触发,先将 ISR 对应位写 1 清除掉中断;

④ 调用 request_irq 注册对应中断的中断处理函数,就是设置 irq_table数组中某一项,设置函数指针。对于 GPIO5_01,处理函数是 key_gpio5_handle_irq;对于 GPIO4_14,处理函数是 key_gpio4_handle_irq。

c
void key_irq_init(void)
{
	/* if set detects any edge on the corresponding input signal*/
	GPIO5->EDGE_SEL |= (1 << 1);
	/* if set 1, unmasked, Interrupt n is enabled */
	GPIO5->IMR |= (1 << 1);
	/* clear interrupt first to avoid unexpected event */
	GPIO5->ISR |= (1 << 1);

	GPIO4->EDGE_SEL |= (1 << 14);
	GPIO4->IMR |= (1 << 14);
	GPIO4->ISR |= (1 << 14);

	request_irq(GPIO5_Combined_0_15_IRQn, (irq_handler_t)key_gpio5_handle_irq, NULL);
	request_irq(GPIO4_Combined_0_15_IRQn, (irq_handler_t)key_gpio4_handle_irq, NULL);
}

还是以 KEY1 为例说明处理函数 key_gpio5_handle_irq(main.c中),它的功能如下

① 读取 GPIO_DR 寄存器,根据 GPIO5_01 的状态打印信息、操作 LED ;

② 在 GPIO 模块内部清除中断 。

c
void key_gpio5_handle_irq(void)
{
	/* read GPIO5_DR to get GPIO5_IO01 status*/
	if((GPIO5->DR >> 1 ) & 0x1) {
		printf("key 1 is release\r\n");
		/* led off, set GPIO5_DR to configure GPIO5_IO03 output 1 */
		GPIO5->DR |= (1<<3); //led on
	} else {
		printf("key 1 is press\r\n");
		/* led on, set GPIO5_DR to configure GPIO5_IO03 output 0 */
		GPIO5->DR &= ~(1<<3); //led off
	}
	/* write 1 to clear GPIO5_IO03 interrput status*/
	GPIO5->ISR |= (1 << 1);
}

void key_gpio4_handle_irq(void)
{
	/* read GPIO4_DR to get GPIO4_IO014 status*/
	if((GPIO4->DR >> 14 ) & 0x1)
		printf("key 2 is release\r\n");
	else
		printf("key 2 is press\r\n");
	/* write 1 to clear GPIO4_IO014 interrput status*/
	GPIO4->ISR |= (1 << 14);
}

2.5 特定中断号的中断使能和禁止

设置好一切之后,就是使能中断了。对于GIC,程序里使用gic.c 中的 gic_enable_irq,它的功能为:

  • 根据中断号找到对应的 GICD_ISENABLERn 寄存器;
  • 往相应位中写入 1,即可使能中断。

要关闭中断时 ,操作是类似的 ,函数是 gic_disable_irq ,通过往GICD_ICENABLERn 对应的位写入 1 来禁止中断。

c
void gic_enable_irq(IRQn_Type nr)
{
	GIC_Type *gic = get_gic_base();

	/* The GICD_ISENABLERs provide a Set-enable bit for each interrupt supported by the GIC.
	 * Writing 1 to a Set-enable bit enables forwarding of the corresponding interrupt from the
	 * Distributor to the CPU interfaces. Reading a bit identifies whether the interrupt is enabled.
	 */
	gic->D_ISENABLER[nr >> 5] = (uint32_t)(1UL << (nr & 0x1FUL));

}
		  			 		  						  					  				 	   		  	  	 	  
void gic_disable_irq(IRQn_Type nr)
{
	GIC_Type *gic = get_gic_base();

	/* The GICD_ICENABLERs provide a Clear-enable bit for each interrupt supported by the
	 * GIC. Writing 1 to a Clear-enable bit disables forwarding of the corresponding interrupt from
     * the Distributor to the CPU interfaces. Reading a bit identifies whether the interrupt is enabled. 
	 */
	gic->D_ICENABLER[nr >> 5] = (uint32_t)(1UL << (nr & 0x1FUL));
}

2.6 修改 CPSR 使能中断

在 start.S 中,可以看到如下代码,它把 CP15 中 SCTRL 的值读出后,把I bit 清零,再写入。这就是在 CPU 核中使能 IRQ 中断。

assembly
Reset_Handler:
	/* Reset SCTlr Settings */
	mrc 	p15, 0, r0, c1, c0, 0	  /* read SCTRL, Read CP15 System Control register		*/
	bic 	r0,  r0, #(0x1 << 13)	  /* Clear V bit 13 to use normal exception vectors  	*/
	bic 	r0,  r0, #(0x1 << 12)	  /* Clear I bit 12 to disable I Cache					*/
	bic 	r0,  r0, #(0x1 <<  2)	  /* Clear C bit  2 to disable D Cache					*/
	bic 	r0,  r0, #(0x1 << 2)	  /* Clear A bit  1 to disable strict alignment 		*/
	bic 	r0,  r0, #(0x1 << 11)	  /* Clear Z bit 11 to disable branch prediction		*/
	bic 	r0,  r0, #0x1			  /* Clear M bit  0 to disable MMU						*/
	mcr 	p15, 0, r0, c1, c0, 0	  /* write SCTRL, Write to CP15 System Control register	*/
    /* ...... */

2.7 完整代码

完整代码看这里:07_EXIT/02_my_exit