你的浏览器版本过低,可能导致网站不能正常访问!
为了你能正常使用网站功能,请使用这些浏览器。

ucosII在stm32的疑问,困扰许久【悬赏问答】

[复制链接]
helloRMB 提问时间:2012-7-5 16:28 /
任务切换是通过产生一个PendSV中断,在中断服务程序中实现的。进入中断时保存了近一半的寄存器的值。我的理解是应该把这些值保存到任务堆栈中,但下面的代码并没有体现出。我很困惑,这些寄存器的值究竟保存在什么地方,与任务的堆栈的是否有关系?困扰许久,跪求高人指点
OS_CPU_PendSVHandler
    CPSID   I                                                   ; Prevent interruption during context switch
    MRS     R0, PSP                                             ; PSP is process stack pointer
    CBZ     R0, OS_CPU_PendSVHandler_nosave                     ; Skip register save the first time
    SUBS    R0, R0, #0x20                                       ; Save remaining regs r4-11 on process stack
    STM     R0, {R4-R11}
    LDR     R1, =OSTCBCur                                       ; OSTCBCur->OSTCBStkPtr = SP;
    LDR     R1, [R1]
    STR     R0, [R1]                                            ; R0 is SP of process being switched out
                                                                ; At this point, entire context of process has been saved
OS_CPU_PendSVHandler_nosave
    PUSH    {R14}                                               ; Save LR exc_return value
    LDR     R0, =OSTaskSwHook                                   ; OSTaskSwHook();
    BLX     R0
    POP     {R14}
    LDR     R0, =OSPrioCur                                      ; OSPrioCur = OSPrioHighRdy;
    LDR     R1, =OSPrioHighRdy
    LDRB    R2, [R1]
    STRB    R2, [R0]
    LDR     R0, =OSTCBCur                                       ; OSTCBCur  = OSTCBHighRdy;
    LDR     R1, =OSTCBHighRdy
    LDR     R2, [R1]
    STR     R2, [R0]
    LDR     R0, [R2]                                            ; R0 is new process SP; SP = OSTCBHighRdy->OSTCBStkPtr;
    LDM     R0, {R4-R11}                                        ; Restore r4-11 from new process stack
    ADDS    R0, R0, #0x20
    MSR     PSP, R0                                             ; Load PSP with new process SP
    ORR     LR, LR, #0x04                                       ; Ensure exception return uses process stack
    CPSIE   I
    BX      LR                                                  ; Exception return will restore remaining context
    END
收藏 评论9 发布时间:2012-7-5 16:28

举报

9个回答
yjwpm 回答时间:2012-7-12 16:03:32

RE:ucosII在stm32的疑问,困扰许久【悬赏问答】

我的理解是这些是保存在堆栈中的。
 
移植详解1和2中主要讲了移植需要用到的基础知识,本文则对具体的移植过程进行介绍。
    首先从micrium网站上下载官方移植版本(编译器使用ARM/Keil的,V2.86版本,V2.85有问题)。
    下载地址:
http://micrium.com/page/downloads/ports/st/stm32
    解压缩后得到如下文件夹和文件:
    Micrium\
       AppNotes
       Licensing
       Software
       ReadMe.pdf

    AppNotes包含ucosii移植说明文件。这两个文件中我们仅需关心Micrium\AppNotes\AN1xxx-RTOS\AN1018-uCOS-II-Cortex-M3\AN-1018.pdf。因为这个文件对ucosii在CM3内核移植过程中需要修改的代码进行了说明。
   Licensing包含ucosii使用许可证。
   Software下有好几个文件夹,在本文的移植中仅需关心uCOS-II即可。
       CPU: stm32标准外设库
       EvalBoards: micrium官方评估板相关代码
       uc-CPU: 基于micrium官方评估板的ucosii移植代码
       uC-LCD:micrium官方评估板LCD驱动代码
       uc-LIB: micrium官方的一个库代码
       uCOS-II: ucosii源代码
       uC-Probe: 和uC-Probe相关代码
   ReadMe.pdf就不说了。

    好了,官方的东西介绍完了,该我们自己建立工程着手移植了。关于建立工程,并使用stm32标准外设库在我之前的文章《stm32标准外设库使用详解》已有介绍,这里请大家下载其中模板代码http://download.csdn.net/source/3448543),本文的移植是基于这个工程的。
    建立文件夹template\src\ucosii, template\src\ucosii\src, template\src\ucosii\port;
    把Micrium\Software\uCOS-II\Source下的文件拷贝至template\src\ucosii\src;
    把Micrium\Software\uCOS-II\Ports\ARM-Cortex-M3\Generic\RealView下的文件拷贝至

template\src\ucosii\port;
   ucosii\src下的代码是ucosii中无需修改部分,ucosii\port下的代码是移植时需要修改的。为防止对
源码的误改动造成移植失败,可以把ucosii\src下的代码文件设为只读。
   这里根据AN-1018.pdf和移植详解1、2中介绍的移植基础知识,对ucosii\port下的代码解释一下。

