嵌入式系统中的中断设计

来源:互联网 发布:spss软件下载官方 编辑:程序博客网 时间:2024/04/28 02:30
           实时操作系统μC/OS-Ⅱ移植到ARM7体系的CPU上时,对IRQ进行了管理,其ISR(中断服务程序)代码的是按一定的规则来编写的。 
 一、中断服务程序的结构
        整个中断服务程序包括以下三大块:
        1、进中断;2、运行功能代码;3、退出中断。
        受操作系统管理的中断服务程序的进中断和退出中断的操作流程是相同的,而且与具体的CPU结构有关。对于实时操作系统的用户(用实时操作系统进行开发的开发人员)来说,一方面,进出中断这两部分对任何一个中断来说处理方式不会有变化的,也就没有必要进行重复的机械劳动,而是在移植操作系统到具体的CPU上时用一个汇编宏来实现(移植文件IRQ.inc,后面将会对这个文件进行详细的分析),进行系统开发时,开发人员就只需完成这样几件工作:一是编写具体的中断功能代码部分;二是配置中断句柄,以供汇编宏调用用C编写的功能代码部分;三是完成相关中断源和向量控制器的配置。另一方面,如果由开发人员来全部编写ISR代码,势必会触及到操作系统的内核和使用汇编对最底层的寄存器进行操作,这是比较复杂而且有风险的,也不符合开发者使用操作系统的原则(个人认为操作系统提供给开发人员的就是一些API,你可以研究一下那些功能函数的实现原理以及怎样协同工作,但是使用时最好不要触及内部甚至改变它们)。因此将ISR中与具体功能无关的这部分进出中断的代码剥离出来,用上面提及的汇编宏来实现并作为实时操作系统内核的一部分,提供给用户。
        中断服务子程序的示意性代码如下所列:
保存CPU寄存器;
调用OSIntEnter()或OSIntNesting直接加1;
 if(OSIntNesting==1)
{
   OSTCBCur->OSTCBStkPtr=SP;
}
清中断源;
重新开中断;
执行用户代码做中断服务;
调用OSIntExit() ;
恢复所有CPU寄存器;
执行中断返回指令;
二、进出中断功能代码——汇编宏分析
       用ZLG公司 提供的工程模板在ADS1.2中建立一个项目后,在项目文件夹下的arm文件夹中可以找到文件名为了irq.inc的文件,这个文件中包含了一个汇编宏代码。下面给出对这个文件中代码的一个详细分析。
//EQU是汇编中的一个伪指令,为程序中的常量定义一个字符名称,类似于C中的#define
NoInt       EQU 0x80               //禁止IRQ中断,通用程序状态寄存器(cpsr)的I标志位置1
USR32Mode   EQU 0x10     //用户模式,cpsr的低四位决定处理器的工作模式
SVC32Mode   EQU 0x13     //管理模式
SYS32Mode   EQU 0x1f      //系统模式
IRQ32Mode   EQU 0x12     //中断模式
FIQ32Mode   EQU 0x11     //快速中断模式

//引入的外部标号在这声明,IMPORT伪指令通知编译器要引用的标号在其它源文件中定义
        IMPORT  OSIntCtxSw                      //任务切换函数
        IMPORT  OSIntExit                           //中断退出函数
        IMPORT  OSTCBCur                       //μC/OS-Ⅱ中正在运行的任务指针
        IMPORT  OSTCBHighRdy             //μC/OS-Ⅱ任务就绪表中优先级别最高的优先级
        IMPORT  OSIntNesting                  //中断嵌套计数器
        IMPORT  StackUsr                         //用户模式堆栈
        IMPORT  OsEnterSum                  //开关中断的次数
/********************************************************************************************************************/   
    CODE32
    AREA    IRQ,CODE,READONLY
    MACRO                                                 //与最后的MEND成对使用,将一段代码定义为一个整体,称为宏指令,//然后在程序中可以通过宏指令多次调用这段代码,这一对伪指令的详细使用方法可参考文章最后[1]
$IRQ_Label HANDLER $IRQ_Exception_Function
        EXPORT  $IRQ_Label                      ; 输出的标号
        IMPORT  $IRQ_Exception_Function         ; 引用的外部标号
$IRQ_Label
 /**********************************************************************************************************************
**保存CPU寄存器
***********************************************************************************************************************/  
        SUB     LR, LR, #4                      ; 计算返回地址
//进入中断后,返回地址应该怎样计算呢?假如当前正在执行的指令地址是0x8000,下一条要执行的指令地址就是0x8004,且这条指令已处于译码位置。由于ARM7的三级流水线结构(取指、译码、执行),PC指向的是当前执行指令地址加8的地址,如果这时中断到来,LR中放的是PC,执行完0x8000处的这条指令,流水线中其它指令被放弃而进入中断,中断结束后应返回到0x8004处执行。所以要LR(PC)减4。
        STMFD   SP!, {R0-R3, R12, LR}           ; 保存任务环境
//为何不保存R4-R11呢?是因为它们里面装的是局部变量,在发生函数跳转时,编译器会自动保护它们
        MRS     R3, SPSR                        ; 保存状态
        STMFD   SP, {R3, SP, LR}^               ; 保存用户状态的R3,SP,LR,注意不能回写(为什么不能?要是回写了后面就不用调整SP了啊?另外,为什么要保存R3?)
                                                ; 如果回写的是用户的SP,所以后面要调整SP
