ucos歷程總括

第一篇 UCOS介紹

第一篇 UCOS介紹

 

這個大家都知道。呵呵。考慮到咱們學習的完整性還是在這裏嘮叨一下。讓大家再熟悉一下。高手們忍耐一下吧! uC/OS II(Micro Control Operation System Two)是一個可以基於ROM運行的、可裁減的、搶佔式、實時多任務內核,具有高度可移植性,特別適合於微處理器和控制器,是和很多商業操作系統性能相當的實時操作系統(RTOS)。爲了提供最好的移植性能,uC/OS II最大程度上使用ANSI C語言進行開發,並且已經移植到近40多種處理器體系上,涵蓋了從8位到64位各種CPU(包括DSP)。   

uC/OS II可以簡單的視爲一個多任務調度器,在這個任務調度器之上完善並添加了和多任務操作系統相關的系統服務,如信號量、郵箱等。其主要特點有公開源代碼,代碼結構清晰、明瞭,註釋詳盡,組織有條理,可移植性好,可裁剪,可固化。內核屬於搶佔式,最多可以管理60個任務。

μC/OS-II 的前身是μC/OS,最早出自於1992 年美國嵌入式系統專家Jean J.Labrosse 在《嵌入式系統編程》雜誌的5 月和6 月刊上刊登的文章連載,並把μC/OS 的源碼發佈在該雜誌的B B S 上。   

μC/OS 和μC/OS-II 是專門爲計算機的嵌入式應用設計的, 絕大部分代碼是用C語言編寫的。CPU 硬件相關部分是用彙編語言編寫的、總量約200行的彙編語言部分被壓縮到最低限度,爲的是便於移植到任何一種其它的CPU 上。用戶只要有標準的ANSI 的C交叉編譯器,有彙編器、連接器等軟件工具,就可以將μC/OS-II嵌人到開發的產品中。μC/OS-II 具有執行效率高、佔用空間小、實時性能優良和可擴展性強等特點, 最小內核可編譯至 2KB 。μC/OS-II 已經移植到了幾乎所有知名的CPU 上。   

嚴格地說uC/OS-II只是一個實時操作系統內核,它僅僅包含了任務調度,任務管理,時間管理,內存管理和任務間的通信和同步等基本功能。沒有提供輸入輸出管理,文件系統,網絡等額外的服務。但由於uC/OS-II良好的可擴展性和源碼開放,這些非必須的功能完全可以由用戶自己根據需要分別實現。

uC/OS-II目標是實現一個基於優先級調度的搶佔式的實時內核,並在這個內核之上提供最基本的系統服務,如信號量,郵箱,消息隊列,內存管理,中斷管理等。

uC/OS-II以源代碼的形式發佈,但並不意味着它是開源軟件。你可以將其用於教學和私下研究(peaceful research);但是如果你將其用於商業用途,那麼你必須通過Micrium獲得商用許可。

雖然uCOS-II在商業上使用時需要的得到授權並且費用也是一筆不小的數字,但是他的開源畢竟帶領我們走入了內核的世界。在此我代表嵌入式工程師向Mr Jean J.Labrosse 致謝。


任務管理

uC/OS-II 中最多可以支持64 個任務,分別對應優先級0~63,其中0 爲最高優先級。63爲最低級,系統保留了4個最高優先級的任務和4個最低優先級的任務,所有用戶可以使用的任務數有56個。   

uC/OS-II提供了任務管理的各種函數調用,包括創建任務,刪除任務,改變任務的優先級,任務掛起和恢復等。   

系統初始化時會自動產生兩個任務:一個是空閒任務,它的優先級最低,該任務僅給一個整形變量做累加運算;另一個是系統任務,它的優先級爲次低,該任務負責統計當前cpu的利用率。

在系統初始化完畢後啓動任務時必須創建一份用戶任務,也就是說必須有一個應用程序(用戶任務,使用應用程序對於我們經常使用Windows用戶容易接受一些。呵呵),否則系統會崩潰。當然還有一些其他的要求,咱們後續再說,下面簡要概述一下任務管理相關的函數

1:建立任務OSTaskCreat()/OSTaskCreatExt()
如果想讓UCOS管理用戶的任務,必須先建立任務。可以通過將任務的地址和其他參數傳遞到以下兩個函數之一來建立任務。當調用OSTaskCreat()時,需要四個參數:
OSTaskCreate(void(*task)(void*pd),void*pdata,OS_STK*ptos,INTU prio)
Task:是指向任務代碼的指針,pdata:是任務開始執行是,傳遞給任務的參數的指針,ptos:是分配給任務的堆棧的棧頂指針,prio是分配給任務的優先級。
也可以用OSTaskCreatExt(),不過該函數需要9個參數,前四個參數與OSTaskCreat()一樣,例如:
INT8U OSTaskCreateExt(void(*task)(void *pd),void *pdata,OS_STK *ptos, INT8U prio, INT16U   id, OS_STK  *pbos, OS_STK  *pbos, OS_STK  *pbos, INT16U  opt)
id參數爲要建立的任務創建一個特殊的標識符。pbos是指向任務的堆棧棧底的指針,用於堆棧的檢驗。stk  _size用於指定堆棧成員數目的容量。pext是指向用戶附加的數據域的指針,用來擴展任務的OS_TCB。opt用於設定OSTaskCreateExt()的選項,指定是否允許堆棧檢驗,是否將堆棧清零,任務是否要進行浮點操作等等。
2:任務堆棧OS_STK()
每個任務都有自己的堆棧,堆棧必須申明爲OS_STK類型,並且由連續的內存空間組成。可以靜態分配堆棧空間,也可以動態分配堆棧空間。
3:堆棧檢驗OSTaskStkChk()
有時確定任務實際需要的堆棧空間的大小是很有必要的,因爲這樣就可以避免爲任務分配過多的堆棧空間,從而減少應用程序代碼所需的RAM空間。
4:刪除任務OSTaskDel()
有時需要刪除任務,刪除任務,是說任務返回並處於休眠態,並不是說任務的代碼被刪除了,只是任務的代碼不再被UCOS調用。刪除任務前應保證所刪任務並非空閒任務。
5:請求刪除任務OSTaskDelReq()
有時,任務會佔用一些內存緩衝或信號量一類的資源。這時,假如另一個任務試圖刪除該任務,這些被佔用的資源就會因爲沒有被釋放而丟失。在這種情況下,需想辦法擁有這些資源的任務在使用完資源後先釋放資源,再刪除自己。
6:改變任務的優先級OSTaskChangePrio()
在建立任務時,會分配給任務一個優先級。在程序運行期間,可以通過調用該函數改變任務的優先級。也就是說,UCOS允許動態的改變任務的優先級。
7:掛起任務OSTaskSuspend()
任務掛起是一個附加功能,也就是說,如果任務在被掛起的同時也在等待延遲時間到,那麼,需要對任務做取消掛起的操作,並且等待延遲時間到,任務才能轉讓就緒狀態。任務可以掛起自己或者其他任務。
8:恢復任務OSTaskResume()
掛起的任務只有通過該函數才能被恢復。
9:獲得任務的信息OSTaskQuery()
通過調用該函數,來獲得自身或其他應用任務的信息


時間管理

