打印

NXP的ARM7带ucos中硬中断与软中断响应详细分析

[复制链接]
5404|14
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
db10|  楼主 | 2008-10-27 12:31 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式

            NXP的ARM7带ucos中硬中断与软中断响应详细分析



一.带UCOS系统的软中断响应过程    1
1.第一步:    2
2.第二步:    2
二.带UCOS系统的硬中断响应过程    6





  下面的主要分析LPC系列ARM7的中断响应,以周立公老师带ucos移植程序为分析对象,对其他的ARM中带UCOS的项目也有参考价值。

一.带UCOS系统的软中断响应过程
UCOS操作系统是以任务为单元的执行块,可以理解为linux中线程,任务需要进行切换,轮巡的执行,这种任务切换是通过ARM的软中断来实现的。ARM的软中断是在向量表中就存在,写在startup.s文件中,如:
;interrupt vectors
;中断向量表
Reset
        LDR     PC, ResetAddr
        LDR     PC, UndefinedAddr
        LDR     PC, SWI_Addr
        LDR     PC, PrefetchAddr
        LDR     PC, DataAbortAddr
        DCD     0xb9205f80
        LDR     PC, [PC, #-0xff0]
        LDR     PC, FIQ_Addr

这个向量表是ARM具备的,放在程序的开始位置,具体怎么放置和定位,要在编译时候设置分散定位表(这里不仔细讲),ARM复位启动的时候就会自动跑到开始行PC, ResetAddr。第三行LDR    PC, SWI_Addr为软中断响应行。附带说明,这个向量表程序在编译后的地址(程序在运行时可以看到汇编程序显示的每行地址,也就是常说的中断向量表中所有数据32位累加和为0)累加和为0,所以可以看到程序中为什么有DCD     0xb9205f80这行,数字是手动可以调整的。
  软中断和硬中断如果从程序角度分析,其实是一样的,都是响应的一种中断,响应开始要求环境和其他变量入桟保护起来,然后调用到中断响应的C程序中(就是自己写中断响应程序),执行完中断响应程序后,再进行堆栈的出桟,恢复之前保护的环境与变量,再掉转程序到开始进入中断前的位置,这样程序接着运行。其实这个过程,跟51一样,软中断与硬中断也一样。那软中断和硬中断到底有什么不同,软中断是可以人为控制的,硬中断是ARM芯片里面控制的,自动反应,如串口中断,是串口收到数据后,串口自动产生中断。软中断是通过手动调用程序后产生的中断。以我的理解,不知道是否还有其他区别。
  现在我们具体可以分析软中断响应过程了:
 1.第一步:
在UCOS中,当要进行任务切换,直接手动调用OS_TASK_SW()函数的调用,调用之后,相当执行了os_cpu.h文件中的
__swi(0x00) void OS_TASK_SW(void);            /*  任务级任务切换函数          */
这是ADS编译器可以认识的软中断执行程序。这样系统会响应软中断,程序会跳到上面讲过的startup.s文件中断向量表中LDR     PC, SWI_Addr,
2.第二步:
在startup.s文件中有SWI_Addr            DCD     SoftwareInterrupt,相当再跳到SoftwareInterrupt函数,这个函数在OS_cpu_a.s文件中:
;软件中断
SoftwareInterrupt
        LDR     SP, StackSvc            ; 重新设置堆栈指针
        STMFD   SP!, {R0-R3, R12, LR}
        MOV     R1, SP                  ; R1指向参数存储位置

        MRS     R3, SPSR
        TST     R3, #T_bit              ; 中断前是否是Thumb状态
        LDRNEH  R0, [LR,#-2]            ; 是: 取得Thumb状态SWI号
        BICNE   R0, R0, #0xff00
        LDREQ   R0, [LR,#-4]            ; 否: 取得arm状态SWI号
        BICEQ   R0, R0, #0xFF000000
                                        ; r0 = SWI号,R1指向参数存储位置
        CMP     R0, #1
        LDRLO   PC, =OSIntCtxSw
        LDREQ   PC, =__OSStartHighRdy   ; SWI 0x01为第一次任务切换

        BL      SWI_Exception
        
        LDMFD   SP!, {R0-R3, R12, PC}^
        
StackSvc           DCD     (SvcStackSpace + SVC_STACK_LEGTH * 4 - 4)

这个函数,是自己写的,可以进行改动。这其中就是上面说过的环境的一些入桟,出桟,还有查询是否有更高优先级任务的响应。软中断响应时因为有不同的中断,如关OS_ENTER_CRITICAL,开OS_EXIT_CRITICAL等很多软中断,所以程序执行上面中的SWI_Exception执行自己的外部中断C响应程序,里面实现具体的软中断:
void SWI_Exception(int SWI_Num, int *Regs)
{
    OS_TCB   *ptcb;
    
    switch(SWI_Num)
    {
        //case 0x00:      /* 任务切换函数OS_TASK_SW,参考os_cpu_s.s文件     */
        //    break;
        //case 0x01:      /* 启动任务函数OSStartHighRdy,参考os_cpu_s.s文件 */
        //    break;
        case 0x02:       /* 关中断函数OS_ENTER_CRITICAL(),参考os_cpu.h文件 */
            __asm
            {
                MRS     R0, SPSR
                ORR     R0, R0, #NoInt
                MSR     SPSR_c, R0
            }
            OsEnterSum++;
            break;
        case 0x03:       /* 开中断函数OS_EXIT_CRITICAL(),参考os_cpu.h文件 */
            if (--OsEnterSum == 0)
            {
                __asm
                {
                    MRS     R0, SPSR
                    BIC     R0, R0, #NoInt
                    MSR     SPSR_c, R0
                }
            }
            break;
#if OS_SELF_EN > 0 
        case 0x40:
                                        /* 返回指定系统服务函数的地址       */
                                        /* 函数地址存于数组_OSFunctionAddr中*/
                                        /* 数组_OSFunctionAddr需要另外定义  */
                                        /* Regs[0] 为第一个参数,也是返回值 */
                                        /* Regs[1] 为第二个参数             */
                                        /* Regs[2] 为第三个参数             */
                                        /* Regs[3] 为第四个参数             */
                                        /* 仅有一个参数为系统服务函数的索引 */
            Regs[0] =  _OSFunctionAddr[Regs[0]];
            break;
        case 0x41:
                                        /* 返回指定用户的服务函数的地址     */
                                        /* 函数地址存于数组_UsrFunctionAddr中*/
                                        /* 数组_UsrFunctionAddr需要另外定义 */
                                        /* Regs[0] 为第一个参数,也是返回值 */
                                        /* Regs[1] 为第二个参数             */
                                        /* Regs[2] 为第三个参数             */
                                        /* Regs[3] 为第四个参数             */
                                        /* 仅有一个参数为用户服务函数的索引 */
            Regs[0] =  _UsrFunctionAddr[Regs[0]];
            break;
        case 0x42:                      /* 中断开始处理 */
            OSIntNesting++;
            break;
        case 0x43:                      /*  判断中断是否需要切换             */
            if (OSTCBHighRdy == OSTCBCur)
            {
                Regs[0] = 0;
            }
            else
            {
                Regs[0] = 1;
            }
            break;
#endif
        case 0x80:                      /* 任务切换到系统模式 */
            __asm
            {
                MRS     R0, SPSR
                BIC     R0, R0, #0x1f
                ORR     R0, R0, #SYS32Mode    
                MSR     SPSR_c, R0
            }
            break;
        case 0x81:                      /* 任务切换到用户模式 */
            __asm
            {
                MRS     R0, SPSR
                BIC     R0, R0, #0x1f
                ORR     R0, R0, #USR32Mode    
                MSR     SPSR_c, R0
            }
            break;
        case 0x82:                      /* 任务是ARM代码 */
            if (Regs[0] <= OS_LOWEST_PRIO)
            {
                ptcb = OSTCBPrioTbl[Regs[0]];
                if (ptcb != NULL)
                {
                    ptcb -> OSTCBStkPtr[1] &= ~(1 << 5);
                }
            }
            break;
        case 0x83:                      /* 任务是THUMB代码 */
            if (Regs[0] <= OS_LOWEST_PRIO)
            {
                ptcb = OSTCBPrioTbl[Regs[0]];
                if (ptcb != NULL)
                {
                    ptcb -> OSTCBStkPtr[1] |= (1 << 5);
                }
            }
            break;
        default:
            break;
    }
}
这部分程序也是移植时自己写的,里面有汇编。
其中的switch部分程序,对应的关系可以os_cpu.h文件中:
  __swi(0x00) void OS_TASK_SW(void);       /*  任务级任务切换函数          */
__swi(0x01) void _OSStartHighRdy(void);         /*  运行优先级最高的任务        */
__swi(0x02) void OS_ENTER_CRITICAL(void);  /*  关中断                      */
__swi(0x03) void OS_EXIT_CRITICAL(void);   /*  开中断                      */

__swi(0x40) void *GetOSFunctionAddr(int Index); /*  获取系统服务函数入口        */
__swi(0x41) void *GetUsrFunctionAddr(int Index);/*  获取自定义服务函数入口      */
__swi(0x42) void OSISRBegin(void);              /*  中断开始处理                */
__swi(0x43) int  OSISRNeedSwap(void);           /*  判断中断是否需要切换        */

__swi(0x80) void ChangeToSYSMode(void);         /*  任务切换到系统模式          */
__swi(0x81) void ChangeT**oid);         /*  任务切换到用户模式          */
__swi(0x82) void TaskIsARM(INT8U prio);    /*  任务代码是ARM代码           */
__swi(0x83) void TaskIsTHUMB(INT8U prio);  /*  任务代码是THUMB             */

注意:上面的程序OS_TASK_SW任务切换,_OSStartHighRdy优先级最高的任务切换,不是在void SWI_Exception(int SWI_Num, int *Regs)函数中实现,被屏蔽了,直接改在OS_cpu_a.s文件中实现的:
;软件中断
SoftwareInterrupt
。。。。

为什么要调整在OS_cpu_a.s文件中实现,这个没有深入研究,我想应该都是可以的。

到现在整个软中断过程分析完了。
注意:上面的各个部分,每个阶段具体安排在哪里和哪个文件中,是可以认为改动的,甚至响应的形式也可以调整,但是每一个软中断的过程和要完成的工作是一样的,因为在用lpc21xx 系列时上面的整个程序结构基本一致的,现在用LPC23XX的时候有一些变化。

二.带UCOS系统的硬中断响应过程
   硬中断,就是我们常说的中断,一般指的是外围设备中断,如串口,I2C,TCP,USB等。
  硬中断一般分为普通和快速中断,对应上面所说的中断向量表中:
        LDR     PC, [PC, #-0xff0]
        LDR     PC, FIQ_Addr

  如果发生了普通的硬中断,程序会跳到LDR     PC, [PC, #-0xff0]行,由于对于一个ARM7不同的硬件中断响应时的入桟和出桟以及现场保护是相同的,所以写一个汇编的宏定义就可以实现多个硬中断响应共用,这个宏在IQR.INC有:
MACRO
$IRQ_Label HANDLER $IRQ_Exception_Function

        EXPORT  $IRQ_Label                      ; 输出的标号
        IMPORT  $IRQ_Exception_Function         ; 引用的外部标号

$IRQ_Label
        SUB     LR, LR, #4                      ; 计算返回地址
        STMFD   SP!, {R0-R3, R12, LR}           ; 保存任务环境
        MRS     R3, SPSR                        ; 保存状态
        STMFD   SP, {R3, SP, LR}^               ; 保存用户状态的R3,SP,LR,注意不能回写
                                                ; 如果回写的是用户的SP,所以后面要调整SP
        LDR     R2,  =OSIntNesting              ; OSIntNesting++
        LDRB    R1, [R2]
        ADD     R1, R1, #1
        STRB    R1, [R2]

        SUB     SP, SP, #4*3
        
        MSR     CPSR_c, #(NoInt | SYS32Mode)    ; 切换到系统模式
        CMP     R1, #1
        LDREQ   SP, =StackUsr
        
        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
        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
每个中断要申明一个宏,如:
 /*定时器0中断*/
;/*Time0 Interrupt*/
Timer0_Handler  HANDLER Timer0_Exception
其中:Timer0_Exception为自己的C响应中断函数。

说明:在上面的宏汇编程序中:
BL      $IRQ_Exception_Function         ; 调用c语言的中断处理程序
是每次中断响应时c语言的中断处理程序,这个是每个中断初始化时给每个中断向量地址的(对应nxp芯片的寄存器)。中断响应时会跳到中断向量表LDR     PC, [PC, #-0xff0]行,然后系统根据中断响应的通道号值(NXP的arm7每个外围设备对应唯一的中断通道值,51里也是这样的,就是中断号)执行对应的宏汇编程序,在汇编里执行
BL      $IRQ_Exception_Function         ; 调用c语言的中断处理程序初始化时
这个IRQ_Exception_Function 对应与中断初始化时的函数,如定时器的:VICVectAddr0 = (uint32)Timer0_Handler;  //定时器0中断函数地址分配
这样可以响应中断函数了。

注意:上面的硬件中断过程,也有可能写法不完全一致,但是对应nxp的ARM7的响应过程是一致的,对于快速中断响应过程也是一样的。

                                                  
献给ARM的所有移植者        写于2008.10.27


相关帖子

沙发
zyok| | 2008-10-27 12:33 | 只看该作者

谢谢!

使用特权

评论回复
板凳
Swd21ic| | 2008-11-1 23:57 | 只看该作者

re

10兄咋不用官方的例程..
官方的移植都是针对某一种构架的.
不同的MCU通过bsp.c来实现,提供TICK和中断控制器操作等..

使用特权

评论回复
地板
db10|  楼主 | 2008-11-3 10:24 | 只看该作者

。。。。

 LS,谢谢捧场。
 官方的比较高深吧,没有注意过,俺不想学,所以就这样子。。。 
 如果有机会俺们多聊,我之前ucos搞的少,1年前才开始真正搞,之前搞linux 多点。

使用特权

评论回复
5
andrewpei| | 2008-11-3 19:29 | 只看该作者

楼主在周工划的这个圈圈中跳舞,不知是否在浪费时间与精

周工使用软中断在ARM7内核的MCU上实现OS的系统调用,这种做法实际上并不可取。因为ARM7内核根本没有内存保护机制,也没有MMU这样的硬件,使用软中断并不能实现内存空间的代码隔离,反而增加了无谓的CPU开销。这样子玩玩还可以,实际使用中,价值不大!

周工的东西是不错,可是如果两只眼睛都盯着他的而无暇其它,那就真是的读死书了。

使用特权

评论回复
6
Swd21ic| | 2008-11-3 22:47 | 只看该作者

:)

www.micrium.com里的port

你可以去下一个atmel的例程.和nxp的例程比较一下.

里面的os_cpu.c  os_cpu_a.asm op_cpu.h 都是一样的.只是bsp中来实现他们的AIC和VIC部分..  

注意arm port最新的版本是1.84  但文档只更新到了1.83.
不过从1.83起,官方的移植已经支持中断嵌套了,所以都差不多.
以前大多的例程的port是1.82, 不能支持中断嵌套.
年初我琢磨一段时间..可现在又不用了..改cortex-m3了.
m3的移植比arm7简单很多.主要还是内核构架上已经充分考虑OS的因素.~

使用特权

评论回复
7
Swd21ic| | 2008-11-3 22:49 | 只看该作者

re

官方的移植特别是针对arm7-9构架的.已经经过多个版本的演变.
肯定考虑的是比zlg的版本要好的.. (只要没下套..)

使用特权

评论回复
8
db10|  楼主 | 2008-11-3 23:08 | 只看该作者

andrewpei

 估计你读书蛮厉害的??

使用特权

评论回复
9
andrewpei| | 2008-11-4 08:58 | 只看该作者

db10

看来db10有些不乐意了。

我也是象你那样潜心研究过周工的东西的,而且痴迷程序不亚于你在顶楼开贴时的那种热情。但是后来因为偶然看了一本作者叫黄燕平出的uC/OS移植详解的书,引起了我的很多思考。然后仔细研究了一下eCos系统、参考了一些linux的设备驱动,期间也看了一些t-kernel的代码,所以才会有上面的一些感慨。我也相信现在周工公司里内部开发所应用的框架肯定与顶楼中的那个方法有极大的差别,而且他也绝对不会公开的。

凭心而论,周工的东东还是不错,但是不错归不错,它并不是圣经,它只是引你入门,但是如果你抱着这个拐杖不放的话,恐怕永远学不会跑。嵌入式技术本身就是一个多彩的世界,何必听见有点不同的意见,就作这样的反应呢?

使用特权

评论回复
10
sodwell| | 2008-11-4 09:53 | 只看该作者

re

看周工的东西,我越看越迷糊,真的是有读死书的感觉。
所以现在基本不会看他的任何东西,不感兴趣

使用特权

评论回复
11
db10|  楼主 | 2008-11-4 15:14 | 只看该作者

andrewpei

 呵呵,我没有对ZLG得东西说好说坏,我只是看到了分析下而已,你怎么知道我对他得东西这么感兴趣?我就没有看过别的东西?呵呵。。。 

使用特权

评论回复
12
db10|  楼主 | 2008-11-4 15:29 | 只看该作者

。。。

andrewpei 如果分析不对的地方和别的更好的,我是非常赞成的,这样让别人可以学得更多和更好,但是不要针对人说事,就不好了啊。。。不是好同志啊。

使用特权

评论回复
13
chenzhufly| | 2008-11-4 16:53 | 只看该作者

都是好同志啊

andrewpei的建议是很好的,需要好好的琢磨啊,应该是一个过来人的忠言,古往今来,往往都是忠言逆耳。。。

db10也是一位很好的同志,很勤奋,很认真,无私奉献的精神可嘉。

有些东西只有自己切身的经历了,才知道好与不好。抽象的说一个东西的好于不好,总是招来争论,呵呵
有时间和精力的就多做点尝试吧,经验也就是这样磨练出来的。想走走捷径的,那就要多听听过来人的经验之谈吧

使用特权

评论回复
14
zyok| | 2008-11-4 19:31 | 只看该作者

顶,andrewpei大侠也贴一段出来吧...

使用特权

评论回复
15
Swd21ic| | 2008-11-4 22:04 | 只看该作者

re

Cortex-M3的官方移植就是基于类似ARM7软中断的机制.~
的确没有mpu或mmu无法做到应用与系统隔离..
ucos在ARM7-9的移植就全部基于管理模式

在C中插入汇编的做法也不是很规范,还是分开按ATPCS调用比较好...

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

77

主题

2230

帖子

0

粉丝