前一段时刻,我参加了一个SNMP网管板的项目,我担任硬件规划和单板软件开发。该板的硬件由 MCS51+RTL8019AS组成,有64K FLASH 和64K SRAM。软件部分有操作体系和TCPIP协议栈。硬件比较简略,用了一个月就搞定了,协议栈我参阅了老古开发板的部分程序又上网找了SNMP源代码也很快完成了,可是测验时发现当运用较低时钟频率的CPU时(为了降低本钱),由于ASN.1编解码部分过于巨大,而我的程序又是一个大循环,AGENT的响应速度遭到严重影响,用户界面也反应迟钝。更坏的音讯是公司为了习惯市场需求,还要在上面跑PPP和HTTP。那样的话,我就得用40MHz的 AT89C51RD2或许人为的把程序断成几部分然后用状况机的办法在运转时再把它们连接起来。不过,我不想添加本钱,也不想把程序搞乱,无可奈何,只好运用操作体系。 说实在的,一开端我也不是很有掌握,一来我不清楚51的FLASH是否装得下这么多代码,二来我只做过OS运用开发,关于它的移植想都不敢想。不过,我在 BBS上查找了一阵儿后仍是有了一些条理。我找到了几个OS的源代码(我喜爱用现成的),依照代码巨细、实时性、运用人数、世人口碑等标准,最终选定了 uCOS2。我感觉它的实时性有确保,延时可猜测,代码听说可小到2K,网上评论这个论题的人也比较多,并且它的网站上有针对KEIL C51的移植实例。 经过一番查找,我得到了5个版别。其间3个是用KEIL编译的。原本我想直接把OS代码嵌到运用程序中,但后来发现没有一个能够直接运用。有的无法用 KEIL直接编译,有的需求修正DLL在软件仿真下运用。而我需求的是能在串口输入输出,不需求修正任何无关软件,能在软件仿真和硬件上运转的实时多使命操作体系。没有办法,我只好硬着头皮去改编。 我剖析了自己的下风:1。KEIL刚开端运用,不太熟悉;2。混合编程曾经从没有作过;3。时刻急迫,要在1个月内搞定。而我的优势便是有5个移植实例可供参阅,能够上网查资料。一开端,我用“仓库”、“混合编程”、“汇编”、“ucos”等要害字在C51BBS和老古论坛上检索相关信息并逐条阅览,读过之后,脑筋中的思路逐步明晰了。我了解到在KEIL的HLP目录下有A51.PDF和C51.PDF十分全面的介绍了汇编和C51,是KEIL的威望用户手册;SP初始化、内存清0等操作在STARTUP.A51文件中完成,用户能够改写它;KEIL的变量,子程序等的分配信息能够在.M51文件里查到;KEIL自己的论坛里有许多疑难问题的回答……经过阅览并经过考虑,处理了仓库起点、仓库空间巨细的设定等要害问题。论坛里的问题有些是我没有想到的,这使我发现了自己的遗漏。 在网上取得很多信息后,我开端阅览《uCOSII》中文版,总共读了3遍。第一遍是阅读,了解到uCOSII包含使命调度、时刻办理、内存办理、资源办理(信号量、邮箱、音讯行列)四大部分,没有文件体系、网络接口、输入输出界面。它的移植只与4个文件相关:汇编文件(OS_CPU_A.ASM)、处理器相关C文件(OS_CPU.H、OS_CPU_C.C)和配置文件(OS_CFG.H)。有64个优先级,体系占用8个,用户可创立56个使命,不支持时刻片轮转。第二遍首要是把整个作业进程在脑筋里过了一下,不明白当地有针对性的查书,要点是考虑作业原理和流程。我发现其实它的思路挺简略的。便是 “近似地每时每刻总是让优先级最高的使命处于运转状况” 。为了确保这一点,它在调用体系API函数、中止完毕、守时中止完毕时总是履行调度算法。原作者经过事前核算好数据,简化了运算量,经过精心规划安排妥当表结构,使得延时可预知。使命的切换是经过模仿一次中止完成的。第三遍要点看了移植部分的内容。对照实例,研讨了代码的详细完成办法。 前期预备用了20几天,真实编写代码只用了1.5天,调试用了2天。详细进程如下: (1)复制书后附赠光盘sourcecode目录下的内容到C:\YY下,删去不必要的文件和EX1L.C,只剩下p187(《uCOSII》)上列出的文件。 (2)改写最简略的OS_CPU.H 数据类型的设定见C51.PDF第176页。留意BOOLEAN要界说成unsigned char 类型,由于bit类型为C51特有,不能用在结构体里。 EA=0关中止;EA=1开中止。这样界说即减少了程序行数,又避免了退出临界区后关中止形成的死机。 MCU-51仓库从下往上增加(1=向下,0=向上),OS_STK_GROWTH界说为0 #define OS_TASK_SW() OSCtxSw() 由于MCU-51没有软中止指令,所以用程序调用替代。两者的仓库格局相同,RETI指令复位中止体系,RET则没有。实践标明,关于MCU-51,用子程序调用入栈,用中止回来指令RETI出栈是没有问题的,反之中止入栈RET出栈则不行。总归,关于入栈,子程序调用与中止调用作用是相同的,能够混用。在没有中止发生的情况下复位中止体系也不会影响体系正常运转。详见《uC/OS-II》第八章193页第12行 (3)改写OS_CPU_C.C 我规划的仓库结构如下图所示: ********************************************************************************* * * ———- * |OSTCBCur| * ———- * | * | ———————– ———- * \—->|OSTCBCur->OSTCBStkPtr| SP—->| | * ———————– ———- * | | | * | ———- – ———- * | | | | | . | * | ———- | | . | * | | | | | . | * | ———- | ———- * | | . |长度 | | +1 * | | . | | ———- * | | . | | OSStack—->| | 0 * | ———- | ———- * | | | | OSStkStart—->| 不关怀 | -1 低地址 * | ———- – ———- * \——–>| 长度 | 低地址 体系仓库 * ———- * 用户仓库 长度=SP-OSStkStart ********************************************************************************* 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参加项目 以上是我这次移植uCOS51的一些心得,写出来仅仅让预备在51上运转操作体系的同行们少走弯路并增强运用决心。我强烈推荐我们在自己的51体系中运用 uCOS这个简略有用的自己的操作体系。它的巨细应该不是问题,性能上的进步却是明显的。期望此文能对朋友们有所协助,过错在所难免,期望各位大虾纠正,诸位高手们见笑了! 注:悉数源码可来信索要(asdjf@163.com),以下仅为要害代码部分。 文件名 : 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第169页 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 %26;amp; 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; /* Prevent compiler warning */ } void OSTaskDelHook (OS_TCB *ptcb) reentrant { ptcb = ptcb; /* Prevent compiler warning */ } void OSTimeTickHook (void) reentrant { } #endif //初始化守时器0 void InitTimer0(void) reentrant { TMOD=TMOD%26;amp;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; } 文件名 : YY.C #include #define MAX_STK_SIZE 64 void TaskStartyya(void *yydata) reentrant; void TaskStartyyb(void *yydata) reentrant; void TaskStartyyc(void *yydata) reentrant; OS_STK TaskStartStkyya[MAX_STK_SIZE+1];//留意:我在ASM文件中设置?STACK空间为40H即64,不要超出范围。 OS_STK TaskStartStkyyb[MAX_STK_SIZE+1];//用户栈多一个字节存长度 OS_STK TaskStartStkyyc[MAX_STK_SIZE+1]; void main(void) { OSInit(); InitTimer0(); InitSerial(); InitSerialBuffer(); OSTaskCreate(TaskStartyya, (void *)0, %26;amp;TaskStartStkyya[0],2); OSTaskCreate(TaskStartyyb, (void *)0, %26;amp;TaskStartStkyyb[0],3); OSTaskCreate(TaskStartyyc, (void *)0, %26;amp;TaskStartStkyyc[0],4); OSStart(); } void TaskStartyya(void *yydata) reentrant { yydata=yydata; clrscr(); PrintStr(\n\t\t*******************************\n); PrintStr(\t\t* Hello! The world. *\n); PrintStr(\t\t*******************************\n\n\n); for(;;){ PrintStr(\tAAAAAA111111 is active.\n); OSTimeDly(OS_TICKS_PER_SEC); } } void TaskStartyyb(void *yydata) reentrant { yydata=yydata; for(;;){ PrintStr(\tBBBBBB333333 is active.\n); OSTimeDly(3*OS_TICKS_PER_SEC); } } void TaskStartyyc(void *yydata) reentrant { yydata=yydata; for(;;){ PrintStr(\tCCCCCC666666 is active.\n); OSTimeDly(6*OS_TICKS_PER_SEC); } } * – 本贴最终修正时刻:2003-5-29 11:25:08 修正者:gdtyy * – 修正原因:+ 作者信箱 asdjf@163.com 社区原文 http://www.21icbbs.com/club/bbs/list.asp?boardid=8%26;amp;page=1%26;amp;t=338692%26;amp;tp=uCOS51%u79FB%u690D%u5FC3%u5F97