uC/OS-II的時間管理是通過定時中斷來實現的,該定時中斷一般爲10毫秒或100毫秒發生一次(這個時間片段是OS的作者推薦的,大家可以參考邵貝貝翻譯的《嵌入式實時操作系統ucos-II》這本書),時間頻率取決於用戶對硬件系統的定時器編程來實現。中斷髮生的時間間隔是固定不變的,該中斷也成爲一個時鐘節拍。這裏隱含的意思就是你選擇的芯片如果想使用UCOS系統,前提條件一定要有一個Timer。   
uC/OS-II要求用戶在定時中斷的服務程序中,調用系統提供的與時鐘節拍相關的系統函數,例如中斷級的任務切換函數,系統時間函數。
uCOS時間管理的相關函數
1:任務延遲函數OSTimeDly()
Ucos提供一個可以被任務調用而將任務延時一段特定時間的功能函數,即OSTimeDly().任務調用OSTimeDly()後,一旦規定的時間期滿或者有其他的任務通過調用OSTimeDlyResume()取消了延時,他就會進入就緒狀態。只有當該任務在所有就緒態任務中具有最高的優先級,它纔會立即運行。
2:按時,分,秒延時函數OSRimeDLyHMSM()
與OSTimeDly()一樣,調用OSRimeDlyHMSM()函數也會是UCOS進行一次任務調度,並且執行下一個優先級最高的就緒任務。當OSTimeDlyHMSM()後,一旦規定的時間期滿,或者有OSTimeDlyResume(),它就會馬上處於就緒態。同樣,只有當該任務在所有就緒態任務中具有最高的優先級,他纔開始運行。
3:恢復延時的任務OSTimeDlyResume()
延時的任務可以不等待延時的期滿,而是通過其他任務取消延時而使自己處於就緒態,可以通過該函數來實現,實際上,OSTimeDlyResume()也可以喚醒正在等待的事件。
4:系統時間OSTimeGet()和OSTimeSet()

內存管理

在ANSI C中是使用malloc和free兩個函數來動態分配和釋放內存。例如在Linux系統中就是這樣。但在嵌入式實時系統中,多次這樣的操作會導致內存碎片,因爲嵌入式系統尤其是uCOS是實地址模式,這種模式在分配任務堆棧時需要整塊連續的空間,否則任務無法正確運行。且由於內存管理算法的原因,malloc和free的執行時間也是不確定。這點是實時內核最大的矛盾。

基於以上的原因uC/OS-II中把連續的大塊內存按分區管理。每個分區中包含整數個大小相同的內存塊,但不同分區之間的內存快大小可以不同。用戶需要動態分配內存時,系統選擇一個適當的分區,按塊來分配內存。釋放內存時將該塊放回它以前所屬的分區,這樣能有效解決碎片問題,同時執行時間也是固定的。

同時uCOS-II根據以上的處理封裝了適合於自己的動態內存分配函數OSMemGet()和OSMemPut(),但是使用這兩個函數動態分配內存前需要先創建內存空間,也就是第二段咱們介紹的內存分塊。呵呵,不羅嗦了,具體的關於內存管理的函數如下:

內存控制塊的數據結構
Typedef
struct

{void   *osmemaddr    ;指向內存分區起始地址的指針。
Void   *osmemfreelist  ;指向下一個空餘內存控制塊或者下一個空餘內存塊的指針,
Int32u  osmemblksize  ;
內存分區中內存塊的大小,是建立內存分區時定義的。
Int32u osmemnblks     ;
內存分區中總的內存塊數量,也是建立該內存分區時定義的。
Int32u  osmemnfree    ;
內存分區塊中當前獲得的空餘塊數量。
}os_mem;
1;建立一個內存分區,OSMemCreate()
2:分配一個內存塊,OSMemGet()
應用程序通過調用該函數,從已經建立的內存分區中申請一個內存塊。該函數唯一的參數是指向特定內存分區的指針。
3:釋放一個內存塊,OSMemPut()
當應用程序不再使用一個內存塊時,必須及時的把它釋放,並放回到相應的內存分區中,這個操作就是通過調用該函數實現的。
4:查詢一個內存分區的狀態,OSQMemQuery()


任務間通信與同步

對一個多任務的操作系統來說,任務間的通信和同步是必不可少的。uC/OS-II中提供了4種同步對象,分別是信號量,郵箱,消息隊列和事件。所有這些同步對象都有創建,等待,發送,查詢的接口用於實現進程間的通信和同步。
對於這4種同步對象將在後面一一討論。

任務調度

uC/OS-II 採用的是可剝奪型實時多任務內核。可剝奪型的實時內核在任何時候都運行就緒了的最高優先級的任務。
uC/os-II的任務調度是完全基於任務優先級的搶佔式調度,也就是最高優先級的任務一旦處於就緒狀態,則立即搶佔正在運行的低優先級任務的處理器資源。爲了簡化系統設計,uC/OS-II規定所有任務的優先級不同,因爲任務的優先級也同時唯一標誌了該任務本身。
UCOS的任務調度在一下情況下發生:
1) 高優先級的任務因爲需要某種臨界資源,主動請求掛起,讓出處理器,此時將調度就緒狀態的低優先級任務獲得執行,這種調度也稱爲任務級的上下文切換。
2) 高優先級的任務因爲時鐘節拍到來,在時鐘中斷的處理程序中,內核發現高優先級任務獲得了執行條件(如休眠的時鐘到時),則在中斷態直接切換到高優先級任務執行。這種調度也稱爲中斷級的上下文切換。
這兩種調度方式在uC/OS-II的執行過程中非常普遍,一般來說前者發生在系統服務中,後者發生在時鐘中斷的服務程序中。
調度工作的內容可以分爲兩部分:最高優先級任務的尋找和任務切換。其最高優先級任務的尋找是通過建立就緒任務表來實現的。u C / O S 中的每一個任務都有獨立的堆棧空間,並有一個稱爲任務控制塊TCB(Task Control Block)的數據結構,其中第一個成員變量就是保存的任務堆棧指針。任務調度模塊首先用變量OSTCBHighRdy 記錄當前最高級就緒任務的TCB 地址,然後調用OS_TASK_SW()函數來進行任務切換。


第二章 搭建UCOS-II 2.52版的調試平臺

在這一章中我們主要討論UCOSII的源碼調試環境,爲了給大家一個共同的學習平臺,我搜集整理了一寫資料,就是以X86爲平臺,使用BC31(這個堪稱骨灰級的編譯器)來調試UCOSII源碼。當然你也可以用BC45或更高版本的編譯器,具體方法大同小異,我在此就不再囉嗦。

本章節的主要內容包括四點:

1、下載並安裝BC31編譯器

2、下載並安裝UCOS-II2.52版本源代碼

3、使用BC31編譯UCOS-II源碼

4、讓OS的第一個任務RUN起來

接下來會在每個帖子中討論一點。耐心等待哦!



下載並安裝BC31編譯器

我在這裏提供給大家這個骨灰級的編譯器BC31.需要的可以下載。見附件(骨灰級編譯器BC31)由於這個軟件的比較大,分成兩個壓縮包。下班了,先到這裏,回家再傳附件!


讓自己的第一個任務Run起來

前面已經給大家介紹瞭如何在PC機上調試UCOS,方法和需要的軟件都介紹給大家了,相信有興趣的朋友已經安裝調試了,下面咱們就讓自己的第一個任務在PC上Run起來。

OK,下面我就分步介紹建立自己的第一個任務

第一步:CopyC:\SOFTWARE\uCOS-II目錄下的EX1_x86L文件夾。作爲我們的工程模板

第二步:修改工程模板的名字爲:HelloEEWorld

