uC/OS-II在51單片機上的移植2
#i nclude
#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文件中設(shè)置?STACK空間為40H即64,不要超出范圍。
OS_STK TaskStartStkyyb[MAX_STK_SIZE+1];//用戶棧多一個字節(jié)存長度
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);
}
}
重入問題的解決:
任務(wù)函數(shù)中帶有形參和局部變量時若使用reentrant關(guān)鍵字會引起重入,從C51.PDF 129-131頁的內(nèi)容知:為了函數(shù)重入,形參和局部變量必須保存在堆棧里,由于51硬件堆棧太小,KEIL將根據(jù)內(nèi)存模式在相應(yīng)內(nèi)存空間仿真堆棧(生長方向由上向下,與硬件棧相反)。對于大模式編譯,函數(shù)返回地址保存在硬件堆棧里,形參和局部變量放在仿真堆棧中,棧指針為?C_XBP,XBPSTACK=1時,起始值在startup.a51中初始化為FFFFH+1。仿真堆棧效率低下,KEIL建議盡量不用,但為了重入操作必須使用。KEIL可以混合使用3種仿真堆棧(大、中、小模式),為了提高效率,針對51推薦統(tǒng)一使用大模式編譯。
為了支持重入,重新設(shè)計了堆棧結(jié)構(gòu)(如下圖)。增加了保存仿真堆棧指針?C_XBP和堆棧內(nèi)容的數(shù)據(jù)結(jié)構(gòu)。相應(yīng)改變的文件有:OS_CPU_A.ASM、OS_CPU_C.C、OS_CPU.H、YY.C。由圖可知,用戶棧中保存的仿真棧與硬件棧相向生長,中間為空閑間隔,顯然uCOSII的堆棧檢測函數(shù)失效。硬件棧的保存恢復(fù)詳見上節(jié),仿真堆棧的保存與8086移植中的一樣,OS只提供堆棧空間和只操作堆棧指針,不進(jìn)行內(nèi)存拷貝,效率相對很高。
建議使用統(tǒng)一的固定大小的堆??臻g,盡管uCOSII原作者把不同任務(wù)使用不同空間看成是優(yōu)點,但為了在51上有效實現(xiàn)任務(wù)重入,針對51筆者還是堅持不使用這個優(yōu)點。
用戶堆棧空間的大小是可以精確計算出來的。用戶堆??臻g=硬件堆棧空間+仿真堆??臻g。硬件棧占用內(nèi)部RAM,內(nèi)部RAM執(zhí)行效率高,如果堆??臻g過大,會影響KEIL編譯的程序性能。如果堆??臻g小,在中斷嵌套和程序調(diào)用時會造成系統(tǒng)崩潰。綜合考慮,我把硬件堆??臻g大小定成了64字節(jié),用戶根據(jù)實際情況可以自行設(shè)定。仿真堆棧大小取決于形參和局部變量的類型及數(shù)量,可以精確算出。因為所有用戶棧使用相同空間大小,所以取占用空間最大的任務(wù)函數(shù)的空間大小為仿真堆棧空間大小。這樣用戶堆棧空間大小就唯一確定了。我將用戶堆棧空間大小用宏定義在OS_CFG.H文件中,宏名為MaxStkSize。
51的SP只有8位,無法在64K空間中自由移動,只好采用拷貝全部硬件堆棧內(nèi)容的笨辦法。51 本來就弱,這么一來缺點更明顯了。其實,引入OS必然要付出代價,一般OS要占用CPU10%-20%的負(fù)荷能力,請權(quán)衡利弊決定。切換頻率決定了CPU的耗費,頻率越高耗費越大,大到一定程度就該換更強的CPU了。我選了50Hz的切換頻率,不高也不低,用戶可以根據(jù)需要自行定奪。在耗費無法避免的情況下,我采取了幾個措施來提高效率:1。ret和reti混用減少代碼;2。IE、SP不入出棧,通過另外方式解決;3。用IDATA關(guān)鍵字聲明在匯編中用到的全局變量,變DPTR操作為Ri操作;4。設(shè)計堆棧結(jié)構(gòu),簡化算法;5。讓串口輸入輸出工作在系統(tǒng)態(tài),不占用任務(wù)TCB和優(yōu)先級,增加彈性緩沖區(qū),減少等待。
在51單片機上硬件仿真uCOS51的說明:
zyware網(wǎng)友2002/11/22來信詢問uCOS51在單片機上的硬件仿真問題,具體情況是“在51上用uCOS51核,以及一些構(gòu)件,keilc上仿真通過,用wave接硬件仿真程序亂飛,wave仿真以前的程序沒有問題,不知是何緣故”。
由于我的OS程序已經(jīng)在KEIL軟件仿真和硬件上實際測試過,所以不可能是程序錯??赡艿脑蛑荒苁怯布抡孳浖O(shè)置問題。本人用的是Medwin軟件,在Insight上調(diào)試,使用uCOS51編譯測試程序一樣跑飛。即使添加修改后的startup.a51(詳見《在51單片機上固化uCOS51的說明》)也不正常。我發(fā)現(xiàn)Medwin似乎沒有編譯startup.a51,因為它把該文件加在了other Files目錄下而不是source Files目錄,于是我猜測只有放在source Files目錄下的文件才被編譯。由觀察知,以.c和.asm做后綴的文件均被放在此目錄下且被編譯。于是我立即將startup.a51改成startup.asm并加入項目編譯,結(jié)果測試正常。不必?fù)?dān)心startup改名造成沖突,KEIL在鏈接目標(biāo)文件時會自動處理重名段,本目錄的文件優(yōu)先級高(我是這么理解的,具體原理不清楚,這只是根據(jù)實踐得到的結(jié)論,希望了解此處理過程的朋友能告之,不勝感激。)。
具體做法如下:
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的說明:
近來,收到多位網(wǎng)友來信詢問uCOS51在51單片機上的固化問題,歸納其焦點就是:為什么OS在KeilC51上模擬可以正常運行,但把它燒錄在CPU上卻不能工作?理論上,程序在軟件仿真通過測試后,將其燒錄在硬件上,硬件調(diào)試應(yīng)該一次成功。許多網(wǎng)友也有這個經(jīng)驗,可為什么在調(diào)試uCOS51時失效了呢?難道操作系統(tǒng)調(diào)試很特殊嗎?
其實問題出在重入函數(shù)的引入。原來KEILC51軟件仿真在不修改startup.a51文件的情況下,缺剩使用64K外部RAM,它把0000H-FFFFH全部仿真為可讀寫的RAM,而用戶的硬件系統(tǒng)可能沒有用到那么大的RAM空間,比如只用了8K/16K/32K等,或者用戶把一些地址空間映射給了別的設(shè)備,比如8019AS等。在沒有調(diào)用OSTaskCreate前,定義為reentrant的函數(shù)將用FFE0H做仿真堆棧棧頂指針,而此處在用戶的系統(tǒng)里不是RAM,造成程序跑飛。比如在我的用戶板上,將FE00H-FFFFH空間的一部分分配給8019AS使用,如果把demo程序編譯后直接燒到51上,將不能運行。解決辦法是根據(jù)系統(tǒng)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的系統(tǒng)上可以正常運行。用戶可根據(jù)自己XRAM的實際配置情況修改startup.a51相關(guān)參數(shù),并將其添加到項目里編譯。不必理會KEIL/C51/LIB目錄下的同名文件,此處的startup.a51優(yōu)先級高,KEIL將按此處該文件的配置編譯項目。
這也解釋了有些網(wǎng)友問到的,“為什么加入reentrant關(guān)鍵字,在軟件仿真時正確,燒在芯片上就死機,去掉reentrant后兩者都正常”的問題。由于大多數(shù)人很少使用重入函數(shù),往往不了解這個細(xì)節(jié),特此提請大家注意。
關(guān)于uCOS51不能正常工作的原因還可能是因為串口波特率和OS_TICKS_PER_SEC及TH0、TL0設(shè)置不正確引起的。demo程序默認(rèn)使用22.1184MHz晶體,19200波特率,切換頻率為50Hz。為此,1。在SERIAL.C中設(shè)置“TL1=0xFD;TH1=0xFD;”使波特率為19200;2。在OS_CPU_C.C和OS_CPU_A.ASM中設(shè)置“TH0=0x70;TL0=0x00;”使時鐘節(jié)拍tick=50次/秒;3。在OS_CFG.H中設(shè)置OS_TICKS_PER_SEC為50Hz。用戶應(yīng)根據(jù)實際情況,相應(yīng)地修改這些參數(shù),否則運行不正確。
定時器初值設(shè)置:
定時器0用于時鐘節(jié)拍發(fā)生器
/
} 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==’