现在的位置: 首页 > 综合 > 正文

对uC/OS-II 移植到STM32F103VCT6理解

2018年03月16日 ⁄ 综合 ⁄ 共 8179字 ⁄ 字号 评论关闭

网上看了很多关于uC/OS-II移植的文章和资料,自己也模仿着做了一个移植,为了加深理解, 把自己的理解和思路记录下来,水平有限,欢迎拍砖~

我使用的芯片是ST的STM32F103VCT6,是Cortex-M3内核,Cortex-M3内核是ARM公司推出的最新的基于ARMv7构架的面向微控制领域的处理器。要想移植uC/OS-II,首先要了解uC/OS-II的内核结构和Cortex-M3内核编程模型:uC/OS-II的内核结构如下图:

现在从上往下看,用户的应用程序在整个uC/OS-II 的架构的最上方,用户的自己的应用程序就属于这一层的范畴。

接下来下面的左边是uC/OS-II 的与处理器无关的代码, 这部分是uC/OS-II 的主要部分, 在移植过程中是不需要修改的。

右边是uC/OS-II 与应用程序相关的代码,OS_CFG.H是为了实现uC/OS-II 内核功能的裁剪。通过配置这个头文件,uC/OS-II 可以方便的实现裁剪,以适应不同的嵌入式系统。而INCLUDE.H则包含了所有的头文件,INCLUDE.H是一个主头文件,它出现在每个.C文件的第1行。可以通过重新编辑INCLUDE.H,增加自己的头文件,但头文件必须添加在头文件列表的最后。这样在应用程序包含头文件时只需要将此头文件包含进去,就能包含uC/OS-II 的所有头文件了。

最下面是uC/OS-II 与处理器相关的代码,这部分是移植工作的只要内容,如图所示,有三个文件:OS-CPU.H ,OS_CPU_A.ASM和OS_CPU_C.C

OS-CPU.H 包括了用﹟define语句定义的、与处理器相关的常数、宏以及类型。 如下所示:

/******************************************************************************
*                   定义与处理器无关的数据类型
******************************************************************************/

typedef unsigned char  BOOLEAN;
typedef unsigned char  INT8U;			/* Unsigned  8 bit quantity       */
typedef signed   char  INT8S;			/* Signed    8 bit quantity       */
typedef unsigned short INT16U;			/* Unsigned 16 bit quantity       */
typedef signed   short INT16S;			/* Signed   16 bit quantity       */
typedef unsigned int   INT32U;			/* Unsigned 32 bit quantity       */
typedef signed   int   INT32S;			/* Signed   32 bit quantity       */
typedef float          FP32;			/* Single precision floating point*/
typedef double         FP64;			/* Double precision floating point*/

//STM32是32位位宽的,这里OS_STK和OS_CPU_SR都应该是32位数据类型
typedef unsigned int   OS_STK;			/* Each stack entry is 32-bit wide*/
typedef unsigned int   OS_CPU_SR;		/* Define size of CPU status register*/

--->定义与处理器无关的数据类型:

在STM32处理器及keil MDK或者IAR编译环境中可以通过查手册得知short 类型是16位而int 类型是32位,这对于Cortex-M3内核是一致的。故这部分代码无需修改。 尽管μC/OS-II定义了float 类型和double类型,但为了方便移植它们在μC/OS-II源代码中并未使用。为了方便使用堆栈,μC/OS-II定义了一个堆栈数据类型。在Cortex-M3中寄存器为32位,故定义堆栈的长度也为32位。Cortex-M3状态寄存器为32位,定义OS_CPU_SR主要是为了在进出临界代码段保存状态寄存器。

--->临界代码断:

μC/OS-II 为了保证某段代码的完整执行,需要临时的关闭中断,在这段代码执行完成之后再打开中断。这样的代码段称作临界代码段。μC/OSII 通过定义两个宏OS_ENTER_CRITICAL()和OS_EXIT_CRITICAL()来分别实现中断的关闭和打开。一般来说,采用方法3 来实现这两个宏。这两个宏分别定义如下

#define      OS_CRITICAL_METHOD     3 
#define      OS_ENTER_CRITICAL()      {cpu_sr = OS_CPU_SR_Save();}   
#define      OS_EXIT_CRITICAL()           {OS_CPU_SR_Restore(cpu_sr);} 

函数OS_CPU_SR_Save() 和 OS_CPU_SR_Restore(cpu_sr) 在OS_CPU_A.ASM中定义。同时得注意,在使用这两个宏之前,必须定义OS_CPU_SR  cpu_sr;  否则编译时将出错。

--->栈的增长方向

尽管μC/OS-II支持两种方向生长的栈,但对于以Cortex-M3为内核的STM32微处理器来说,它支持向下增长的满栈,故需要定义栈增长方向宏为1。即定义成如下形式

#define   OS_STK_GROWTH    1 

--->任务级任务切换
任务级任务切换调用宏OS_TASK_SW()来实现。因为这个宏也是与处理器相关的,因此这个宏在OS_CPU_A.ASM中描述。
--->其他函数声明
在OS_CPU.H 中,还声明了以下几个函数,这几个函数均在OS_CPU_A.ASM中实现。

