前言 其实关于uCOS-II的51单片机移植教程和例子网上已经有很多了,但是大部分都是基于Proteus仿真外扩内存的,下载之后也不能直接在硬件上使用,也没有具体的移植教程。这对于一个想学习操作系统而又无从下手小白的来说简直就是噩梦。 由于51内核的特殊性和keil编译器原因(51的系统堆栈指针和Keil编译后仿真堆栈指针增长的方向是相反的)带来移植的困难。网上的例程处理堆栈的方式有两种(至于不太懂的同学可以移步看看这篇帖子) https://blog.csdn.net/s111sw/article/details/6012720,一种是用小模式Small,另外一种是大模式Large。Small模式是把系统堆栈数据和仿真堆栈数据一起复制到用户堆栈XDATA区,这种方式编译代码小但是任务切换速度慢。而Large模式在编译的时候Keil默认是把仿真堆栈数据设置在XDATA区,所以在任务切换的时候只需要把系统堆栈和仿真堆栈的当前地址保存到用户堆栈就行,这样的方式编译代码大但任务切换速度快(现在都是用这种方式,STC12C5A60S2在22.1184MHz晶振下任务切换时间37us)。可是,前面已经说了51的系统堆栈指针是向上增长的,而Keil编译的仿真指针是向下增长的,这就导致了一个 问题---uCOS操作系统的堆栈检测函数OSTaskStkChk没办法使用。上面链接帖子里面用的方法是修改uCOS的内核函数实现堆栈检测功能的。正是因为我不想修改内核函数的原因所以才有了我现在移植的uCOS的版本。 图1
虽然51的系统堆栈指针只能向上增长,但是在代码里面我们可以人为的把里面的数据按照自己的意愿存放到用户堆栈里。下面是我移植的堆栈结构,把系统堆栈增长方向和仿真堆栈统一起来就可以实现堆栈连续存放数据了。 图2 开始移植 准备工具 1、 电脑一台(废话!) 2、 Keil4 3、 下载配套的代码一份,我移植的是比较经典的版本uCOS-II 2.52
下载的代码已经是移植好STC12C5A60S2的例程 功能就是两个任务用堆栈检测函数OSTaskStkChk检测当前自己堆栈使用情况,然后串口发出 波特率115200
下面开始讲解怎么把uCOS移植到不同型号51单片机 打开工程\51_uCOS-IIV2.52\Project\ uCOS.uvproj
第1步:把keil配置为大模式,就是让Keil把默认变量定义到XDATA,下图3
图3
第2步:打开STARTUP .A51启动文件修改一些启动参数来对应单片机资源 根据单片机内部资源作调整,因为STC12C5A60S2有1280字节的SRAM,包括内部0-0xFF 256字节和外部0-0x3FF 1024字节 图4把IDATALEN改成100H(内部256),XDATALEN改成0x03FF(外部内存根据不同型号芯片的大小配置,最大是0xFFFF),这样单片机初始化的时候就会把相应的RAM清0。
图4 接着把图5的XBPSTACK设置为1使能图6的仿真堆栈初始化代码,XBPSTACKTOP设置成 0x03FF,就是单片机外部RAM的末尾地址。 图5
图6
这样就配置好启动文件了。 第3 步:配置uCOS文件 uCOS系统需要一个时钟节拍,节拍频率10Hz-1000Hz。我用的是T0定时器每10ms中断一次。打开OS_CPU_C.C文件找到void InitHardware(void) T0定时器初始化函数,配置成想要的中断时间,然后修改中断函数void OSTickISR(void) interrupt 1里面TL0和TL1的初值,如果是有 自动重装功能的定时器就可以注释掉这两句,最后配置OS_CFG.H文件里最下面的宏定义#define OS_TICKS_PER_SEC,这个就是1秒钟的节拍数,例如10ms中断一次就是100,20ms中断一次就是50。 #define OS_TICKS_PER_SEC100 /* Set the number of ticks in one second */ ----------------------------------移植完毕---------------------------------------
堆栈结构解释 图7 任务创建之后堆栈指针一直指向用户栈顶,图7左是堆栈初始化之后里面的数据结构,用户堆栈的最高3个字节一直固定保存“系统堆栈长度”和“?C_XBP(仿真堆栈指针)”因为任务初始化的时候仿真堆栈还没有使用,所以?C_XBP指向的堆栈下一个地址就是空闲堆栈,紧跟着就是系统堆栈数据。 在启动任务调度置后仿真堆栈被使用之后变成图7右的结构,任务切换时从?C_XBP指向的下一个地址开始保存系统堆栈数据,保存的数据长度由“系统堆栈长度” 决定 ,这样就实现了堆栈向下连续增长而不需要修改uCOS的堆栈检测函数。
//堆栈初始化函数
OS_STK *OSTaskStkInit(void (*task)(void *pd) reentrant, void *p_arg, OS_STK *ptos, INT16U opt)reentrant { OS_STK *stk;
p_arg =p_arg; opt = opt; //opt没被用到,保留此语句防止告警产生 stk =ptos; //用户堆栈最低有效地址 *stk-- =15; //系统堆栈长度 *stk-- =(INT16U)(ptos-3) >> 8; //?C_XBP 仿真堆栈指针高8位 *stk-- = (INT16U)(ptos-3) &0xFF; //?C_XBP 仿真堆栈指针低8位 最高3个字节一直被占 //用所以减3 *stk-- =0x07; //R7 *stk-- =0x06; //R6 *stk-- =0x05; //R5 *stk-- =0x04; //R4 *stk-- =0x01; //R3 *stk-- =0x02; //R2 *stk-- =0x01; //R1 *stk-- =0x00; //R0 *stk-- =0x00; //PSW *stk-- =0x00; //DPL *stk-- =0x00; //DPH *stk-- =0x0B; //B *stk-- =0x0A; //ACC *stk-- =(INT16U)task >> 8; //任务地址高8位 *stk-- =(INT16U)task & 0xFF; //任务地址低8位
stk = ptos;//堆栈指针一直指向栈顶
return stk; }
;***************************************************************************************** ;* uC/OS-II ;* 实时内核 ;* ;* (c) Copyright1992-1998, Jean J. Labrosse, Plantation, FL ;* 版权所有 ;* ;* MCU-51 专用代码 ;* KEIL C51大模式编译 ;* ;* 文件名 : OS_CPU_A.ASM ;* 作者 : Jean J. Labrosse ;*****************************************************************************************
;声明:本代码仅供学习研究uCOS-II使用,如用作其他用途出现问题本人概不负责。
;伪指令详细用法请查A51.PDF文件 ;程序结构详见《uC/OS-II》193-198页
;不用此语句!!! $CASE ;标号和变量名区分大小写
$NOMOD51 EA BIT 0A8H.7 SP DATA 081H B DATA 0F0H ACC DATA 0E0H DPH DATA 083H DPL DATA 082H PSW DATA 0D0H TR0 BIT 088H.4 TH0 DATA 08CH TL0 DATA 08AH
NAMEOS_CPU_A ;模块名
;定义重定位段 ?PR?OSStartHighRdy?OS_CPU_A SEGMENT CODE ?PR?OSCtxSw?OS_CPU_A SEGMENT CODE ?PR?OSIntCtxSw?OS_CPU_A SEGMENT CODE
;?PR?OSTickISR?OS_CPU_A SEGMENT CODE ;?PR?_?serial?OS_CPU_A SEGMENT CODE
;声明引用全局变量和外部子程序 EXTRNDATA (?C_XBP) ;仿真堆栈指针用于重入局部变量保存,为V2.51能被C使用定义在本模块中
EXTRNIDATA (OSTCBCur) EXTRNIDATA (OSTCBHighRdy) EXTRNIDATA (OSRunning) EXTRNIDATA (OSPrioCur) EXTRN IDATA (OSPrioHighRdy) EXTRNDATA (EA_Nesting)
; EXTRNCODE (OSTaskSwHook) EXTRNCODE (OSIntEnter) EXTRNCODE (OSIntExit) EXTRNCODE (OSTimeTick)
;对外声明4个不可重入函数 PUBLICOSStartHighRdy PUBLICOSCtxSw PUBLICOSIntCtxSw
; PUBLICOSTickISR ; PUBLICSerialISR
;分配堆栈空间,?STACK和STARTUP.A51中同名,编译器会将两个?STACK段合并,堆栈大小在STARTUP.A51中定义 ?STACK SEGMENT IDATA RSEG ?STACK ;------------------------------------------------------------------------------- PUSHALL MACRO ;定义压栈出栈宏 PUSH ACC PUSH B PUSH DPH PUSH DPL PUSH PSW MOV PSW,#0x00 PUSH0x00 ;R0-R7入栈 PUSH 0x01 PUSH 0x02 PUSH 0x03 PUSH 0x04 PUSH 0x05 PUSH 0x06 PUSH 0x07 ENDM
POPALL MACRO POP 0x07 ;R0-R7出栈 POP 0x06 POP 0x05 POP 0x04 POP 0x03 POP 0x02 POP 0x01 POP 0x00 POP PSW POP DPL POP DPH POP B POP ACC ENDM ;------------------------------------------------------------------------- ; 启动任务------切换堆栈指针,恢复堆栈数据 ;------------------------------------------------------------------------- RSEG?PR?OSStartHighRdy?OS_CPU_A OSStartHighRdy: USING0 ; ; LCALL OSTaskSwHook CtxSw: ;OSTCBCur ===> DPTR 获得当前TCB指针,详见C51.PDF第178页 MOV R0,#LOW (OSTCBCur) ;获得OSTCBCur指针低地址, INC R0 ;指针占3字节。+0类型+1高8位数据+2低8位数据 MOV DPH,@R0 ;全局变量OSTCBCur在IDATA中 INC R0 MOV DPL,@R0
;OSTCBCur->OSTCBStkPtr ===> DPTR 获得用户堆栈指针 INC DPTR ;指针占3字节。+0类型+1高8位数据+2低8位数据 MOVXA,@DPTR ;取用户栈顶指针OSTCBStkPtr MOV R0,A INC DPTR MOVXA,@DPTR
ADD A,#0FEH ;DPTR-2指向保存?C_XBP低8位的地址 MOV DPL,A MOV A,R0 ADDC A,#0FFH MOV DPH,A
;恢复仿真堆栈指针 MOVX A,@DPTR MOV ?C_XBP+1,A ;?C_XBP仿真堆栈指针低8位 INC DPTR MOVX A,@DPTR MOV ?C_XBP,A ;?C_XBP仿真堆栈指针高8位
INC DPTR MOVXA,@DPTR ;恢复系统堆栈长度 MOV R5,A ; ;因为DPTR没有自减1指令,所以只能计算出系统堆栈的末尾地址然后用INC DPTR复制数据提高效率 CLR C ;系统堆栈末尾地址 = 当前仿真堆栈地址-系统堆栈长度 MOV A,?C_XBP+1 SUBB A,R5 MOV DPL,A MOV A,?C_XBP SUBB A,#0 MOV DPH,A
MOV R0,#?STACK-1 ;获取堆栈起址
RestoreStk: ;恢复现场堆栈内容 INC DPTR INC R0 MOVXA,@DPTR MOV @R0,A DJNZR5,RestoreStk
MOV SP,R0 ;恢复堆栈指针SP
;OSRunning=TRUE MOV R0,#LOW (OSRunning) MOV @R0,#01
POPALL
MOVEA_Nesting,#0 ;切换任务清零EA嵌套 SETBEA ;开中断 RETI ;------------------------------------------------------------------------- ; 任务级切换函数 ;------------------------------------------------------------------------- RSEG?PR?OSCtxSw?OS_CPU_A OSCtxSw: PUSHALL;任务堆栈进栈 IntCtxSw: ;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位数据 MOVXA,@DPTR ;取用户栈顶指针OSTCBStkPtr MOV R0,A INC DPTR MOVXA,@DPTR
ADD A,#0FEH ;DPTR-2指向保存?C_XBP低8位的地址 MOV DPL,A MOV A,R0 ADDC A,#0FFH MOV DPH,A
;保存仿真堆栈指针?C_XBP MOV A,?C_XBP+1 ;?C_XBP 仿真堆栈指针低8位 MOVX @DPTR,A INC DPTR MOV A,?C_XBP ;?C_XBP 仿真堆栈指针高8位 MOVX @DPTR,A
MOV A,SP ;计算系统堆栈长度 CLR C SUBBA,#?STACK-1 MOV R5,A ;保存系统堆栈长度 INC DPTR MOVX @DPTR,A ;因为DPTR没有自减1指令,所以只能计算出系统堆栈的末尾地址然后用INC DPTR复制数据提高效率 CLR C ;系统堆栈末尾地址 = 当前仿真堆栈地址-系统堆栈长度 MOV A,?C_XBP+1 SUBB A,R5 MOV DPL,A MOV A,?C_XBP SUBB A,#0 MOV DPH,A
MOV R0,#?STACK-1 ;获取堆栈起址 SaveStk: INC DPTR ;保存系统堆栈到任务堆栈 INC R0 MOV A,@R0 MOVX @DPTR,A DJNZR5,SaveStk
; LCALL OSTaskSwHook ;调用用户程序
;获取新任务TCB指针 MOV R0,#OSTCBCur ;OSTCBCur= OSTCBHighRdy MOV R1,#OSTCBHighRdy MOV A,@R1 MOV @R0,A ;指针类型 INC R0 INC R1 MOV A,@R1 MOV @R0,A ;指针高8位 INC R0 INC R1 MOV A,@R1 MOV @R0,A ;指针低8位
;获取新任务优先级 MOV R0,#OSPrioCur ;OSPrioCur =OSPrioHighRdy MOV R1,#OSPrioHighRdy MOV A,@R1 MOV @R0,A
LJMP CtxSw ;------------------------------------------------------------------------- ; 中断级任务切换 ;------------------------------------------------------------------------- RSEG?PR?OSIntCtxSw?OS_CPU_A OSIntCtxSw: ;调整SP指针去掉在调用OSIntExit(),OSIntCtxSw()过程中压入堆栈的多余内容 ;SP=SP-4 MOV A,SP CLR C SUBBA,#4 MOV SP,A
LJMPIntCtxSw
END
|