不同的处理器对中断的处理流程大体相同,但是具体的实现细节会差别。
ARM中断也是异常的一种,ARM处理器有以下几种异常源:
中断虽然是异常,但不完全是错误,异常也不是,而是为了完成某些功能的东西,只有Undefined Instruction异常是真正的系统错误。
这些在ARM异常机制也介绍过,这里主要介绍的是IRQ。
在ARM处理器中,CPU收到一个中断时,以IRQ为例,大概会执行以下过程:
在ARM处理器中,有上百个中断源,可能同时产生多个中断信号,会产生问题。
比如:
在复杂封装的ARM处理器芯片中还会产生一系列问题,这时候就需要一个中断控制器GIC,去统一管理外部产生的中断信号。
中断控制器做了至少四件事:
这样就解决了前面提到的几个问题,除此之外中断控制器还做了一系列管理中断的事情。
Exynos4412这种SOC要比单片机功能强大很多,相关的寄存器配置也及其复杂,SOC一般会安装一个操作系统,这些事情一般都由操作系统内核去调度。
这里裸机实验就一切从简,只实现基本的中断功能,对于设置中断类型、优先级和选择处理器等不做配置。
外围电路原理图:按键Key2连接的引脚是UART_RING。
核心板原理图:所连接的是GPX1_1引脚,同时它是外部中断9的中断源:XEINT9。
Exynos4412中断机制比较复杂,有160个中断,分为三种中断类型:
我们使用的是SPI,提供的SPI类型中断有128个(0-127)。
1. 首先是中断源的配置,即引脚相关寄存器:
GPX1CON GPIO配置寄存器,GPX1CON[1]设为中断功能,0xF。
前面提到我们使用的是外部中断9,但是它使用的中断配置寄存器叫EXT_INT41,不知道三星公司为什么这样设计,但是它正好在寄存器列表的GPX相关寄存器下面。
EXT_INT41_CON 中断配置寄存器,EXT_INT41_CON[1]设为下降沿触发,0x2。
EXT_INT41_MASK 中断屏蔽寄存器,默认是打开屏蔽,需要关闭屏蔽使能中断功能,EXT_INT41_MASK[1]设为0。
EXT_INT41_PEND,挂起状态寄存器
EXT_INT41_PEND寄存器在该引脚产生中断时,会自动挂起变成1,在CPU处理了这个中断后,这个寄存器的值不会改变,会不断的发出中断信号,所以中断处理程序在处理完这个中断之后需要修改这个寄存器的值。
这个寄存器很特殊,写0的效果是变成1,写1变成0,所以中断处理程序需要写1,它会变成0。
至此中断源就配置好了,但是Exynos4412的CPU不直接去接收中断信号,而是通过一个中断控制器去管理中断,所以还需要配置一系列中断控制器的配置。
2. 中断控制器相关配置:
首先要找到这个中断源对应的中断号是多少。
中断表的中断号是第二列ID,第一列只是中断类型为SPI的编号。
...
ID: 141-68略
...
可以看到我们要使用的外部中断9的中断号是57,后面会用到。
ICDDCR寄存器
ICDDCR,整个中断控制器的总开关,让中断控制器能够接收外部的中断信号,必须要打开。ICDDCR = 1。
ICDISER_CPU
这组寄存器的作用是使能某个中断与CPU的接口(它的表述上是打开中断与CPU的接口,实际上只是打开中断与中断控制器的接口,真正与CPU交互的是中断控制器)。
Exynos4412有160个中断,每个寄存器是32位,所以可以控制32个中断,一共需要5个寄存器。
(但是它提供了8个寄存器,后面3个是与PPI和SGI相关寄存器,与SPI中断无关)
它的对应关系如下:
57号中断所在的寄存器地址偏移量为0x0104,结合上一张图0x0104对应的寄存器名为ICDISER1_CPU0,在三星提供的头文件中寄存器名是ICDISER1。
所以打开中断57与CPU0的接口就是设置 ICDISER1_CPU0[25]=1。
ICDIPTR_CPU
ICDIPTR_CPU寄存器设置用于告诉中断控制器这个中断该转发给哪个CPU。
它的数量很多对应关系比较复杂,直接给出结果:ICDIPTR14[15:8] = 0x01。
ICCICR_CPU
至此只是告诉中断控制器这个中断转发给哪个CPU,但是中断控制器与这个CPU之间的接口还没打开。
刚刚设置了要转发给CPU0,那么需要打开GIC与CPU0的接口:ICCICR_CPU0 = 1。
ICCEOIR
这个寄存器的作用是当中断处理程序执行完以后,CPU告诉GIC当前处理的中断号程序已经处理完,GIC如果还有挂起的中断,就可以发给CPU了。
在中断处理程序结束之前:ICCEOIR_CPU0 = 57(告诉GIC处理完57号中断)。
配置完以上寄存器就配置了以下功能:
3. 除此之外,还有其它一系列寄存器设置,前面也提到只实现基本的中断功能,所以其它寄存器就不做设置。
中断实验需要修改中断向量表,另外一篇会介绍裸机实验的工程模板,还没写好,这里不作介绍。
启动汇编程序start.S(这里只需要关注7-14行和93-98行):
1 .text 2 .global _start 3 _start: 4 /* 5 * Vector table 6 */ 7 b reset 8 b . 9 b . 10 b . 11 b . 12 b . 13 b irq_handler 14 b . 15 16 reset: 17 /* 18 * Set vector address in CP15 VBAR register 19 */ 20 ldr r0, =_start 21 mcr p15, 0, r0, c12, c0, 0 @Set VBAR 22 23 /* 24 * Set the cpu to SVC32 mode, Disable FIQ/IRQ 25 */ 26 mrs r0, cpsr 27 bic r0, r0, #0x1f 28 orr r0, r0, #0xd3 29 msr cpsr ,r0 30 31 /* 32 * Defines access permissions for each coprocessor 33 */ 34 mov r0, #0xfffffff 35 mcr p15, 0, r0, c1, c0, 2 36 37 /* 38 * Invalidate L1 I/D 39 */ 40 mov r0, #0 @Set up for MCR 41 mcr p15, 0, r0, c8, c7, 0 @Invalidate TLBs 42 mcr p15, 0, r0, c7, c5, 0 @Invalidate icache 43 44 /* 45 * Set the FPEXC EN bit to enable the FPU 46 */ 47 mov r3, #0x40000000 48 fmxr FPEXC, r3 49 50 /* 51 * Disable MMU stuff and caches 52 */ 53 mrc p15, 0, r0, c1, c0, 0 54 bic r0, r0, #0x00002000 @Clear bits 13 (--V-) 55 bic r0, r0, #0x00000007 @Clear bits 2:0 (-CAM) 56 orr r0, r0, #0x00001000 @Set bit 12 (---I) Icache 57 orr r0, r0, #0x00000002 @Set bit 1 (--A-) Align 58 orr r0, r0, #0x00000800 @Set bit 11 (Z---) BTB 59 mcr p15, 0, r0, c1, c0, 0 60 61 /* 62 * Initialize stacks 63 */ 64 init_stack: 65 /*svc mode stack*/ 66 msr cpsr, #0xd3 67 ldr sp, _stack_svc_end 68 69 /*undef mode stack*/ 70 msr cpsr, #0xdb 71 ldr sp, _stack_und_end 72 73 /*abort mode stack*/ 74 msr cpsr,#0xd7 75 ldr sp,_stack_abt_end 76 77 /*irq mode stack*/ 78 msr cpsr,#0xd2 79 ldr sp, _stack_irq_end 80 81 /*fiq mode stack*/ 82 msr cpsr,#0xd1 83 ldr sp, _stack_fiq_end 84 85 /*user mode stack, enable FIQ/IRQ*/ 86 msr cpsr,#0x10 87 ldr sp, _stack_usr_end 88 89 /*Call main*/ 90 b main 91 92 /* IRQ的异常处理程序 */ 93 irq_handler: 94 /* 因为产生IRQ异常时LR自动保存的是当前执行指令的下下条指令 */ 95 sub lr,lr,#4 /* 修正LR位为中断前执行指令的下条指令 */ 96 stmfd sp!,{r0-r12,lr} /* 压栈保护现场 */ 97 bl do_irq /* 跳转到中断处理程序 */ 98 ldmfd sp!,{r0-r12,pc}^ /* 出栈恢复现场,同时恢复中断之前的模式 */ 99 100 101 _stack_svc_end: 102 .word stack_svc + 512 103 _stack_und_end: 104 .word stack_und + 512 105 _stack_abt_end: 106 .word stack_abt + 512 107 _stack_irq_end: 108 .word stack_irq + 512 109 _stack_fiq_end: 110 .word stack_fiq + 512 111 _stack_usr_end: 112 .word stack_usr + 512 113 114 .data 115 stack_svc: 116 .space 512 117 stack_und: 118 .space 512 119 stack_abt: 120 .space 512 121 stack_irq: 122 .space 512 123 stack_fiq: 124 .space 512 125 stack_usr: 126 .space 512
interface.c:
/* * 按下K2,串口打印信息 * 按下K3,LED点亮,再按熄灭 * */ #include "exynos_4412.h" void do_irq() { /* 从中断控制器中获取当前中断的中断号 */ unsigned int IrqNum = CPU0.ICCIAR & 0x3FF; switch(IrqNum) { case 57: printf("Key2 pressed\n"); /* 另一个文件实现的重定向到UART的printf */ EXT_INT41_PEND = (1<<1); /* 把中断控制器的挂起清除 */ /* 告诉中断控制器当前中断已经处理完成 */ /* 把当前中断的中断号写回中断控制器 */ CPU0.ICCEOIR = CPU0.ICCEOIR & (~0x3FF) | 57; break; case 58: LED2_Turn(); EXT_INT41_PEND = (1<<2); /* 把中断控制器的挂起清除 */ /* 告诉中断控制器当前中断已经处理完成 */ /* 把当前中断的中断号写回中断控制器 */ CPU0.ICCEOIR = CPU0.ICCEOIR & (~0x3FF) | 58; break; default: break; } } void KEY2_INT_Init() { /* 外围层次 */ /* GPX1_1设为中断功能 */ GPX1.CON = GPX1.CON | (0xF<<4); /* 下降沿触发 */ EXT_INT41_CON = EXT_INT41_CON & (~(0x7<<4)) | (0x2<<4); /* 打开中断,写 0 Enable */ EXT_INT41_MASK = EXT_INT41_MASK & (~(0x1<<1)); /* SOC内层 中断控制器 */ /* 中断控制器全局使能,让中断控制器接收外部的中断信号 */ ICDDCR = ICDDCR | 1; /* 中断控制器中使能57号中断 */ ICDISER.ICDISER1 = ICDISER.ICDISER1 | (1<<25); /* 设置57号中断发给CPU0处理 */ ICDIPTR.ICDIPTR14 = ICDIPTR.ICDIPTR14 & (~(0xFF<<8)) | (0x01<<8); /* 打开中断控制器与CPU0的接口 */ CPU0.ICCICR = CPU0.ICCICR | 1; } void KEY3_INT_Init() { /* 外围层次 */ /* GPX1_1 设为中断功能 */ GPX1.CON = GPX1.CON | (0xF<<8); /* EXT_INT41_CON[2] 设为下降沿触发 */ EXT_INT41_CON = EXT_INT41_CON & (~(0x7<<8)) | (0x2<<8); /* EXT_INT41_MASK[2] 打开中断,写 0 Enable */ EXT_INT41_MASK = EXT_INT41_MASK & ~(0x1<<2); /* SOC内层 中断控制器 */ /* 中断控制器全局使能,让中断控制器接收外部的中断信号 */ ICDDCR = ICDDCR | 1; /* 中断控制器中使能58号中断 */ ICDISER.ICDISER1 = ICDISER.ICDISER1 | (1<<26); /* 设置58号中断发给CPU0处理 */ ICDIPTR.ICDIPTR14 = ICDIPTR.ICDIPTR14 & (~(0xFF<<16)) | (0x1<<16); /* 打开中断控制器与CPU0的接口 */ CPU0.ICCICR = CPU0.ICCICR | 1; } void LED2_Init() { GPX2.CON = GPX2.CON & (~(0xF<<28)) | (0x1<<28); } void LED2_On() { GPX2.DAT |= (1<<7); } void LED2_Off() { GPX2.DAT &= (~(1<<7)); } void LED2_Turn() { if(GPX2.DAT & (1<<7))/* 条件为真,则关掉LED */ LED2_Off(); else LED2_On(); } int main() { KEY2_INT_Init(); KEY3_INT_Init(); LED2_Init(); while(1); return 0; }
按下K2,终端会打印信息: Key2 pressed。
按下K3,LED2会打开,再次按下LED2会熄灭。