os_cpu.h
#ifdef   OS_CPU_GLOBALS
#define  OS_CPU_EXT
#else
#define  OS_CPU_EXT  extern
#endif

typedef unsigned char BOOLEAN;
typedef unsigned char INT8U;
typedef signed char INT8S;
typedef unsigned short INT16U;
typedef signed short INT16S;
typedef unsigned int INT32U;
typedef signed int INT32S;
typedef float FP32;
typedef double FP64;
就不解释了。

typedef unsigned int OS_STK;
typedef unsigned int OS_CPU_SR;

   因为CM3是32位宽的,所以OS_STK(堆栈的数据类型)被类型重定义为unsigned int。
   因为CM3的状态寄存器(xPSR)是32位宽的,因此OS_CPU_SR被类型重定义为unsigned int。OS_CPU_SR
是在OS_CRITICAL_METHOD方法3中保存cpu状态寄存器用的。在CM3中,移植OS_ENTER_CRITICAL(),OS_EXIT_CRITICAL()选方法3是最合适的。
#define  OS_CRITICAL_METHOD   3
#if OS_CRITICAL_METHOD == 3
#define  OS_ENTER_CRITICAL()  {cpu_sr = OS_CPU_SR_Save();}
#define  OS_EXIT_CRITICAL()   {OS_CPU_SR_Restore(cpu_sr);}
#endif

   具体定义宏OS_ENTER_CRITICAL()和OS_EXIT_CRITICAL(),其中OS_CPU_SR_Save()和OS_CPU_SR_Restore()是用汇编代码写的,代码在os_cpu_a.asm中,到时再解释。
#define  OS_STK_GROWTH        1
   CM3中,栈是由高地址向低地址增长的,因此OS_STK_GROWTH定义为1。

#define OS_TASK_SW() OSCtxSw()
   定义任务切换宏,OSCtxSw()是用汇编代码写的,代码在os_cpu_a.asm中,到时再解释。

#if OS_CRITICAL_METHOD == 3                       
OS_CPU_SR  OS_CPU_SR_Save(void);
void       OS_CPU_SR_Restore(OS_CPU_SR cpu_sr);
#endif

void       OSCtxSw(void);
void       OSIntCtxSw(void);
void       OSStartHighRdy(void);
void       OS_CPU_PendSVHandler(void);
 
void       OS_CPU_SysTickHandler(void);
void       OS_CPU_SysTickInit(void);
INT32U     OS_CPU_SysTickClkFreq(void);

    申明几个函数,这里要注意最后三个函数需要注释掉,为什么呢?
    OS_CPU_SysTickHandler()定义在os_cpu_c.c中,是SysTick中断的中断处理函数,而stm32f10x_it.c,
中已经有该中断函数的定义SysTick_Handler(),这里也就不需要了,是不是很奇怪官方移植版为什么会这样弄吧,后面我会解释的。
    OS_CPU_SysTickInit()定义在os_cpu_c.c中,用于初始化SysTick定时器,它依赖于
OS_CPU_SysTickClkFreq(),而此函数我们自己会实现,所以注释掉。
    OS_CPU_SysTickClkFreq()定义在BSP.C (Micrium\Software\EvalBoards)中,而本文移植中并未用到
BSP.C,后面我们会自己实现,因此可以把它注释掉。
os_cpu_c.c
    ucosii移植时需要我们写10个相当简单的C函数。

    OSInitHookBegin()
    OSInitHookEnd()
    OSTaskCreateHook()
    OSTaskDelHook()
    OSTaskIdleHook()
    OSTaskStatHook()
    OSTaskStkInit()
    OSTaskSwHook()
    OSTCBInitHook()
    OSTimeTickHook()

    这些函数除了OSTaskStkInit(),都是一些hook函数。这些hook函数如果不使能的话,都不会用上,也都比较简单,看看就应该明白了,所以就不介绍。
    下面就说一说OSTaskStkInit()。说之前还是得先说一下任务切换,因为初始化任务堆栈,是为任务
切换服务的。代码在正常运行时,一行一行往下执行,怎么才能跑到另一个任务(即函数)执行呢?首先大家可以回想一下中断过程,当中断发生时,原来函数执行的地方(程序计数器PC、处理器状态寄存器及所有通用寄存器,即当前代码的现场)被保存到栈里面去了,然后开始取中断向量,跑到中断函数里面执行。执行完了呢,想回到原来函数执行的地方,该怎么办呢,只要把栈中保存的原来函数执行的信息恢复即可(把栈中保存的代码现场重新赋给cpu的各个寄存器),一切就都回去了,好像什么事都没发生一样。这个过程大家应该都比较熟悉,任务切换和这有什么关系,试想一下,如果有3个函数foo1(), foo2(), foo3()像是刚被中断,现场保存到栈里面去了,而中断返回时做点手脚(调度程序的作用),想回哪个回哪个,是不是就做了函数(任务)切换了。看到这里应该有点明白OSTaskStkInit()的作用了吧,它被任务创建函数调用,所以要在开始时,在栈中作出该任务好像刚被中断一样的假象。(关于任务切换的原理邵老师书中的3.06节有介绍)。
    那么中断后栈中是个什么情形呢,中9.1.1有介绍,xPSR,PC,LR,R12
