文件名 : 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; /* 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&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, &TaskStartStkyya[0],2);
- OSTaskCreate(TaskStartyyb, (void *)0, &TaskStartStkyyb[0],3);
- OSTaskCreate(TaskStartyyc, (void *)0, &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);
- }
- }
重入问题的解决:
任务函数中带有形参和局部变量时若使用 reentrant关键字会引起重入,从C51.PDF 129-131页的内容知:为了函数重入,形参和局部变量必须保存在堆栈里,由于51硬件堆栈太小,KEIL将根据内存模式在相应内存空间仿真堆栈(生长方向由上向下,与硬件栈相反)。对于大模式编译,函数返回地址保存在硬件堆栈里,形参和局部变量放在仿真堆栈中,栈指针为?C_XBP,XBPSTACK =1时,起始值在startup.a51中初始化为FFFFH+1。仿真堆栈效率低下,KEIL建议尽量不用,但为了重入操作必须使用。KEIL可以混合使用3种仿真堆栈(大、中、小模式),为了提高效率,针对51推荐统一使用大模式编译。
为了支持重入,重新设计了堆栈结构(如下图)。增加了保存仿真堆栈指针?C_XBP和堆栈内容的数据结构。相应改变的文件有:OS_CPU_A.ASM、OS_CPU_C.C、OS_CPU.H、YY.C。由图可知,用户栈中保存的仿真栈与硬件栈相向生长,中间为空闲间隔,显然uCOSII的堆栈检测函数失效。硬件栈的保存恢复详见上节,仿真堆栈的保存与 8086移植中的一样,OS只提供堆栈空间和只操作堆栈指针,不进行内存拷贝,效率相对很高。
建议使用统一的固定大小的堆栈空间,尽管uCOSII原作者把不同任务使用不同空间看成是优点,但为了在51上有效实现任务重入,针对51笔者还是坚持不使用这个优点。
用户堆栈空间的大小是可以精确计算出来的。用户堆栈空间=硬件堆栈空间+仿真堆栈空间。硬件栈占用内部RAM,内部RAM执行效率高,如果堆栈空间过大,会影响KEIL编译的程序性能。如果堆栈空间小,在中断嵌套和程序调用时会造成系统崩溃。综合考虑,我把硬件堆栈空间大小定成了64字节,用户根据实际情况可以自行设定。仿真堆栈大小取决于形参和局部变量的类型及数量,可以精确算出。因为所有用户栈使用相同空间大小,所以取占用空间最大的任务函数的空间大小为仿真堆栈空间大小。这样用户堆栈空间大小就唯一确定了。我将用户堆栈空间大小用宏定义在OS_CFG.H文件中,宏名为MaxStkSize。
51 的SP只有8位,无法在64K空间中自由移动,只好采用拷贝全部硬件堆栈内容的笨办法。51 本来就弱,这么一来缺点更明显了。其实,引入OS必然要付出代价,一般OS要占用CPU10%-20%的负荷能力,请权衡利弊决定。切换频率决定了CPU 的耗费,频率越高耗费越大,大到一定程度就该换更强的CPU了。我选了50Hz的切换频率,不高也不低,用户可以根据需要自行定夺。在耗费无法避免的情况下,我采取了几个措施来提高效率:1。ret和reti混用减少代码;2。IE、SP不入出栈,通过另外方式解决;3。用IDATA关键字声明在汇编中用到的全局变量,变DPTR操作为Ri操作;4。设计堆栈结构,简化算法;5。让串口输入输出工作在系统态,不占用任务TCB和优先级,增加弹性缓冲区,减少等待。
在51单片机上硬件仿真uCOS51的说明:
zyware网友2002/11/22来信询问uCOS51在单片机上的硬件仿真问题,具体情况是“在51上用uCOS51核,以及一些构件,keilc上仿真通过,用wave接硬件仿真程序乱飞,wave仿真以前的程序没有问题,不知是何缘故”。
由于我的OS程序已经在KEIL软件仿真和硬件上实际测试过,所以不可能是程序错。可能的原因只能是硬件仿真软件设置问题。本人用的是Medwin软件,在 Insight上调试,使用uCOS51编译测试程序一样跑飞。即使添加修改后的startup.a51(详见《在51单片机上固化uCOS51的说明》)也不正常。我发现Medwin似乎没有编译startup.a51,因为它把该文件加在了other Files目录下而不是source Files目录,于是我猜测只有放在source Files目录下的文件才被编译。由观察知,以.c和.asm做后缀的文件均被放在此目录下且被编译。于是我立即将startup.a51改成 startup.asm并加入项目编译,结果测试正常。不必担心startup改名造成冲突,KEIL在链接目标文件时会自动处理重名段,本目录的文件优先级高(我是这么理解的,具体原理不清楚,这只是根据实践得到的结论,希望了解此处理过程的朋友能告之,不胜感激。
具体做法如下:
1。按《在51单片机上固化uCOS51的说明》一文修改startup.a51,并将其更名为startup.asm。
2。将startup.asm、yy1.c、os_cpu_c.c、ucos_ii.c、os_cpu_a.asm五个文件加入项目编译。
3。运行
在51单片机上固化uCOS51的说明:
近来,收到多位网友来信询问uCOS51在51单片机上的固化问题,归纳其焦点就是:为什么OS在KeilC51上模拟可以正常运行,但把它烧录在CPU上却不能工作?理论上,程序在软件仿真通过测试后,将其烧录在硬件上,硬件调试应该一次成功。许多网友也有这个经验,可为什么在调试uCOS51时失效了呢?难道操作系统调试很特殊吗?
其实问题出在重入函数的引入。原来KEILC51软件仿真在不修改startup.a51文件的情况下,缺剩使用64K外部 RAM,它把0000H-FFFFH全部仿真为可读写的RAM,而用户的硬件系统可能没有用到那么大的RAM空间,比如只用了8K/16K/32K等,或者用户把一些地址空间映射给了别的设备,比如8019AS等。在没有调用OSTaskCreate前,定义为reentrant的函数将用FFE0H做仿真堆栈栈顶指针,而此处在用户的系统里不是RAM,造成程序跑飞。比如在我的用户板上,将FE00H-FFFFH空间的一部分分配给8019AS使用,如果把demo程序编译后直接烧到51上,将不能运行。解决办法是根据系统RAM配置,修改startup.a51文件,并将其加入项目编译,如下所示:
XBPSTACK EQU 1 ; set to 1 if large reentrant is used.
XBPSTACKTOP EQU 07FFFH+1; set top of stack to highest location+1.
按此修改后,在有32K外部RAM的系统上可以正常运行。用户可根据自己XRAM的实际配置情况修改startup.a51相关参数,并将其添加到项目里编译。不必理会KEIL/C51/LIB目录下的同名文件,此处的startup.a51优先级高,KEIL将按此处该文件的配置编译项目。
这也解释了有些网友问到的,“为什么加入reentrant关键字,在软件仿真时正确,烧在芯片上就死机,去掉reentrant后两者都正常”的问题。由于大多数人很少使用重入函数,往往不了解这个细节,特此提请大家注意。
关于uCOS51不能正常工作的原因还可能是因为串口波特率和 OS_TICKS_PER_SEC及TH0、TL0设置不正确引起的。demo程序默认使用22.1184MHz晶体,19200波特率,切换频率为 50Hz。为此,1。在SERIAL.C中设置“TL1=0xFD;TH1=0xFD;”使波特率为19200;2。在OS_CPU_C.C和 OS_CPU_A.ASM中设置“TH0=0x70;TL0=0x00;”使时钟节拍tick=50次/秒;3。在OS_CFG.H中设置 OS_TICKS_PER_SEC为50Hz。用户应根据实际情况,相应地修改这些参数,否则运行不正确。
定时器初值设置:
定时器0用于时钟节拍发生器
//*****************************************************************************
//初值计算公式:
// (2^16-x)*F=Fosc/12
// 其中:F=时钟节拍频率tick;Fosc=晶体或晶振频率;x=初值;
// 本例中,F=50;Fosc=21.1184MHz;所以x=0x7000。
//*****************************************************************************
定时器1用于波特率发生器
//*****************************************************************************
//初值计算公式:
// TH1=256-(2^SMOD/32*Fosc/12*1/Bound)
// 其中:SMOD=0,1;Fosc=晶体或晶振频率;Bound=波特率
// 本例中,SMOD=0;Fosc=21.1184MHz;Bound=19200,所以TH1=0xFD。
//*****************************************************************************
demo程序项目中增加按如上方法改写的startup.a51后,在我的用户板硬件上运行正确。
为uCOS51增加Shell界面:
uCOSII只提供了操作系统内核,用户要自己添加文件处理、人机界面、网络接口等重要部分。其中Shell(人机界面)提供了人与机器交互的界面,是机器服务于人的体现,是系统必不可少的重要组成部分。现代的很多OS如UNIX、DOS、VxWorks都提供了友好的命令行界面。Windows更是提供了GUI。大部分人认识OS都是从这里开始的。uCOS51同样拥有Shell,它是我从以前写的前后台程序中移植过来的。
命令行Shell的工作原理比较简单,主要思路就是单片机接收用户键盘输入的字符存入命令缓冲区,并回显到屏幕,当用户按下回车键,触发软件状态机状态变迁,从输入态转移到命令解释态,然后根据用户命令调用相关子程序执行相应操作,执行完毕后重新回到输入态。
我感觉原理很好掌握,程序也不长,但是细节部分要反复调试多次才能稳定工作。比如:命令行左右边界的保护、退格键的处理、词表的设计等等。
Shell程序由词表、取词子程序、状态机框架程序(输入回显和命令解释执行)、命令相关子程序组成(详见源程序清单)。
词表结构如程序清单所示,由词数目,左括号数,右括号数,每个词的具体信息(长度,字符串)构成。左右括号数用于括号匹配检查;词数目用于程序循环;词的具体信息作为解释/执行程序的输入参数。
取词子程序从命令行语句中提取单词并存入词表同时进行匹配检查和词法分析。默认字符为:0-9、a-z、A-Z、'.';定界符为:空格、逗号,左/右括号。建议用户补充默认字符集(? / \ -)以便实现更灵活的语法。注意:现在版本的Shell只检查左右括号数量的匹配,无优先级和语法含义。
输入回显程序循环检查用户键盘输入。如果输入回车,程序状态转入解释执行态;如果输入退格(8)则回显退格、空格、退格,模拟删除字符,同时输入缓冲区清除相应字节,清除前先检查左边界是否越界。如越界则鸣响报警且不执行清除操作;其他字符输入直接存入输入缓冲区并回显,此前检查右边界是否溢出,如果溢出则鸣响报警且抛弃刚输入的字符。
命令解释程序调用取词子程序分析用户命令行输入,根据词表第一个单词在散转表中的位置调用相应执行子程序处理命令,如果散转表中无此单词,则打印“Bad command!”。取词子程序返回错误指示时也打印此句。
命令解释程序向相应的命令相关子程序传入词表指针,具体执行由用户自行决定。由命令相关子程序返回后重新回到命令输入态,完成一次输入执行全过程。此过程周而复始地循环执行。
Shell界面的命令按功能分为以下几组:
1。操作系统相关命令:
查看就绪任务lt / 中止任务kill / 恢复任务执行call / CPU利用率usage / 版本查询ver / 查某个任务信息(TCB、堆栈内容)lt
查看切换次数和时间lts
2。网络相关命令:
显示配置MAC地址macadr / 显示配置主机IP地址host / 显示配置子网掩码mask / 显示配置缺省网关gateway
显示网络配置总情况lc / 连通测试命令ping / 用户数据报发送命令udp / telnet命令tel / 相关应用命令**
显示ARP高速缓冲区地址对ls / 显示发送缓冲区信息lti
3。屏幕显示相关命令:
清屏clr / 帮助help / 功能键F3、F7处理 / 组合键Ctrl+C、Ctrl+B处理
4。外设(闪盘X5045和I/O口)相关命令:
读闪盘rdx / 读I/O口rdp / 写闪盘wdx
5。安全相关命令:
身份认证密码权限usr、pass
6。应用相关命令:
用户自行定义
用户命令大小写不敏感,程序将命令字符串统一成小写形式。程序中各种参数(如:最大词长度、词数量……)定义成宏放在一个头文件中,随时可修改配置,很方便。Shell作为一个任务工作于内核之外,占用一个任务号。
源程序:
词表
- typedef struct{
- int Num;
- int LeftCurveNum,RightCurveNum;
- struct{
- int Length;
- unsigned char Str[MaxLenWord+1]; /*for '\0'*/
- } wt[MaxLenWordTable];
- } WORDTABLE;
取词
- bit GetWord(unsigned char *ComBuf,WORDTABLE *WordTable)
- {
- int i=0; /*ComBuf String pointer*/
- int j=0; /*Length of Word */
- int k=-1; /*The number of WordTable*/
- int StrFlag=0; /*There is "0-9/a-z/A-Z" before " ,()"*/
- int SentenceEndFlag=0; /*Sentence end*/
- char ch;
- WordTable->Num=0;
- WordTable->LeftCurveNum=0;
- WordTable->RightCurveNum=0;
- ch=ComBuf[0];
- while(!SentenceEndFlag&&i
- if((ch>='0'&&ch<='9')||(ch>='a'&&ch<='z')||(ch>='A'&&ch<='Z')||(ch=='.')){
- if(StrFlag==0){
- StrFlag=1;k=k+1;j=0;
- if(k>=MaxLenWordTable) return 0;
- WordTable->wt[k].Str[j]=ch;
- WordTable->Num=k+1;
- }
- else{
- j=j+1;
- if(j>=MaxLenWord) return 0;
- WordTable->wt[k].Str[j]=ch;
- }
- }
- else if(ch==' '||ch==','||ch=='('||ch==')'||ch=='\0'){
- if(ch=='(') WordTable->LeftCurveNum++;
- if(ch==')') WordTable->RightCurveNum++;
- if(StrFlag==1){
- StrFlag=0;j=j+1;
- WordTable->wt[k].Str[j]='\0';
- WordTable->wt[k].Length=j;
- }
- if(ch=='\0') SentenceEndFlag=1;
- }
- else{
- return 0;
- }
- i=i+1;
- ch=ComBuf[i];
- }
- if(i
- if(WordTable->LeftCurveNum==WordTable->RightCurveNum) return 1;
- else return 0;
- }
- else{
- return 0;
- }
- }
输入回显和命令解释执行
- void yyshell(void *yydata) reentrant
- {
- yydata=yydata;
- clrscr();
- PrintStr("\t\t***********************************************\n");
- PrintStr("\t\t* Welcom to use this program *\n");
- PrintStr("\t\t* Author:YangYi 20020715 *\n");
- PrintStr("\t\t***********************************************\n\n\n");
- /*Login & Password*/
- PrintStr("% ");
- while(!ShellEnd){
- switch(State){
- case StatInputCom:{
- if(yygetch(&ch)){
- if(ch==13) /*Enter return key*/
- {
- PrintStr("\n");
- ComBuf[i+1]='\0';
- if(i+1==0) PrintStr("% ");
- else
- State=StatExeCom;
- }
- else{
- i=i+1;
- if((i>=MaxLenComBuf)&&(ch!=8)){
- PrintChar(7);
- i=MaxLenComBuf-1;
- }
- else{
- if(ch==8){
- i=i-2;
- if(i<-1) {i=-1;PrintChar(7);}
- else{
- PrintChar(8);
- PrintChar(' ');
- PrintChar(8);
- }
- }
- else{
- PrintChar(ch);
- ComBuf[i]=ch;
- }
- }
- }
- break;
- }
- else{
- //OSTimeDly(10);
- break;
- }
- }
- case StatExeCom:{
- if(GetWord(ComBuf,&WordTable)==1&&WordTable.Num!=0){
- yystrlwr(WordTable.wt[0].Str);
- for(tem=0;tem
- if(yystrcmp(WordTable.wt[0].Str,ComTable[tem])==0) ComMatchFlag=1;
- if(ComMatchFlag){
- tem--;
- switch(tem){
- case 0:{DisplayTask(&WordTable);break;}
- case 1:{Kill(&WordTable);break;}
- case 2:{PingCommand(&WordTable);break;}
- case 3:{UDPCommand(&WordTable);break;}
- case 4:{CfgHost(&WordTable);break;}
- case 5:{CfgMask(&WordTable);break;}
- case 6:{CfgGateway(&WordTable);break;}
- case 7:{
- //ShellEnd=1;
- PrintStr("\n\tThis Command is limited!\n\n");
- break;
- }
- case 8:{PrintConfig(&WordTable);break;}
- case 9:{clrscr();break;}
- case 10:{DisplayHelpMenu(&WordTable);break;}
- }
- }
- else
- PrintStr(" Bad command!\n\n");
- }
- else{
- if(WordTable.Num) PrintStr(" Bad command!\n\n");
- }
- ComMatchFlag=0;
- State=StatInputCom;
- if(ShellEnd) {PrintStr("\n\n");}
- else PrintStr("% ");
- i=-1;
- break;
- }
- default:{
- //ShellEnd=1;
- PrintStr("System fatal error!\n");
- PrintChar(7);PrintChar(7);PrintChar(7);
- }
- }
- }
- }
以上是我这次移植uCOS51的一些心得,写出来只是让准备在 51上运行操作系统的同行们少走弯路并增强使用信心。我强烈推荐大家在自己的51系统中使用uCOS这个简单实用的自己的操作系统。它的大小应该不是问题,性能上的提高却是显著的。但愿此文能对朋友们有所帮助,错误在所难免,希望读者指正