ucos-II 移植

本文主要记录ucos-II的移植,目标芯片为STC12C5A60S2.

参考文献《增强型8051单片机使用开发技术》、《嵌入式实时操作系统 uc/os-II》。

编译环境为keil uV4,注:编译ucos需要用破解版的keil,否则提示代码长度限制。这边破解成功后仍然提示代码限制,问题在于工程需要重新创建!!

移植ucos-II 涉及三个文件OS_CPU.H、OS_CPU_A.ASM、OS_CPU_C.C,分别作如下修改:

OS_CPU.H:

  • 定义临界段宏

 

#if      OS_CRITICAL_METHOD == 1
#define  OS_ENTER_CRITICAL()  EA=0    //关中断
#define  OS_EXIT_CRITICAL()   EA=1    //开中断

 

  • 定义任务切换宏:
  • 定义数据类型:

OS_CPU_C.C:

  • 初始化任务堆栈:
OS_STK *OSTaskStkInit (void (*task)(void *pd) reentrant, void *ppdata, OS_STK *ptos, INT16U opt) reentrant
{    
    OS_STK *stk;

    ppdata = ppdata;
    opt    = opt;                               //opt没被用到,保留此语句防止告警产生    
    stk    = ptos;                              //用户堆栈最低有效地址
    *stk++ = 15;                                //用户堆栈长度
    *stk++ = (INT16U)task & 0xFF;               //任务地址低8位
    *stk++ = (INT16U)task >> 8;                 //任务地址高8位    
    *stk++ = 0x0A;                              //ACC
    *stk++ = 0x0B;                              //B
    *stk++ = 0x00;                              //DPH
    *stk++ = 0x00;                              //DPL
    *stk++ = 0x00;                              //PSW
    *stk++ = 0x00;                              //R0
    
    //R3、R2、R1用于传递任务参数ppdata,其中R3代表存储器类型,R2为高字节偏移,R1为低字节位移。
    //通过分析KEIL汇编,了解到任务的void *ppdata参数恰好是用R3、R2、R1传递,不是通过虚拟堆栈。
    *stk++ = (INT16U)ppdata & 0xFF;             //R1
    *stk++ = (INT16U)ppdata >> 8;               //R2
    *stk++ = 0x01;                              //R3  因为我用的全是XDATA,所以存储器类型固定为1,见C51.PDF第178页说明。

    *stk++ = 0x04;                              //R4
    *stk++ = 0x05;                              //R5
    *stk++ = 0x06;                              //R6
    *stk++ = 0x07;                              //R7
                                                //不用保存SP,任务切换时根据用户堆栈长度计算得出。    
    *stk++ = (INT16U) (ptos+MaxStkSize) >> 8;   //?C_XBP 仿真堆栈指针高8位
    *stk++ = (INT16U) (ptos+MaxStkSize) & 0xFF; //?C_XBP 仿真堆栈指针低8位
        
    return ((void *)ptos);
}

 

  • 系统时钟初始化
