单片机论坛

 找回密码
 立即注册

QQ登录

只需一步,快速开始

搜索
查看: 4899|回复: 0
收起左侧

周立功lpc21xx/lpc22xx(ARM7)系列工程模板中中断嵌套代码详解

[复制链接]
liuda 发表于 2015-1-23 04:02 | 显示全部楼层 |阅读模式
IRQ.S分析
以下是“IRQ.S分析”的部分摘录
一直想对中断嵌套有更深层次的了解。原因就在于一个项目中在中断频率较高的情况下,又按键中断触发,有时候会导致死机。
先来看两个问题:
1.为什么除了进入复位异常模式外,在别的异常处理模式中都允许FIQ中断?
答:所以FIQ可以用来处理实时性较高或是系统中至关重要的事。
2.数据访问中止异常的优先级大于FIQ异常,为什么在数据访问异常处理模式中,还允许   FIQ中断呢?这样不就成了:在高优先级异常处理中允许低优先级的中断发生吗?即使这样,因为FIQ中断的优先级 < 数据异常中断优先级,也不会进入FIQ中断处理程序啊,这样不就更没有用处了??
答:在高优先级异常处理中允许低优先级的中断发生,是可以的。(1)如果CPU不支持中断嵌套,会丢弃低优先级的中断。(2)如果CPU支持中断嵌套,低优先级的中断会被挂起,待退出高优先级后再处理低级中断。
    这两个问题说明了各种异常模式也是有优先级的,并且低优先级异常能被高优先级异常中断。当CPU不支持嵌套时,会丢弃低优先级中断,支持嵌套时会挂起,等高优先级处理后再处理低优先级。


优先级

异常

1

复位

2

数据异常终止

3

FIQ

4

IRQ

5

预取异常终止

6

未定义指令

7

SWI



RAM7中断优先级表
既然不支持嵌套的cpu不能被低优先级的中断打断,那么为什么arm7tdmi的中断处理过程会被其他中断打断呢?

... ...

简单描述下这个中断处理过程:1.计算返回地址 2.保存环境 3.切换到系统模式 4.实际中断处理 5.切换回IRQ模式 6.恢复环境 7.返回

    这里的动作就像括号一样层层包围着实际中断处理函数,就是为了让实际中断处理函数在系统模式下进行,而非irq模式下。正是在系统模式下,中断才能被新的IRQ打断,才会造成类似嵌套的效果。


在一般的中断处理程序中,需要按照以下步骤进行响应:
1.计算返回地址
2.保存会有改变的寄存器组
3.调用实际中断响应函数
4.恢复寄存器组
5.返回被中断处
    为了使arm7这类不支持中断嵌套的cpu能使用中断嵌套,必须在进入实际中断响应函数前将cpu模式切换为sys32系统模式,在处理结束后继续恢复为IRQ模式。因为sys32模式能被IRQ异常模式打断,所以在中断处理的过程中仍然能响应新的中断。
注:原模板中在“Startup.s”中有如下的语句
;Build the SYS stack
;设置系统模式堆栈
        MSR     CPSR_c, #0xdf
        LDR     SP, =StackUsr
        MOV     PC, R0
为了中断嵌套,需将“MSR     CPSR_c, #0xdf”语句改为“MSR     CPSR_c, #0x5f”,即在系统模式下允许IRQ中断。(注部分是自己添加的内容)
    所以arm7这类不支持嵌套的cpu的中断嵌套的响应流程如下:
1.计算返回地址(注意:此时处理器是在IRQ模式下)
2.保存会有改变的寄存器组
3.切换为sys32系统模式
4.调用实际中断响应函数
5.切换回IRQ模式
6.恢复寄存器组
7.返回被中断处
实现代码如下:
IRQ中断,先保存前一中断在sys32模式下的处理程序的寄存器组,然后切换到sys32模式,再进入此中断的响应程序。当处理完此中断之后,再恢复前一中断在sys32模式下的寄存器组,直到中断处理完之后,恢复成程序的寄存器组。
    因此,可以将中断看作是随机的函数调用,需要在进入时保存寄存器组,在退出时恢复寄存器组。因为中断不同于普通的函数调用,同事发生了模式的变化,所以在保存和恢复寄存器组的时候,应该加上CPSR寄存器。又由于arm7在IRQ模式下不能进行中断嵌套,所以需要切换到sys32模式进行实际的中断响应。