void OSCtxSw(void); 
void OSIntCtxSw(void);    
void OSStartHighRdy(void);   
void OS_CPU_PendSVHandler(void); 

OS_CPU_A.ASM定义了与处理器相关的代码

在OS_CPU_A.ASM中实现的是下面五个与处理器相关的函数。

OS_CPU_SR_Save(); 
OS_CPU_SR_Restore(); 
OSStartHighRdy(); 
OSCtxSw();   
OSIntCtxSw();   

下面介绍每个函数的具体实现:

--->关中断函数(OS_CPU_SR_Save())

定义方法3 来实现开关中断。即先保存当前的状态寄存器然后关中断。故关中断实现代码如下

OS_CPU_SR_Save 
     MRS R0, PRIMASK;
     CPSID I 
     BX LR 

这也是宏OS_ENTER_CRITICAL()的最终实现。

--->恢复中断函数(OS_CPU_SR_Restore())

这是宏OS_EXIT_CRITICAL()的最终实现。也就是将状态寄存器的内容从R0中恢复,然后跳转回去。此函数完成的将中断状态恢复到关中断前的状态。其代码如下:

OS_CPU_SR_Restore 
        MSR  PRIMASK,  R0 
        BX LR 

Cortex-M3处理器有单独的指令来打开或者关闭中断,所以这两个函数实现起来很简单。

--->启动最高优先级任务运行(OSStartHighRdy())

OSStart()调用OSStartHighRdy()来启动最高优先级任务的运行,从而启动整个系统。OSStartHighRdy()主要完成以下几项工作:

① 为任务切换设置PendSV的优先级

② 为第一次任务切换设置栈指针为0,

③ 设置OSRunning = TRUE,以表明系统正在运行

④ 触发一次PendSV,打开中断等待第一次任务的切换。

--->任务级和中断级任务切换

因为Cortex-M3 进入异常自动保存寄存器R3-R0,R12,LR,PC和xPSR这种的特殊机制,这两个函数都是触发一次PendSV来实现任务的切换。首先是微处理器自动保存上面提到的寄存器,然后把当前的堆栈指针保存到任务的栈中,将要切换的任务的优先级和任务控制块的指针赋值给运行时的最高优先级指针和运行时的任务控制块指针,最后再把要运行的任务的堆栈指针赋值给微处理器的堆栈指针,这样就可以退出中断服务程序了。中断服务程序退出的时候将自动出栈R3-R0,R12,LR,PC 和xPSR。具体的PendSV服务程序的伪代码如下:

OS_CPU_PendSVHandler:
   //进入异常,处理器自动保存R3-R0,R12,LR,PC和xPSR   
   if (PSP != NULL) //判断不是开始第一次任务
   { 
      保存R4-R11到任务的堆栈;
      OSTCBCur->OSTCBStkPtr = SP; //保存堆栈的指针到任务控制块
    } 
   OSTaskSwHook();  //实现用户扩展功能而定义的钩子
   OSPrioCur = OSPrioHighRdy; //设置运行任务为最高优先级就绪任务
   OSTCBCur = OSTCBHighRdy; //设置运行的任务控制块为最高
   //就绪任控制块务
   PSP = OSTCBHighRdy->OSTCBStkPtr;//将要切换的任务堆栈指
   //针赋给微处理器的堆栈指
   //针从而实现切换
   从堆栈中恢复R4-R11; 
   从异常中返回;
   //退出异常,处理器自动恢复R3-R0,R12,LR,PC和xPSR   

这样很容易写出PendSV中断服务程序的代码了。

OS_CPU_C.C定义了与CPU相关的C函数和钩子函数、

这个文件中包含10个函数,具体如下:

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

这10个函数有9个是为了扩展用户功能而定义的钩子函数,这些钩子函数可以都为空函数,也可以加上一些用户需要的扩展功能。另外一个不是钩子函数,

它是OSTaskStkInit ()。这个函数的功能是当一个任务被创建时,它完成这个任务堆栈的初始化。这个函数首先将用户为任务分配的堆栈顶地址赋值给一个栈指针变量,然后再通过这个栈指针向任务的栈空间写入初值。这个初值无关紧要,为0就可以了。这个函数的代码时下如下:

/*
*********************************************************************************************************
*                                        INITIALIZE A TASK'S STACK
*
* Description: This function is called by either OSTaskCreate() or OSTaskCreateExt() to initialize the
*              stack frame of the task being created.  This function is highly processor specific.
*
* Arguments  : task          is a pointer to the task code
*
*              p_arg         is a pointer to a user supplied data area that will be passed to the task
*                            when the task first executes.
*
*              ptos          is a pointer to the top of stack.  It is assumed that 'ptos' points to
*                            a 'free' entry on the task stack.  If OS_STK_GROWTH is set to 1 then
*                            'ptos' will contain the HIGHEST valid address of the stack.  Similarly, if
*                            OS_STK_GROWTH is set to 0, the 'ptos' will contains the LOWEST valid address
*                            of the stack.
*
*              opt           specifies options that can be used to alter the behavior of OSTaskStkInit().
*                            (see uCOS_II.H for OS_TASK_OPT_xxx).
*
* Returns    : Always returns the location of the new top-of-stack once the processor registers have
*              been placed on the stack in the proper order.
*
* Note(s)    : 1) Interrupts are enabled when your task starts executing.
*              2) All tasks run in Thread mode, using process stack.
*********************************************************************************************************
*/

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);
}