,R3-R0被自动保存到栈中的,R11-R4如果需要保存,只能手工保存。因此OSTaskStkInit()的工作就是在任务自己的栈中保存cpu的所有寄存器。这些值里R1-R12都没什么意义,这里用相应的数字代号(如R1用0x01010101)主要是方便调试。
    其他几个:
    xPSR = 0x01000000L,xPSR T位(第24位)置1,否则第一次执行任务时Fault,
    PC肯定得指向任务入口,
    R14 = 0xFFFFFFFEL,最低4位为E,是一个非法值,主要目的是不让使用R14,即任务是不能返回的。
    R0用于传递任务函数的参数,因此等于p_arg。
  
OS_STK *OSTaskStkInit (void (*task)(void *p_arg), void *p_arg, OS_STK *ptos, INT16U opt)
{
    OS_STK *stk;


    (void)opt;                        /* 'opt' is not used, prevent warning */
    stk       = ptos;                 /* Load stack pointer                 */

    /* Registers stacked as if auto-saved on exception */
    *(stk)    = (INT32U)0x01000000L;  /* xPSR        */
    *(--stk)  = (INT32U)task;         /* Entry Point */
    /* R14 (LR) (init value will cause fault if ever used)*/
    *(--stk)  = (INT32U)0xFFFFFFFEL; 
    *(--stk)  = (INT32U)0x12121212L;  /* R12 */
    *(--stk)  = (INT32U)0x03030303L;  /* R3  */
    *(--stk)  = (INT32U)0x02020202L;  /* R2  */
    *(--stk)  = (INT32U)0x01010101L;  /* R1  */
    *(--stk)  = (INT32U)p_arg;        /* R0 : argument  */

    /* Remaining registers saved on process stack */
    *(--stk)  = (INT32U)0x11111111L;  /* R11 */
    *(--stk)  = (INT32U)0x10101010L;  /* R10 */
    *(--stk)  = (INT32U)0x09090909L;  /* R9  */
    *(--stk)  = (INT32U)0x08080808L;  /* R8  */
    *(--stk)  = (INT32U)0x07070707L;  /* R7  */
    *(--stk)  = (INT32U)0x06060606L;  /* R6  */
    *(--stk)  = (INT32U)0x05050505L;  /* R5  */
    *(--stk)  = (INT32U)0x04040404L;  /* R4  */

    return (stk);
}

把OS_CPU_SysTickHandler(), OS_CPU_SysTickInit()注释掉。
#define  OS_CPU_CM3_NVIC_ST_CTRL    (*((volatile INT32U *)0xE000E010))
#define  OS_CPU_CM3_NVIC_ST_RELOAD  (*((volatile INT32U *)0xE000E014))
#define  OS_CPU_CM3_NVIC_ST_CURRENT (*((volatile INT32U *)0xE000E018))
#define  OS_CPU_CM3_NVIC_ST_CAL     (*((volatile INT32U *)0xE000E01C))

#define  OS_CPU_CM3_NVIC_ST_CTRL_COUNT                    0x00010000  
#define  OS_CPU_CM3_NVIC_ST_CTRL_CLK_SRC                  0x00000004  
#define  OS_CPU_CM3_NVIC_ST_CTRL_INTEN                    0x00000002  
#define  OS_CPU_CM3_NVIC_ST_CTRL_ENABLE                   0x00000001 

把上面这些宏定义也注释掉,因为它们都用于OS_CPU_SysTickHandler(), OS_CPU_SysTickInit()。
os_cpu_a.asm
这个文件包含着必须用汇编写的代码。

    EXTERN  OSRunning    ; External references
    EXTERN  OSPrioCur
    EXTERN  OSPrioHighRdy
    EXTERN  OSTCBCur
    EXTERN  OSTCBHighRdy
    EXTERN  OSIntNesting
    EXTERN  OSIntExit
    EXTERN  OSTaskSwHook
    申明这些变量是在其他文件定义的,本文件只做引用(有几个好像并未引用,不过没有关系)。

    EXPORT  OS_CPU_SR_Save   ; Functions declared in this file
    EXPORT  OS_CPU_SR_Restore
    EXPORT  OSStartHighRdy
    EXPORT  OSCtxSw
    EXPORT  OSIntCtxSw
    EXPORT  OS_CPU_PendSVHandler
    申明这些函数是在本文件中定义的。

NVIC_INT_CTRL   EQU     0xE000ED04   ;中断控制及状态寄存器ICSR的地址
NVIC_SYSPRI14   EQU     0xE000ED22   endSV优先级寄存器的地址
NVIC_PENDSV_PRI EQU           0xFF   endSV中断的优先级为255(最低)
NVIC_PENDSVSET  EQU     0x10000000   ;位28为1
    定义几个常量,类似C语言中的#define预处理指令。

