Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1148559
  • 博文数量: 241
  • 博客积分: 10
  • 博客等级: 民兵
  • 技术积分: 2279
  • 用 户 组: 普通用户
  • 注册时间: 2012-11-27 19:53
个人简介

JustForFun

文章分类

全部博文(241)

文章存档

2023年(8)

2022年(2)

2021年(3)

2020年(30)

2019年(11)

2018年(27)

2017年(54)

2016年(83)

2015年(23)

我的朋友

分类: 嵌入式

2015-07-21 22:22:12

在os_cpu_c.c  os_cpu.h  os_cpu_a.asm 三文件是移植文件
因为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_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,后面我们会自己实现,因此可以把它注释掉。 


那么中断后栈中是个什么情形呢,<<ARM Cortex-M3权威指南>>中9.1.1有介绍,xPSR,PC,LR,R12,R3-R0被自动保存到栈中的,R11-R4如果需要保存,只能手工保存。因此OSTaskStkInit()的工作就是在任务自己的栈中保存cpu的所有寄存器。这些值里R1-R12都没什么意义,这里用相应的数字代号(如R1用0x01010101)主要是方便调试。
//uc/os -ii os_cpu_c.c
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                                            */
    *(--stk)  = (INT32U)0xFFFFFFFEL;           /* R14 (LR) (init value will cause fault if ever used)    */
    *(--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);
}
    其他几个:
    xPSR = 0x01000000L,xPSR T位(第24位)置1,否则第一次执行任务时Fault,
    PC肯定得指向任务入口,
    R14 = 0xFFFFFFFEL,最低4位为E,是一个非法值,主要目的是不让使用R14,即任务是不能返回的。
    R0用于传递任务函数的参数,因此等于p_arg。
   
 

NVIC_INT_CTRL   EQU     0xE000ED04   ;中断控制及状态寄存器ICSR的地址
NVIC_SYSPRI14   EQU     0xE000ED22   ;PendSV优先级寄存器的地址
NVIC_PENDSV_PRI EQU           0xFF   ;PendSV中断的优先级为255(最低)
NVIC_PENDSVSET  EQU     0x10000000   ;位28为1



//os_cpu_a.asm中

OS_CPU_SR_Save
    MRS     R0, PRIMASK   ;读取PRIMASK到R0中 ,
                                           ;用R0来保存进入中断前系统中断屏蔽寄存器组PRIMASK的值
                                           ;如果中断里其他函数传参数,用到R0了呢?
                                            ;传参数时好像函数调用时R0-R4是自动保存的,函数返回后又会恢复
    CPSID   I             ;PRIMASK=1,关中断(NMI和硬fault可以响应)
    BX      LR            ;返回

 

OS_CPU_SR_Restore
    MSR     PRIMASK, R0   ;读取R0到PRIMASK中, PRIMASK是M3内核中断屏蔽寄存器组
    BX      LR            ;返回

//Cortex-M3还在内核水平上搭载了若干特殊功能寄存器,包括
                                                   程序状态字寄存器组(PSRs) 

                                                   中断屏蔽寄存器组(PRIMASK, FAULTMASK, BASEPRI)
                                                   控制寄存器(CONTROL) 
////////////////////////////////////////////////////////以下为注释///////////////////////////////////////////////////////////////////////////////////////////
//os_cpu.h中

#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
1. Cortex-M3的异常/中断屏蔽寄存器组
注:只有在特权级下,才允许访问这3个寄存器。

名 字

功能描述

PRIMASK

只有单一比特的寄存器。置为1后,就关掉所有可屏蔽异常,只剩下NMI和硬Fault可以响应。默认值是0,表示没有关闭中断。

FAULTMASK

只有单一比特的寄存器。置为1后,只有NMI可以响应。默认值为0,表示没有关异常。


BASEPRI

该寄存器最多有9位(由表达优先级的位数决定)。定义了被屏蔽优先级的阈值。当它被设置为某个值后,所有优先级号大于等于此值的中断都被关。若设置成0,则不关断任何中断,0为默认值。

