移植uCOS-II到Cortex-M3平台

[复制链接]
1808|8
手机看帖
扫描二维码
随时随地手机跟帖
catking25|  楼主 | 2013-10-31 14:57 | 显示全部楼层 |阅读模式
本帖最后由 catking25 于 2013-10-31 14:59 编辑

本文的目的是希望读者能够通过本文的内容掌握移植uCOS-II 的规范方法。如果只是需要移植文件,可以直接去Micriμm的官网上下载。

移植uCOS-II,主要的移植工作是编写如下三个文件:

OS_CPU.H

OS_CPU_C.C

OS_CPU_A.ASM

下面就按照这三个文件的顺序来介绍。本文以STM32F107+RealView Compiler 开发环境为例。如果使用的其他的开发环境,个别代码可能需要做些小修改。


OS_CPU.H

OS_CPU.H 的第一部分是定义了一个宏OS_CPU_EXT。这一部分暂时可以先不去管。

#ifdef   OS_CPU_GLOBALS

#define  OS_CPU_EXT

#else

#define  OS_CPU_EXT  extern

#endif

接下来是一系列的类型定义。这一部分的移植需参考RealView Compiler Reference Guide的如下章节:

RealView Compiler Reference Guide->C and C++ Implementation Details->Basic data types

从这里可以得到如下信息。

Type
Size in bits
Natural alignment in bytes
char
8
1   (byte-aligned)
short
16
2   (halfword-aligned)
int
32
4   (word-aligned)
long
32
4   (word-aligned)
long long
64
8   (doubleword-aligned)
float
32
4   (word-aligned)
double
64
8   (doubleword-aligned)
long double
64
8   (doubleword-aligned)
All pointers
32
4   (word-aligned)
_Bool
8
1   (byte-aligned)

根据上面的信息,形成下面的代码:

[cpp] view plaincopy


  • 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;   

上面代码中OS_STK 表示堆栈出栈、入栈的基本数据长度。我们知道Cortex-M3 的所有堆栈操作都是以字为单位的,所以这里为 unsigned int 型。OS_CPU_SR 对应的是程序状态寄存器PSRs,自然也是unsigned int 型。

然后是关于临界区的处理,一般来说我们都喜欢使用第三种方法来实现临界区,这里也不例外。这里多说几句,第一种直接开关中断的实现临界区的方法很少采用,因为这种方法可能将原本关闭了的中断意外的打开。第二种方法是最高效的实现方法,但是这种方法调整了堆栈指针,对于需要利用堆栈指针间接寻址局部变量的系统并不适用。(x86通常采用第二种方法,因为它有单独的寄存器来做局部变量的寻址)第三种方法最保险,虽然效率比第二种方法略低一点。

[cpp] view plaincopy


  • #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);}  
  •   
  • #if OS_CRITICAL_METHOD == 3  
  • OS_CPU_SR OS_CPU_SR_Save(void);  
  • void OS_CPU_SR_Restore(OS_CPU_SR cpu_sr);  
  • #endif  


这两个函数可以用汇编(OS_CPU_A.ASM)来编写:[plain] view plaincopy


  •     EXPORT  OS_CPU_SR_Save      
  •   EXPORT  OS_CPU_SR_Restore  
  • OS_CPU_SR_Save  
  •     MRS     R0, PRIMASK ; Set prio int mask to mask all (except faults)  
  •     CPSID   I  
  •     BX      LR  
  •   
  • OS_CPU_SR_Restore  
  •     MSR     PRIMASK, R0  
  •   BX      LR  


也可以通过C代码(OS_CPU_C.C)中插入汇编的方式来实现:
[cpp] view plaincopy


  • __asm OS_CPU_SR OS_CPU_SR_Save(void)  
  • {  
  •     MRS     R0, PRIMASK ; Set prio int mask to mask all (except faults)  
  •     CPSID   I  
  •     BX      LR  
  • }  
  • __asm void OS_CPU_SR_Restore(OS_CPU_SR cpu_sr)  
  • {  
  •     MSR     PRIMASK, R0  
  •     BX      LR  
  • }  

