CPU核只有一个中断引脚和一个快速中断引脚。还需要一个中断控制器,中断控制器是外围设备,用于实现多中断引脚、优先级控制等。
中断控制器属于外围设备,外接各中断引脚,内接CPU核,控制中断的产生和优先级等。
中断处理框图如下:
s3c2440通过下列寄存器来控制中断控制器:
中断源编号,和寄存器中的位对应:
Source | Bit | Source | Bit | Source | Bit | Source | Bit |
INT_ADC | 31 | INT_UART1 | 23 | INT_UART2 | 15 | nBATT_FLT | 7 |
INT_RTC | 30 | INT_SPI0 | 22 | INT_TIMER4 | 14 | INT_CAM | 6 |
INT_SPI1 | 29 | INT_SDI | 21 | INT_TIMER3 | 13 | EINT8_23 | 5 |
INT_UART0 | 28 | INT_DMA3 | 20 | INT_TIMER2 | 12 | EINT4_7 | 4 |
INT_IIC | 27 | INT_DMA2 | 19 | INT_TIMER1 | 11 | EINT3 | 3 |
INT_USBH | 26 | INT_DMA1 | 18 | INT_TIMER0 | 10 | EINT2 | 2 |
INT_USBD | 25 | INT_DMA0 | 17 | INT_WDT_AC97 | 9 | EINT1 | 1 |
INT_NFCON | 24 | INT_LCD | 16 | INT_TICK | 8 | EINT0 | 0 |
子中断源和映射:
SUBSRC | Bit | MAP to Source | SUBSRC | Bit | MAP to Source |
INT_AC97 | 14 | INT_WDT_AC97 | INT_RXD2 | 6 | INT_UART2 |
INT_WDT | 13 | INT_WDT_AC97 | INT_ERR1 | 5 | INT_UART1 |
INT_CAM_P | 12 | INT_CAM | INT_TXD1 | 4 | INT_UART1 |
INT_CAM_C | 11 | INT_CAM | INT_RXD1 | 3 | INT_UART1 |
INT_ADC_S | 10 | INT_ADC | INT_ERR0 | 2 | INT_UART0 |
INT_TC | 9 | INT_ADC | INT_TXD0 | 1 | INT_UART0 |
INT_ERR2 | 8 | INT_UART2 | INT_RXD0 | 0 | INT_UART0 |
INT_TXD2 | 7 | INT_UART2 |
SRCPND
Source pending register,32位,每一位表示一个中断源,中断产生时,对应的位由控制器置1,且不会自动置为0,必须由软件手动置0。如果没有置0,将不停的产生该中断。置0的方法是向SRCPND赋值,对应位为1的,SRCPND中的位将被清掉。
Register | Address | R/W | Description | Reset Value |
SRCPND | 0x4a000000 | R/W | Indicate the interrupt request status. 0 = the interrupt has bot been requested. 1 = the interrupt source has asserted the interrupt request | 0x0 |
INTMOD
Interrupt mode register,32位,表示产生FIQ还是IRQ。1表示FIQ,0表示IRQ。只能有一个FIQ,所以只能有1位为1。
Register | Address | R/W | Description | Reset Value |
INTMOD | 0x4a000004 | R/W | Interrupt mode register 0 = IRQ mode 1 = FIQ mode | 0x0 |
INTMSK
Interrupt mask register,32位,对应位为1,CPU不服务该中断。被masked的中断,对应的SRCPND仍然会被置为1。
Register | Address | R/W | Description | Reset Value |
INTMSK | 0x4a000008 | R/W | Determine which interrupt source is masked. The masked interrupt source will not be serviced 0 = Interrupt service is available 1 = Interrupt service is masked | 0xFFFFFFFF |
PRIORITY
Priority register,20位,中断优先级控制寄存器。
Register | Address | R/W | Description | Reset Value |
PRIORITY | 0x4a00000C | R/W | IRQ priority control register | 0x7F |
本篇不打算分析优先级。
INTPND
Interrupt pending register,仲裁器选择一个优先级最高的中断后,将INTPND置为1,然后触发CPU该中断。由于INTPND位于优先级逻辑之后,所以只有1位能置为1,那个中断请求产生IRQ到CPU。中断服务程序中,可以通过该寄存器判断是哪个中断。和SRCPND类似,需要在ISR中手动清除被设置的位,清除方法和SRCPND相同。
Register | Address | R/W | Description | Reset Value |
INTPND | 0x4a000010 | R/W | Indicate the interrupt request status. 0 = the interrupt has bot been requested. 1 = the interrupt source has asserted the interrupt request | 0x0 |
快速中断模式,对应的位不会打开,INTPND只服务IRQ模式。
INTOFFSET
Interrupt offset register,这个就是INTPND的位的整数,如16,便于获取中断的编号。清除SRCPND和INTPND时自动清除。
Register | Address | R/W | Description | Reset Value |
INTOFFSET | 0x4a000014 | R/W | Indicate the interrupt request status. | 0x0 |
SUBSRCPND
Sub source pending register,14位,表示子中断,多个子中断对应到主中断。
Register | Address | R/W | Description | Reset Value |
SUBSRCPND | 0x4a000018 | R/W | Indicate the interrupt request status. 0 = the interrupt has bot been requested. 1 = the interrupt source has asserted the interrupt request | 0x0 |
和SRCPND需要在ISR中手动清除,清除方法相同。
INTSUBMSK
Interrupt sub mask register,用来屏蔽子中断。和SUBMSK类似。
Register | Address | R/W | Description | Reset Value |
INTSUBMSK | 0x4a00001C | R/W | Determine which interrupt source is masked. The masked interrupt source will not be serviced 0 = Interrupt service is available 1 = Interrupt service is masked | 0xFFFF |
注意,中断的清除顺序必须从前往后清除:SUBSRCPND->SRCPND->INTPND。否则高处被清除后会被重新触发置位。
程序状态寄存器(CPSR)的F位和I位控制是否接收FIQ和IRQ,如果设置为1,则不接收对应类型的中断。
发生异常时,处理器先执行下列操作,再跳转到vector table,这些操作由硬件自动完成:
根据接收到的中断类型,切换到对应的处理器模式。
将之前模式的CPSR拷贝到新模式的SPSR。
将之前模式的PC保存到新模式的LR。
禁止中断(设置CPSR的对应位),或者IRQ,或者IRQ与FIQ。
跳转到vector table对应的入口。
异常中断发生时,跳转到异常入口处执行,同时通常也意味着模式切换:
中断名称 | 跳转入口地址 | 含义 |
复位 | 0x0 | 复位引脚有效时,系统产生复位异常中断。 |
未定义指令 | 0x4 | |
软中断 SWI | 0x8 | |
指令预取中止 prefech abort | 0x0c | 预取的指令地址不存在,或不允许访问。 |
数据访问中止 Data abort | 0x10 | |
外部中断请求 | 0x18 | |
快速中断请求 | 0x1c |
Link Register Offset
中断发生时,处理器将当前PC赋值给中断模式的LR,而PC是当前执行指令地址加8,从中断返回时,我们要执行被中断的下一条指令,所以,应该跳转到LR-4。
Non-nested interrupt handling
这是最简单的,只要在中断返回前关闭中断即可。
Nested interrupt handling
下一篇学习
我手头上的JZ2440开发板,按键和LED引脚映射如下:
实现按一个键就开始闪一段时间对应的灯,之后进入熄灭状态。
代码如下:
start.S
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | .text .global _start _start: b reset b loop b loop b loop b loop b loop b int b loop reset: ldr sp, =4096 bl init bl main b loop int: sub lr, lr, #4 stmdb sp!, {r0-r12,lr} bl irq_handler ldmia sp!, {r0-r12,pc}^ loop: b loop |
如上,vector table实现了reset和中断的跳转。
reset处理:设置栈,跳转到init()执行相关初始化。跳转到main()。
int处理:中断处理返回后,应该跳转到下一条指令执行,所以先将lr减4,然后保存栈,这里stmdb等价于stmfd,保存栈后就可以跳转到中断处理函数执行,返回后,恢复栈。
init.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 | // watchdog #define WTCON (*(volatile unsigned long *)0x53000000) #define GPFCON (*(volatile unsigned long *)0x56000050) #define GPFDAT (*(volatile unsigned char *)0x56000054) #define GPGCON (*(volatile unsigned long *)0x56000060) #define EINTMSK (*(volatile unsigned long *)0x560000a4) #define EINTPND (*(volatile unsigned long *)0x560000a8) typedef unsigned long uint32_t; typedef unsigned char uint8_t; struct S3C2440_INT_CONT { uint32_t SRCPND; uint32_t INTMOD; uint32_t INTMSK; uint32_t PRIO; uint32_t INTPND; uint32_t INTOFFSET; uint32_t SUBSRCPND; uint32_t INTSUBMSK; }; volatile struct S3C2440_INT_CONT *intcont = ( volatile struct S3C2440_INT_CONT *)0x4a000000; void disable_watchdog() { WTCON = 0; } void init_gpio() { #define set_gpio(r,v,n) r &= ~(3 << 2*n); r |= (v << 2*n) unsigned long r1 = GPFCON; unsigned long r2 = GPGCON; // 4,5,6 LED output mode set_gpio(r1,1,4); set_gpio(r1,1,5); set_gpio(r1,1,6); // 0(EINT0),2(EINT2) Button INT Mode set_gpio(r1,2,0); set_gpio(r1,2,2); // 3(EINT11) BUtton INT Mode set_gpio(r2,2,3); GPFCON = r1; GPGCON = r2; } void init_interrupt_controller() { // enable EINT11 EINTMSK &= ~(1 << 11); intcont->INTMSK = ~ ((1<<0)|(1<<2)|(1<<5)); } void init() { disable_watchdog(); init_gpio(); init_interrupt_controller(); asm volatile ( "msr cpsr_c, #0xd2\n\t" // enter IRQ mode "ldr sp, =3072\n\t" // set IRQ mode stack "msr cpsr_c, #0x53" ); // re-enter svc mode and enable IRQ and FIQ } void main() { int i = 0; while (1) { i++; if (i == 300) { GPFDAT = -1; i = 0; } } } void irq_handler() { unsigned long off = intcont->INTOFFSET; unsigned long led = 0; int i = 0; int j = 30; if (off == 0) led = 6; else if (off == 2) led = 5; else if (off == 5) led = 4; if (led != 0) while (1) { i++; if (i == 10000) { GPFDAT = -1; } else if (i == 20000) { GPFDAT = ~(1 << led); j--; i = 0; if (j == 0) break ; } } // clear int if (off == 5) { EINTPND = 1 << 11; } intcont->SRCPND = 1 << off; intcont->INTPND = 1 << off; } |
init()关看门狗,设置GPIO模式,LED设置为output,按键设置为中断模式。设置中断,主要是清除对应的掩码。最后通过修改CPSR进入中断模式,设置中断模式的栈,然后在返回svc模式,同时开启中断,整个初始化完成。
main()死循环,关闭所有灯。
irq_handler()获取是哪个中断,闪对应的灯,然后清除中断源。
1 可嵌套中断,只有高优先级的中断才可以打断低优先级的中断。因为中断处理时,不会清除中断控制器中的源,中断控制只会冒泡高优先级的中断。
2 可嵌套中断应该在非中断模式执行,因为如果在中断模式执行,而中断处理代码有函数调用,那么LR被当前PC覆盖,导致之前的中断处理代码无法从函数返回。通常使用system模式执行可嵌套中断。
3 中断由于优先级低或者中断暂时被屏蔽时,中断控制器仍然会保持对应的中断,当中断在后续仍然可以被处理。这样保证中断不会丢失。如在非嵌套中断,一个中断在处理,按了另外一个中断键, 那么该中断处理完毕后,会执行新的中断。
下一节将学习可嵌套中断。
[1] Ahmed Fathy Mohammed Abdelrazek. Exception and Interrupt Handling in ARM.
http://www.iti.uni-stuttgart.de/~radetzki/Seminar06/08_report.pdf
[2] Chapter 5, Handling Processor Exceptions. ARM Developer Suite v1.2. Developer Guide.
[3] A2.6 Exceptions. ARM ARM
[4] Chapter 9. Exception and Interrupt Handling. ARM sytem developer's guide, Designing and optimizing system software.