注:寄存器BASEPRI的有效位数受系统中表达优先级的位数影响,如果系统中只使用3个位来表达优先级,则BASEPRI有意义的值仅为0x00、0x20、0x40、0x60、0x80、0xA0、0xC0和0xE0

使用MRS/MSR指令访问这三个寄存器,比如:

MRS R0, BASEPRI ;读取BASEPRI到R0中 MSR BASEPRI, R0 ;将R0数据写入到BASEPRI中

为了快速的开关中断,CM3还专门设置了一条CPS指令,有四种用法:

CPSID I ;PRIMASK=1,关中断 CPSIE I ;PRIMASK=0,开中断 CPSID F ;FAULTMASK=1,关异常 CPSIE F ;FAULTMASK=0,开异常

 ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

 

 

 

 

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               ;PSP = R0

    ;设置OSRunning为TRUE
    LDR     R0, =OSRunning        ;R0 = OSRunning//OSRunning为全局变量标志OS running状态
    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中断的优先级应该为最低优先级,原因在<<ARM Cortex-M3权威指南>>的7.6节已有说明。
#2.PSP设置为0,是告诉具体的任务切换程序(OS_CPU_PendSVHandler()),这是第一次任务切换。做过
切换后PSP就不会为0了,后面会看到。
#3.往中断控制及状态寄存器ICSR(0xE000ED04)第28位写1即可产生PendSV中断。这个<<ARM Cortex-M3权
威指南>>8.4.5 其它异常的配置寄存器有说明。

    当一个任务放弃cpu的使用权,就会调用OS_TASK_SW()宏,而OS_TASK_SW()就是OSCtxSw()。OSCtxSw()应该做任务切换。但是在CM3中,所有任务切换都被放到PendSV的中断处理函数中去做了,因此OSCtxSw()只需简单的触发PendSV中断即可。OS_TASK_SW()是由OS_Sched()调用。
////////////////////////////////////////////////////////////////
NVIC_INT_CTRL EQU 0xE000ED04 ; 中断控制寄存器
 
NVIC_SYSPRI14 EQU 0xE000ED22 ; 系统优先级寄存器(优先级14).
NVIC_PENDSV_PRI EQU 0xFF ; PendSV优先级(最低).
NVIC_PENDSVSET EQU 0x10000000 ; PendSV触发值

//////////////////////////////////////////////////////////////// 



//os_sched()调度函数调用OSCtxSw()

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 
//运行到此触发OS_CPU_PendSVHandler?????????
    BX      LR                    ;返回

NVIC_INT_CTRL   EQU     0xE000ED04                              ; Interrupt control state register.
NVIC_SYSPRI14   EQU     0xE000ED22                              ; System priority register (priority 14).
NVIC_PENDSV_PRI EQU           0xFF                              ; PendSV priority value (lowest).
NVIC_PENDSVSET  EQU     0x10000000                              ; Value to trigger PendSV exception. 

  1. 摒弃软中断任务切换,改用函数任务切换方式。这样可以保证OSCtxSw()或OSIntCtxSw()执行完成了任务一定切换完成。
  2. 将软中断PendSV_Handler(),触发优先级提至最高。

   
当一个中断处理函数退出时,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中断来处理




/////////////////////////////////////////////////////////////////////////////////////////////////////////////
//os_sched() ->OSCtxSw()触发OS_CPU_PendSVHandler          

