您的位置 首页 新能源

uC/OS-II在51单片机上的移植1

引言:随着各种应用电子系统的复杂化和系统实时性需求的提高,并伴随应用软件朝着系统化方向发展的加速,在16位32位单片机中广泛使用了嵌

导言:跟着各种运用电子体系的复杂化和体系实时性需求的进步,并随同运用软件朝着体系化方向开展的加快,在16位/32位单片机中广泛运用了嵌入式实时操作体系。可是实践运用中却存在着很多8位单片机,从经济性考虑,对某些运用场合,在8位MCU上运用操作体系是可行的。从学习操作体系视点,uC/OS-II for 51即简略又全面,学习本钱低价,值得推行。

结语:μC/OS-II具有免费、简略、可靠性高、实时性好等长处,但也有缺少便当开发环境等缺陷,特别不像商用嵌入式体系那样得到广泛运用和持续的研讨更新。但开放性又使得开发人员能够自行削减和增加所需的功用,在许多运用领域发挥着一起的作用。当然,是否在单片机体系中嵌入μC/OS-II应视所开发的项目而定,关于一些简略的、低本钱的项目来说,就没必要运用嵌入式操作体系了。

uC/OS-II原理:
uCOSII包含使命调度、时刻办理、内存办理、资源办理(信号量、邮箱、音讯行列)四大部分,没有文件体系、网络接口、输入输出界面。它的移植只与4个文件相关:汇编文件(OS_CPU_A.ASM)、处理器相关C文件(OS_CPU.H、OS_CPU_C.C)和配置文件(OS_CFG.H)。有64个优先级,体系占用8个,用户可创立56个使命,不支持时刻片轮转。它的基本思路便是 “近似地每时每刻总是让优先级最高的安排妥当使命处于运转状况” 。为了确保这一点,它在调用体系API函数、中止完毕、守时中止完毕时总是履行调度算法。原作者经过事前核算好数据,简化了运算量,经过精心规划安排妥当表结构,使得延时可预知。使命的切换是经过模仿一次中止完结的。
uCOSII作业中心原理是:近似地让最高优先级的安排妥当使命处于运转状况。
操作体系将在下面状况中进行使命调度:调用API函数(用户主动调用),中止(体系占用的时刻片中止OsTimeTick(),用户运用的中止)。
调度算法书上讲得很清楚,我首要讲一下全体思路。
(1)在调用API函数时,有或许引起堵塞,假如体系API函数察觉到运转条件不满足,需求切换就调用OSSched()调度函数,这个进程是体系主动完结的,用户没有参加。OSSched()判别是否切换,假如需求切换,则此函数调用OS_TASK_SW()。这个函数模拟一次中止(在51里没有软中止,我用子程序调用模仿,作用相同),好象程序被中止打断了,其实是OS成心制作的假象,意图是为了使命切换。既然是中止,那么回来地址(即紧邻OS_TASK_SW()的下一条汇编指令的PC地址)就被主动压入仓库,接着在中止程序里保存CPU寄存器(PUSHALL)……。仓库结构不是恣意的,而是严厉依照uCOSII标准处理。OS每次切换都会保存和康复悉数现场信息(POPALL),然后用RETI回到使命断点持续履行。这个断点便是OSSched()函数里的紧邻OS_TASK_SW()的下一条汇编指令的PC地址。切换的整个进程便是,用户使命程序调用体系API函数,API调用OSSched(),OSSched()调用软中止OS_TASK_SW()即OSCtxSw,回来地址(PC值)压栈,进入OSCtxSw中止处理子程序内部。反之,切换程序调用RETI回来紧邻OS_TASK_SW()的下一条汇编指令的PC地址,从而回来OSSched()下一句,再回来API下一句,即用户程序断点。因而,假如使命从运转到安排妥当再到运转,它是从调度前的断点处运转。
(2)中止会引发条件改变,在退出前有必要进行使命调度。uCOSII要求中止的仓库结构契合标准,以便正确和谐中止退出和使命切换。前面现已提到使命切换实践是模仿一次中止事情,而在真实的中止里省去了模仿(自身便是中止嘛)。只需规则中止仓库结构和uCOSII模仿的仓库结构相同,就能确保在中止里进行正确的切换。使命切换发生在中止退出前,此刻还没有回来中止断点。仔细观察中止程序和切换程序最终两句,它们是一模相同的,POPALL+RETI。即要么直接从中止程序退出,回来断点;要么先保存现场到TCB,比及康复现场时再从切换函数回来原本的中止断点(由于中止和切换函数遵从一起的仓库结构,所以退出操作相同,作用也相同)。用户编写的中止子程序有必要依照uCOSII标准书写。使命调度发生在中止退出前,是十分及时的,不会比及下一时刻片才处理。OSIntCtxSw()函数对仓库指针做了简略调整,以确保一切挂起使命的栈结构看起来是相同的。
(3)在uCOSII里,使命有必要写成两种方式之一(《uCOSII中文版》p99页)。在有些RTOS开发环境里没有要求显式调用OSTaskDel(),这是由于开发环境主动做了处理,实践原理都是相同的。uCOSII的开发依赖于编译器,现在没有专用开发环境,所以呈现这些不方便之处是能够了解的。
移植进程:
(1)复制书后附赠光盘sourcecode目录下的内容到C:\YY下,删去不必要的文件和EX1L.C,只剩下p187(《uCOSII》)上列出的文件。
(2)改写最简略的OS_CPU.H
数据类型的设定见C51.PDF第176页。留意BOOLEAN要界说成unsigned char 类型,由于bit类型为C51特有,不能用在结构体里。
EA=0关中止;EA=1开中止。这样界说即减少了程序行数,又避免了退出临界区后关中止形成的死机。
MCS-51仓库从下往上增加(1=向下,0=向上),OS_STK_GROWTH界说为0
#define OS_TASK_SW() OSCtxSw() 由于MCS-51没有软中止指令,所以用程序调用替代。两者的仓库格局相同,RETI指令复位中止体系,RET则没有。实践标明,关于MCS-51,用子程序调用入栈,用中止回来指令RETI出栈是没有问题的,反之中止入栈RET出栈则不行。总归,关于入栈,子程序调用与中止调用作用是相同的,能够混用。在没有中止发生的状况下复位中止体系也不会影响体系正常运转。详见《uC/OS-II》第八章193页第12行
(3)改写OS_CPU_C.C
我规划的仓库结构如下图所示:

TCB结构体中OSTCBStkPtr总是指向用户仓库最低地址,该地址空间内寄存用户仓库长度,其上空间寄存体系仓库映像,即:用户仓库空间巨细=体系仓库空间巨细+1。
SP总是先加1再存数据,因而,SP初始时指向体系仓库开始地址(OSStack)减1处(OSStkStart)。很明显体系仓库存储空间巨细=SP-OSStkStart。
使命切换时,先保存当时使命仓库内容。办法是:用SP-OSStkStart得出保存字节数,将其写入用户仓库最低地址内,以用户仓库最低地址为起址,以OSStkStart为体系仓库起址,由体系栈向用户栈复制数据,循环SP-OSStkStart次,每次复制前先将各自栈指针增1。
其次,康复最高优先级使命体系仓库。办法是:取得最高优先级使命用户仓库最低地址,从中取出“长度”,以最高优先级使命用户仓库最低地址为起址,以OSStkStart为体系仓库起址,由用户栈向体系栈复制数据,循环“长度”数值指示的次数,每次复制前先将各自栈指针增1。
用户仓库初始化时从下向上顺次保存:用户仓库长度(15),PCL,PCH,PSW,ACC,B,DPL,DPH,R0,R1,R2,R3,R4,R5,R6,R7。不保存SP,使命切换时依据用户仓库长度核算得出。
OSTaskStkInit函数总是回来用户栈最低地址。
操作体系tick时钟我运用了51单片机的T0守时器,它的初始化代码用C写在了本文件中。
最终还有几点有必要留意的事项。原本原则上咱们不必修正与处理器无关的代码,可是由于KEIL编译器的特殊性,这些代码仍要多处改动。由于KEIL缺省状况下编译的代码不行重入,而多使命体系要求并发操作导致重入,所以要在每个C函数及其声明后标示reentrant关键字。别的,“pdata”、“data”在uCOS顶用做一些函数的形参,但它一起又是KEIL的关键字,会导致编译过错,我经过把“pdata”改成“ppdata”,“data”改成“ddata”处理了此问题。OSTCBCur、OSTCBHighRdy、OSRunning、OSPrioCur、OSPrioHighRdy这几个变量在汇编程序顶用到了,为了运用Ri拜访而不必DPTR,应该用KEIL扩展关键字IDATA将它们界说在内部RAM中。
(4)重写OS_CPU_A.ASM
A51宏汇编的大致结构如下:
NAME 模块名 ;与文件名无关
界说重定位段 有必要依照C51格局界说,汇编恪守C51标准。段名格局为:?PR?函数名?模块名
声明引证大局变量和外部子程序 留意关键字为“EXTRN”没有‘E’
大局变量名直接引证
无参数/无寄存器参数函数 FUNC
带寄存器参数函数 _FUNC
重入函数 _?FUNC
分配仓库空间
只关怀巨细,仓库起点由keil决议,经过标号能够取得keil分配的SP起点。切莫自己分配仓库起点,只需用DS告诉KEIL预留仓库空间即可。
?STACK段名与STARTUP.A51中的段名相同,这意味着KEIL在LINK时将把两个同名段拼在一起,我预留了40H个字节,STARTUP.A51预留了1个字节,LINK完结后仓库段总长为41H。检查yy.m51知KEIL将仓库起点定在21H,长度41H,处于内部RAM中。
界说宏
宏名 MACRO 实体 ENDM
子程序
OSStartHighRdy
OSCtxSw
OSIntCtxSw
OSTickISR
SerialISR
END ;声明汇编源文件完毕