OS_CPU_SR_Save
    MRS     R0, PRIMASK   ;读取PRIMASK到R0中,R0为返回值
    CPSID   I             RIMASK=1,关中断(NMI和硬fault可以响应)
    BX      LR            ;返回

OS_CPU_SR_Restore
    MSR     PRIMASK, R0   ;读取R0到PRIMASK中,R0为参数
    BX      LR            ;返回

OSStartHighRdy()由OSStart()调用,用来启动最高优先级任务,当然任务必须在OSStart()前已被创建。
OSStartHighRdy
    ;设置PendSV中断的优先级 #1
    LDR     R0, =NVIC_SYSPRI14    ;R0 = NVIC_SYSPRI14
    LDR     R1, =NVIC_PENDSV_PRI  ;R1 = NVIC_PENDSV_PRI
    STRB    R1, [R0]              ;*(uint8_t *)NVIC_SYSPRI14 = NVIC_PENDSV_PRI

    ;设置PSP为0 #2
    MOVS    R0, #0                ;R0 = 0
    MSR     PSP, R0               SP = R0

    ;设置OSRunning为TRUE
    LDR     R0, =OSRunning        ;R0 = OSRunning
    MOVS    R1, #1                ;R1 = 1
    STRB    R1, [R0]              ;OSRunning = 1
 
    ;触发PendSV中断 #3
    LDR     R0, =NVIC_INT_CTRL    ;R0 = NVIC_INT_CTRL
    LDR     R1, =NVIC_PENDSVSET   ;R1 = NVIC_PENDSVSET
    STR     R1, [R0]              ;*(uint32_t *)NVIC_INT_CTRL = NVIC_PENDSVSET

    CPSIE   I                     ;开中断                 
OSStartHang                       ;死循环,应该不会到这里
    B       OSStartHang

#1.PendSV中断的优先级应该为最低优先级,原因在的7.6节已有说明。
#2.PSP设置为0,是告诉具体的任务切换程序(OS_CPU_PendSVHandler()),这是第一次任务切换。做过
切换后PSP就不会为0了,后面会看到。
#3.往中断控制及状态寄存器ICSR(0xE000ED04)第28位写1即可产生PendSV中断。这个8.4.5 其它异常的配置寄存器有说明。

    当一个任务放弃cpu的使用权,就会调用OS_TASK_SW()宏,而OS_TASK_SW()就是OSCtxSw()。OSCtxSw()应该做任务切换。但是在CM3中,所有任务切换都被放到PendSV的中断处理函数中去做了,因此OSCtxSw()只需简单的触发PendSV中断即可。OS_TASK_SW()是由OS_Sched()调用。
void  OS_Sched (void)
{
#if OS_CRITICAL_METHOD == 3
    OS_CPU_SR  cpu_sr = 0;
#endif


    OS_ENTER_CRITICAL();
    if (OSIntNesting == 0) {
        if (OSLockNesting == 0) {
            OS_SchedNew();
            if (OSPrioHighRdy != OSPrioCur) {
                OSTCBHighRdy = OSTCBPrioTbl[OSPrioHighRdy];
#if OS_TASK_PROFILE_EN > 0
                OSTCBHighRdy->OSTCBCtxSwCtr++;
#endif
                OSCtxSwCtr++;
                OS_TASK_SW();    /* 触发PendSV中断 */
            }
        }
    }
    /* 一旦开中断,PendSV中断函数会执行(当然要等更高优先级中断处理完) */
    OS_EXIT_CRITICAL(); 
}

OSCtxSw
    ;触发PendSV中断
    LDR     R0, =NVIC_INT_CTRL    ;R0 = NVIC_INT_CTRL
    LDR     R1, =NVIC_PENDSVSET   ;R1 = NVIC_PENDSVSET
    STR     R1, [R0]              ;*(uint32_t *)NVIC_INT_CTRL = NVIC_PENDSVSET
    BX      LR                    ;返回

    当一个中断处理函数退出时,OSIntExit()会被调用来决定是否有优先级更高的任务需要执行。如果有OSIntExit()对调用OSIntCtxSw()做任务切换。
OSIntCtxSw
    ;触发PendSV中断
    LDR     R0, =NVIC_INT_CTRL
    LDR     R1, =NVIC_PENDSVSET
    STR     R1, [R0]
    BX      LR

    看到这里有些同学可能奇怪怎么OSCtxSw()和OSIntCtxSw()完全一样,事实上,这两个函数的意义是不一样的,OSCtxSw()做的是任务之间的切换,如任务A因为等待某个资源或是做延时切换到任务B,而OSIntCtxSw()则是中断退出时,由中断状态切换到另一个任务。由中断切换到任务时,CPU寄存器入栈的工作已经做完了,所以无需做第二次了(参考邵老师书的3.10节)。这里只不过由于CM3的特殊机制导致了在这两个函数中只要做触发PendSV中断即可,具体切换由PendSV中断来处理。
    前面已经说过真正的任务切换是在PendSV中断处理函数里做的,由于CM3在中断时会有一半的寄存器自动保存到任务堆栈里,所以在PendSV中断处理函数中只需保存R4-R11并调节堆栈指针即可。