OS_CPU_PendSVHandler  
                                              //  ;xPSR, PC, LR, R12, R0-R3已自动保存
    CPSID   I         ;任务切换期间需要关中断 Prevent interruption during context switch
    MRS     R0, PSP   ;R0 = PSP PSP is process stack pointer 线程堆栈指针   从
                                  ;OS_CPU_PendSVHandler返回后,自动变为选择PSP堆栈(线程堆栈)。
                                 ;主堆栈叫MSP。

    CBZ     R0, OS_CPU_PendSVHandler_nosave    ;如果PSP==0跳转到         
                                                ;OS_CPU_PendSVHandler_nosave去执行,
                                           ;在多任务的初始化时PSP被初始化为0  Skip register save the first time
                              ;若果PSP如果是0,标示任务没有运行过,那么不需要压栈
    SUBS    R0, R0, #0x20    ;R0 -= 0x20 保存R4-R11到任务堆栈共32个字节 
                                        ;满递减堆栈 先让堆栈指针减到堆栈完成后的位置,
                                            ;再装值(入站)完成堆栈。             

    STM     R0, {R4-R11}     ;压栈R4-R11, 其他8个寄存器是在异常时自动压栈的
    LDR     R1, =OSTCBCur    ;获取OSTCBCur->OSTCBStkPtr   
                                               //; d) OSTCBCur      points to the OS_TCB of the task to suspend
                                               //OSStart()里有OSTCBCur      = OSTCBHighRdy;OSStartHighRdy();

    LDR     R1, [R1]         ;R1 = *R1 (R1 = OSTCBCur)
    STR     R0, [R1]         ;*R1 = R0 (*OSTCBCur = SP) R0 is SP of process being switched out
                             ;将当前任务的堆栈保存到自己的任务控制块
                             ;OSTCBCur->OSTCBStkPtr = PSP
                             ;程序运行此位置,已经保存了当前任务的context了
                             ; At this point, entire context of process has been saved


    
                   

OS_CPU_PendSVHandler_nosave
    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   
                                          ///R0为要切换的任务堆栈地址,任务堆栈里保存了LR,本任务PC
                                 ;SP = OSTCBHighRdy->OSTCBStkPtr #3  
    LDM     R0, {R4-R11}         ;从任务堆栈SP恢复R4-R11      
    ADDS    R0, R0, #0x20        ;R0 += 0x20 这部调整后那psp岂不是指向下图绿色位置?
                                                 //要是那样的话内核自动出栈的话是不是先出xPSR?  

    MSR     PSP, R0              ;PSP = R0,用新任务的SP加载PSP
    ORR     LR, LR, #0x04        ;确保LR位2为1,返回后使用进程堆栈 #4     
    CPSIE   I                    ;开中断
    BX      LR                   ;中断返回   //OS_CPU_PendSVHandler也在这里返回中断
                                              //跳到r14(LR)链接寄存器    怎么就切换到别的TCB里去了?  PUSH    {R14} 
                                     //为什么要SUBS R0, R0, #0x20 ????
                                    //STM32发生异常时不是自动会把XPSP ,pc,lr,r12,r3,r2,r1,r0保存到堆栈中,
                                    //难道不更新PSP,要我们手动更新PSP(SUBS R0, R0, #0x20 ),
                                    //还是什么作用?这个问题我非常纠结!             

    END

#1 如果PSP == 0,说明OSStartHighRdy()启动后第一次做任务切换,而任务刚创建时R4-R11已经保存在堆栈中了,所以不需要再保存一次了,OS_CPU_PendSVHandler_nosave中的nosave就是这意思。
#2 OSTCBStkPtr是任务控制块结构体的第一个变量,所以*OSTCBCur = SP(不是很科学)就是OSTCBCur->OSTCBStkPtr = SP;
#3 和#2类似。
#4 因为在中断处理函数中使用的是MSP,所以在返回任务后必须使用PSP,所以LR位2必须为1。
u C / O S 中的每一个任务都有独立的堆栈空间,并有一个称为任务控制块TCB(Task Control Block)的数据结构,其中第一个成员变量就是保存的任务堆栈指针 