其他的钩子函数都为空函数。

(1)    OSTaskStkInit( )  初始化任务的栈结构

              OSTaskCreate( )和OSTaskCreatExt()通过调用它来初始化任务的栈结构;因此,堆栈看起来就像中断刚发生过一样,所有的寄存器都保存在堆栈中。另外,在初始化堆栈以后,OSTaskStkInit( )应当返回堆栈指针所指向的地址。
(2)    OSTaskCreateHook( )
              每当添加任务时,OS-TCBInit()函数都会调用OSTaskCreateHook( )
函数,当其被调用时,它会收到指向刚刚建立任务的任务控制块的指针。这样,它就可以访问任务控制块结构的所有的成员了。若用OSTaskCreate()建立任务,OSTaskCreateHook( )的功能是有限的;但若使用OSTaskCreateExt()建立任务时,会得到OS-TCB中的扩展指针(OSTCBExtPtr)。该指针可用来访问任务的附加数据,如浮点寄存器、MMU寄存器、任务计数器、以及调试信息。可以检查OS-TCBInit()看做了哪些工作。
(3)    OSTaskDelHook( )
              在任务从就绪列表或等待列表中被删除后,OSTaskDel()就会调用OSTaskDelHook( )。当调用其时,它会收到一个指向正在被删除任务的任务控制块的指针,使它可以访问任务控制块结构的所有的成员。
(4)    OSTaskSwHook( )
              任务切换时被调用,可以直接访问OSTCBCur和OSTCBHighRdy这2个全局变量。OSTCBCur指向将被切换出去的任务的任务控制块,OSTCBHighRdy指向新任务的任务控制块。
(5)    OSTaskIdleHook( )
              OSTaskIdle()可调用OSTaskIdleHook( )实现CPU的低功耗模式。
(6)    OSTaskStatHook( )
              每秒都会被统计任务OSTaskStat()调用一次,可以用其扩展统计任务功能。例如,可以跟踪并显示每个任务的执行时间、每个任务所用的CPU份额以及每个任务执行的频率等等。
(7)    OSTimeTickHook( )
              每个时钟节拍都会被OSTimeTick()调用。
(8)    OSInitHookBegin( )
进入OSInit()函数后,OSInitHookBegin( )就立即被调用,添加其原因在于,这个函数使得用户可以将自己特定代码也放在OSInit()中,使代码简洁明了。
(9)    OSInitHookEnd( )
              与OSInitHookBegin( )相似,只是它在OSInit()函数返回之前被调用。
(10)OSTCBInitHook( )
             OS-TCBInit()函数在调用OSTaskCreateHook( )之前,会先调用OSTCBInitHook( )。原因在于,用户可以在OSTCBInitHook( )中做一些与初始化控制块OS-TCB有关的处理;在OSTaskCreateHook( )中做一些以初始化任务有关的处理。同OSTaskCreateHook( )一样,OSTCBInitHook( )会收到指向新添加任务的任务控制块的指针。

这样整个移植的代码就介绍完了。整个移植的过程比较容易。剩下的工作就是编写用户任务了

下面说一下Cortex-M3内核编程模型:

           Cortex-M3内部有20个寄存器,其模型为图3-1所示[9]。其中通用寄存器为R0~R12。R13~R15有特殊的用途:R13用于堆栈指针(SP);R14用作链接寄存器(LR);R15用作程序计数器(PC)。程序状态寄存器有三个,分别记录处理器在三种类型模式下的状态。

          它的三种状态类型如下所述。Cortex-M3特殊的工作模式和状态可以分为特权级线程模式、特权级处理者模式和用户级线程模式。

          Cortex-M3只支持Thumb指令(包括Thumb指令和Thumb2指令)。故在程序中若想跳转到ARM指令状态将会引发一个错误。

          Cortex-M3中断采用NVIC(嵌套向量中断控制器)来管理。除了复位、不可屏蔽中断和硬故障外其他的中断可以配置。它在响应中断进入中断服务程序时硬件将自动将R0 R3, R12, LR, PSR  ‐ 和PC压栈,中断服务程序结束时,又将它们自动弹出。这个入栈出栈顺序极其重要。在后面讲到的PendSV中会用到。

         Cortex-M3内核还粗线条的定义了存储映射。在每一个范围对应哪一种外设都在内核构架上定义好了,半导体厂商可以根据自己具体的需要来实现存储映射的细节。这一点很方便代码在不同厂商生产的微处理器上移植。Cortex-M3寄存器模型如下所示:

抱歉!评论已关闭.