第三部:按照咱們前面的《使用 BC31 工具編譯 UCOS‐II 的源碼過程 》修改配置文件;

第四步:修改Test.c文件,建立自己的第一個任務

具體的內容我就不再帖子上寫了。大家可以參考附件HelloEEWorld.rar裏面的Test.c文件。然後編譯



關於UCOS任務的理解

UCOS的運行是基於任務運行的,爲了能夠好的使用UCOS我們先要對UCOS的任務的概念做一個理解

在學習UCOS任務前我們先對我們以前使用的模式做一個回顧--前後臺模式。

這種系統可稱爲前後臺系統或超循環系統(Super-Loops)。應用程序是一個無限的循環,循環中調用相應的函數完成相應的操作,這部分可以看成後臺行爲(background)。中斷服務程序處理異步事件,這部分可以看成前臺行 foreground。後臺也可以叫做任務級。前臺也叫中斷級。時間相關性很強的關鍵操作(Critical operation)一定是靠中斷服務來保證的。因爲中斷服務提供的信息一直要等到後臺程序走到該處理這個信息這一步時才能得到處理,這種系統在處理信息的及時性上,比實際可以做到的要差。這個指標稱作任務級響應時間。最壞情況下的任務級響應時間取決於整個循環的執行時間。因爲循環的執行時間不是常數,程序經過某一特定部分的準確時間也是不能確定的。進而,如果程序修改了,循環的時序也會受到影響。

這種系統是在我們上學時和做小項目時經常用到,很多工程師稱這種方式爲“裸奔”。哈哈!我大學畢業後的錢三年寫的項目都是在裸奔。

UCOS-II是基於任務運行的。一個任務,也稱作一個線程,是一個簡單的程序,該程序可以認爲 CPU 完全只屬該程序自己。實時應用程序的設計過程,包括如何把問題分割成多個任務,每個任務都是整個應用的某一部分,每個任務被賦予一定的優先級,有它自己的一套 CPU 寄存器和自己的棧空間(如下圖所示)。

可以這麼理解,UCOS-II的每一個任務都有一個CPU,任務在運行時佔用CPU的全部資源,同時擁有自己的一套寄存器,當任務執行完畢後(時間片到),他把自己的CPU寄存器所有內容保存到自己的堆棧中,同時把CPU讓給別的任務,那麼得到CPU使用權的任務把自己的CPU寄存器從自己的堆棧中放到真正的CPU寄存器中開始運行,就這樣周而復始。

大家一定不要把任務的運行當成是函數的調用,這完全是兩回事。這個我們到後面的任務調度時在細說。每個任務都是一個無限的循環。每個任務都處在以下 5種狀態之一的狀態下,這5種狀態是休眠態, 就緒態、 運行態、 掛起態(等待某一事件發生)和被中斷態(參見下圖)   休眠態相當於該任務駐留在內存中,但並不被多任務內核所調度。就緒意味着該任務已經準備好, 可以運行了, 但由於該任務的優先級比正在運行的任務的優先級低, 還暫時不能運行。運行態的任務是指該任務掌握了 CPU 的控制權,正在運行中。掛起狀態也可以叫做等待事件態WAITING,指該任務在等待,等待某一事件的發生, (例如等待某外設的 I/O 操作,等待某共享資源由暫不能使用變成能使用狀態, 等待定時脈衝的到來或等待超時信號的到來以結束目前的等待,等等) 。最後,發生中斷時,CPU提供相應的中斷服務,原來正在運行的任務暫不能運行,就進入了被中斷狀態。如下圖表示μC/OS-Ⅱ中一些函數提供的服務,這些函數使任務從一種狀態變到另一種狀態。


簡單的我們可以把每一次任務的切換當成一次中斷,這個中斷不同於我們在使用前後臺模式時的中斷,那個中斷是硬件中斷,中斷時需要保存的CPU寄存器是由硬件實現的,而在UCOS中的任務切換是軟中斷,CPU保存了必要的寄存器後在切換時系統會在保存任務使用的寄存器。


補充知識-可剝奪型內核和不可剝奪型內核

不可剝奪型內核

不可剝奪型內核要求每個任務自我放棄CPU 的所有權。 不可剝奪型調度法也稱作合作型多任務,各個任務彼此合作共享一個 CPU。異步事件還是由中斷服務來處理。中斷服務可以使一個高優先級的任務由掛起狀態變爲就緒狀態。 但中斷服務以後控制權還是回到原來被中斷了的那個任務,直到該任務主動放棄 CPU 的使用權時,那個高優先級的任務才能獲得 CPU的使用權。

不可剝奪型內核允許每個任務運行,直到該任務自願放棄 CPU的控制權。中斷可以打入運行着的任務。 中斷服務完成以後將 CPU 控制權還給被中斷了的任務。任務級響應時間要大大好於前後系統,但仍是不可知的,商業軟件幾乎沒有不可剝奪型內核。

不可剝奪型內核的工作過程見下圖:

可剝奪型內核

 當系統響應時間很重要時,要使用可剝奪型內核。因此,μC/OS-Ⅱ以及絕大多數商業上銷售的實時內核都是可剝奪型內核。 最高優先級的任務一旦就緒, 總能得到CPU 的控制權。當一個運行着的任務使一個比它優先級高的任務進入了就緒態, 當前任務的CPU使用權就被剝奪了,或者說被掛起了,那個高優先級的任務立刻得到了 CPU的控制權。如果是中斷服務子程序使一個高優先級的任務進入就緒態,中斷完成時,中斷了的任務被掛起,優先級高的那個任務開始運行。使用可剝奪型內核,最高優先級的任務什麼時候可以執行,可以得到 CPU的控制權是可知的。使用可剝奪型內核使得任務級響應時間得以最優化。

可剝奪型內核的工作過程是這樣的:



UCOS-II 任務調度

任務調度是內核的主要職責之一,就是要決定該輪到哪個任務運行了。多數實時內核是基於優先級調度法的,UCOS也不例外。每個任務根據其重要程度的不同被賦予一定的優先級。基於優先級的調度法指,CPU總是讓處在就緒態的優先級最高的任務先運行。然而,究竟何時讓高優先級任務掌握CPU 的使用權,有兩種不同的情況,這要看用的是什麼類型的內核,是不可剝奪型的還是可剝奪型內核。

上一次咱們已經介紹了可剝奪型內核和不可剝奪型內核的工作過程了。在此不再贅述!

當多任務內核決定運行另外的任務時,它保存正在運行任務的當前狀態,即CPU寄存器中的全部內容。這些內容保存在任務的當前狀況保存區,也就是任務自己的棧區之中,上一次討論的內容中有這個圖示。入棧工作完成以後,就是把下一個將要運行的任務的當前狀況從該任務的棧中重新裝入 CPU 的寄存器,並開始下一個任務的運行。這個過程叫做任務切換。任務切換過程增加了應用程序的額外負荷。CPU的內部寄存器越多,額外負荷就越重。做任務切換所需要的時間取決於CPU有多少寄存器要入棧。實時內核的性能不應該以每秒鐘能做多少次任務切換來評價。而是要看OS總的關中斷時間。總的關中斷時間越短說明這個內核的實時性越好。這個問題在前面一個壇友的問題中我做了詳細的描述,有興趣的朋友可以在UCOS這個版塊找找這個帖子。