上面的代码利用的RealView Compiler 的特殊功能(Embedded assembler),如需进一步的信息,可以参考RealView Compiler Reference Guide中Using the Inline and Embedded Assemblers这一章的内容。

然后是堆栈增长方向,ARM Cortex-M3 的堆栈是倒生的:

[cpp] view plaincopy


  • #define OS_STK_GROWTH 1  

任务切换,OSCtxSw()在OS_CPU_A.ASM 中定义:


[cpp] view plaincopy


  • #define OS_TASK_SW() OSCtxSw()  


最后是一些函数原型声明:

[cpp] view plaincopy


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

OS_HOOK.C

在原本uCOS-II 的移植代码中是没有这个文件的。由于下面这9个函数的函数体基本都是空的,并且移植时几乎不需要更改,所以我就将其拿出到一个单独的文件中来了。

OSInitHookBegin()

OSInitHookEnd()

OSTaskCreateHook()

OSTaskDelHook()

OSTaskIdleHook()

OSTaskStatHook()

OSTaskSwHook()

OSTCBInitHook()

OSTimeTickHook()

这9个函数的代码都很简单,下面是代码,不多介绍。

[cpp] view plaincopy


  • #if OS_CPU_HOOKS_EN > 0 && OS_VERSION > 203  
  • void  OSInitHookBegin (void)  
  • {  
  •     #if OS_TMR_EN > 0   
  •         OSTmrCtr = 0;   
  •     #endif  
  • }  
  • #endif  
  •   
  • #if OS_CPU_HOOKS_EN > 0 && OS_VERSION > 203  
  • void  OSInitHookEnd (void)  
  • {  
  • }  
  • #endif  
  •   
  • #if OS_CPU_HOOKS_EN > 0  
  • void  OSTaskCreateHook (OS_TCB *p_tcb)  
  • {  
  • #if OS_VIEW_MODULE > 0  
  •     OSView_TaskCreateHook(p_tcb);  
  • #else  
  •     (void)p_tcb ; /* Prevent compiler warning  */  
  • #endif  
  • }  
  • #endif  
  •   
  • #if OS_CPU_HOOKS_EN > 0  
  • void  OSTaskDelHook (OS_TCB *p_tcb)  
  • {  
  •     (void)p_tcb ; /* Prevent compiler warning  */  
  • }  
  • #endif  
  •   
  • #if OS_CPU_HOOKS_EN > 0 && OS_VERSION >= 251  
  • extern volatile unsigned long   wdg_clr_flag;  
  • void  OSTaskIdleHook (void)  
  • {  
  • }  
  • #endif  
  •   
  • #if OS_CPU_HOOKS_EN > 0  
  • void  OSTaskStatHook (void)  
  • {  
  • }  
  • #endif  
  •   
  • #if (OS_CPU_HOOKS_EN > 0) && (OS_TASK_SW_HOOK_EN > 0)  
  • void  OSTaskSwHook (void)  
  • {  
  • #if OS_VIEW_MODULE > 0  
  •     OSView_TaskSwHook();  
  • #endif  
  • }  
  • #endif  
  •   
  • #if OS_CPU_HOOKS_EN > 0 && OS_VERSION > 203  
  • void  OSTCBInitHook (OS_TCB *ptcb)  
  • {  
  •     (void) ptcb; /* Prevent Compiler warning */  
  • }  
  • #endif  
  •   
  • #if (OS_CPU_HOOKS_EN > 0) && (OS_TIME_TICK_HOOK_EN > 0)  
  • void  OSTimeTickHook (void)  
  • {  
  •     #if OS_VIEW_MODULE > 0  
  •         OSView_TickHook();   
  •     #endif  
  •       
  •     #if OS_TMR_EN > 0  
  •         OSTmrCtr++;   
  •         if (OSTmrCtr >= (OS_TICKS_PER_SEC / OS_TMR_CFG_TICKS_PER_SEC))   
  •         {  
  •             OSTmrCtr = 0;  
  •             OSTmrSignal();   
  •         }   
  •     #endif  
  • }  
catking25|  楼主 | 2013-10-31 14:58 | 显示全部楼层
OS_CPU_C.C OS_CPU_A.ASM

