新闻中心

EEPW首页 > 嵌入式系统 > 设计应用 > uC/OS-II在51单片机上的移植2

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

作者:时间:2016-11-22来源:网络收藏
文件名 : YY.C

#i nclude

本文引用地址:http://www.eepw.com.cn/article/201611/319610.htm

#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("ntt*******************************n");
PrintStr("tt* Hello! The world. *n");
PrintStr("tt*******************************nnn");

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用于时钟节拍发生器
/
} wt[MaxLenWordTable];
} WORDTABLE;

取词
bit GetWord(unsigned char *ComBuf,WORDTABLE *WordTable)
{
int i=0;
int j=0;
int k=-1;
int StrFlag=0;
int SentenceEndFlag=0;
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==’’){
if(ch==’(’) WordTable->LeftCurveNum++;
if(ch==’)’) WordTable->RightCurveNum++;
if(StrFlag==1){
StrFlag=0;j=j+1;
WordTable->wt[k].Str[j]=’’;
WordTable->wt[k].Length=j;
}
if(ch==’’) 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("tt***********************************************n");
PrintStr("tt* Welcom to use this program *n");
PrintStr("tt* Author:YangYi 20020715 *n");
PrintStr("tt***********************************************nnn");

PrintStr("% ");
while(!ShellEnd){

switch(State){
case StatInputCom:{
if(yygetch(&ch)){
if(ch==13)
{
PrintStr("n");
ComBuf[i+1]=’’;
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("ntThis Command is limited!nn");
break;
}
case 8:{PrintConfig(&WordTable);break;}
case 9:{clrscr();break;}
case 10:{DisplayHelpMenu(&WordTable);break;}
}
}
else
PrintStr(" Bad command!nn");
}
else{
if(WordTable.Num) PrintStr(" Bad command!nn");
}

ComMatchFlag=0;
State=StatInputCom;
if(ShellEnd) {PrintStr("nn");}
else PrintStr("% ");
i=-1;
break;
}
default:{
//ShellEnd=1;
PrintStr("System fatal error!n");
PrintChar(7);PrintChar(7);PrintChar(7);
}
}
}
}

以上是我这次移植uCOS51的一些心得,写出来只是让准备在51上运行操作系统的同行们少走弯路并增强使用信心。我强烈推荐大家在自己的51系统中使用uCOS这个简单实用的自己的操作系统。它的大小应该不是问题,性能上的提高却是显著的。但愿此文能对朋友们有所帮助,错误在所难免,希望读者指正。



关键词: uCOS-II51单片机移

评论


相关推荐

技术专区

关闭