LPC2000中断嵌套设计 nesting of interrupts in LPC2000

1. 前言

            这篇应用说明提供了在LPC200上处理中断嵌套的示例代码,文章将以下面的方式组织。

                    1.中断处理概述

                    2.中断嵌套

                    3.示例代码

            文章假设读者是熟悉ARM7TDMI-S体系结构的。此外,关于一般情况下FIQ和IRQ的中断处理代码

            请参阅在线的帮助文档AN10254_1。本文所提供的示例代码都是基于Keil MicroVision3 ARM
            compiler构建的(该版本的评估版可以在www.keil.com上免费下载)

 

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)为当前返回地址(即下一条指令的地址,处理器理解的下一条指令地址是PC所指向的指令的下一条指:PC+4, 有时候有偏移量,就是PC+8:译者注)

                                    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":

                                    译者注)。(当用BL指令调用子函数的时候,进人子函数的时候没有保存lr,从子函数退出使用的是mov pc lr这样的方法,如果用这种方法调用子函数,并且在子函数中发生了中断,那么子函数的返回地址就被中断的返回地址覆盖了)

 

                                    还有,使用编译器关键字的时候,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 <LPC21xx.H> /* LPC21xx definitions */
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 <LPC213x.H> /* LPC21xx definitions */
// 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;
}

 

5. 参考文献 References

     1. ARM7TDMI-S Technical Reference Manual
     2. ARM Developer Suite (v1.2) Developer Guide

 

备注:1. 本人琢磨中断嵌套两天,把这篇文档看了一遍又一遍,终于弄懂了原理。隧翻译以记之。

            2. 原文的下载地址:http://www.nxp.com/#/pip/pip=[pip=LPC2114_2124_6]|pp=[t=pip,i=LPC2114_2124_6]|

 

                        

 

你可能感兴趣的:(timer,汇编,assembly,compiler,编译器,nested)