重要的移植工作都在这两个文件中提供,由于RealView Compiler 支持在C文件中插入汇编代码,所以OS_CPU_A.ASM 文件实际上可以去掉。所有的函数都在OS_CPU_C.C 中实现。下面分别介绍。

OSTaskStkInit()

OSTaskStkInit 的移植是比较有难度的。这个函数是用来初始化各个任务的堆栈,使各个任务的堆栈就像是刚才中断处理函数中返回那样。

[cpp] view plaincopy


  • OS_STK *OSTaskStkInit (void  (*p_task)(void  *p_arg), void  *p_arg, OS_STK  *p_tos, INT16U  opt)  
  • {  
  •     OS_STK *stk;  
  •     (void)opt;                                        /*  'opt' is not used, prevent  warning */  
  •     stk = p_tos;                                      /*  Load stack pointer          */                                    
  • /*  Registers stacked as if auto-saved on exception   */  
  •     *(stk) = (INT32U)0x01000000L;                     /*  xPSR, the ‘T’ bit is set    */   
  •     *(--stk) = (INT32U)p_task;                        /*  Entry Point of the task     */  
  •     *(--stk) = (INT32U)OS_TaskReturn;                 /* the return address of the task */  
  •                                                                              
  •     *(--stk) = (INT32U)0x12121212L;                   /*  R12                         */  
  •     *(--stk) = (INT32U)0x03030303L;                   /*  R3                          */  
  •     *(--stk) = (INT32U)0x02020202L;                   /*  R2                          */  
  •     *(--stk) = (INT32U)0x01010101L;                   /*  R1                          */  
  •     *(--stk) = (INT32U)p_arg;                         /*  R0 : 1st argument to the task */  
  •       
  • /*  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);  
  • }  

想要理解上面的代码需要知道Cortex-M3在响应外部中断时对寄存器的压栈顺序,还需知道函数的第一个参数是通过R0来传递的。建议阅读ARM Cotex M3 权威指南,里面有详细的介绍。这里我只说一处,就是OS_TaskReturn 位置对应的是任务的返回地址。我们知道,uCOS-II 中任务就是简单的函数。普通的函数执行完成后会返回到调用它的地方的下一条语句处继续执行。这个位置就记录在堆栈中,也就是OS_TaskReturn所在的位置。uCOS-II要求任务必须是无限的循环,不允许退出。所以理论上永远不会跳转到OS_TaskReturn处执行。OS_TaskReturn的作用是当程序异常退出时不至于程序跑飞。在现在的移植代码中OS_TaskReturn 也是个简单的函数,没有加入额外的保护代码。

[cpp] view plaincopy


  • void OS_TaskReturn(void)  
  • {  
  •   while(1);  
  • }  

OSStartHighRdy

这个函数只被OSStart()调用。用来运行最高优先级的任务。代码如下。

[cpp] view plaincopy


  • __asm void OSStartHighRdy(void)  
  • {  
  •     LDR     R0, =NVIC_SYSPRI14                                  ; 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, =OSRunning                                      ; OSRunning = TRUE  
  •     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异常的优先级为最低。

[plain] view plaincopy


  • LDR     R0, =NVIC_SYSPRI14                                  ; Set the PendSV exception priority  
  • LDR     R1, =NVIC_PENDSV_PRI  
  • STRB    R1, [R0]  

相当于如下的C代码:

[cpp] view plaincopy


  • NVIC->IP[14] = 0xFF;  

下面两行代码的作用是使线程堆栈指针 PSP = 0。PendSV_Handler 中需要根据它来判断是否是OSStartHighRdy 引起的PendSV,因为这时要特殊处理一下。

[plain] view plaincopy


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

最后四行的作用是引起一次 PendSV。相当于下面的C代码:

[cpp] view plaincopy


  • SCB->ICSR |= SCB_ICSR_PENDSVSET_Msk;  

OSCtxSw 和 OSIntCtxSw

在其他处理器的移植代码中,这两个函数还是有些工作要做的。但是对于Cortex-M3 就简单的多了,只要引起一次PendSV 就行了,具体的任务切换由PendSV来处理。

[cpp] view plaincopy


  • void OSCtxSw(void)  
  • {  
  •     SCB->ICSR |= SCB_ICSR_PENDSVSET_Msk;  
  • }  
  •   
  • void OSIntCtxSw(void)  
  • {  
  •     SCB->ICSR |= SCB_ICSR_PENDSVSET_Msk;  
  • }  

也可以写为汇编代码,写为汇编的好处是两个函数可以共用一个函数体:

[plain] view plaincopy


  • OSCtxSw  
  • OSIntCtxSw  
  •   LDR     R0, =NVIC_SYSPRI14                                  ; Set the PendSV exception priority  
  •   LDR     R1, =NVIC_PENDSV_PRI  
  •   STRB    R1, [R0]  

SysTick_Handler

SysTick 用来处理操作系统的计时。代码很简单,无需多说。


[cpp] view plaincopy


  • void SysTick_Handler(void)  
  • {  
  •     OS_CPU_SR  cpu_sr;  
  •   
  •     OS_ENTER_CRITICAL();        /* Tell uC/OS-II that we are starting an ISR  */  
  •     OSIntNesting++;  
  •     OS_EXIT_CRITICAL();  
  •   
  •     OSTimeTick();               /* Call uC/OS-II's OSTimeTick()               */  
  •   
  •     OSIntExit();  
  • }  