当发生中断嵌套时,程序是怎么工作的呢?
-程序0正在正常运行,突然被中断1打断后,进入到中断处理程序。
-保存程序0当中的寄存器组,然后切换到sys32模式,进行中断1的实际响应。
-此时又允许被中断,中断1处理过程中,又被中断2打断。
-中断2响应时候,保存中断1中断的寄存器组,进入sys32模式进行中断2的响应
-当中断2响应结束后,恢复到中断1在sys32模式下的响应程序的寄存器状态
-当中断1响应结束后,恢复到程序0的寄存器状态。
要读懂源码,还得看ARM汇编伪指令宏的应用。
【转】ARM汇编伪指令宏的用法详解(MACRO MEND)


宏是一段独立的程序代码,它是通过伪指令定义的,在程序中使用宏指令即可调用宏。当程序被汇编时,汇编程序将对每个调用进行展开,用宏定义取代源程序中的宏指令。

MACRO、MEND

语法格式:

MACRO

[$ label] macroname{ $ parameter1, $ parameter,……    }

指令序列

MEND

MACRO伪操作标识宏定义的开始,MEND标识宏定义的结束。用MACRO及MEND定义一段代码,称为宏定义体,这样在程序中就可以通过宏指令多次调用该代码段。其中, $ label在宏指令被展开时,label会被替换成相应的符号,通常是一个标号。在一个符号前使用$表示程序被汇编时将使用相应的值来替代$后的符号。macroname为所定义的宏的名称。$parameter为宏指令的参数。当宏指令被展开时将被替换成相应的值,类似于函数中的形式参数,可以在宏定义时为参数指定相应的默认值。

宏指令的使用方式和功能与子程序有些相似,子程序可以提供模块化的程序设计、节省存储空间并提高运行速度。但在使用子程序结构时需要保护现场,从而增加了系统的开销,因此,在代码较短且需要传递的参数较多时,可以使用宏汇编技术。

首先使用MACRO和MEND等伪操作定义宏。包含在 MACRO 和 MEND 之间的代码段称为宏定义体,在MACRO伪操作之后的一行声明宏的原型(包含宏名、所需的参数),然后就可以在汇编程序中通过宏名来调用它。在源程序被汇编时,汇编器将宏调用展开,用宏定义体代替源程序中的宏定义的名称,并用实际参数值代替宏定义时的形式参数。宏定义中的$label是一个可选参数。当宏定义体中用到多个标号时,可以使用类似$label.$internallabel的标号命名规则使程序易读。

MACRO 、 MEND 伪操作可以嵌套使用。

使用示例:

MACRO

$HandlerLabel HANDLER $HandleLabel ;宏的名称为HANDLER,有1个参数$HandleLabel

$HandlerLabel

sub sp,sp,#4 ;decrement sp(to store jump address)

stmfd sp!,{r0} ;PUSH the work register to stack(lr does not push because it return to original address)

ldr     r0,=$HandleLabel;load the address of HandleXXX to r0

ldr     r0,[r0] ;load the contents(service routine start address) of HandleXXX