PendSV中断处理函数伪代码如下:
OS_CPU_PendSVHandler()
{
        if (PSP != NULL) {
                Save R4-R11 onto task stack;
                OSTCBCur->OSTCBStkPtr = SP;
        }
        OSTaskSwHook();
        OSPrioCur = OSPrioHighRdy;
        OSTCBCur = OSTCBHighRdy;
        PSP = OSTCBHighRdy->OSTCBStkPtr;
        Restore R4-R11 from new task stack;
        Return from exception;
}

OS_CPU_PendSVHandler             ;xPSR, PC, LR, R12, R0-R3已自动保存
    CPSID   I                    ;任务切换期间需要关中断
   
    MRS     R0, PSP              ;R0 = PSP
    ;如果PSP == 0,跳到OS_CPU_PendSVHandler_nosave执行 #1
    CBZ     R0, OS_CPU_PendSVHandler_nosave

    ;保存R4-R11到任务堆栈
    SUBS    R0, R0, #0x20        ;R0 -= 0x20                        
    STM     R0, {R4-R11}         ;保存R4-R11到任务堆栈

    ;OSTCBCur->OSTCBStkPtr = SP;
    LDR     R1, =OSTCBCur        ;R1 = &OSTCBCur
    LDR     R1, [R1]             ;R1 = *R1 (R1 = OSTCBCur)
    STR     R0, [R1]             ;*R1 = R0 (*OSTCBCur = SP) #2                      

OS_CPU_PendSVHandler_nosave
    ;调用OSTaskSwHook()
    PUSH    {R14}                ;保存R14,因为后面要调用函数           
    LDR     R0, =OSTaskSwHook    ;R0 = &OSTaskSwHook
    BLX     R0                   ;调用OSTaskSwHook()
    POP     {R14}                ;恢复R14

    ;OSPrioCur = OSPrioHighRdy;
    LDR     R0, =OSPrioCur       ;R0 = &OSPrioCur
    LDR     R1, =OSPrioHighRdy   ;R1 = &OSPrioHighRdy
    LDRB    R2, [R1]             ;R2 = *R1 (R2 = OSPrioHighRdy)
    STRB    R2, [R0]             ;*R0 = R2 (OSPrioCur = OSPrioHighRdy)

    ;OSTCBCur = OSTCBHighRdy;
    LDR     R0, =OSTCBCur        ;R0 = &OSTCBCur     
    LDR     R1, =OSTCBHighRdy    ;R1 = &OSTCBHighRdy
    LDR     R2, [R1]             ;R2 = *R1 (R2 = OSTCBHighRdy)
    STR     R2, [R0]             ;*R0 = R2 (OSTCBCur = OSTCBHighRdy)

    LDR     R0, [R2]             ;R0 = *R2 (R0 = OSTCBHighRdy), 此时R0是新任务的SP
                                 ;SP = OSTCBHighRdy->OSTCBStkPtr #3  
    LDM     R0, {R4-R11}         ;从任务堆栈SP恢复R4-R11      
    ADDS    R0, R0, #0x20        ;R0 += 0x20
    MSR     PSP, R0              SP = R0,用新任务的SP加载PSP
    ORR     LR, LR, #0x04        ;确保LR位2为1,返回后使用进程堆栈 #4     
    CPSIE   I                    ;开中断
    BX      LR                   ;中断返回                

    END
#1 如果PSP == 0,说明OSStartHighRdy()启动后第一次做任务切换,而任务刚创建时R4-R11已经保存在堆栈中了,所以不需要再保存一次了。
#2 OSTCBStkPtr是任务控制块结构体的第一个变量,所以*OSTCBCur = SP(不是很科学)就是OSTCBCur->OSTCBStkPtr = SP;
#3 和#2类似。
#4 因为在中断处理函数中使用的是MSP,所以在返回任务后必须使用PSP,所以LR位2必须为1。

os_dbg.c
用于系统调试,可以不管。

    需要修改的代码就介绍到这里,如果还有不明白之处,就再看看AN-1018.pdf,邵老师的书和。
航天航海 回答时间:2012-7-12 16:05:45

RE:ucosII在stm32的疑问,困扰许久【悬赏问答】

是的,应该都是保存在堆栈中的。
航天航海 回答时间:2012-7-12 16:08:29

RE:ucosII在stm32的疑问,困扰许久【悬赏问答】

SUBS    R0, R0, #0x20                                       ; Save remaining regs r4-11 on process stack
是的,应该都是保存在堆栈中的。
xiaodc88 回答时间:2012-7-12 19:53:57

RE:ucosII在stm32的疑问,困扰许久【悬赏问答】

没记错的话是保存在堆栈中
cx032302 回答时间:2012-7-13 09:57:48

RE:ucosII在stm32的疑问,困扰许久【悬赏问答】

理论上是保存在堆栈中
starcool 回答时间:2012-7-13 12:20:54

