【uCOS_51的移植概述】
uCOS_51是uCOS-II v2.52在MCS-51系列單片機上的移植實例,采用大模式,須外部擴展64KB的SRAM,內核的移植簡單地歸納為如下幾條:
(1)聲明11個數據類型(OS_CPU.H);
(2)用#define聲明4個宏(OS_CPU.H);
(3)用C語言編寫10個簡單的函數(OS_CPU_C.C);
(4)編寫4個匯編語言函數(OS_CPU_A.ASM)。
上述為一般移植過程中所要進行的工作,除此之外,我還增設了其它措施,以便于應用。
【uCOS_51的技術支持】
uCOS_51由本人休閑在家編寫,由于時間精力和能力的有限,難免有所疏乎,歡迎有志人士一起學習探討。
作者:華兄
郵箱:xxxxxxxx@qq.com
“uCOS_51”是我給工程起的名稱,“望文生義”,我還習慣Version的簡寫使用小寫字母,uCOS-II v2.52,而不是uCOS-II V2.52,哈哈,習慣了就好。我原本想去移植從官網下的最新版本uCOS-II內核《Micrium-uCOS-II-V290》,我只移植作簡單地測試,并不打算試用其它的功能,我又熱忠uCOS-II v2.52這個版本,對其內核源碼非常了解,于是打消了移植最新版本內核的念頭,感興趣的朋友可以去試試。其實這篇文章早就醞釀要寫了,由于沒有工作的壓力,也就沒有學習的動力,三天打魚兩天曬網,想到什么,寫什么,沒有什么邏輯性,不要見怪,哈哈!
我不想談移植工作和具體的實現,這些隨大流的東西,源碼是最好的老師,里面有詳盡的注釋。我就隨寫自己的想法吧。
每個接觸過uCOS_51的朋友,心里面總會有一些想法:它究竟是如何運作的?OS內核究竟有多奇妙?小小51單片機也能跑操作系統 ……?
想必不少朋友聽說過或者使用過RTX-51實時系統,這是我使用過的最小操作系統內核,Keil自帶的,內核源碼完全使用匯編語言編寫。談起OS內核,核心任務之一就是調度:為任務分配資源和時間,決定任務運行的次序,從而使系統滿足特定的性能要求。這些說起來過于籠統,一句話就是不停地切換CPU寄存器——保存寄存器:入棧;彈出寄存器:出棧;保存寄存器:入棧;恢復寄存器:出棧。講來講去,都跟棧有關系,那就談談uCOS_51的任務棧和硬件堆棧。
每個任務都會有自己的任務棧用于保存現場——CPU寄存器和其它信息,uCOS_51中通過OSTaskStkInit函數(源碼見工程,建議下載uCOS_51修訂版)來初始化所有的任務棧,棧底保存堆棧長度,然后就是任務代碼起始地址,接下來CPU寄存器,最后仿真堆棧指針。接下來談談硬件堆棧,硬件堆棧在OS_CPU_A.ASM中通過保留一定數量的存儲單元獲得,它只關心存儲單元數量,堆棧起始地址OSStack由Keil指定。所謂硬件堆棧,就是專門給占用CPU的任務使用,如何使用的呢?就是把任務棧內容彈出到硬件堆棧,然后用去恢復現場,還有保存運行過程中的中間數據。發生任務切換保存現場時,也是按初始化任務棧的次序,從硬件堆棧中彈出任務運行信息包括其它信息(如任務調用子函數產生的斷點信息)、任務代碼地址,Oh,no!此時叫斷點,還有CPU寄存器。其實是由OS_CPU_A.ASM中的出、入棧次序決定了OSTaskStkInit函數中的出、入棧次序。你會發現沒有從硬件堆棧中彈出堆棧長度以及仿真堆棧指針!堆棧長度在OS_CPU_A.ASM中是通過硬件堆棧棧頂SP減去OSStkStart(OSStkStart=OSStack-1)得來的,怎么不會是初始值15呢?當然不是啦,誰知道程序在哪里由于突然發生調度而中斷運行,任務運行過程中調用子函數就會有出入棧行為,還有其它因素影響堆棧長度。在uCOS-II v2.52內核中,中斷嵌套不允許發生任務的調度,只有當一層中斷嵌套時才能進行中斷級的任務切換,如若此時發生任務的切換,會調整硬件堆棧棧項SP,SP=SP-4,去掉在調用OSIntExit()、OSIntCtxSw()過程中壓入堆棧的多余內容(就是兩個斷點)。接下來就是仿真堆棧指針,它是賦給了?C_XBP這個變量,保存現場時也是從這里獲取,我們來專門談談?C_XBP這個仿真堆棧指針。
?C_XBP初始讓人覺得異常神秘,我也是,移植時差些被它迷糊了。查閱資料發現,這個仿真堆棧非常特殊,它是向下生長,而普通堆棧是向上生長,所以我們要把仿真堆棧的高地址值賦給?C_XBP。在STARTUP.A51用戶上電初始化程序中,我把?C_XBP設在了外部RAM的最高地址FFFFH+1處,這里較少被占用。哈哈,其實這些都是門面工作,uCOS_51中每個任務都會有自己的仿真堆棧指針?C_XBP,OSTaskStkInit函數中,它是設在任務棧項端,也就是任務棧棧底ptos+MAX_STK_SIZE地址處,那么,ptos+MAX_STK_SIZE除去任務運行正常使用,其余就用作仿真堆棧。哈哈,你不必擔心,仿真堆棧我也不熟,uCOS_51不使用它。
我們再談些什么好呢,真想出去走走了,外邊下著雨,算了,辛苦一下 …,先指正uCOS_51修訂版中的三個錯誤吧:(一)在OS_CPU_A.ASM中,?PR?_?SerialISR?OS_CPU_A SEGMENT CODE,串口中斷服務子程序不可重入,應該是?PR?SerialISR?OS_CPU_A SEGMENT CODE;(二)在serial.c中,InitSerial函數里注釋有誤,SCON=0x50;PCON=0x00; // 模式2,SM2=0,SMOD=0,應該是模式1,這也是張義和老師書上的錯誤;(三)在uCOS_II.H中,OS_TaskIdle函數聲明以及OS_TaskStat函數聲明的參數少寫了一個'd'變成ppata,應該是ppdata;(四)這個錯誤來自內核源碼,在OS_TASK.C中,OSTaskDel函數定義self這個布爾變量卻沒有使用,編譯器發出警告,該定義已在修訂版中去除,這里只是引起朋友的關注。
我再談談測試uCOS_51過程中發現的一個非常有意思的問題,就是工作狀態指示燈閃爍異常,起初我設置1秒閃爍一次,而實際發現幾乎要2秒才會閃爍。系統只有兩個任務——空閑任務和工作狀態指示任務,負荷不重,折騰了我老半天,最后我添加另外一個低優先級任務,高于空閑任務,里面置死循環,完完全全的空任務,使得空閑任務無法得到執行,這下發現指示燈正常閃爍起來了,然后盤根究底,發現問題出在空閑計數器OSIdleCtr上,"OSIdleCtr" 變量務必設置為 "idata" 存儲類型,否則任務運行節拍變慢。這就是答案,我特意注釋在main.c中。
由上面的問題引發我們思考,為什么設置idata存儲類型就正常了呢?由于uCOS_51采用大模式,全部變量默認為xdata存儲類型,存儲在外部RAM中,訪問需要花費更多的CPU時間,而空閑任務是經常被調度的任務,自然空閑計數器OSIdleCtr也就成了經常被訪問的變量。訪問頻率如此之高,如若設在外部RAM中,也就要耗費更多的CPU時間,增加了系統延時,閃爍也就變得慢了。因此需要把它放在內部RAM中,其它同理。
好了,我要出去了,哈哈 …
今天先談談#define REENTRANT reentrant這個宏,也就是Keil里reentrant這個關鍵字。原則上我們不需要修改任何與處理器無關的代碼,由于Keil編繹器的特殊性,這些代碼仍需作改動。Keil缺省情況下編譯出來的代碼不可重入,考慮多任務并發執行,系統要求可重入代碼,因此,需要在每個C函數及其聲明后加上關鍵字reentrant。代碼的可重入是指可以被多個任務并發使用,而數據不會遭到破壞;不可重入是指不能由多個任務所共享,除非能確保互斥訪問。想要了解更多,還請朋友自發去查閱資料,我就不再贅述!
下面說說uCOS-II v2.52里pdata這個參數,pdata是Keil里保留的關鍵字,代表分頁式存儲類型。所以避免沖突,給內核源碼所有pdata改名為ppdata,這是追隨楊大俠的做法。
在uCOS-II v2.52里,有BOOLEAN這個數據類型,它是布爾型,取值0或1。雖然你很想用bit這個數據類型去typedef,但強烈建議最好不要這么去做,因為bit無法在結構體里使用,所以把BOOLEAN定義成uchar類型。
... ...
貌似很久沒有更新過了 。。。
修正兩個錯誤吧,OS_EXT DF_IDATA OS_TCB *OSTCBCur; 以及 OS_EXT DF_IDATA OS_TCB *OSTCBHighRdy; 改為 OS_EXT OS_TCB *DF_IDATA OSTCBCur; 和 OS_EXT OS_TCB *DF_IDATA OSTCBHighRdy;,原因很簡單,OSTCBCur和OSTCBHighRdy存放于idata中,所指向的對象才存放在xdata。
討論一下OSTCBCur和OSTCBHighRdy這兩個指針,根據Keil C51用戶手冊,這兩個指針分別占用三個字節的地址空間,+0表示指針所指向對象的數據類型,+1表示高8位數據,+2表示低8位數據,+1數據和+2數據共同組成對象的16位地址。
這里講一下uCOS_51中的模塊化編程,uCOS_II.C中,#define OS_GLOBALS這個宏,使得uCOS_II.H中的普通變量、結構體變量等對于內核來說是全局變量(見uCOS_II.H之#define OS_EXT);內核以外,由于沒有這個宏,BSP、應用層將視為extern類型(見uCOS_II.H之#define OS_EXT extern)。#define OS_MASTER_FILE防止內核重復包含INCLUDES.H這個頭文件。uCOS_II.C包含頭文件和內核文件,編譯預處理階段,就會加載這些頭文件和內核文件信息,編譯時,一塊編譯成uCOS_II.O目標文件。
#include "..\ucos_51\ucos-ii\inc\includes.h",這個東東,起初也許你會覺得它比較冗長,沒有辦法,#include "includes.h"編譯器報錯找不到文件,只能使用笨方法為編譯器指定文件路徑。
扯一下就緒表吧,uCOS-II v2.52支持64個優先級,OSRdyGrp、OSRdyTbl[]、OSUnMapTbl[]這幾個變量功不可沒。我們可以通過void OS_Sched (void);這個函數(見OS_CORE.C)來研究uCOS-II v2.52查找當前就緒的最高優先級算法。特別談一下OSUnMapTbl[]這個東西,它是由256個元素組成的數組,作用:查找一個8位二進數中,最低位為1的是哪個位。如0x80,最低位為1的是第7位;0x00和0xff,最低位為1的是第0位。我們來看看uCOS-II v2.52查找當前就緒的最高優先級算法,下面代碼來自void OS_Sched (void);(見OS_CORE.C)。
y = OSUnMapTbl[OSRdyGrp]; /* Get pointer to HPT ready to run */
OSPrioHighRdy = (INT8U)((y << 3) + OSUnMapTbl[OSRdyTbl[y]]);
OSUnMapTbl[OSRdyGrp]獲得OSRdyGrp中最低位為1的位置y,即最高優先級所在就緒表OSRdyTbl[]的分組,一共8組(0~7)。OSRdyTbl[y]可以獲得最高優先級所在就緒表OSRdyTbl[]的分組信息,一串8位二進制數。OSUnMapTbl[OSRdyTbl[y]]可以獲得這串二制數中最低位為1的位,假設為x。在uC/OS中,數值越小,優先級越大,y和x都是OSRdyGrp以及OSRdyTbl[y]位最低的,uCOS-II v2.52中優先級計算公式:Prio=y<<3+x。y和x都是最低最小,則Prio最小,優先級就越大。
uCOS-II v2.52只支持64個優先級,我記得應該在uCOS-II v2.80以后,就開始支持256個優先級了。我們拿uCOS-II v2.83舉例說明,它沒有改變OSUnMapTbl[]這個數組,但改變了OSRdyGrp和OSRdyTbl[],有8位和16位兩種定義,也改變了OSPrioHighRdy的長度,由8位變為16位長度。我們來看看uCOS-II v2.83查找當前就緒的最高優先級算法。
下面代碼來自uCOS_II.H。
#if OS_LOWEST_PRIO <= 63 // 優先級數少于64時
OS_EXT INT8U OSRdyGrp; /* Ready list group */
OS_EXT INT8U OSRdyTbl[OS_RDY_TBL_SIZE]; /* Table of tasks which are ready to run */
#else // 優先級數多于63時,注意變量數據長度的變化
OS_EXT INT16U OSRdyGrp; /* Ready list group */
OS_EXT INT16U OSRdyTbl[OS_RDY_TBL_SIZE]; /* Table of tasks which are ready to run */
#endif
下面代碼來自static void OS_SchedNew (void);(見OS_CORE.C),此函數會被OS_Sched ()調用,用于查找當前就緒的最高優先級。
static void OS_SchedNew (void)
{
#if OS_LOWEST_PRIO <= 63 /* See if we support up to 64 tasks */
INT8U y;
y = OSUnMapTbl[OSRdyGrp];
OSPrioHighRdy = (INT8U)((y << 3) + OSUnMapTbl[OSRdyTbl[y]]);
#else /* We support up to 256 tasks */
// ---------------------------------------- 上半部分與uCOS-II v2.52一樣 ----------------------------------------
INT8U y;
INT16U *ptbl;
if ((OSRdyGrp & 0xFF) != 0) { // OSRdyGrp低8位不為0,最高優先級信息必定分布在OSRdyGrp低8位
y = OSUnMapTbl[OSRdyGrp & 0xFF]; // 查找OSRdyGrp低8位中最低位為1的位置y
} else { // OSRdyGrp低8位為0,最高優先級信息必定分布在OSRdyGrp高8位
y = OSUnMapTbl[(OSRdyGrp >> 8) & 0xFF] + 8; // OSRdyGrp右移8位,查找OSRdyGrp低8位中最低位為1的位置,再加8才是最高優先級信息分布在OSRdyGrp中的實際位置y,這是高明之處
}
ptbl = &OSRdyTbl[y]; // 注意,OSRdyTbl[y]長度為16位
if ((*ptbl & 0xFF) != 0) { // OSRdyTbl[y]低8位不為0,最高優先級信息必定分布在OSRdyTbl[y]低8位
OSPrioHighRdy = (INT8U)((y << 4) + OSUnMapTbl[(*ptbl & 0xFF)]); // OSUnMapTbl[(*ptbl & 0xFF)])類似計算y,不過,這里計算優先級的方法有所不同而已
} else { // OSRdyTbl[y]低8位為0,最高優先級信息必定分布在OSRdyTbl[y]高8位
OSPrioHighRdy = (INT8U)((y << 4) + OSUnMapTbl[(*ptbl >> 8) & 0xFF] + 8); // OSUnMapTbl[(*ptbl >> 8) & 0xFF] + 8類似計算y
}
#endif
}
前半截優先級數少于64時,與uCOS-II v2.52一樣,咱們看后半截,我就直接在代碼里面注釋說明。
|