str     r0,[sp,#4]      ;store the contents(ISR) of HandleXXX to stack

ldmfd   sp!,{r0,pc}     ;POP the work register and pc(jump to ISR)

MEND

;在程序中调用该宏

HandlerFIQ      HANDLER HandleFIQ ;通过宏的名称HANDLER调用宏,其中宏的标号为HandlerFIQ,参数为HandleFIQ

HandlerIRQ      HANDLER HandleIRQ

HandlerUndef    HANDLER HandleUndef

HandlerSWI     HANDLER HandleSWI

HandlerDabort   HANDLER HandleDabort

HandlerPabort   HANDLER HandlePabort

也许我们会问想格式中的[$ label]到底有什么作用?

当宏定义体内部跳转时,这个参数会起到至关重要的作用。要想在宏内部跳转,就必须在宏定义体内部有程序标号如(LOOP),如果不使用参数($ label),当在一个程序段内调用两次宏的时候,编译器就会出现错误,因为当汇编时产生了两个相同名字的程序标号。

例子:   

宏的定义体:

MACRO

$PM        DELAY $CanShu

$PM

LDR     R7,=$CanShu   ;

;LDR  R7,[R7] ;此时参数是一个立即数  如果是变量的话 是会用到这一句

$PM.LOOP

SUBS R7,R7,#0X01

BNE   $PM.LOOP

MEND

在程序段中的使用:(使用两次)

... ...

AA    DELAY 0X000005F0

... ...

BB    DELAY 0X00000FF0

...

此时调用多次,编译器就不会出现问题,例子中的AA和BB仅仅是一个标号,用户可以自行书写,因为在宏指令呗展开时,这个符号在汇编时将使用相应的值替代,0x00000FF0是一个参数 在此处是一个立即数,用户可自行使用为变量等。

现在进行源码分析
源码文件:IRQ.inc
;/****************** Copyright (c)**************************
;**                               广州周立功单片机发展有限公司
;**                                     研    究    所
;**                                        产品一部
;**
;**                              
;**
;**--------------文件信息--------------------------------
;**文   件   名: IRQ.inc
;**创   建   人: 陈明计
;**最后修改日期: 2004年8月27日
;**描        述: 定义IRQ汇编接口代码宏
;**
;**--------------历史版本信息------------------------------
;** 创建人: 陈明计
;** 版  本: v1.0
;** 日 期: 2004年8月27日
;** 描 述: 原始版本
;**
;**--------------当前版本修订------------------------------
;** 修改人:
;** 日 期:
;** 描 述:
;**
;**----------------------------------------------------------
;************************************** /

NoInt       EQU 0x80
USR32Mode   EQU 0x10
SVC32Mode   EQU 0x13
SYS32Mode   EQU 0x1f
IRQ32Mode   EQU 0x12
FIQ32Mode   EQU 0x11

;引入的外部标号在这声明
        IMPORT  OSIntCtxSw                      ;任务切换函数
        IMPORT  OSIntExit                         ;中断退出函数
        IMPORT  OSTCBCur
        IMPORT  OSTCBHighRdy
        IMPORT  OSIntNesting                      ;中断嵌套计数器
        IMPORT  StackUsr
        IMPORT  OsEnterSum

    CODE32
    AREA    IRQ,CODE,READONLY
    MACRO
$IRQ_Label HANDLER $IRQ_Exception_Function
        EXPORT  $IRQ_Label                       ; 输出的标号
        IMPORT  $IRQ_Exception_Function         ; 引用的外部标号
$IRQ_Label
        SUB     LR, LR, #4                        ; 计算返回地址
;分析,在执行此指令时,处理器是由于产生了IRQ中断被迫从其它模式切换到IRQ模式的,此时的PC值是被中断了的其它模式下的预取指令的地址(由3级流水线导致),是当前执行指令的地址+8(RAM状态下,Thumb下为+4),当已进入中断时,LR里面装的是PC,所以下一条要执行的指令地址就是LR-4(RAM状态下)。
        STMFD   SP!, {R0-R3, R12, LR}           ; 保存任务环境
;这里为什么只把R0-R3,R12,LR保存呢,其它不用吗,是这样的,我们可以从你装的ADS1.2目录下的PDF文件夹里面的ADS_DeveloperGuide_D.PDF文件的2.2就可以发现R4-R11装的是局部变量,在进行函数跳转时,编译器它会自动保护它们的。

MRS     R3, SPSR                         ; 保存状态
        STMFD   SP, {R3, SP, LR}^               ; 保存用户状态的R3,SP,LR,注意不能回写
                                                                         ; 如果回写的是用户的SP,所以后面要调整SP
;分析,1.先看看SPSR,在7中模式中,用户模式和系统模式没有SPSR,其它模式各有自己的SPSR,它的作用是保存异常发生前的CPSR的值。2.再看看“^”,当在STM中使用时,表示加载的寄存器列表“{R3, SP, LR}”是用户模式的寄存器,而不是当前模式的寄存器,3.关于回写,即如果语句是STMFD   SP!, {R3, SP, LR}^,就是多一个感叹号,对于这个“!”,本人理解是跟C语言的指针后跟“++”或“—”类似,至于是“++”还是“—”取决于STM后面的模式标识。
《ARM嵌入式系统基础教程——周立功等编著》中有如下一段:
LDF和STM——多寄存器加载/存储指令(P83)
指令格式:
STM{cond}<模式>  Rn{!},reglist{^}
当Rn在寄存器列表中且使用后追“!”时,对于STM指令,若Rn为寄存器列表中的最低数字寄存器,则会将Rn的初值保存;其它情况下Rn的加载值和存储值不可预知。(P84)
但看源文件后面的注释“; 如果回写的是用户的SP,所以后面要调整SP”,说明回写的话保存的是用户的SP初值,不解???既然跟了“^”,说明两个SP并非同一个寄存器,“!”跟“^”到底谁是老大,谁说了算啊??
再看“IRQ.S分析”一文中的解释:
因为寄存器列表中包涵有SP,且第一个操作数寄存器也是SP,虽然意义上不是同一个SP,但是此时如果使用:STMFD   SP!, {R3, SP, LR}^ 回写SP是不行的,编译就不会通过,违反了ARM汇编的规则设置。所以要写成:STMFD   SP, {R3, SP, LR}^   然后后面(注意:好像还不能立刻减回来,要隔个一两句再减,不然可能有警告!)再把SP减回来:SUB     SP, SP, #4*3而 STMFD   SP!, {R3,LR}^ 用ADS编译可能会有一个警告,但是功能是对的。
我用H-JTAG + S3C2410 仿真看过
对于出栈的情况也是一样。
        LDR     R2,  =OSIntNesting               ; OSIntNesting++
        LDRB    R1, [R2]
        ADD     R1, R1, #1
        STRB    R1, [R2]
;分析:以上几行代码功能是实现中断嵌套计数器OSIntNesting +1操作
        SUB     SP, SP, #4*3
;分析:这里就是源文件的注释“; 如果回写的是用户的SP,所以后面要调整SP”的实现,即调整堆栈指针SP的位置。
        MSR     CPSR_c, #(NoInt | SYS32Mode)    ; 切换到系统模式
;分析:只有切换到系统模式,让后面的服务程序在系统模式下运行,才能实现嵌套,那再一次开中断又再哪儿进行呢??请看$IRQ_Exception_FunctionC语言要实现中断嵌套的代码规则。
        CMP     R1, #1
        LDREQ   SP, =StackUsr
;分析:以上两句是判断是否是第一次进入中断,如果是第一次进入中断则设定系统模式的堆栈指针。
        BL      $IRQ_Exception_Function         ; 调用c语言的中断处理程序
;这一句就不再多说了。

;重点说说IRQ_Exception_Function该怎么写,以下是一个模板,见《μC/OSII下的ARM7中断过程分析及优化方法》一文。

3 中断的优化

  改写μC/OSII 内核中 HANDLER 宏可以实现ARM的中断嵌套,这样做虽然提高了系统的实时性,但损害了系统运行的稳定性和可移植性。通过对中断过程的分析,下面给出一种编写中断服务程序的模板,充分利用ISR执行在特权模式——系统模式这一特点来实现中断嵌套的条件。中断服务程序模板如下:

void ISR(void)

{

  OS_ENTER_CRITICAL();//在中断服务程序中关中断

      /*清中断标志*/                //防止没有清中断标志使得中断多次进入

      /*禁止低优先级中断*/         //禁止低优先级中断

  S_EXIT_CRITICAL();       //在中断服务程序中开中断

      VICVectAddr=0;              //将中断服务程序的入口地址置0

      /*用户的C语言代码*/          //进行用户在中断中要做的工

}