任務調度的算法有很多種。一種是基於優先級的。一種是基於時間片的。這兩種算法在邵貝貝教授翻譯的《UCOS-II內核詳解》這本書中有詳細解釋。我就不再重複。如果罈子裏有朋友對此有什麼不明白。可以在這裏留言。咱們再討論。



UCOS-II的文件結構

前面我們對UCOS的基礎知識做了瞭解,其中有些地方由於邵貝貝翻譯的樹上講解的很少我就沒有班門弄斧,大家可以結合那本書來看。有問題或不明白的在這裏討論,歡迎大家剔除問題。

這次我們主要了解UCOS-II的文件結構。等對UCOS文件結構瞭解以後,我們就逐一的去講解其各章的重點和難點,達到在短時間內學會使用UCOS。

我們利用這張圖片把UCOS的內部做一個解剖,我們可以清楚的看到UCOS內核的結構及層次,在這個圖的最下面是我們使用的硬件,就是我們的移植平臺,比如STM32F103XX系列的最小系統版、51最小系統版。呵呵,我本人覺得把UCOS移植到51上的意義不大。只是學習可以,使用我就不建議了!從圖中我們可以知道,要想移植UCOS你的硬件平臺必須具備一個定時器,也就是上圖中的TIMER。這個TIMER是用來給UCOS提供時鐘節拍的,相當於我們人的心跳。如果沒有這個TIMER,統統就無法運行。

再往上就是軟件了,軟件的第一層是我們移植的重點,這三個文件內主要包括一些與處理器相關的代碼,在後面我們我們再講解移植過程的時候會詳細的討論到這三個文件。

在往上左側就是系統內核源碼的各個文件。有興趣的壇友可以參考邵貝貝教授翻譯的書進行深入學習,由於我在這裏的主要任務是告訴大家如何使用UCOS,故不再過多的講解源碼部分,只是告訴大家如何使用即可。當然,如果你在研究過程中遇到問題可以拿出來和大家共同討論,右側是系統的配置文件,相對比較簡單,主要涉及到一些功能的裁剪。

最上層是我們的應用軟件,相當於我們在電腦上使用的Office軟件等,當然這裏是你自己的任務代碼。

 


UCOS的任務及狀態

任務的資源主要包括以下幾部分,ECB控制塊、任務堆棧、任務代碼及與CPU共用的寄存器和CPU的使用權



第4章_uCOS-II及其任務.ppt

這是一個講解任務的詳細資料,提供給大家參考

嵌入式實時操作系統uCOS-II_邵貝貝.pdf

http://ishare.iask.sina.com.cn/f/11711644.html

關於UCOS信號量

一:信號量的理解:

   (1)信號量可以分爲兩種:一種是二值信號量(0和1),一種是N值信號量(計數式信號量)。

    二值信號量的意思是可以有多少任務同時享用這個信號量。比如二值信號,就是隻有1個任務可以使用。當有一個任務使用該信號量的時候,那麼其他需要使用該信號量的任務就必須等待,直到該任務釋放該信號量。這種信號量可以看作一把鑰匙。

    對於N值信號量(計數式信號量),就是說可以同時有N-1個任務同時使用該信號量。對於二值信號量,N=1。

  (2)建立信號量的工作必須在任務級代碼中或者多任務啓動之前完成。

二:任務如何得到信號量的問題:

   想得到信號量的任務,必須執行等待操作(pend)。在信號量的建立的時候,我們首先確定了該信號量可以被共享的資源數(N),並將其賦值給pevent->OSEventCnt。如果信號量有效(非0),即pevent->OSEventCnt>0,則信號量減1,任務得以繼續運行。如果信號量無效,即pevent->OSEventCnt==0,則等待信號量的任務就被列入等待信號量的任務表中。許多內核允許定義等待超時,當等待時間超過了設定值,該信號量還是無效,則等待該信號量的任務進入就緒態,準備運行,並返回出錯代碼(等待超時錯誤)。

三:任務對信號量的釋放問題:

    任務執行發信號(post)操作來釋放信號量。如果沒有任務等待信號量,那麼信號量的值僅是簡單的加1(則信號量大於0,有效);如果有任務等待該信號量,那麼就會有另一個任務進入就緒態,信號量的值就不加1。

之後,這個釋放的信號量給那個等待中的任務,要看內核如何調度的。收到信號量的任務可能是如下兩者之一:

    ◆等待任務中,優先級最高的;(uc/os-ii僅支持這種方式)。

    ◆最早開始等待信號量的任務(如果是按先進先出FIFO原則)。

四:信號量的有效與無效的問題:

    信號量有效:信號量的計算器非0(.OSEventCnt!=0)。信號量有效表示任務對資源可用。

    信號量無效:信號量的計算器爲0。信號量無效表示任務對目前資源不可用,需要等待其他另一個任務(或者中斷服務子程序)發出該信號量(OSSemPost)。

五:關於信號量的三個重要函數:

◆OSSemCreate() 創建一個信號量  (注:由任務或啓動代碼操作)

    創建工作必須在任務級代碼中或者多任務啓動之前完成。功能只要是先獲取一個事件控制塊ECB,寫入一些參數。其中調用了OS_EeventWaitListInt()函數,對事件控制塊的等待任務列表進行初始化。完成初始化工作後,返回一個該信號量的句柄(Handle)。


◆OSSemPend() 等待一個信號量                  (注:只能由任務操作)

   本函數應用於任務試圖獲得共享資源的使用權、任務需要與其他任務或中斷同步及任務需要等待特定事件發生的場合。

   如果任務Task_A調用OSSemPend(),且信號量的值有效(非0),那麼OSSemPend()遞減信號量計數器(.OSEventCnt),並返回該值。換句話說,Task_A獲取到共享資源的使用權了,之後就執行該資源。

   如果如果任務Task_A調用OSSemPend(),信號量無效(爲0),那麼OSSemPend()調用OS_EventTaskWait()函數,把Task_A放入等待列表中。(等待到什麼時候呢?要看OSSemPost()(或者等待超時情況),由它釋放信號量並檢查任務執行權,見下資料)

◆OSSemPost() 發出(釋放)一個信號量          (注:由任務或中斷操作)

    本函數其中調用OS_EventTaskRdy()函數,把優先級最高的任務Task_A(在這假如是Task_A,另外假設當前調用OSSemPost()的任務是Task_B)從等待任務列表中去除,並使它進入就緒態。然後調用OSSched()進行任務調度。如果Task_A是當前就緒態中優先級最高的任務,則內核執行Task_A;否則,OSSched()直接返回,Task_B繼續執行。



UCOS另類信號量--互斥信號量

在UCOS的信號量使用過程中,我們經常會用的是二值信號量,而在二值信號兩種用的醉的情況就是互斥信號量。互斥信號是本身是一種二進制信號,具有超出uCOS-II提供的一般信號機制的特性。由於其特殊性,UCOS的作者將其獨立成章,單獨對待。組織了一套對於互斥信號量管理的單獨函數。互斥信號量具有以下特點: 1) 降解優先級反轉。 2) 實現對資源的獨佔式訪問(二值信號量)。