PendSV_Handler

最终的任务切换工作都在这里完成。下面先给出伪代码。从这里就可以看出OSStartHighRdy 中将PSP 写为0 的作用了。

[plain] view plaincopy


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

使用特权

评论回复
catking25|  楼主 | 2013-10-31 14:58 | 显示全部楼层

下面给出真实的代码,可以看出与伪代码是对应的:

[cpp] view plaincopy


  • __asm void PendSV_Handler(void)  
  • {         
  •     EXTERN  OSPrioCur  
  •     EXTERN  OSPrioHighRdy  
  •     EXTERN  OSTCBCur  
  •     EXTERN  OSTCBHighRdy  
  •     EXTERN  OSTaskSwHook  
  •     CPSID   I                         ; Prevent interruption during context switch  
  •     MRS     R0, PSP                   ; PSP is process stack pointer  
  •     CBZ     R0, 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  
  • 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  
  • }  

至此,移植工作完成。uCOS-II Cortex-M3上的移植与其他单片机上移植代码的最大区别在于所有的任务切换工作都放到了 PendSV 中进行,而PendSV 中断的优先级被设为最低,这样就能保证更高优先级的中断能够及时被处理。不过,在PendSV 中断处理代码中第一条语句就是关中断,这时如果来了更高优先级的中断,也是无法响应的。能否进一步改善中断响应性能还需再思考。个人认为应该还有进一步优化的可能,不过具体该如何优化,暂时还没有头绪。

使用特权

评论回复
cool_coder| | 2013-11-6 16:10 | 显示全部楼层
也可以写为汇编代码,写为汇编的好处是两个函数可以共用一个函数体:

1.OSCtxSw  
2.OSIntCtxSw  
3.  LDR     R0, =NVIC_SYSPRI14                                  ; Set the PendSV exception priority  
4.  LDR     R1, =NVIC_PENDSV_PRI  
5.  STRB    R1, [R0]

这个用C也没问题,宏定义个别名就可以了。

使用特权

评论回复
xufei043| | 2013-11-6 17:12 | 显示全部楼层
有时间在GD32的评估板上移植一下。

使用特权

评论回复
gyh974| | 2013-11-6 20:29 | 显示全部楼层

使用特权

评论回复
legendluo| | 2013-11-22 11:27 | 显示全部楼层
大爱啊·~!

使用特权

评论回复
huangjia22| | 2013-11-23 09:44 | 显示全部楼层
学习了

使用特权

评论回复
zxm19820916| | 2013-11-26 18:00 | 显示全部楼层
学习学习。

使用特权

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

本版积分规则

个人签名:Get busy living or get busy dying~~~ 自己选择的路

67

主题

202

帖子

5

粉丝