分类: 嵌入式
2009-09-22 13:42:36
1. 前言
这篇应用说明提供了在LPC200上处理中断嵌套的示例代码,文章将以下面的方式组织。
1.中断处理概述
2.中断嵌套
3.示例代码
文章假设读者是熟悉ARM7TDMI-S体系结构的。此外,关于一般情况下FIQ和IRQ的中断处理代码
请参阅在线的帮助文档AN10254_1。本文所提供的示例代码都是基于Keil MicroVision3 ARM
compiler构建的(该版本的评估版可以在上免费下载)
2. 中断处理概述
2.1 ARM7核的中断等级 Interrupt levels in the ARM7 core
ARM7核具有两种中断等级:快速中断(FIQ)和普通中断(IRQ)。ARM推荐只把一个中断分配
为FIQ中断。其他的中断都分配为IRQ中断,IRQ中断可以被编程为可嵌套(重入)的。IRQ
又可以被分配为向量IRQ和非向量IRQ(本文只讨论向量IRQ)。
2.2 向量中断控制器 Vectored Interrupt Controller (VIC)
向量中断控制器管理所有来自不同中断源的中断,并把他们分为3个类别,FIQ、向量IRQ和
非向量IRQ。其中,FIQ具有最高的优先级。向量IRQ可以处理16个中断请求, 并把他们按优
先级次序分配到16个不同优先级的向量槽中(vectored IRQ slots),其中0号槽优先级最高,
15号最低。非向量IRQ的优先级又低于向量IRQ。向量中断控制器把所有的向量和非向量IRQ
请求信号相或来产生一个送往ARM7核的IRQ请求信号(FIQ有单独的连接到ARM7核的引脚:译者注)
2.3 ARM7核对FIQ和IRQ的相应 ARM7 core’s response to an IRQ/FIQ
一旦发生IRQ或FIQ中断,ARM7核将做以下的响应:
1. 拷贝CPSR到即将处理该中断的模式对应的SPSR中
2. 改变CPSR的模式位,以便:
a. 转换处理器模式到响应该中断的模式并为该影射合适的寄存器(组)
b. 禁止中断,任何中断发生后,IRQ中断都将被禁止。FIQ只在发送FIQ和复位(中断)
时被禁止。
3. 设置LR_该中断模式(如LR_irq)为当前返回地址(有时候有偏移量:译者注)
4. 把中断向量的地址赋值给PC,这就强制处理器转到合适的中断处理程序入口(irq的为0x18:译者注)。
例如,下面是IRQ中断时,ARM7核的响应过程:
1.LR_irq=下一条将被执行的指令地址加4;
2.SPSR_irq=CPSR
3.CPSR[4:0]=10010(进人IRQ模式的模式位值)
4.CPSR[5]=0 (在ARM状态下处理中断)
5.CPSR[7]=1 (禁止IRQ中断)
6.PC=0x18(IRQ中断向量入口地址)
在应用编程中,用户不需要关心ARM7核对IRQ或FIQ的响应。请按指南上的下一节来看对IRQ和FIQ中断的简单
处理过程。
2.4 LPC2000中简单的中断处理 Simple interrupt handling in the LPC2000
下面是一些在LPC2000上处理中断的时候需要注意的一些重要方面。关于下面每个方面的示例代码请参考
在线帮助文档 IRQ和FIQ中断的简单处理(AN10254_1)。
1. 芯片复位时,ARM7核已经将中断禁止了。需要在CPSR中使能中断。这一般在汇编启动文件中完成
Keil环境的样板工程中有这样的汇编文件。大部分的编译器都提供这样的基于ARM的汇编启动文件
2. 异常向量必须要正确链接和编写。这通常由连接器来完成。但同时也需要在合适的地址放置正确的
处理句柄(handler)。比如,在IRQ向量入口处必须放入下面的指令,如果ISR的地址是直接从VIC
地址寄存器(寄存器地址:0xFFFF030)中读取的。
LDR PC [PC,#-0xFFFF030]
3. 必须为IRQ和FIQ设置正确的栈指针。
4. 必须正确地给向量中断控制器编写ISR的地址。这是在应用程序中设置的。
5. 应用编译器支持的关键字编写中断服务程序。比如在Keil环境下,中断服务程序具有下面的格式:
void IRQ_Handler() __irq
更多的关于编译器关键字的信息在下面一节中。
2.5 中断服务程序的编译器支持 Compiler support for writing ISR’s
ARM编译器提供为编写FIQ和IRQ中断服务程序而设置的关键字,它们可以用于C函数前,所以可以用C
编写整个中断服务程序。下面就是一个在Keil ARM编译器环境下的典型例子:
void IRQ_Handler __irq{
// Clear the source of the interrupt
// Additional statements
// Update the VIC by writing to VIC Vector Address Register
}
通过使用__irq关键字,编译器将为上面的函数添加下面的代码
1. 在函数入口处,工作寄存器(包括ATPCS的敏感(易被破坏的)寄存器)被压栈。
2. 在函数返回处,出栈上面保存的寄存器
3. 使用SUBS PC,R14,#4从中断服务程序中返回。这条指令恢复了PC和CPSR。
注意:使用这个关键字的情况下,SPSR_irq没有被保存。这就是使用关键字的方法处理中断不能实现嵌套
的一个原因。
2.5.1 使用关键字来处理中断嵌套时出现的问题 Problems with nesting of interrupts using compiler keywords
两个主要的原因都是跟LR_irq和SPSR_irq这两个寄存器有关。
如果一个中断服务程序重新使能中断,调用一个子程序,在子程序中发生了中断,这时,子程序的返回
地址(保存在LR_irq中)将被破坏。下面我们以一个例子来说明:
void IRQ_Handler __irq{
//reenable intertupts
......
foo();
//地址A
}
当PC跳转到IRQ_Handler的时候(执行完0x18处的指令后),从ISR返回的地址已经写入LR_irq了。假定
它已经压栈(SP_irq)了。现在,foo()函数被调用,LR_irq的值被foo调用处的下一条指令的地址所覆盖。
再进一步假设,在foo函数中运行的时候,一个更高优先级的中断发生了。这时,LR_irq又被返回到foo函
数被中断的地方的地址所覆盖,所以这就破坏了返回到IRQ_Handler的地址(即上面的函数中的" 地址A":
译者注)。
还有,使用编译器关键字的时候,SPSR_irq也不会被保存。
一个可重入的中断服务程序必须保存IRQ模式的状态信息,切换处理器模式,在转入嵌套C函数之前保存
这个新处理器模式(从IRQ模式切换后的模式,即后面推荐使用的系统模式:译者注)的状态信息。ARM
推荐编写可重入中断处理程序的时候选择转入系统模式。
转入系统模式的原因在于,一旦在系统模式中,活动的链接寄存器就是LR_sys,这个寄存器将在调用新的
子函数之前被压栈。如果真的有更高优先级的中断打断了这个子函数,那么LR_irq的值将被改变(新的
中断的返回地址:译者注),但是子函数的返回地址在LR_sys中安然无恙。
3.1 中断嵌套的步骤 Steps for Nesting of Interrupts
用汇编语言编写顶层中断服务程序ISR的方法如下:
1. 保存ISR将会使用的寄存器和SPSR_irq
2. 清中断
3. 切换到系统模式并开中断(译者认为,开中断在第五步比较好)
4. 保存系统模式下链接寄存器LR_sys和被调用函数不保存的寄存器(non callee-saved registers)
5. 调用C中断处理函数
6. 当C中断处理函数返回时,恢复系统模式的寄存器并关中断
7. 恢复工作寄存器和SPSR_irq
8.从IRQ中断处理程序中返回
4. 示例代码 Code examples
该例子使用定时器1和外部中断1作为IRQ中断源。定时器1的优先级比外部中断的优先级高。该应用建立于
Keil MCB2100/2130/2140开发板。这个板子上有8个LED。还有一个按钮连接在外部中断1的引脚上。LED分为
两组。一组供Timer1使用,一组供EXTINT1使用。
设置外部中断为电平触发。设置Timer1周期地复位自身并中断ARM核。在外部中断服务程序中,LED1、2、3、4
闪烁。在Timer1中,LED5、6、7、8闪烁。Timer1中断服务程序执行完后,LED1、2、3、4开始闪烁(假设按钮是
按下的状态)
要观察这个程序的运行,按住连接在外部中断1引脚上的按钮,LED1、2、3、4就会闪烁,当Timer1中断发生后,
它将打断外部中断服务程序,并使LED5、6、7、8会闪烁。
下面提供了两套代码示例:
1. 嵌套中断示例(使用顶层汇编处理程序)
2. Keil的中断嵌套处理方法(使用内嵌汇编)
为了示例完整,我们提供两套代码,其实两者是一样的。实际上,对于不需要关心汇编文件的用户来说,第二种
方法更有效。
4.1 中断嵌套示例(使用顶层汇编处理程序)(Nested interrupt example (top level handler in assembly)
4.1.1 汇编代码
该项目包含以下汇编文件:
1. 启动汇编文件(在Keil中叫Startup.s)。这个文件包含中断向量表和其他的基本的启动代码。我们没有
给出这个文件。关于简单中断处理的在线的应用指南和随Keil MicroVision3一起附带的示例工程中都有
这个文件。
2. 顶层中断服务程序(Top level ISR Handler)。项目可以只使用一个这样的程序,也可以为每个中断源
写一个。如果只用一个的话,那么在清除中断源的时候,需根据VIC的中断状态寄存器判断是哪个中
段源。本项目运用第二种方案。下面只提供了外部中断的ISR Handler的示例文件
顶层中断服务程序 Top level ISR Handler:
// *********************************************************************
VECTADDR EQU 0xFFFFF030
EXTINT EQU 0xE01FC140
// The following four lines of code are specific to the Keil Assembler
AREA ISRCODE,CODE
PUBLIC isr_ext?A
EXTERN CODE32 (ext?A)
isr_ext?A PROC CODE32
// Registers to be used in the ISR should be stacked along with SPSR_irq and LR_irq
STMFD SP!,{R0,R1,LR}
MRS R0,SPSR
STMFD SP!,{R0}
// Clear the source of the interrupt
MOV R1,#0x2
LDR R0,=EXTINT
STR R1,[R0]
// Move to System mode and re-enable interrupts
MSR cpsr_c,#0x1f
// Stack lr_sys and other register
STMFD SP!,{R0-R3,R12,LR}
// Branch to C function
BL ext?A
// Pop lr_sys and ATPCS registers
LDMFD SP!,{R0-R3,R12,LR}
// Move to IRQ and disable interrupts. For considering the scenario that an interrupt
// occurs while IRQ is disabled please refer the ARM FAQ online.
MSR cpsr_c,#0x92
// Update VIC
MOV R1,#0xff
LDR R0,=VECTADDR
STR R1,[R0]
// Pop registers,SPSR_irq,lr_irq
LDMFD SP!,{R0}
MSR SPSR_cf,R0
LDMFD SP!,{R0,R1,LR}
// Return
SUBS PC,R14,#0x04
ENDP
END
// *********************************************************************
4.1.2 C代码部分 C code
#include
void Initialise(void);
//Functions defined as seperate assembly files
extern void isr_timer(void) __irq;
extern void isr_ext(void) __irq;
// C Functions called from the Top level assembly handler
void timer_ISR() __arm;
void ext()__arm;
int main (void)
{
int i=0,j;
Initialise();
/* Start timer */
T1TCR=0x1;
while (1)
{
}
}
// Basic Initialzation routine
void Initialise()
{
// LED’s are connected to P1.16..23
IODIR1 = 0xFF0000; /* P1.16..23 defined as Outputs */
IOCLR1 = 0xFF0000;
/* Initialize Timer 1 */
T1TCR=0x0;
T1TC=0x0;
T1PR=0x4;
T1PC=0x0;
/* End user has to fill in the match value */
T1MR0=0x...;
/* Reset and interrupt on match */
T1MCR=0x3;
/* External interrupts setup as level-sensitive triggered. Some of the steps mentioned
here are taking into considertaion the EXTINT.2 Errata */
EXTINT=0x2;
VPBDIV=0x0;
EXTMODE=0x0;
VPBDIV=0x0;
EXTPOLAR=0x0;
VPBDIV=0x0;
/* Initialize VIC */
VICIntSelect=0x0; /* Timer 1 selected */
VICIntEnable= 0xf020; /* Timer 1 interrupt */
VICVectCntl1=0x2f;
//isr_ext is defined in the assembly file. Shown in the assembly code above
VICVectAddr1=(unsigned long )isr_ext;
VICVectCntl0= 0x25;
//isr_timer is defined in the assembly file. Not shown in the assembly code above
VICVectAddr0=(unsigned long)isr_timer;
}
void timer_ISR() __arm
{
int i,j;
// Blink LEDs 5,6,7,8 five times
for ( j=0;j<5;j++)
{
for(i=0;i<2000000;i++){}
IOSET1 = 0x00F00000;
for(i=0;i<2000000;i++){}
IOCLR1 = 0x00F00000;
}
}
void ext()__arm
{
int i,j;
EXTINT=0x2;
for (j=0;j<3;j++){
for(i=0;i<2000000;i++){}
IOSET1 = 0x000F0000;
for(i=0;i<2000000;i++){}
IOCLR1 = 0x000F0000;
}
}
4.2 Keil的中断嵌套处理方法 Keil’s approach of nesting interrupts
Keil的中断嵌套处理方法实现了两个宏(内嵌汇编)。分别用于中断处理函数的开始和结束部分。如下面的C代
码所示。与上面方法不同的地方以粗体显示。
4.2.1 C代码 C code
#include
// Macro for enabling interrupts, moving to System mode and relevant stack operations
#define IENABLE /* Nested Interrupts Entry */ \
__asm { MRS LR, SPSR } /* Copy SPSR_irq to LR */ \
__asm { STMFD SP!, {LR} } /* Save SPSR_irq */ \
__asm { MSR CPSR_c, #0x1F } /* Enable IRQ (Sys Mode) */ \
__asm { STMFD SP!, {LR} } /* Save LR */ \
// Macro for disabling interrupts, switching back to IRQ and relevant stack operations
#define IDISABLE /* Nested Interrupts Exit */ \
__asm { LDMFD SP!, {LR} } /* Restore LR */ \
__asm { MSR CPSR_c, #0x92 } /* Disable IRQ (IRQ Mode) */ \
__asm { LDMFD SP!, {LR} } /* Restore SPSR_irq to LR */ \
__asm { MSR SPSR_cxsf, LR } /* Copy LR to SPSR_irq */ \
void Initialise(void);
// Timer and External Interrupt ISR
void timer_ISR() __irq;
void ext()__irq;
int main (void)
{
int i=0;
Initialise();
/* Start timer */
T1TCR=0x1;
while (1)
{ }
}
// Basic Initialzation routine
void Initialise()
{
// LEDs are connected to P1.16..23
IODIR1 = 0xFF0000;
IOCLR1 = 0xFF0000;
/* Initialize Timer 1 */
T1TCR=0x0;
T1TC=0x0;
T1PR=0x4;
T1PC=0x0;
/* End user has to fill in the match value */
T1MR0=0x...;
/* Reset and interrupt on match */
T1MCR=0x3;
/* External interrupts setup as level-sensitive triggered. Some of the steps mentioned
here are taking into considertaion the EXTINT.2 Errata */
EXTINT=0x2;
VPBDIV=0x0;
EXTMODE=0x0;
VPBDIV=0x0;
EXTPOLAR=0x0;
VPBDIV=0x0;
/* Initialize VIC */
VICIntSelect=0x0;
VICIntEnable= 0xf020;
// Setting up VIC to handle the external interrupt. External Interrupt1 has priorty 1
VICVectCntl1=0x2f;
VICVectAddr1=(unsigned long )ext;
// Setting up Timer0 to handle the external interrupt. Timer0 has priorty 0
VICVectCntl0= 0x25; /* Address of the ISR */
VICVectAddr0=(unsigned long)timer_ISR;
}
// Timer1 ISR
void timer_ISR() __irq
{
int i,j;
// Clear the Timer interrupt
T1IR=0x1;
IENABLE;
// Blink LEDs 5,6,7,8 five times
for ( j=0;j<5;j++)
{
for(i=0;i<2000000;i++){}
IOSET1 = 0x00F00000;
for(i=0;i<2000000;i++){}
IOCLR1 = 0x00F00000;
}
IDISABLE;
// Update the VIC
VICVectAddr =0xff;
}
//External Interrupt 1 ISR
void ext()__irq
{
int i,j;
// Clear External Interrupt1
EXTINT=0x2;
IENABLE;
// Blink LEDs 1,2,3 and 4
for ( j=0;j<3;j++)
{
for(i=0;i<2000000;i++){}
IOSET1 = 0x000F0000;
for(i=0;i<2000000;i++){}
IOCLR1 = 0x000F0000;
}
IDISABLE;
// Update VIC
VICVectAddr =0xff;
}