在應用程序中使用互斥信號是爲了減少優先級翻轉問題,當一個高優先級的任務需要的資源被一個低優先級的任務使用時,就會發生優先級翻轉問題。爲了減少優先級翻轉問題,內核可以提高的優先級任務的優先級,先於高優先級的任務運行,釋放佔用的資源。
爲了實現互斥,實時內核需要具有支持在同一優先級具有多個任務的能力。不幸的是,UC/OS-II不允許在相同的優先級有多個任務,必須只有一個任務。但是我們有另外的方法解決這個問題。可以把需要資源的高優先級任務上面的一個任務使用Mutex保留,允許提高的優先級任務的優先級。
舉一個mutexes信號工作的例子,如l下面的程序所示。
其中有三個任務可以使用共同的資源,爲了訪問這個資源,每個任務必須在互斥信號 ResourceMutex上等待(pend),任務#1有最高優先級10,任務#2優先級爲15,任務#3優先級爲20,一個沒有使用的正好在最高優先級之上的優先級#9用來作爲優先級繼承優先級。如main()所示,代碼中(1)進行uC/OS-II初始化,並通過調用OSMutexCreate()代碼中(2)創建了一個互斥信號。需要注意的是,OSMutexCreate()函數使用PIP最爲參數。然後創建三個任務代碼中(3),啓動uC/OS-II 代碼中(4).
假設任務運行了一段時間,在某個時間點,任務#3最先訪問了共同的資源,並得到了互斥信號,任務#3運行了一段時間後被任務#1搶佔。任務#1需要使用這個資源,並通過調用OSMutexPend()企圖獲得互斥信號,這種情況下,OSMutexPend()會發現一個高優先級的任務需要這個資源,就會把任務#3的優先級提高到9,同時強迫進行上下文切換退回到任務#3執行。 任務#3可以繼續執行然後釋放佔用的共同資源。任務#3通過調用OSMutexPost()釋放佔用的mutex信號,OSMutexPost()會發現mutex被一個優先級提升的低優先級的任務佔有,就會把任務#3的優先級返回到20。把資源釋放給任務#1使用,執行上下文切換到任務#1 
-----------------------------------------------------------------
OS_EVENT *ResourceMutex;
OS_STK TaskPrio10Stk[1000];
OS_STK TaskPrio15Stk[1000];
OS_STK TaskPrio20Stk[1000];
void main (void)
{
INT8U err;
OSInit(); /* (1) */
/* ---------- 應用程序初始化 ---------- */
OSMutexCreate(9, &err); /* (2) */
OSTaskCreate(TaskPrio10, (void *)0, &TaskPrio10Stk[999], 10); /* (3) */
OSTaskCreate(TaskPrio15, (void *)0, &TaskPrio15Stk[999], 15);
OSTaskCreate(TaskPrio20, (void *)0, &TaskPrio20Stk[999], 20);
/* ---------- Application Initialization ---------- */
OSStart(); /* (4) */
}
void TaskPrio10 (void *pdata)
{
INT8U err;
pdata = pdata;
while (1) {
/* --------- 應用程序代碼 ---------- */
OSMutexPend(ResourceMutex, 0, &err);
/* ------- 訪問貢獻資源 ------ */
OSMutexPost(ResourceMutex);
/* --------- 應用程序代碼 ---------- */
}
}
void TaskPrio15 (void *pdata)
{
INT8U err;
pdata = pdata;
while (1) {
/* ---------應用程序代碼 ---------- */
OSMutexPend(ResourceMutex, 0, &err);
/* ------- 訪問共享資源 ------ */
OSMutexPost(ResourceMutex);
/* --------- 應用程序代碼 ---------- */
}
}
void TaskPrio20 (void *pdata)
{
INT8U err;
pdata = pdata;
while (1) {
/* ---------應用程序代碼---------- */
OSMutexPend(ResourceMutex, 0, &err);
/* -------訪問共享資源------ */
OSMutexPost(ResourceMutex);
/* ---------應用程序代碼---------- */
}
}
上面代碼爲互斥信號使用示例
uC/OS-II'互斥信號包含三個元素,一個flag表示當前mutex是否能夠獲得(0或1);一個priority表示使用這個mutex的任務,以防一個高優先級的任務需要訪問mutex;還包括一個等待這個mutex的任務列表。
爲了啓動uC/OS-II’s mutex服務,應該在OS_CFG.H中設置OS_MUTEX_EN=1。在使用一個互斥信號之前應該首先創建它,創建一個mutex信號通過調用OSMutexCreate()完成,mutex的初始值總是設置爲1,表示資源可以獲得。
uC/OS-II提供了六種訪問互斥信號量的操作 OSMutexCreate(), OSMutexDel(),OSMutexPend(), OSMutexPost(), OSMutexAccept() and OSMutexQuery(). 
展示了任務和互斥信號量的關係。一個互斥信號量只能被任務訪問。在以後的資料中使用鑰匙符號表示互斥信號。鑰匙符號表明互斥信號用來訪問共享資源。沒有鑰匙就無法訪問。只有得到鑰匙的任務纔有資格訪問共享資源




UCOS互斥信號量操作函數分析

//建立並初始化一個互斥型信號量(優先級繼承優先級(PIP)、出錯代碼指針)

OS_EVENT  *OSMutexCreate (INT8U prio, INT8U *err)

{

#if OS_CRITICAL_METHOD == 3          /* Allocate storage for CPU status register */

    OS_CPU_SR  cpu_sr;

#endif    

    OS_EVENT  *pevent; 



    if (OSIntNesting > 0) {                 /* See if called from ISR ...               */

        *err = OS_ERR_CREATE_ISR;       /* can''t CREATE mutex from an ISR    */

        return ((OS_EVENT *)0);

    }//不能從ISR中建立,不允許在ISR中調用此函數

#if OS_ARG_CHK_EN > 0

    if (prio >= OS_LOWEST_PRIO) {              /* Validate PIP         */

        *err = OS_PRIO_INVALID;

        return ((OS_EVENT *)0);

    }//不合理的PIP

#endif

    OS_ENTER_CRITICAL();

if (OSTCBPrioTbl[prio] != (OS_TCB *)0) {    /* Mutex priority must nalready exist*/ 



//確認PIP沒有被任何任務佔用。OSTCBPrioTbl[ ]中的一個指向NULL的空指針指示//PIP有效

              

        OS_EXIT_CRITICAL();          /* Task already exist at priority ...       */

        *err = OS_PRIO_EXIST;            /* ... inheritance priority      */

              //如果優先級存在 ,則出錯。

        return ((OS_EVENT *)0);                            

    }

    OSTCBPrioTbl[prio] = (OS_TCB *)1;    /* Reserve the table entry       */

       //置非空指針,將這個優先級保留下來。

    pevent             = OSEventFreeList;   /* Get next free event control block       */

       //從空餘ECB中得到一塊空的ECB。

    if (pevent == (OS_EVENT *)0) {         /* See if an ECB was available    */

              //看ECB是否可用

        OSTCBPrioTbl[prio] = (OS_TCB *)0;        /* No, Release the table entry */

              //如果不可用,釋放此優先級表入口

        OS_EXIT_CRITICAL();

        *err = OS_ERR_PEVENT_NULL;        /* No more event control blocks */

        return (pevent);

    }

    OSEventFreeList = (OS_EVENT *)OSEventFreeList->OSEventPtr;   /* Adjust the free list     

       //如果可用,重新調整事件控制塊的表頭

    OS_EXIT_CRITICAL();

    pevent->OSEventType = OS_EVENT_TYPE_MUTEX;   //將其標記爲互斥型信號量

    pevent->OSEventCnt  = (prio << 8) | OS_MUTEX_AVAILABLE;/* Resource is available                    */              // (#define  OS_MUTEX_AVAILABLE      0x00FF)

       //mutex爲有效值,同時將PIP保存起來。值得注意的是,事件計數器.OSEventCnt

    //在此處的用法不同,高八位用於保存PIP的值,低侂位在資源無任務佔用

    //時的值爲0xff,有任務佔用時爲佔用mutex任務的優先級。這個避免了增加額

    //外的空間,節約對RAM的佔用量

    pevent->OSEventPtr  = (void *)0;          /* No task owning the mutex       */

       //消息正在初始化,所以沒有等待這個mutex的任務

    OS_EventWaitListInit(pevent);//初始化事件等待列表

    *err                = OS_NO_ERR;

    return (pevent);

}