由于Handler宏中已将LR、SPSR、返回地址和发生中断前的堆栈指针等寄存器入栈保存,所以接下来要做的就只剩下开关中断的工作。由于 在进入C中断处理程序之前进入的是关中断系统模式,所以必须在C语言中重新打开中断,而C语言是不能进行寄存器操作的,因此必须调用软中断 OS_EXIT_CRITICAL()重新打开中断。在开中断之前,要判断将全局变量OsEnterSum减1后是否为0,所以必须在调用开中断之前调用 软中断OS_ENTER_CRITICAL()将OsEnterSum变成1。在临界区中可以进行一些处理,如清中断标志、关低优先级中断等。进行C语言 中断服务程序之后要将VICVectAddr置位为0,这是ARM7处理器核的要求必须进行这样的编写,否则会导致一些错误(如不能第2次进入中断等)。

实例:

void IRQ_FIFOP(void)
{
    uint32 temp, temp32;

    temp=VICIntEnable;                           // 保存中断信息
    VICIntEnClr=(1<<16);                        // 禁止当前中断
    EnableIRQ();                                      // 打开IRQ中断
    VICVectAddr=0x00;                           // 清除中断逻辑,以便VIC可以响应更高优先级IRQ中断

    while( (EXTINT&0x04)!=0 )                 //EINT2 1<<16
    {

          MACISR();

          EXTINT = 0x0F;                           // 清除EINT0中断标志
    }
  
    VICIntEnable=temp;                           //恢复中断使能

}

  
MSR     CPSR_c, #(NoInt | SYS32Mode)    ; 切换到系统模式
;因为在执行C语言的中断响应函数时,还可以响应中断,处理的模式会发生变化,所以在函数执行完时强制返回到系统模式,做好中断退出的准备。
;以下的代码还需结合uCOS来分析。
        LDR     R2, =OsEnterSum                 ; OsEnterSum,使OSIntExit退出时中断关闭
        MOV     R1, #1
        STR     R1, [R2]