RE:ucosII在stm32的疑问,困扰许久【悬赏问答】

 
任务切换是在PendSV中断处理函数里做的,由于CM3在中断时会有一半的寄存器自动保存到任务堆栈里,所以在PendSV中断处理函数中只需保存R4-R11并调节堆栈指针即可,参考下这个例程的
 
当一个任务放弃cpu的使用权,就会调用OS_TASK_SW()宏,而OS_TASK_SW()就是OSCtxSw()。OSCtxSw()应该做任务切换。但是在CM3中,所有任务切换都被放到PendSV的中断处理函数中去做了,因此OSCtxSw()只需简单的触发PendSV中断即可。OS_TASK_SW()是由OS_Sched()调用。
void  OS_Sched (void)
{
#if OS_CRITICAL_METHOD == 3
    OS_CPU_SR  cpu_sr = 0;
#endif

    OS_ENTER_CRITICAL();
    if (OSIntNesting == 0) {
        if (OSLockNesting == 0) {
            OS_SchedNew();
            if (OSPrioHighRdy != OSPrioCur) {
                OSTCBHighRdy = OSTCBPrioTbl[OSPrioHighRdy];
#if OS_TASK_PROFILE_EN > 0
                OSTCBHighRdy->OSTCBCtxSwCtr++;
#endif
                OSCtxSwCtr++;
                OS_TASK_SW();    /* 触发PendSV中断 */
            }
        }
    }
    /* 一旦开中断,PendSV中断函数会执行(当然要等更高优先级中断处理完) */
    OS_EXIT_CRITICAL(); 
}
OSCtxSw
    ;触发PendSV中断
    LDR     R0, =NVIC_INT_CTRL    ;R0 = NVIC_INT_CTRL
    LDR     R1, =NVIC_PENDSVSET   ;R1 = NVIC_PENDSVSET
    STR     R1, [R0]              ;*(uint32_t *)NVIC_INT_CTRL = NVIC_PENDSVSET
    BX      LR                    ;返回
    当一个中断处理函数退出时,OSIntExit()会被调用来决定是否有优先级更高的任务需要执行。如果有OSIntExit()对调用OSIntCtxSw()做任务切换。
OSIntCtxSw
    ;触发PendSV中断
    LDR     R0, =NVIC_INT_CTRL
    LDR     R1, =NVIC_PENDSVSET
    STR     R1, [R0]
    BX      LR
    看到这里有些同学可能奇怪怎么OSCtxSw()和OSIntCtxSw()完全一样,事实上,这两个函数的意义是不一样的,OSCtxSw()做的是任务之间的切换,如任务A因为等待某个资源或是做延时切换到任务B,而OSIntCtxSw()则是中断退出时,由中断状态切换到另一个任务。由中断切换到任务时,CPU寄存器入栈的工作已经做完了,所以无需做第二次了(参考邵老师书的3.10节)。这里只不过由于CM3的特殊机制导致了在这两个函数中只要做触发PendSV中断即可,具体切换由PendSV中断来处理。
    前面已经说过真正的任务切换是在PendSV中断处理函数里做的,由于CM3在中断时会有一半的寄存器自动保存到任务堆栈里,所以在PendSV中断处理函数中只需保存R4-R11并调节堆栈指针即可。
PendSV中断处理函数伪代码如下:
OS_CPU_PendSVHandler()
{
        if (PSP != NULL) {
                Save R4-R11 onto task stack;
                OSTCBCur->OSTCBStkPtr = SP;
        }
        OSTaskSwHook();
        OSPrioCur = OSPrioHighRdy;
        OSTCBCur = OSTCBHighRdy;
        PSP = OSTCBHighRdy->OSTCBStkPtr;
        Restore R4-R11 from new task stack;
        Return from exception;
}
OS_CPU_PendSVHandler             ;xPSR, PC, LR, R12, R0-R3已自动保存
    CPSID   I                    ;任务切换期间需要关中断
   
    MRS     R0, PSP              ;R0 = PSP
    ;如果PSP == 0,跳到OS_CPU_PendSVHandler_nosave执行 #1
    CBZ     R0, OS_CPU_PendSVHandler_nosave
    ;保存R4-R11到任务堆栈
    SUBS    R0, R0, #0x20        ;R0 -= 0x20                        
    STM     R0, {R4-R11}         ;保存R4-R11到任务堆栈
    ;OSTCBCur->OSTCBStkPtr = SP;
    LDR     R1, =OSTCBCur        ;R1 = &OSTCBCur
    LDR     R1, [R1]             ;R1 = *R1 (R1 = OSTCBCur)
    STR     R0, [R1]             ;*R1 = R0 (*OSTCBCur = SP) #2                      