PIP是該函數的參數,指定優先級繼承優先級。當發生優先級反轉時,將佔用該mutex的任務的優先級太高到PIP。



UCOS互斥信號量操作函數分析

等待(申請)一個互斥信號量:OSMutexPend()

關鍵代碼剖析:

void  OSMutexPend (OS_EVENT *pevent, INT16U timeout, INT8U *err)

{

   //不得在中斷中調用該函數

    if (OSIntNesting > 0) {                                

        *err = OS_ERR_PEND_ISR;                            

        return;

}

OS_ENTER_CRITICAL();    

//信號量可用                                              

if ((INT8U)(pevent->OSEventCnt & OS_MUTEX_KEEP_LOWER_8) 

                               == OS_MUTEX_AVAILABLE) {

        pevent->OSEventCnt &= OS_MUTEX_KEEP_UPPER_8;      

        //將計數器低8爲置成佔用該mutex的任務(當前任務)的優先級。

        pevent->OSEventCnt |= OSTCBCur->OSTCBPrio;

        //在mutex中保存佔用信號量的任務:修改該mutex的OSEventPtr ,使其指向當前任務

        pevent->OSEventPtr  = (void *)OSTCBCur;            

        OS_EXIT_CRITICAL();

        //信號量可用,正常返回。

        *err  = OS_NO_ERR;

        return;

}

//信號量不可用:即已被佔用

//從該信號量中獲得PIP

pip   = (INT8U)(pevent->OSEventCnt >> 8); 

//從該信號量中獲得佔用該信號量的任務的優先級。                    

mprio = (INT8U)(pevent->OSEventCnt & OS_MUTEX_KEEP_LOWER_8);  

//從信號量中獲得佔用該信號量的任務

ptcb  = (OS_TCB *)(pevent->OSEventPtr);  

/*

     如果原先佔用該mutex的優先級比提出申請該mutex的任務的優先級低

    (mprio > OSTCBCur->OSTCBPrio),則提升原任務的優先級至PIP

*/

    if (ptcb->OSTCBPrio != pip && mprio > OSTCBCur->OSTCBPrio) {  

        if ((OSRdyTbl[ptcb->OSTCBY] & ptcb->OSTCBBitX) != 0x00) {     

            if ((OSRdyTbl[ptcb->OSTCBY] &= ~ptcb->OSTCBBitX) == 0x00) {

                OSRdyGrp &= ~ptcb->OSTCBBitY;

            }

            //若原任務已就緒,則將其從就緒表中刪除,並置就緒標誌rdy

            rdy = TRUE;

        } else {

            rdy = FALSE;                                          

        }

        //修改優先級,及相關參數

        ptcb->OSTCBPrio  = pip;                     

        ptcb->OSTCBY    = ptcb->OSTCBPrio >> 3;

        ptcb->OSTCBBitY  = OSMapTbl[ptcb->OSTCBY];

        ptcb->OSTCBX     = ptcb->OSTCBPrio & 0x07;

        ptcb->OSTCBBitX   = OSMapTbl[ptcb->OSTCBX];

        //如果原任務是就緒的,則繼續讓新的優先級就緒

        if (rdy == TRUE) {                                

            OSRdyGrp               |= ptcb->OSTCBBitY;    

            OSRdyTbl[ptcb->OSTCBY] |= ptcb->OSTCBBitX;

        }

        OSTCBPrioTbl[pip]   = (OS_TCB *)ptcb;

}

//讓提出申請的任務先等待(從就緒表中刪除,如mutex的等待隊列)………

    OSTCBCur->OSTCBStat |= OS_STAT_MUTEX;            

    OSTCBCur->OSTCBDly   = timeout;                   

    OS_EventTaskWait(pevent);  

OS_EXIT_CRITICAL();

//執行任務切換(如果原來低優先級的任務優先級被擡高了,則該任務將被執行)

    OS_Sched();                                        

OS_ENTER_CRITICAL();

//提出申請的任務被喚醒繼續執行

if (OSTCBCur->OSTCBStat & OS_STAT_MUTEX) {

    //1)由於等待超時被定時器喚醒        

        OS_EventTO(pevent);

        OS_EXIT_CRITICAL();

        *err = OS_TIMEOUT;                            

        return;

}

/*

      2)原先佔用mutex的任務執行完成釋放了mutex

         並喚醒了等待該mutex的最高優先級的任務

*/

    OSTCBCur->OSTCBEventPtr = (OS_EVENT *)0;

    OS_EXIT_CRITICAL();

    *err = OS_NO_ERR;

}

釋放一個互斥信號量

釋放一個互斥信號量:OSMutexPost()

關鍵代碼剖析:

INT8U  OSMutexPost (OS_EVENT *pevent)

{

   //不得在中斷中調用該函數

    if (OSIntNesting > 0) {                           

        return (OS_ERR_POST_ISR);                    

    }

OS_ENTER_CRITICAL();

//從該信號量中獲得PIP

pip  = (INT8U)(pevent->OSEventCnt >> 8);          

/*

    從該信號量中獲得佔用該信號量的任務的優先級。

    在OSEventCnt中的低8位保存佔用mutex的任務的原始優先級,

    不隨優先級的提高而改變。

*/

prio = (INT8U)(pevent->OSEventCnt & OS_MUTEX_KEEP_LOWER_8);  

/*

    確認釋放mutex的任務確實是佔用mutex的任務自身。

    佔用/申請mutex的任務的優先級可能是pip(被提高),也可能是原先任務的優先級。

*/

    if (OSTCBCur->OSTCBPrio != pip && 

        OSTCBCur->OSTCBPrio != prio) {               

        OS_EXIT_CRITICAL();

        //若釋放mutex的任務非佔用/申請的任務,則返回錯誤信息。

        return (OS_ERR_NOT_MUTEX_OWNER);

}

   //若當前釋放mutex的任務的優先級爲pip,則需將該任務的優先級降到原來水平

if (OSTCBCur->OSTCBPrio == pip) {

   //首先將pip從就緒表刪除

        if ((OSRdyTbl[OSTCBCur->OSTCBY] &= ~OSTCBCur->OSTCBBitX) == 0) {

            OSRdyGrp &= ~OSTCBCur->OSTCBBitY;

        }

        //將任務優先級修改爲原始優先級,並修改相關參數

        OSTCBCur->OSTCBPrio   = prio;

        OSTCBCur->OSTCBY     = prio >> 3;

        OSTCBCur->OSTCBBitY   = OSMapTbl[OSTCBCur->OSTCBY];

        OSTCBCur->OSTCBX      = prio & 0x07;

        OSTCBCur->OSTCBBitX    = OSMapTbl[OSTCBCur->OSTCBX];

        //將修改優先級後的任務重新如就緒表

        OSRdyGrp           |= OSTCBCur->OSTCBBitY;

        OSRdyTbl[OSTCBCur->OSTCBY] |= OSTCBCur->OSTCBBitX;

        OSTCBPrioTbl[prio]    = (OS_TCB *)OSTCBCur;

    }

OSTCBPrioTbl[pip] = (OS_TCB *)1;                  

//若mutex的等待列表不空,喚醒等待列表中最高優先級的任務,並將mutex分配給它

if (pevent->OSEventGrp != 0x00) {           

    /*

        喚醒等待列表中最高優先級的任務(從mutex等待列表中刪除,使其入就緒表),

        清除等待任務的OS_STAT_MUTEX標誌,並返回其優先級prio

    */

        prio  = OS_EventTaskRdy(pevent, (void *)0, OS_STAT_MUTEX);

        pevent->OSEventCnt &= OS_MUTEX_KEEP_UPPER_8;  

        //將mutex分配給新任務:置OSEventCnt爲prio

        pevent->OSEventCnt |= prio;

        //在mutex中保存佔用信號量的任務

        pevent->OSEventPtr  = OSTCBPrioTbl[prio];     

        OS_EXIT_CRITICAL();

        //任務切換(如果喚醒的任務優先級比當前任務高,則使喚醒的任務得到運行)

        OS_Sched();                                   

        return (OS_NO_ERR);

}

//mutex的等待列表爲空,即該mutex可用:置mutex可用標誌及佔用任務指針。

    pevent->OSEventCnt |= OS_MUTEX_AVAILABLE;        

    pevent->OSEventPtr  = (void *)0;

    OS_EXIT_CRITICAL();

    return (OS_NO_ERR);

}