ISR执行完毕并退出后,PendSV服务例程开始执行,并且在里面执行上下文切换, 当 PendSVhandler执行完毕后,回到任务 A,同时系统再次进入线程模式。
//                 |     ....        |
;//                 |-----------------|
;//                 |     ....        |
;//                 |-----------------|
;//                 |     ....        |
;//                 |-----------------|       |---- 任务切换时PSP
;//     Low Memory  |     ....        |       |
;//                 |-----------------|       |    |---------------|      |----------------|
;//        ^        |       R4        |  <----|----|--OSTCBStkPtr  |<-----|   (OS_TCB *)   |
;//        ^        |-----------------|            |---------------|      |----------------|
;//        ^        |       R5        |            |               |         OSTCBHighRdy
;//        |        |-----------------|            |---------------|
;//        |        |       R6        |            |               |
;//        |        |-----------------|            |---------------|
;//        |        |       R7        |            |               |
;//        |        |-----------------|            |---------------|
;//        |        |       R8        |                 Task's
;//        |        |-----------------|                 OS_TCB
;//        |        |       R9        |
;//        |        |-----------------|
;//        |        |      R10        |
;//      Stack      |-----------------|
;//      Growth     |      R11        |
;//       = 1       |-----------------|
;//        |        |    R0 = p_arg   |  <-------- 异常时的PSP (向上生长的满栈)    指向这里
;//        |        |-----------------|
;//        |        |       R1        |
;//        |        |-----------------|
;//        |        |       R2        |
;//        |        |-----------------|
;//        |        |       R3        |
;//        |        |-----------------|
;//        |        |       R12       |
;//        |        |-----------------|
;//        |        |       LR        |
;//        |        |-----------------|
;//        |        |    PC = task    |
;//        |        |-----------------|
;//        |        |      xPSR       |<---------------调整指针后指向这里
;//    High Memory  |-----------------| 


;*********************************************************************************************************
;                                          START MULTITASKING
;                                       void OSStartHighRdy(void)
;
; Note(s) : 1) This function triggers a PendSV exception (essentially, causes a context switch) to cause
;              the first task to start.
;
;           2) OSStartHighRdy() MUST:
;              a) Setup PendSV exception priority to lowest;
;              b) Set initial PSP to 0, to tell context switcher this is first run;
;              c) Set OSRunning to TRUE;
;              d) Trigger PendSV exception;
;              e) Enable interrupts (tasks will run with interrupts enabled).
;*********************************************************************************************************

OSStartHighRdy
    LDR     R0, =NVIC_SYSPRI2                                   ; Set the PendSV exception priority
    LDR     R1, =NVIC_PENDSV_PRI
    STRB    R1, [R0]

    MOVS    R0, #0                                              ; Set the PSP to 0 for initial context switch call
    MSR     PSP, R0

    LDR     R0, __OS_Running                                    ; OSRunning = TRUE
                                                      ; __OS_Running   DCD     OSRunning
    MOVS    R1, #1
    STRB    R1, [R0]

    LDR     R0, =NVIC_INT_CTRL          ; Trigger the PendSV exception (causes context switch)
    LDR     R1, =NVIC_PENDSVSET
    STR     R1, [R0]

    CPSIE   I

另一个相关的异常是PendSV,它和SVC协同使用。一方面,SVC异常是必须执行SVC指令后立即得到响应的,应用程序执行SVC时都是希望所需的请求立即得到响应是。另一方面,PendSV则不同,它可以像普通的中断一样被挂起,OS可以利用它“缓期执行”一个异常——直到其它重要的任务完成后才执行动作,挂起PendSV的方法是:手工往NVIC的PendSV挂起寄存器中写1,挂起后,如果优先级不够高,则将缓期执行。

(参见<CORTEX-M3权威指南>130页)






PendSV典型使用场合是在上下文切换时(在不同任务间切换)。例如:一个系统中有两个就绪任务,上下文切换被触发的场合可以是:

● 执行一个系统调用

● 系统滴答定时器中断(轮转调度中需要)

PendSV异常会自动延尺上下文切换的请求,直到其他的ISR都完成了处理后才放行。为了实现这个机制,需要把PendSV编程为最低优先级的异常。如果OS检测到某IRQ正在活动并且被SysTick抢占,它将挂起一个PendSV异常,以便缓期执行上下文切换。




 

    需要修改的代码就介绍到这里,如果还有不明白之处,就再看看AN-1018.pdf,邵老师的书和<<ARM Cortex-M3权威指南>>。

    

阅读(2217) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~