OS_CPU_PendSVHandler_nosave
    ;调用OSTaskSwHook()
    PUSH    {R14}                ;保存R14,因为后面要调用函数           
    LDR     R0, =OSTaskSwHook    ;R0 = &OSTaskSwHook
    BLX     R0                   ;调用OSTaskSwHook()
    POP     {R14}                ;恢复R14
    ;OSPrioCur = OSPrioHighRdy;
    LDR     R0, =OSPrioCur       ;R0 = &OSPrioCur
    LDR     R1, =OSPrioHighRdy   ;R1 = &OSPrioHighRdy
    LDRB    R2, [R1]             ;R2 = *R1 (R2 = OSPrioHighRdy)
    STRB    R2, [R0]             ;*R0 = R2 (OSPrioCur = OSPrioHighRdy)
    ;OSTCBCur = OSTCBHighRdy;
    LDR     R0, =OSTCBCur        ;R0 = &OSTCBCur     
    LDR     R1, =OSTCBHighRdy    ;R1 = &OSTCBHighRdy
    LDR     R2, [R1]             ;R2 = *R1 (R2 = OSTCBHighRdy)
    STR     R2, [R0]             ;*R0 = R2 (OSTCBCur = OSTCBHighRdy)
    LDR     R0, [R2]             ;R0 = *R2 (R0 = OSTCBHighRdy), 此时R0是新任务的SP
                                 ;SP = OSTCBHighRdy->OSTCBStkPtr #3  
    LDM     R0, {R4-R11}         ;从任务堆栈SP恢复R4-R11      
    ADDS    R0, R0, #0x20        ;R0 += 0x20
    MSR     PSP, R0              SP = R0,用新任务的SP加载PSP
    ORR     LR, LR, #0x04        ;确保LR位2为1,返回后使用进程堆栈 #4     
    CPSIE   I                    ;开中断
    BX      LR                   ;中断返回                
    END
#1 如果PSP == 0,说明OSStartHighRdy()启动后第一次做任务切换,而任务刚创建时R4-R11已经保存在堆栈中了,所以不需要再保存一次了。
#2 OSTCBStkPtr是任务控制块结构体的第一个变量,所以*OSTCBCur = SP(不是很科学)就是OSTCBCur->OSTCBStkPtr = SP;
#3 和#2类似。
#4 因为在中断处理函数中使用的是MSP,所以在返回任务后必须使用PSP,所以LR位2必须为1。
os_dbg.c
用于系统调试,可以不管。
    需要修改的代码就介绍到这里,如果还有不明白之处,就再看看AN-1018.pdf,邵老师的书和。
详解3中有一个问题还没解释,就是stm32f10x_it.c中已经有SysTick中断函数的定义SysTick_Handler(),为什么官方版非要弄个OS_CPU_SysTickHandler()。答案就在启动文件上,一般我们自己开发基于stm32芯片的软件,都会使用标准外设库CMSIS中提供的启动文件,而官方移植的启动文件却是自己写的,在两个文件init.s,vectors.s中(Micrium\Software\EvalBoards\ST\STM3210B-EVAL\RVMDK)。init.s负责进入main(),vectors.s设置中断向量。OS_CPU_SysTickHandler和OS_CPU_PendSVHandler就是在vectors.s中被设置的。
    我的移植是使用标准外设库CMSIS中startup_stm32f10x_hd.s作为启动文件的,那该怎么在这个文件中设置OS_CPU_SysTickHandler呢,事实上在startup_stm32f10x_hd.s文件中,PendSV中断向量名为PendSV_Handler,所以只需用OS_CPU_PendSVHandler把所有出现PendSV_Handler的地方替换掉就可以了。
    那么为什么OS_CPU_SysTickHandler不用这种方式处理呢,这样也就不用注释os_cpu.c中的OS_CPU_SysTickHandler(),这主要是基于两个原因:
    1. startup_stm32f10x_hd.s尽量少该,能不改就不改。
    2. 如果保留OS_CPU_SysTickHandler(),在以后开发过程中,改动OS_CPU_SysTickHandler()中的内容可能性是非常大的,如果一不小把该文件其他部分改了造成了问题,这个bug就非常难查了,所以我一般移植好后就把ucosii的这些文件设置为只读。
    对于上面的原因1,一开始移植时,我曾做过在PendSV_Handler()中调用OS_CPU_PendSVHandler(),后来发现这样不行,这是为什么呢?问题出在LR寄存器上。
PendSV_Handler()
{
        OS_CPU_PendSVHandler();
}
汇编出来的代码会是这样:
PendSV_Handler PROC
   PUSH     {r4,lr}
   BL       OS_CPU_PendSVHandler
   POP      {r4,pc}
ENDP
    这样在进入OS_CPU_PendSVHandler之后,LR寄存器中存放的是指令POP {r4,pc}的地址+1。在OS_CPU_PendSVHandler中的ORR LR, LR, #0x04就不会起作用,也就无法使用PSP,移植因此失败。其实在AN-1018.pdf的3.04.06中也有强调OS_CPU_PendSVHandler必须被放置在中断向量表中。一开始我也没注意。
    到这里移植的大部分工作都做完了,下面剩下的就是把工程配置好,SysTick中断处理好。
    在工程中建立ucosii组,把ucosii下的文件都加进该组。这里别忘了把os_cpu_a.asm加入。
    在工程的Options中,c/c++选项卡的Include Paths中添加.\src\ucosii\src;.\src\ucosii\port。
    编译工程,会发现缺少app_cfg.h和os_cfg.h文件,app_cfg.h是用来配置应用软件的,主要是任务的优先级和堆栈大小,中断优先级等信息。目前还没有基于ucosii开发应用软件,所以只需在include文件夹中创建一个空的app_cfg.h文件即可。os_cfg.h是用来配置ucosii系统的。拷贝Micrium\Software\EvalBoards\ST\STM3210B-EVAL\RVMDK\OS-Probe\os_cfg.h到template\include,对其做如下修改:
#define OS_APP_HOOKS_EN           0
#define OS_DEBUG_EN               0
#define OS_EVENT_MULTI_EN         0
#define OS_SCHED_LOCK_EN          0
#define OS_TICK_STEP_EN           0
#define OS_TASK_CHANGE_PRIO_EN    0
#define OS_TASK_QUERY_EN          0
#define OS_TASK_STAT_EN           0
#define OS_TASK_STAT_STK_CHK_EN   0
#define OS_TASK_SUSPEND_EN        0
#define OS_FLAG_EN                0
#define OS_MBOX_EN                0
#define OS_TIME_DLY_HMSM_EN       0
#define OS_TIME_DLY_RESUME_EN     0
#define OS_TIME_GET_SET_EN        0
#define OS_TIME_TICK_HOOK_EN      0
    所做的修改主要是把一些功能给去掉,减少内核大小,也利于调试。等移植完成后,如果需要该功能,再做开启。
    接下来就剩下处理好SysTick中断和启动任务了。SysTick是系统的“心跳”,本质上来说就是一个定时器。先把原来main.c中的内容删除,添加如下代码:
#include "ucos_ii.h"
#include "stm32f10x.h"
static OS_STK startup_task_stk[STARTUP_TASK_STK_SIZE];
static void systick_init(void)
{
        RCC_ClocksTypeDef rcc_clocks;
        RCC_GetClocksFreq(&rcc_clocks);
        SysTick_Config(rcc_clocks.HCLK_Frequency / OS_TICKS_PER_SEC);
}
static void startup_task(void *p_arg)
{
        systick_init();     /* Initialize the SysTick. */
#if (OS_TASK_STAT_EN > 0)
        OSStatInit();      /* Determine CPU capacity. */
#endif
 /* TODO: create application tasks here */
       
        OSTaskDel(OS_PRIO_SELF);
}

int main(void)
{
        OSInit();
        OSTaskCreate(startup_task, (void *)0,
              &startup_task_stk[STARTUP_TASK_STK_SIZE - 1],
              STARTUP_TASK_PRIO);
         OSStart();
         return 0;
}
systick_init()用来初始化并启动SysTick定时器。
     RCC_GetClocksFreq()用来获取系统时钟。
     SysTick_Config()初始化并使能SysTick定时器。
     这里要注意的是OS_TICKS_PER_SEC,它是每秒钟的ticks数,如果为1000,就是1s中1000个ticks,也就是说1ms就会产生一个SysTick中断。系统的时间片为1ms。
    在邵老师的书中3.11节已有明确说明,必须在调用OSStart()之后,才能开启时钟节拍器(SysTick)。一般会把它放在第一个任务(启动任务)中。
    startup_task()用来创建其他应用任务,创建完其他任务后,就会自己删除自己。
    文件中的STARTUP_TASK_STK_SIZE,STARTUP_TASK_PRIO需要在app_cfg.h中定义。代码如下:
/* task priority */
#define STARTUP_TASK_PRIO                          4
/* task stack size */
#define STARTUP_TASK_STK_SIZE                  80
在stm32f10x_it.c中,还需要添加SysTick中断的处理代码:
void SysTick_Handler(void)

        OSIntEnter();
        OSTimeTick();
        OSIntExit();
}
火木 回答时间:2012-7-17 09:40:30

RE:ucosII在stm32的疑问,困扰许久【悬赏问答】

保存在堆栈中了,单片机在执行过程中,主要用到的是寄存器,暂时用不上的临时变量或者函数地址指针,都会压栈,程序顺序执行下去,按照出栈顺序还原成需要的变量
mikeliujia 回答时间:2012-7-19 22:05:54

回复:ucosII在stm32的疑问,困扰许久【悬赏问答】

推荐一个ucosii在STM32上的移植详解
UCos-ii_在STM32上的移植详解.pdf (400.74 KB, 下载次数: 82)
xieyuanfu 回答时间:2012-11-13 22:10:49

RE:ucosII在stm32的疑问,困扰许久【悬赏问答】

一中断 就有一部分寄存器 自动压到堆栈里了,还有一部分是手动压栈的。

所属标签

相似问题

关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
STM32N6 AI生态系统
STM32MCU,MPU高性能GUI
ST ACEPACK电源模块
意法半导体生物传感器
STM32Cube扩展软件包
关注我们
st-img 微信公众号
st-img 手机版