UCOS事件標誌組管理

今天我們就看看事件標誌組的使用和管理吧

事件標誌組(event flag)包含兩部分:

typedef struct

{

INT8U OSFlagType;

void *OSFlagWaitList;

OS_FLAGS OSFlagFlags;

}OS_FLAG_GRP;

1 組中各事件狀態的標誌位

2 等待這些標誌位或清除的任務列表 (這裏是雙向鏈表) 用於刪除標誌時檢查是否有等待該

標誌的任務鏈表包含3個數據結構: OS_FLAG_GRP, OS_TCB,OS_FLAG-NODE 用來記錄任務在等待哪些標誌位及等待方式(與/或),當一個任務開始等待某些標誌位時建立一個OS_FLAG-NODE,當這些等待的事件標誌位發生後,刪除數據結構。

當某個任務需要與多個任務同步時,須要使用事件標誌組。
⒈弄清楚OS_FLAG_GRP、OS_FLAG_NODE和OS_TCB之間的關係。
當一個任務開始等待某些事件標誌位時,就回建立一個事件標誌節點OS_FLAG_NODE數據結構,並且將任務所要等待的事件標誌位寫入OS_FLAG_NODE的分量.OSFlagNodeFlags。然後將該數據結構分量.OSFlagNodeFLagGrp指向事件標誌組OS_FLAG_GRP,將.OSFlagNodeTCB指向該任務的控制塊OS_TCB,建立起任務與事件標誌組之間的聯繫,說明該任務是等待該事件標誌組中某些事件標誌位的任務。當有多個任務都需要等待某個事件標誌組中某些事件標誌位時,這些任務分別建立自己的事件標誌節點。並且將這些事件標誌節點通過分量.OSFlagNodeNext和.OSFlagNodePrev連接成鏈。
⒉任務可以等待事件標誌組中某些位置位1,也可以等待事件標誌組中某些位清0,而置1(或清0)又可以分爲所有事件都發生的“與”型和任何一個事件發生的“或”型。這樣便有了4種不同的類型存放在.OSFlagNodeWaitType(OS_FLAG_NODE)中。
⒊事件標誌組和信號量我覺得是有不同的。
信號量建立以後,假設初始值爲N,前N個任務調用OSSemPend()函數都會得到信號量。之後如果第N+1個任務調用OSSemPend()函數申請信號量,該任務將會被置爲等待事件發生的狀態(睡眠態)。只到前N個任務中有任務運行完了所要運行的程序,調用OSSenmPost()函數,釋放了所佔用了信號量,第N+1個任務。(這裏假設該任務是所有等待信號量任務中優先級最高的任務)纔會獲得信號量,被從睡眠態轉入就緒態。
而事件標誌組是事件標誌組建立之後,某個任務需要事件標誌組中某些事件標誌位(置位或者清0)才能繼續運行,於是任務調用OSFlagPend()函數,而此時若這些標誌位滿足要求,任務返回,繼續執行。否則,任務將被掛起。而當有另外一個任務調用OSFlagPost()函數將前一個任務所需要的標誌位(置位或清0)使之滿足要求,前一個被掛起的任務將被置爲就緒態。因此幾個任務可以同時得到所需要的事件標誌進入就緒態。注意:只要任務所需要的標誌位滿足要求,任務便進入就緒態。與信號量不同,信號量中的任務需要是在等待該信號量中優先級最高的任務才能得到信號量進入就緒態。事件標誌組可以一個任務與多個任務同步,而信號量只能是一個任務與另一個任務同步。

等講完郵箱和消息隊列後然後對UCOS的內部通信機制做一個總結,然後就該講關於移植的過程了
考慮了好多方法。最後還是在有問必答的帖子上有個壇友要51下移植好的UCOS源碼+在Proteus下仿真。我覺得這道是一個好的辦法
所以我決定這幾天抽出點時間移植一個UCOS到STC51單片機上。使用Keil4.0+Proteus仿真。作爲大家共同學習移植的平臺。
基本外設如下:
輸出設備:液晶屏(主要爲以後講解UCGUI做準備)+串口
輸入設備:4*4鍵盤
其它:DS18B20溫度採集+LED跑馬燈
大家有好的主意可以提出來,咱們爭取做一個完善的開發板。用於學習UCOS+UCGUI



uC/OS-II中的消息郵箱

消息郵箱是uC/OS-II中的另一種通信機制,可以使一個任務或者中斷服務子程序向另一個任務發送一個指針型的變量。通常該指針指向一個包含了“消息”的特定數據結構。
各個任務之間把自己的數據封裝完畢後可以以郵箱的形式發送給其它的任務使用,從而達到任務間通信的目的。
應用程序可以使用多少個郵箱,其最大數目是由OS_CFG.H文件中的配置常數OS_MAX_EVENTS設定。
下面以一個實例來向大家講解一下郵箱的使用過程。
具體的郵箱使用過程如下:
第一步:在程序開始先定義一個郵箱,是指針形式的。定義函數如下:
OS_EVENT *MsgBox;
第二步:在主程序中建立郵箱:
MsgBox = OSMboxCreate((void *)0); // 創建郵箱並初始化爲空 
第三步:在接受函數中定義接受時需用到的兩個變量,一個爲指針形式
INT8U   err;
INT32U *msg;
在其循環函數中接受郵箱信息:
msg = OSMboxPend(MsgBox, 0, &err); // 接收Task1發來的消息
Messgae = msg[0]; // 接收消息到指定的變量
第四步:在發送函數中也需要先定義兩個變量
INT8U err;
volatile  INT32U MsgData = 1000;
也是在其循環函數中發送郵箱信息:
err = OSMboxPost(MsgBox, (void*)&MsgData); // 向TaskPWM發送消息
注意:在使用時一定要注意數據類型的定義,一定要統一起來。
下面分別對油箱相關的函數做一個簡要的描述。
任務或者中斷服務子程序都可以調用函數OSMboxPost(),OSMboxPostOpt() ,而只有任務可以調用OSMboxDel() OSMboxPend(), OSMboxQuery()。