void InitHardware(void) reentrant //该函数在main函数中在初始化后被调用
{   
    TMOD &= 0xF0;
    TMOD |= 0x01;   //定时器0:模式1(16位定时器),仅受TR0控制;定时器1:波特率发生器
    TH0  = 0xB8;   //定义Tick=50次/秒(即0.02秒/次)
    TL0  = 0x00;   //OS_CPU_C.C中定时器中断响应也要设置,OS_CFG.H中OS_TICKS_PER_SEC也有关系
    TR0  = 1;

 

OS_CPU_A.ASM

  • OSStartHighRdy:在OSStart中被调用执行当前优先级最高的就绪任务
  • OSCtxSw:即OS_TASK_SW()宏,在OS_Sched()函数结束时被调用,完成任务切换;
  • OSIntCtxSw:OSIntExit中被调用,完成中断退出后的任务切换

/**************************************************************************************************************************

在理解ucos-II在51单片机应用涉及到任务堆栈和系统堆栈的切换,有的文章认为是RAM空间的限制我认为是不全面的,参考网上一些资料整理如下:

1. C51的编译器由于指令集的关系,无法使用堆栈完成函数参数和局部变量的处理,C51为可重入函数(以reentrant为标识)引入模拟堆栈(或仿真堆栈)。根据存储类型的不同分成三类:

  • Small        C_IBP1字节)   间接访问的内部数据存储器(IDATA),栈区最大为256字节
  • Compact   C_PBP1字节)  分页寻址的外部数据存储器(PDATA),栈区最大为256字节
  • Large       C_XBP2字节)  外部数据存储器(XDATA),栈区最大为64K

2.  在ucos的移植中存储方式设置成Large,在该模式下全局变量也默认存储在外部数据存储器(XDATA)中。回过头看ucos的应用代码例子:

OS_STK Task1Stk[MaxStkSize+1];

void Task1(void * ppdata) reentrant
{
    ppdata = ppdata;

    ET0 = 1;

    for(;;){
        OSTimeDly(1000);
        LED1 = ~LED1;
    }
}


void main()
{
    OSInit();

    InitHardware();

    OSTaskCreate(Task1, (void*)0, &Task1Stk[0], 0);

    OSStart();
}

 

由上述代码可以得到几点,1是用户的任务函数要求被设置为reentrant,2. 任务堆栈在全局变量Task1Stk中,该全局变量中通过OSTaskStkInit保存了系统上下文。因此每个TASK的堆栈都存储在外部存储器XDATA中,由全局变量指针C_XBP操作,这个堆栈里有系统上下文,也预留给这个任务的参数和局部变量。所以在任务的切换过程中需要将XDATA中的任务堆栈复制到系统堆栈(内部RAM中),并调整C_XBP指针,如下图:

****************************************************************************************************************************/

 

上述过程在OSStartHighRdy的代码中体现,OSCtxSw和OSIntCtxSw基本原理类似不做赘述。

 

  RSEG ?PR?OSStartHighRdy?OS_CPU_A
OSStartHighRdy:
        USING 0    ;上电后51自动关中断,此处不必用CLR EA指令,因为到此处还未开中断,本程序退出后,开中断。
        LCALL _?OSTaskSwHook

OSCtxSw_in:
    
        ;OSTCBCur ===> DPTR  获得当前TCB指针,详见C51.PDF第178页
        MOV  R0,#LOW (OSTCBCur) ;获得OSTCBCur指针低地址,指针占3字节。+0类型+1高8位数据+2低8位数据
        INC  R0
        MOV  DPH,@R0    ;全局变量OSTCBCur在IDATA中
        INC  R0
        MOV  DPL,@R0
    
        ;OSTCBCur->OSTCBStkPtr ===> DPTR  获得用户堆栈指针
        INC  DPTR        ;指针占3字节。+0类型+1高8位数据+2低8位数据
        MOVX A,@DPTR     ;.OSTCBStkPtr是void指针
        MOV  R0,A
        INC  DPTR
        MOVX A,@DPTR
        MOV  R1,A
        MOV  DPH,R0
        MOV  DPL,R1
    
        ;*UserStkPtr ===> R5  用户堆栈起始地址内容(即用户堆栈长度放在此处)  详见文档说明  指针用法详见C51.PDF第178页    
        MOVX A,@DPTR     ;用户堆栈中是unsigned char类型数据
        MOV  R5,A        ;R5=用户堆栈长度
    
        ;恢复现场堆栈内容
        MOV  R0,#OSStkStart
        
restore_stack:
    
        INC  DPTR
        INC  R0
        MOVX A,@DPTR
        MOV  @R0,A
        DJNZ R5,restore_stack
    
        ;恢复堆栈指针SP
        MOV  SP,R0
    
        ;恢复仿真堆栈指针?C_XBP        
        INC  DPTR
        MOVX A,@DPTR
        MOV  ?C_XBP,A    ;?C_XBP 仿真堆栈指针高8位
        INC  DPTR
        MOVX A,@DPTR
        MOV  ?C_XBP+1,A  ;?C_XBP 仿真堆栈指针低8位
    
        ;OSRunning=TRUE
        MOV  R0,#LOW (OSRunning)
        MOV  @R0,#01
    
        POPALL
        SETB EA    ;开中断
        RETI

 

 

最后三行代码是切换的关键,通过RETI将堆栈数据赋值给PC指针完成跳转。

 

posted @ 2013-02-19 22:21  Fredric_2013  阅读(2653)  评论(0编辑  收藏  举报