//这里保存SP和LR是为了中断嵌套
/*********************************************************************************************************************
**调用OSIntEnter()或OSIntNesting直接加1
*********************************************************************************************************************/
        LDR     R2,  =OSIntNesting              ; OSIntNesting++
        LDRB    R1, [R2]
        ADD     R1, R1, #1
        STRB    R1, [R2]
        SUB     SP, SP, #4*3                     //由于前面没回写SP,压入了3个32位寄存器,所以这里要调整指针,做好弹出三个数据的准备
/  ************** ******************************************************************************************************
** 实现示意性代码中的if语句
**********************************************************************************************************************/    
        MSR     CPSR_c, #(NoInt | SYS32Mode)    ; 切换到系统模式
        CMP     R1, #1
        LDREQ   SP, =StackUsr
/*********************************************************************************************************************
**调用用户用C编写的中断服务功能函数
*********************************************************************************************************************/
        BL      $IRQ_Exception_Function         // 调用c语言的中断处理程序
        MSR     CPSR_c, #(NoInt | SYS32Mode)    //切换到系统模式
        LDR     R2, =OsEnterSum                 //OsEnterSum,使OSIntExit退出时中断关闭
        MOV     R1, #1
        STR     R1, [R2]
        BL      OSIntExit
        LDR     R2, =OsEnterSum                 //因为中断服务程序要退出,所以OsEnterSum=0
        MOV     R1, #0
        STR     R1, [R2]
        MSR     CPSR_c, #(NoInt | IRQ32Mode)    //切换回irq模式
        LDMFD   SP, {R3, SP, LR}^               恢复用户状态的R3,SP,LR,注意不能回写
                                                //如果回写的是用户的SP,所以后面要调整SP
        LDR     R0, =OSTCBHighRdy               //比较OSTCBHighRdy和OSTCBCur
        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
三、配置中断句柄、中断源
        为了使用ISR的汇编宏,每个受操作系统管理的ISR 都要按要求的格式,添加中断句柄。添加的方法是,在所建立的工程模板下的target文件组中IRQ.s的尾部添加中断句柄:XXX-Handler      HANDLER      XXX-Exception。其中,XXX-Handler 是ISR的起始地址,即汇编宏的起始地址,XXX为具体的中断源名称。HANDLER是句柄关键词,必须大写。XXX-Exception是用户用C写的功能函数名。IRQ.s文件中用include把汇编宏包含了进来。
        中断源配置主要是在target.c中进行。要对中断向量控制器和计时器0初始化配置,计时器在提供工程模板时已做好,无须用户改变。要添加的是VIC中与用户中断程序相关的语句。例如,在用户程序中设计了一个中断程序CMX860-Exception。在target.c中做了相应的改变(蓝色为添加的配置):
   void VICInit(void)
{
    extern void IRQ_Handler(void);
    extern void Timer0_Handler(void);
    extern void CMX860_Handler(void);
    VICIntEnClr = 0xffffffff;
    VICDefVectAddr = (uint32)IRQ_Handler;  
 
    VICVectAddr0 = (uint32)Timer0_Handler;
    VICVectCntl0 = (0x20 | 0x04);
    VICIntEnable = 1 << 4;
   
    VICVectAddr1 = (uint32)CMX860_Handler;
    VICVectCntl1 = 0x20 | 0x10;
    VICIntEnable = 1 << 16;
 }
四、用户中断功能函数设计
        用户用C在用户程序中设计的中断功能函数部分的结构如下:
void    XXX-Exception(void)              //由汇编宏BL      $IRQ_Exception_Functio调用的C函数
{
            OS-ENTER-CRITICAL();        //关中断
            清除中断源;                              //中断源必须配合硬件手段进行清除或通过相应代码进行清除
            通知中断控制器中断结束;          //用VICVectAddr=0语句
            OS-EXIT-CRITICAL();            //开中断
            用户中断处理代码;
}
五、结束语
      以上介绍了中断实现的各部分模块以及相应的代码结构分析。由于本人水平有限,一定还存在一些错误,有异议的地方可以一起讨论,还请高手多指教! 
附录
[1] MACRO MEND语法格式:
MACRO
标号 宏名 $ 参数 1 , $ 参数 2 ,…… 
指令序列 
MEND 

MACRO  MEND 伪指令可以将一段代码定义为一个整体,称为宏指令,然后就可以在程序中通过宏指令多次调用该段代码。其中, $ 标号在宏指令被展开时,标号会被替换为用户定义的符号, 宏指令可以使用一个或多个参数,当宏指令被展开时,这些参数被相应的值替换。 

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

包含在 MACRO  MEND 之间的指令序列称为宏定义体,在宏定义体的第一行应声明宏的原型(包含宏名、所需的参数),然后就可以在汇编程序中通过宏名来调用该指令序列。在源程序被编译时,汇编器将宏调用展开,用宏定义中的指令序列代替程序中的宏调用,并将实际参数的值传递给宏定义中的形式参数。 

MACRO  MEND 伪指令可以嵌套使用。 

参考资料:

[1]嵌入式实时操作系统μC/OS-Ⅱ(第2版)

原创粉丝点击