void *OSMboxAccept (OS_EVENT *pevent)  無等待的從郵箱中得到一則消息。

OS_EVENT *OSMboxCreate (void *msg)  建立一個郵箱。
msg:用來初始化建立的消息郵箱,如果該指針不爲空,則建立的消息郵箱將含有消息。
返回值:指向分配給所建立的消息郵箱的事件控制塊的指針。如果沒有可用的事件控制塊,則返回空指針。

OS_EVENT *OSMboxDel (OS_EVENT *pevent, INT8U opt, INT8U *err)
刪除一個郵箱。當將OS_CFG.H文件中的OS_MBOX_DEL_EN設爲1時,該函數纔會被編譯。使用該函數時要注意,多個任務可能試圖操作已經刪除的郵箱。在刪除郵箱之前,必須首先刪除可能操作該郵箱的所有任務。
pevent:指向郵箱的指針。該指針是在郵箱建立時返回給用戶應用程序的指針。
opt:該先項定義郵箱的刪除條件,可以選擇只能在已經沒有任何在等待該郵箱的消息時,才能刪除郵箱(OS_DEL_NO_PEND);或者不管有沒有任務在等待郵箱的消息,立即刪除郵箱(OS_DEL_ALWAYS),在這種情況 下,所有等待郵箱消息的任務都會立即進入就緒態。
err:指向出錯代碼的指針。返回的出錯代碼可以是以下幾種情況之一。
OS_NO_ERR 調用成功,郵箱已經被刪除。
OS_ERR_DEL_ISR 試圖在中斷服務子程序中刪除郵箱。
OS_ERR_INVALID_OPT 無效的opt參數,用戶沒有將opt定義爲上述兩種情況之一。
OS_ERR_EVENT_TYPE pevent不是指向郵箱的指針。
OS_ERR_PEVENT_NULL 已經沒有OS_EVENT數據結構可以使用。
返回值:返回NULL表示郵箱已被刪除;返回pevent表示郵箱沒有刪作。

void *OSMboxPend (OS_EVENT *pevent, INT16U timeout, INT8U *err)  等待郵箱中的消息。
pevent:指向即將接收消息的消息郵箱的指針。
timeout:允許一個任務在經過了指定數目的時鐘節拍後還沒有得到需要的消息時恢復運行。如果該值爲0表示任務將持續等待消息。
err:指向包含錯誤碼的變量的指針。該函數返回的錯誤碼可能爲下述幾種情況
OS_NO_ERR 消息被正確地接收。
OS_TIMEOUT 消息沒有在指定的等待時間內送到。
OS_ERR_EVENT_TYPE pevent不是指向消息郵箱的指針。
OS_ERR_PEND_ISR 從中斷調用該函數。
OS_ERR_PEVENT_NULL pevent是空指針。
返回值:該函數返回接收的消息並將*err置爲OS_NO_ERR.

INT8U OSMboxPost (OS_EVENT *pevent, void *msg)  向郵箱發送一則消息。
pevent:指向即將接收消息的消息郵箱的指針。
msg:即將實際發送給任務的的消息。消息是一個以指針表示的苛種數據類型的變量,在不同的程序中消息的使用也可能不同。不允許傳遞一個空指針,國灰這意味着消息郵箱爲空。
返回值:該函數的返回值爲下述之一:
OS_NO_ERR 消息成功地放到消息郵箱中。
OS_MBOX_FULL 消息郵箱已經包含了其他消息,已滿。
OS_ERR_EVENT_TYPE pevent不是指向消息郵箱的指針。
OS_ERR_PEVENT_NULL pevent是空指針。
OS_ERR_POST_NULL_PTR 用戶試圖發出空指針。根據規則,在這裏不支持空指針。

INT8U OSMboxPostOpt (OS_EVENT *pevent, void *msg, INT8U opt)  向郵箱發送一則消息。該函數可以向等待郵箱的所有任務發送消息(廣播)。
pevent:指向即將接收消息的消息郵箱的指針。
msg:即將實際發送給任務的消息。消息是一個以指針表示的某種數據類型的變量,在不同的程序中消息的使用也可能不同。不允許傳遞一個空指針,因爲這意味着消息郵箱爲空。
opt:定義消息只發給等待郵箱消息的任務中優先級最高的任務(將opt置爲OS_POST_OPT_NONE),或者讓所有等待 郵箱消息的任務都得到消息(將opt置爲OS_POST_OPT_BROADCAST)。
返回值:
err 指向包含錯誤碼的變量指針,返回的錯誤碼可能爲下述幾種之一:
OS_NO_ERR 消息成功地放到消息郵箱中。
OS_MBOX_FULL 消息郵箱已經包含了其他消息,已滿。
OS_ERR_EVENT_TYPE pevent不是指向消息郵箱的指針。
OS_ERR_PEVENT_NULL pevent是空指針。
OS_ERR_POST_NULL_PTR 用戶試圖發出空指針。根據規則,在這裏不支持空指針。 

INT8U OSMboxQuery (OS_EVENT *pevent, OS_MBOX_DATA *p_mbox_data)  查詢一個郵箱的狀態。
pevent:指向即將接收消息的消息郵箱的指針。
pdata:指向OS_MBOX_DATA數據結構的指針,該數據結構包含下述成員。
void *OSMsg; /*消息郵箱中消息的複製*/

INT8U OSEventTbl[OS_EVENT_TBL_SIZE]; /*消息郵箱等待隊列的複製*/
INT8U OSEventGrp
返回值:該函數返回值爲下述之一:
OS_NO_ERR 調用成功
OS_ERR_EVENT_NULL pevent是空指針
OS_ERR_EVENT_TYPE pevent不是指向消息郵箱的指針

郵箱使用之數據存放

郵箱一般用來傳遞數據,不管何種類型都可以傳遞。在傳遞時,先把數據數據用void *進行類型變化,化爲void *這種萬用類型,而在接收郵箱的數據時,再還原成本身的數據類型。比如以下的兩個例子:

一是傳遞指向一個數組的指針。發端採用如下方式:
OSMboxPost(ComSendMbox, (void *)ComBufRec);         
其中的ComBufRec就是數組名,也表示指向該數組的指針,將原來的字符型數組名(或指針)變成(void *)類型,而接收端,利用
send_ptr = (uint8 *)OSMboxPend(ComSendMbox, 0, &err);      
將(void *)類型還原爲指針。

二是傳遞一個字符型變量。發端採用如下方式:
OSMboxPost(KeyMbox, (void *)key_value);             
其中的key_value就是字符型變量,將原來的字符型變量變成(void *)類型,而接收端,利用
key = (INT8U)OSMboxPend(KeyMbox, 0, &err);    
將(void *)類型還原爲字符型變量。


郵箱使用之0值的傳遞

在郵箱的傳遞中,如果把一個0值放入郵箱,經過void *類型變化後,變成了void *0,而判斷郵箱中是否有數據正是通過判斷郵箱中指向Message的指針是否爲0來判斷,這樣雖然放入了一個0變量,但郵箱中卻無法判斷這個0值,認爲郵箱中還是空。
這種情況下最好時避免0值的使用,如果非使用不可。建議在0值前加一個輔助變量。這樣可以避免郵箱接收數據後認爲是空的情況的發生
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章