;以上3句语句是给OsEnterSum赋值为1。为什么赋值为1呢?试想想,中断是怎么嵌套进去的?再一次中断是在执行$IRQ_Exception_Function函数中有可能(如果发生)被嵌套进去,利用C语言中的括号原理:中断1—>{中断2—>(中断3—>())},这里的大括号就相当于第一次$IRQ_Exception_Function函数执行,当程序跑出$IRQ_Exception_Function时,大括号结束了。所以OsEnterSum只能是1了。
        BL      OSIntExit
;这句关键说说OSIntExit与OsEnterSum有何干?OSIntExit里面会调用OS_EXIT_CRITICAL()函数,而OS_EXIT_CRITICAL()是在Os_cpu.h中通过软件中断来实现的(__swi(0x03) void OS_EXIT_CRITICAL(void)),再看看软件中断代码:
void SWI_Exception(int SWI_Num, int *Regs)

……
case 0x03:          /* 开中断函数OS_EXIT_CRITICAL(),参考os_cpu.h文件 */
            if (--OsEnterSum == 0)
            {
                __asm
                {
                    MRS     R0, SPSR
                    BIC     R0, R0, #NoInt
                    MSR     SPSR_c, R0
                }
            }
            break;
……

在代码中如果--OsEnterSum == 0就给把中断关了。那岂不是以后就永远没发响应中断了?关键是周工的东东使用了uCOS的

#define     OS_CRITICAL_METHOD     2            /* 选择开、关中断的方式 */,所以以后能不能再响应中断去看看uCOS。
        LDR     R2, =OsEnterSum                 ; 因为中断服务程序要退出,所以OsEnterSum=0
        MOV     R1, #0
        STR     R1, [R2]
;以上3句为OsEnterSum赋值0,为什么要赋值0呢?因为最后一个中断要退出了,当然没得数可计了啊!
        MSR     CPSR_c, #(NoInt | IRQ32Mode)    ; 切换回irq模式
;切换到IQR中断模式,恢复用户模式的参数。

LDMFD   SP, {R3, SP, LR}^               ; 恢复用户状态的R3,SP,LR,注意不能回写
                                                ; 如果回写的是用户的SP,所以后面要调整SP
        LDR     R0, =OSTCBHighRdy
;读出就绪表中任务最高优先级,判断是否需要任务切换
        LDR     R0, [R0]
        LDR     R1, =OSTCBCur
        LDR     R1, [R1]
        CMP     R0, R1                      ;判断被挂起的任务是不是具有最高优先级
       ADD     SP, SP, #4*3                    ; 如果不是则进行任务切换
        MSR     SPSR_cxsf, R3
        LDMEQFD SP!, {R0-R3, R12, PC}^        ; 不进行任务切换
        LDR     PC, =OSIntCtxSw                 ; 进行任务切换
    MEND
    END
;/*********************************************************************************************************
;**                            End Of File
;********************************************************************************************************/

不好意思有点乱哦!!!
源码文件:IRQ.s
;/******************** Copyright (c)*************************
;**            Guangzou ZLG-MCU Development Co.,LTD.
;**                       graduate school
;**                  
;**
;**--------------File Info-------------------------------------------------------------------------------
;** File Name: IRQ.s
;** Last modified Date:  2004-06-14
;** Last Version: 1.1
;** Descriptions: The irq handle that what allow the interrupt nesting.
;**
;**-----------------------------------------------------------------------------------
;** Created By: Chenmingji
;** Created date:   2004-09-17
;** Version: 1.0
;** Descriptions: First version
;**
;**-----------------------------------------------------------------------------------
;** Modified by:
;** Modified date:
;** Version:
;** Descriptions:
;**
;************************************************************ /

               INCLUDE         ..\..\arm\irq.inc            ; Inport the head file 引入头文件
    CODE32
    AREA    IRQ,CODE,READONLY
;/* 以下添加中断句柄,用户根据实际情况改变 */
;/* Add interrupt handler here,user could change it as needed */

;/*中断*/
;/*Interrupt*/
IRQ_Handler    HANDLER IRQ_Exception

;/*定时器0中断*/
;/*Time0 Interrupt*/
Timer0_Handler  HANDLER Timer0_Exception
    END
;/*********************************
;**                            End Of File
;********************************* /



回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

手机版|小黑屋|51黑电子论坛 |51黑电子论坛6群 QQ 管理员QQ:125739409;技术交流QQ群281945664

Powered by 单片机教程网

快速回复 返回顶部 返回列表