一般指针占3字节。+0类型+1高8位数据+2低8位数据 详见C51.PDF第178页
低位地址存高8位值,高位地址存低8位值。例如0x1234,基址+0:0x12 基址+1:0x34

(5)移植串口驱动程序
在此之前我写过依据中止的串口驱动程序,包含打印字节/字/长字/字符串,读串口,初始化串口/缓冲区。把它改成重入函数即可直接运用。
体系供给的显现函数是并发的,它不是直接显现到串口,而是先输出到显存,用户不必忧虑IO慢速操作影响程序运转。串口输入也采用了相同的技能,他使得用户在CPU忙于处理其他使命时照样能够盲打输入指令。
(6)编写测验程序Demo(YY.C)
Demo程序创立了3个使命A、B、C优先级分别为2、3、4,A每秒显现一次,B每3秒显现一次,C每6秒显现一次。从显现成果看,显现3个A后显现1个B,显现6个A和2个B后显现1个C,成果明显正确。
显现成果如下:
AAAAAA111111 is active
AAAAAA111111 is active
AAAAAA111111 is active
BBBBBB333333 is active
AAAAAA111111 is active
AAAAAA111111 is active
AAAAAA111111 is active
BBBBBB333333 is active
CCCCCC666666 is active
AAAAAA111111 is active
AAAAAA111111 is active
AAAAAA111111 is active
BBBBBB333333 is active
AAAAAA111111 is active
AAAAAA111111 is active
AAAAAA111111 is active
BBBBBB333333 is active
CCCCCC666666 is active
Demo程序经Keil701编译后,代码量为7-8K,可直接在KeilC51上仿真运转。
编译时要将OS_CPU_C.C、UCOS_II.C、OS_CPU_A.ASM、YY.C参加项目

文件名 : OS_CPU_A.ASM

$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

NAME OS_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

声明引证大局变量和外部子程序
EXTRN IDATA (OSTCBCur)
EXTRN IDATA (OSTCBHighRdy)
EXTRN IDATA (OSRunning)
EXTRN IDATA (OSPrioCur)
EXTRN IDATA (OSPrioHighRdy)

EXTRN CODE (_?OSTaskSwHook)
EXTRN CODE (_?serial)
EXTRN CODE (_?OSIntEnter)
EXTRN CODE (_?OSIntExit)
EXTRN CODE (_?OSTimeTick)

对外声明4个不行重入函数
PUBLIC OSStartHighRdy
PUBLIC OSCtxSw
PUBLIC OSIntCtxSw
PUBLIC OSTickISR

PUBLIC SerialISR

分配仓库空间。只关怀巨细,仓库起点由keil决议,经过标号能够取得keil分配的SP起点。
?STACK SEGMENT IDATA
RSEG ?STACK
OSStack:
DS 40H
OSStkStart IDATA OSStack-1

界说压栈出栈宏
PUSHALL MACRO
PUSH PSW
PUSH ACC
PUSH B
PUSH DPL
PUSH DPH
MOV A,R0 ;R0-R7入栈
PUSH ACC
MOV A,R1
PUSH ACC
MOV A,R2
PUSH ACC
MOV A,R3
PUSH ACC
MOV A,R4
PUSH ACC
MOV A,R5
PUSH ACC
MOV A,R6
PUSH ACC
MOV A,R7
PUSH ACC
PUSH SP ;不必保存SP,使命切换时由相应程序调整
ENDM

POPALL MACRO
POP ACC ;不必保存SP,使命切换时由相应程序调整
POP ACC ;R0-R7出栈
MOV R7,A
POP ACC
MOV R6,A
POP ACC
MOV R5,A
POP ACC
MOV R4,A
POP ACC
MOV R3,A
POP ACC
MOV R2,A
POP ACC
MOV R1,A
POP ACC
MOV R0,A
POP DPH
POP DPL
POP B
POP ACC
POP PSW
ENDM

子程序
————————————————————————-
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

OSRunning=TRUE
MOV R0,#LOW (OSRunning)
MOV @R0,#01

POPALL
SETB EA ;开中止
RETI
————————————————————————-
RSEG ?PR?OSCtxSw?OS_CPU_A
OSCtxSw:
PUSHALL

