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_IBP(1字节) 间接访问的内部数据存储器(IDATA),栈区最大为256字节
- Compact ?C_PBP(1字节) 分页寻址的外部数据存储器(PDATA),栈区最大为256字节
- Large ?C_XBP(2字节) 外部数据存储器(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指针完成跳转。