OSIntCtxSw_in:

取得仓库长度和起址
MOV A,SP
CLR C
SUBB A,#OSStkStart
MOV R5,A ;取得仓库长度

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

保存仓库长度
MOV A,R5
MOVX @DPTR,A

MOV R0,#OSStkStart ;取得仓库起址
save_stack:

INC DPTR
INC R0
MOV A,@R0
MOVX @DPTR,A
DJNZ R5,save_stack

调用用户程序
LCALL _?OSTaskSwHook

OSTCBCur = OSTCBHighRdy
MOV R0,#OSTCBCur
MOV R1,#OSTCBHighRdy
MOV A,@R1
MOV @R0,A
INC R0
INC R1
MOV A,@R1
MOV @R0,A
INC R0
INC R1
MOV A,@R1
MOV @R0,A

OSPrioCur = OSPrioHighRdy 运用这两个变量首要意图是为了使指针比较变为字节比较,以便节省时刻。
MOV R0,#OSPrioCur
MOV R1,#OSPrioHighRdy
MOV A,@R1
MOV @R0,A

LJMP OSCtxSw_in
————————————————————————-
RSEG ?PR?OSIntCtxSw?OS_CPU_A

OSIntCtxSw:

调整SP指针去掉在调用OSIntExit(),OSIntCtxSw()进程中压入仓库的剩余内容
SP=SP-4

MOV A,SP
CLR C
SUBB A,#4
MOV SP,A

LJMP OSIntCtxSw_in
————————————————————————-
CSEG AT 000BH ;OSTickISR
LJMP OSTickISR ;运用守时器0
RSEG ?PR?OSTickISR?OS_CPU_A

OSTickISR:

USING 0
PUSHALL

CLR TR0
MOV TH0,#70H ;界说Tick=50次/秒(即0.02秒/次)
MOV TL0,#00H ;OS_CPU_C.C 和 OS_TICKS_PER_SEC
SETB TR0

LCALL _?OSIntEnter
LCALL _?OSTimeTick
LCALL _?OSIntExit
POPALL
RETI
————————————————————————-
CSEG AT 0023H ;串口中止
LJMP SerialISR ;作业于体系态,无使命切换。
RSEG ?PR?_?serial?OS_CPU_A

SerialISR:

USING 0
PUSHALL
CLR EA
LCALL _?serial
SETB EA
POPALL
RETI
————————————————————————-
END
————————————————————————-

文件名 : OS_CPU_C.C

void *OSTaskStkInit (void (*task)(void *pd), void *ppdata, void *ptos, INT16U opt) reentrant
{
OS_STK *stk;

ppdata = ppdata;
opt = opt; //opt没被用到,保存此句子避免告警发生
stk = (OS_STK *)ptos; //用户仓库最低有用地址
*stk++ = 15; //用户仓库长度
*stk++ = (INT16U)task & 0xFF; //使命地址低8位
*stk++ = (INT16U)task >> 8; //使命地址高8位
*stk++ = 0x00; //PSW
*stk++ = 0x0A; //ACC
*stk++ = 0x0B; //B
*stk++ = 0x00; //DPL
*stk++ = 0x00; //DPH
*stk++ = 0x00; //R0
*stk++ = 0x01; //R1
*stk++ = 0x02; //R2
*stk++ = 0x03; //R3
*stk++ = 0x04; //R4
*stk++ = 0x05; //R5
*stk++ = 0x06; //R6
*stk++ = 0x07; //R7
//不必保存SP,使命切换时依据用户仓库长度核算得出。
return ((void *)ptos);
}

#if OS_CPU_HOOKS_EN
void OSTaskCreateHook (OS_TCB *ptcb) reentrant
{
ptcb = ptcb;
}

void OSTaskDelHook (OS_TCB *ptcb) reentrant
{
ptcb = ptcb;
}

void OSTimeTickHook (void) reentrant
{
}
#endif

//初始化守时器0
void InitTimer0(void) reentrant
{
TMOD=TMOD&0xF0;
TMOD=TMOD|0x01; //形式1(16位守时器),仅受TR0操控
TH0=0x70; //界说Tick=50次/秒(即0.02秒/次)
TL0=0x00; //OS_CPU_A.ASM 和 OS_TICKS_PER_SEC
ET0=1; //答应T0中止
TR0=1;
}

声明:本文内容来自网络转载或用户投稿,文章版权归原作者和原出处所有。文中观点,不代表本站立场。若有侵权请联系本站删除(kf@86ic.com)https://www.86ic.net/qiche/xinnengyuan/261766.html

为您推荐

联系我们

联系我们

在线咨询: QQ交谈

邮箱: kf@86ic.com

关注微信
微信扫一扫关注我们

微信扫一扫关注我们

返回顶部