www.久久久久|狼友网站av天堂|精品国产无码a片|一级av色欲av|91在线播放视频|亚洲无码主播在线|国产精品草久在线|明星AV网站在线|污污内射久久一区|婷婷综合视频网站

當前位置:首頁 > 嵌入式 > 嵌入式軟件
[導讀] 在μC/OS-II中,有多種方法可以保護任務之間的共享數(shù)據(jù)和提供任務之間的通訊。在前面的章節(jié)中,已經講到了其中的兩種:一是利用宏OS_ENTER_CRITICAL()和OS_EXIT_CRITICA

 在μC/OS-II中,有多種方法可以保護任務之間的共享數(shù)據(jù)和提供任務之間的通訊。在前面的章節(jié)中,已經講到了其中的兩種:

一是利用宏OS_ENTER_CRITICAL()和OS_EXIT_CRITICAL()來關閉中斷和打開中斷。當兩個任務或者一個任務和一個中斷服務子程序共享某些數(shù)據(jù)時,可以采用這種方法,詳見3.00節(jié)臨界段、8.03.02節(jié) OS_ENTER_CRITICAL() 和OS_EXIT_CRITICAL()及9.03.02節(jié)臨界段,OS_CPU.H;

二是利用函數(shù)OSSchedLock()和OSSchekUnlock()對μC/OS-II中的任務調度函數(shù)上鎖和開鎖。用這種方法也可以實現(xiàn)數(shù)據(jù)的共享,詳見3.06節(jié) 給調度器上鎖和開鎖。

本章將介紹另外三種用于數(shù)據(jù)共享和任務通訊的方法:信號量、郵箱和消息隊列。

圖F6.1介紹了任務和中斷服務子程序之間是如何進行通訊的。

一個任務或者中斷服務子程序可以通過事件控制塊ECB(EventCONtrolBlocks)來向另外的任務發(fā)信號[F6.1A(1)]。這里,所有的信號都被看成是事件(Event)。這也說明為什么上面把用于通訊的數(shù)據(jù)結構叫做事件控制塊。一個任務還可以等待另一個任務或中斷服務子程序給它發(fā)送信號[F6.1A(2)]。這里要注意的是,只有任務可以等待事件發(fā)生,中斷服務子程序是不能這樣做的。對于處于等待狀態(tài)的任務,還可以給它指定一個最長等待時間,以此來防止因為等待的事件沒有發(fā)生而無限期地等下去。

多個任務可以同時等待同一個事件的發(fā)生[F6.1B]。在這種情況下,當該事件發(fā)生后,所有等待該事件的任務中,優(yōu)先級最高的任務得到了該事件并進入就緒狀態(tài),準備執(zhí)行。上面講到的事件,可以是信號量、郵箱或者消息隊列等。當事件控制塊是一個信號量時,任務可以等待它,也可以給它發(fā)送消息。

圖6.1事件控制塊的使用

6.1 事件控制塊ECB

μC/OS-II通過uCOS_II.H中定義的OS_EVENT數(shù)據(jù)結構來維護一個事件控制塊的所有信息[程序清單L6.1],也就是本章開篇講到的事件控制塊ECB。該結構中除了包含了事件本身的定義,如用于信號量的計數(shù)器,用于指向郵箱的指針,以及指向消息隊列的指針數(shù)組等,還定義了等待該事件的所有任務的列表。程序清單L6.1是該數(shù)據(jù)結構的定義。

程序清單L6.1ECB數(shù)據(jù)結構

typedefSTruct{

void*OSEventPtr;/*指向消息或者消息隊列的指針*/

INT8UOSEventTbl[OS_EVENT_TBL_SIZE];/*等待任務列表*/

INT16UOSEventCnt;/*計數(shù)器(當事件是信號量時)*/

INT8UOSEventType;/*時間類型*/

INT8UOSEventGrp;/*等待任務所在的組*/

}OS_EVENT;

.OSEventPtr指針,只有在所定義的事件是郵箱或者消息隊列時才使用。 當所定義的事件是郵箱時,它指向一個消息,而當所定義的事件是消息隊列時,它指向一個數(shù)據(jù)結構,詳見6.06節(jié)消息郵箱和6.07節(jié)消息隊列。

.OSEventTbl[] 和 .OSEventGrp 很像前面講到的OSRdyTbl[]和OSRdyGrp,只不過前兩者包含的是等待某事件的任務,而后兩者包含的是系統(tǒng)中處于就緒狀態(tài)的任務。(見3.04節(jié)就緒表)

.OSEventCnt 當事件是一個信號量時,.OSEventCnt是用于信號量的計數(shù)器,(見6.05節(jié)信號量)。

.OSEventType 定義了事件的具體類型。它可以是信號量(OS_EVENT_SEM)、郵箱

(OS_EVENT_TYPE_MBOX)或消息隊列(OS_EVENT_TYPE_Q)中的一種。用戶要根據(jù)該域的具體值

來調用相應的系統(tǒng)函數(shù),以保證對其進行的操作的正確性。

每個等待事件發(fā)生的任務都被加入到該事件事件控制塊中的等待任務列表中,該列表包括.OSEventGrp和.OSEventTbl[]兩個域。變量前面的[.]說明該變量是數(shù)據(jù)結構的一個域。在這

里,所有的任務的優(yōu)先級被分成8組(每組8個優(yōu)先級),分別對應.OSEventGrp中的8位。當

某組中有任務處于等待該事件的狀態(tài)時,.OSEventGrp中對應的位就被置位。相應地,該任務

在.OSEventTbl[]中的對應位也被置位。.OSEventTbl[]數(shù)組的大小由系統(tǒng)中任務的最低優(yōu)先級

決定,這個值由uCOS_II.H中的OS_LOWEST_PRIO常數(shù)定義。這樣,在任務優(yōu)先級比較少的情況

下,減少μC/OS-II對系統(tǒng)RAM的占用量。

當一個事件發(fā)生后,該事件的等待事件列表中優(yōu)先級最高的任務,也即在.OSEventTbl[]中,所有被置1的位中,優(yōu)先級代碼最小的任務得到該事件。圖F6.2給出

了.OSEventGrp和.OSEventTbl[]之間的對應關系。該關系可以描述為:

當.OSEventTbl[0]中的任何一位為1時,.OSEventGrp中的第0位為1。

當.OSEventTbl[1]中的任何一位為1時,.OSEventGrp中的第1位為1。

當.OSEventTbl[2]中的任何一位為1時,.OSEventGrp中的第2位為1。

當.OSEventTbl[3]中的任何一位為1時,.OSEventGrp中的第3位為1。

當.OSEventTbl[4]中的任何一位為1時,.OSEventGrp中的第4位為1。

當.OSEventTbl[5]中的任何一位為1時,.OSEventGrp中的第5位為1。

當.OSEventTbl[6]中的任何一位為1時,.OSEventGrp中的第6位為1。

當.OSEventTbl[7]中的任何一位為1時,.OSEventGrp中的第7位為1。

圖F6.2事件的等待任務列表

下面的代碼將一個任務放到事件的等待任務列表中。

程序清單L6.2——將一個任務插入到事件的等待任務列表中

pevent->OSEventGrp|=OSMapTbl[prio>>3];

pevent->OSEventTbl[prio>>3]|=OSMapTbl[prio&0x07];

其中,prio是任務的優(yōu)先級,pevent是指向事件控制塊的指針。

從程序清單L6.2可以看出,插入一個任務到等待任務列表中所花的時間是相同的,和表中現(xiàn)有多少個任務無關。從圖F6.2中可以看出該算法的原理:任務優(yōu)先級的最低3位決定了該任務在相應的.OSEventTbl[]中的位置,緊接著的3位則決定了該任務優(yōu)先級在.OSEventTbl[]中的字節(jié)索引。該算法中用到的查找表OSMapTbl[](定義在OS_CORE.C中)一般在ROM中實現(xiàn)。[!--empirenews.page--]

表T6.1OSMapTbl[]

從等待任務列表中刪除一個任務的算法則正好相反,如程序清單L6.3所示。

程序清單L6.3從等待任務列表中刪除一個任務

if((pevent->OSEventTbl[prio>>3]&=~OSMapTbl[prio&0x07])==0){

pevent->OSEventGrp&=~OSMapTbl[prio>>3];

}

該代碼清除了任務在.OSEventTbl[]中的相應位,并且,如果其所在的組中不再有處于等待該事件的任務時(即.OSEventTbl[prio>>3]為0),將.OSEventGrp中的相應位也清除了。和上面的由任務優(yōu)先級確定該任務在等待表中的位置的算法類似,從等待任務列表中查找處于等待狀態(tài)的最高優(yōu)先級任務的算法,也不是從.OSEventTbl[0]開始逐個查詢,而是采用了查找另一個表OSUnMapTbl[256](見文件OS_CORE.C)。這里,用于索引的8位分別代表對應的8組中有任務處于等待狀態(tài),其中的最低位具有最高的優(yōu)先級。用這個值索引,首先得到最高優(yōu)先級任務所在的組的位置(0~7之間的一個數(shù))。然后利用.OSEventTbl[]中對應字節(jié)再在OSUnMapTbl[]中查找,就可以得到最高優(yōu)先級任務在組中的位置(也是0~7之間的一個數(shù))。

這樣,最終就可以得到處于等待該事件狀態(tài)的最高優(yōu)先級任務了。程序清單L6.4是該算法的具體實現(xiàn)代碼。

程序清單L6.4在等待任務列表中查找最高優(yōu)先級的任務

y=OSUnMapTbl[pevent->OSEventGrp];

x=OSUnMapTbl[pevent->OSEventTbl[y]];

prio=(y<<3)+x;

舉例來說,如果.OSEventGrp的值是01101000(二進制),而對應的OSUnMapTbl[.OSEventGrp]值為3,說明最高優(yōu)先級任務所在的組是3。類似地,如果.OSEventTbl[3]的值是11100100(二進制),OSUnMapTbl[.OSEventTbl[3]]的值為2,則處于等待狀態(tài)的任務的最高優(yōu)先級是3×8+2=26。

在μC/OS-II中,事件控制塊的總數(shù)由用戶所需要的信號量、郵箱和消息隊列的總數(shù)決定。

該值由OS_CFG.H中的#defineOS_MAX_EVENTS定義。 在調用OSInit()時 (見3.11節(jié), μC/OS-II

的初始化),所有事件控制塊被鏈接成一個單向鏈表——空閑事件控制塊鏈表(圖F6.3)。每

當建立一個信號量、郵箱或者消息隊列時,就從該鏈表中取出一個空閑事件控制塊,并對它進

行初始化。因為信號量、郵箱和消息隊列一旦建立就不能刪除,所以事件控制塊也不能放回到

空閑事件控制塊鏈表中。

圖F6.3空閑事件控制塊鏈表——Figure6.3

對于事件控制塊進行的一些通用操作包括:

y 初始化一個事件控制塊

y 使一個任務進入就緒態(tài)

y 使一個任務進入等待該事件的狀態(tài)

y 因為等待超時而使一個任務進入就緒態(tài)

為了避免代碼重復和減短程代碼長度,μC/OS-II將上面的操作用4個系統(tǒng)函數(shù)實現(xiàn),它們是:OSEventWaitListInit(),OSEventTaskRdy(),OSEventWait()和OSEventTO()。

6.2 初始化一個事件控制塊,OSEventWaitListInit()

程序清單L6.5是函數(shù)OSEventWaitListInit()的源代碼。當建立一個信號量、郵箱或者消息隊列時,相應的建立函數(shù)OSSemInit(),OSMboxCreate(),或者OSQCreate()通過調用

OSEventWaitListInit()對事件控制塊中的等待任務列表進行初始化。該函數(shù)初始化一個空的等

待任務列表,其中沒有任何任務。該函數(shù)的調用參數(shù)只有一個,就是指向需要初始化的事件控

制塊的指針pevent。

程序清單L6.5初始化ECB塊的等待任務列表

voidOSEventWaitListInit(OS_EVENT*pevent)

{

INT8Ui;

pevent->OSEventGrp=0x00;

for(i=0;i

pevent->OSEventTbl[i]=0x00;

}

}

6.3 使一個任務進入就緒態(tài),OSEventTaskRdy()

程序清單L6.6是函數(shù)OSEventTaskRdy()的源代碼。當發(fā)生了某個事件,該事件等待任務列表中的最高優(yōu)先級任務(HighestPriorityTask–HPT)要置于就緒態(tài)時,該事件對應的OSSemPost(),OSMboxPost(),OSQPost(),和OSQPostFront()函數(shù)調用OSEventTaskRdy()實現(xiàn)該操作。換句話說,該函數(shù)從等待任務隊列中刪除HPT任務(HighestPriorityTask),并把該任務置于就緒態(tài)。圖F6.4給出了OSEventTaskRdy()函數(shù)最開始的4個動作。

該函數(shù)首先計算HPT任務在.OSEventTbl[]中的字節(jié)索引[L6.6/F6.4(1)],其結果是一個從0到OS_LOWEST_PRIO/8+1之間的數(shù),并利用該索引得到該優(yōu)先級任務在.OSEventGrp中的位屏蔽碼[L6.6/F6.4(2)](從表T6.1可以得到該值)。然后,OSEventTaskRdy()函數(shù)判斷HPT任務在.OSEventTbl[]中相應位的位置[L6.6/F6.4(3)], 其結果是一個從0到OS_LOWEST_PRIO/8+1

之間的數(shù),以及相應的位屏蔽碼[L6.6/F6.4(4)]。根據(jù)以上結果,OSEventTaskRdy()函數(shù)計算

出HPT任務的優(yōu)先級[L6.6(5)],然后就可以從等待任務列表中刪除該任務了[L6.6(6)]。

任務的任務控制塊中包含有需要改變的信息。知道了HPT任務的優(yōu)先級,就可以得到指向該任務的任務控制塊的指針[L6.6(7)]。因為最高優(yōu)先級任務運行條件已經得到滿足,必須停止OSTimeTick()函數(shù)對.OSTCBDly域的遞減操作,所以OSEventTaskRdy()直接將該域清澈0[L6.6(8)]。因為該任務不再等待該事件的發(fā)生,所以OSEventTaskRdy()函數(shù)將其任務控制塊中指向事件控制塊的指針指向NULL[L6.6(9)]。如果OSEventTaskRdy()是由OSMboxPost()或者OSQPost()調用的,該函數(shù)還要將相應的消息傳遞給HPT,放在它的任務控制塊中[L6.6(10)]。

另外,當OSEventTaskRdy()被調用時,位屏蔽碼msk作為參數(shù)傳遞給它。該參數(shù)是用于對任務控制塊中的位清零的位屏蔽碼,和所發(fā)生事件的類型相對應[L6.6(11)]。最后,根據(jù).OSTCBStat判斷該任務是否已處于就緒狀態(tài)[L6.6(12)]。如果是,則將HPT插入到μC/OS-II的就緒任務列表中[L6.6(13)]。注意,HPT任務得到該事件后不一定進入就緒狀態(tài),也許該任務已經由于其它[!--empirenews.page--]

原因掛起了。[見4.07節(jié),掛起一個任務,OSTaskSuspend(),和4.08節(jié),恢復一個任務,

OSTaskResume()]。

另外,.OSEventTaskRdy()函數(shù)要在中斷禁止的情況下調用。

程序清單L6.6使一個任務進入就緒狀態(tài)

voidOSEventTaskRdy(OS_EVENT*pevent,void*msg,INT8Umsk)

{

OS_TCB*ptcb;

INT8Ux;

INT8Uy;

INT8Ubitx;

INT8Ubity;

INT8Uprio;

y=OSUnMapTbl[pevent->OSEventGrp];(1)

bity=OSMapTbl[y];(2)

x=OSUnMapTbl[pevent->OSEventTbl[y]];(3)

bitx=OSMapTbl[x];(4)

prio=(INT8U)((y<<3)+x);(5)

if((pevent->OSEventTbl[y]&=~bitx)==0){(6)

pevent->OSEventGrp&=~bity;

}

ptcb=OSTCBPrioTbl[prio];(7)

ptcb->OSTCBDly=0;(8)

ptcb->OSTCBEventPtr=(OS_EVENT*)0;(9)

#if(OS_Q_EN&&(OS_MAX_QS>=2))||OS_MBOX_EN

ptcb->OSTCBMsg=msg;(10)

#else

msg=msg;

#endif

ptcb->OSTCBStat&=~msk;(11)

if(ptcb->OSTCBStat==OS_STAT_RDY){(12)

OSRdyGrp|=bity;(13)

OSRdyTbl[y]|=bitx;

}

}

圖F6.4使一個任務進入就緒狀態(tài)——Figure6.4

6.4 使一個任務進入等待某事件發(fā)生狀態(tài),OSEventTaskWait()

程序清單L6.7是OSEventTaskWait()函數(shù)的源代碼。當某個任務要等待一個事件的發(fā)生時,相應事件的OSSemPend(),OSMboxPend()或者OSQPend()函數(shù)會調用該函數(shù)將當前任務從就緒任務表中刪除,并放到相應事件的事件控制塊的等待任務表中。

程序清單L6.7使一個任務進入等待狀態(tài)

voidOSEventTaskWait(OS_EVENT*pevent)

{

OSTCBCur->OSTCBEventPtr=pevent;(1)

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

OSRdyGrp&=~OSTCBCur->OSTCBBitY;

}

pevent->OSEventTbl[OSTCBCur->OSTCBY]|=OSTCBCur->OSTCBBitX;(3)

pevent->OSEventGrp|=OSTCBCur->OSTCBBitY;

}

在該函數(shù)中,首先將指向事件控制塊的指針放到任務的任務控制塊中[L6.7(1)],接著將任務從就緒任務表中刪除[L6.7(2)],并把該任務放到事件控制塊的等待任務表中[L6.7(3)]。

6.5 由于等待超時而將任務置為就緒態(tài),OSEventTO()

程序清單L6.8是OSEventTO()函數(shù)的源代碼。當在預先指定的時間內任務等待的事件沒有發(fā)生時,OSTimeTick()函數(shù)會因為等待超時而將任務的狀態(tài)置為就緒。在這種情況下,事件的OSSemPend(),OSMboxPend()或者OSQPend()函數(shù)會調用OSEventTO()來完成這項工作。該函數(shù)負責從事件控制塊中的等待任務列表里將任務刪除[L6.8(1)],并把它置成就緒狀態(tài)[L6.8(2)]。最后,從任務控制塊中將指向事件控制塊的指針刪除[L6.8(3)]。用戶應當注意,調用OSEventTO()也應當先關中斷。

程序清單L6.8因為等待超時將任務置為就緒狀態(tài)

voidOSEventTO(OS_EVENT*pevent)

{

if((pevent->OSEventTbl[OSTCBCur->OSTCBY]&=~OSTCBCur->OSTCBBitX) ==0)

{ (1)

pevent->OSEventGrp&=~OSTCBCur->OSTCBBitY;

}

OSTCBCur->OSTCBStat=OS_STAT_RDY;(2)

OSTCBCur->OSTCBEventPtr=(OS_EVENT*)0;(3)

}

6.6 信號量

μC/OS-II中的信號量由兩部分組成:一個是信號量的計數(shù)值,它是一個16位的無符號整數(shù) (0到65,535之間) ; 另一個是由等待該信號量的任務組成的等待任務表。 用戶要在OS_CFG.H中將OS_SEM_EN開關量常數(shù)置成1,這樣μC/OS-II才能支持信號量。

在使用一個信號量之前, 首先要建立該信號量, 也即調用OSSemCreate()函數(shù)(見下一節(jié)) ,

對信號量的初始計數(shù)值賦值。該初始值為0到65,535之間的一個數(shù)。如果信號量是用來表示一個或者多個事件的發(fā)生, 那么該信號量的初始值應設為0。 如果信號量是用于對共享資源的訪問,那么該信號量的初始值應設為1(例如,把它當作二值信號量使用)。最后,如果該信號量是用來表示允許任務訪問n個相同的資源,那么該初始值顯然應該是n,并把該信號量作為一個可計數(shù)的信號量使用。

μC/OS-II提供了5個對信號量進行操作的函數(shù)。它們是:OSSemCreate(),OSSemPend(),

OSSemPost(),OSSemAccept()和OSSemQuery()函數(shù)。圖F6.5說明了任務、中斷服務子程序和

信號量之間的關系。圖中用鑰匙或者旗幟的符號來表示信號量:如果信號量用于對共享資源的

訪問,那么信號量就用鑰匙符號。符號旁邊的數(shù)字N代表可用資源數(shù)。對于二值信號量,該值

就是1;如果信號量用于表示某事件的發(fā)生,那么就用旗幟符號。這時的數(shù)字N代表事件已經發(fā)

生的次數(shù)。從圖F6.5中可以看出OSSemPost()函數(shù)可以由任務或者中斷服務子程序調用,而

OSSemPend()和OSSemQuery()函數(shù)只能有任務程序調用。

圖F6.5任務、中斷服務子程序和信號量之間的關系——Figure6.5

6.6.1 建立一個信號量,OSSemCreate()

程序清單L6.9是OSSemCreate()函數(shù)的源代碼。首先,它從空閑任務控制塊鏈表中得到一個事件控制塊[L6.9(1)],并對空閑事件控制鏈表的指針進行適當?shù)恼{整,使它指向下一個空閑的事件控制塊[L6.9(2)]。如果這時有任務控制塊可用[L6.9(3)],就將該任務控制塊的事件類型設置成信號量OS_EVENT_TYPE_SEM[L6.9(4)]。其它的信號量操作函數(shù)OSSem???()通過檢查該域來保證所操作的任務控制塊類型的正確。例如,這可以防止調用OSSemPost()函數(shù)對一個用作郵箱的任務控制塊進行操作[6.06節(jié),郵箱]。接著,用信號量的初始值對任務控制塊進行初始化[L6.9(5)],并調用 OSEventWaitListInit()函數(shù)對事件控制任務控制塊的等待任務列表進行初始化[見6.01節(jié),初始化一個任務控制塊,OSEventWaitListInit()][L6.9(6)]。因為信號量正在被初始化,所以這時沒有任何任務等待該信號量。最后,OSSemCreate()返回給調用函數(shù)一個指向任務控制塊的指針。以后對信號量的所有操作,如OSSemPend(),OSSemPost(),OSSemAccept()和OSSemQuery()都是通過該指針完成的。因此,這個指針實際上就是該信號量的句柄。如果系統(tǒng)中沒有可用的任務控制塊,OSSemCreate()將返回一個NULL指針。[!--empirenews.page--]

值得注意的是,在μC/OS-II中,信號量一旦建立就不能刪除了,因此也就不可能將一個已分配的任務控制塊再放回到空閑ECB鏈表中。如果有任務正在等待某個信號量,或者某任務的運行依賴于某信號量的出現(xiàn)時,刪除該任務是很危險的。

程序清單L6.9建立一個信號量

OS_EVENT*OSSemCreate(INT16Ucnt)

{

OS_EVENT*pevent;

OS_ENTER_CRITICAL();

pevent=OSEventFreeList;(1)

if(OSEventFreeList!=(OS_EVENT*)0){(2)

OSEventFreeList=(OS_EVENT*)OSEventFreeList->OSEventPtr;

}

OS_EXIT_CRITICAL();

if(pevent!=(OS_EVENT*)0){(3)

pevent->OSEventType=OS_EVENT_TYPE_SEM;(4)

pevent->OSEventCnt=cnt;(5)

OSEventWaitListInit(pevent);(6)

}

return(pevent);(7)

}

6.6.2 等待一個信號量,OSSemPend()

程序清單L6.10是OSSemPend()函數(shù)的源代碼。它首先檢查指針pevent所指的任務控制塊是否是由OSSemCreate()建立的[L6.10(1)]。如果信號量當前是可用的(信號量的計數(shù)值大于0)[L6.10(2)],將信號量的計數(shù)值減1[L6.10(3)],然后函數(shù)將“無錯”錯誤代碼返回給它的調用函數(shù)。顯然,如果正在等待信號量,這時的輸出正是我們所希望的,也是運行OSSemPend()函數(shù)最快的路徑。

如果此時信號量無效(計數(shù)器的值是0),OSSemPend()函數(shù)要進一步檢查它的調用函數(shù)是不是中斷服務子程序[L6.10(4)]。在正常情況下,中斷服務子程序是不會調用OSSemPend()函數(shù)的。這里加入這些代碼,只是為了以防萬一。當然,在信號量有效的情況下,即使是中斷服務

子程序調用的OSSemPend(),函數(shù)也會成功返回,不會出任何錯誤。

如果信號量的計數(shù)值為0,而OSSemPend()函數(shù)又不是由中斷服務子程序調用的,則調

用OSSemPend()函數(shù)的任務要進入睡眠狀態(tài),等待另一個任務(或者中斷服務子程序)發(fā)出該信

號量(見下節(jié))。OSSemPend()允許用戶定義一個最長等待時間作為它的參數(shù),這樣可以避免該

任務無休止地等待下去。如果該參數(shù)值是一個大于0的值,那么該任務將一直等到信號有效或

者等待超時。如果該參數(shù)值為0,該任務將一直等待下去。OSSemPend()函數(shù)通過將任務控制塊

中的狀態(tài)標志.OSTCBStat置1,把任務置于睡眠狀態(tài)[L6.10(5)],等待時間也同時置入任務控

制塊中[L6.10(6)],該值在OSTimeTick()函數(shù)中被逐次遞減。注意,OSTimeTick()函數(shù)對每個

任務的任務控制塊的.OSTCBDly域做遞減操作(只要該域不為0)[見3.10節(jié),時鐘節(jié)拍]。真

正將任務置入睡眠狀態(tài)的操作在OSEventTaskWait()函數(shù)中執(zhí)行[見6.03節(jié),讓一個任務等待

某個事件,OSEventTaskWait()][L6.10(7)]。

因為當前任務已經不是就緒態(tài)了,所以任務調度函數(shù)將下一個最高優(yōu)先級的任務調入,準備運行[L6.10(8)]。當信號量有效或者等待時間到后,調用OSSemPend()函數(shù)的任務將再一次成為最高優(yōu)先級任務。這時OSSched()函數(shù)返回。這之后,OSSemPend()要檢查任務控制塊中的狀態(tài)標志,看該任務是否仍處于等待信號量的狀態(tài)[L6.10(9)]。如果是,說明該任務還沒有被OSSemPost()函數(shù)發(fā)出的信號量喚醒。事實上,該任務是因為等待超時而由TimeTick()函數(shù)把它置為就緒狀態(tài)的。這種情況下,OSSemPend()函數(shù)調用 OSEventTO()函數(shù)將任務從等待任務列表中刪除[L6.10(10)],并返回給它的調用任務一個“超時”的錯誤代碼。如果任務的任務控制塊中的OS_STAT_SEM標志位沒有置位,就認為調用 OSSemPend()的任務已經得到了該信號量,將指向信號量ECB的指針從該任務的任務控制塊中刪除,并返回給調用函數(shù)一個“無錯”的錯誤代碼[L6.10(11)]。

程序清單L6.10等待一個信號量

voidOSSemPend(OS_EVENT*pevent,INT16Utimeout,INT8U*err)

{

OS_ENTER_CRITICAL();

if(pevent->OSEventType!=OS_EVENT_TYPE_SEM){(1)

OS_EXIT_CRITICAL();

*err=OS_ERR_EVENT_TYPE;

}

if(pevent->OSEventCnt>0){(2)

pevent->OSEventCnt--;(3)

OS_EXIT_CRITICAL();

*err=OS_NO_ERR;

}elseif(OSIntNesting>0){(4)

OS_EXIT_CRITICAL();

*err=OS_ERR_PEND_ISR;

}else{

OSTCBCur->OSTCBStat|=OS_STAT_SEM;(5)

OSTCBCur->OSTCBDly=timeout;(6)

OSEventTaskWait(pevent);(7)

OS_EXIT_CRITICAL();

OSSched();(8)

OS_ENTER_CRITICAL();

if(OSTCBCur->OSTCBStat&OS_STAT_SEM){(9)

OSEventTO(pevent);(10)

OS_EXIT_CRITICAL();

*err=OS_TIMEOUT;

}else{

OSTCBCur->OSTCBEventPtr=(OS_EVENT*)0;(11)

OS_EXIT_CRITICAL();

*err=OS_NO_ERR;

}

}

}

6.6.3 發(fā)送一個信號量,OSSemPost()

程序清單L6.11是OSSemPost()函數(shù)的源代碼。它首先檢查參數(shù)指針pevent指向的任務控制塊是否是OSSemCreate()函數(shù)建立的[L6.11(1)],接著檢查是否有任務在等待該信號量[L6.11(2)]。如果該任務控制塊中的.OSEventGrp域不是0,說明有任務正在等待該信號量。這時,就要調用函數(shù)OSEventTaskRdy()[見6.02節(jié),使一個任務進入就緒狀態(tài),OSEventTaskRdy()],把其中的最高優(yōu)先級任務從等待任務列表中刪除[L6.11(3)]并使它進入就緒狀態(tài)。然后,調用OSSched()任務調度函數(shù)檢查該任務是否是系統(tǒng)中的最高優(yōu)先級的就緒任務[L6.11(4)]。如果是,這時就要進行任務切換[當OSSemPost()函數(shù)是在任務中調用的],準備執(zhí)行該就緒任務。如果不是,OSSched()直接返回,調用 OSSemPost()的任務得以繼續(xù)執(zhí)行。如果這時沒有任務在等待該信號量,該信號量的計數(shù)值就簡單地加1[L6.11(5)]。

上面是由任務調用OSSemPost()時的情況。當中斷服務子程序調用該函數(shù)時,不會發(fā)生上面的任務切換。如果需要,任務切換要等到中斷嵌套的最外層中斷服務子程序調用OSIntExit()函數(shù)后才能進行(見3.09節(jié),μC/OS-II中的中斷)。[!--empirenews.page--]

程序清單L6.11發(fā)出一個信號量

INT8UOSSemPost(OS_EVENT*pevent)

{

OS_ENTER_CRITICAL();

if(pevent->OSEventType!=OS_EVENT_TYPE_SEM){(1)

OS_EXIT_CRITICAL();

return(OS_ERR_EVENT_TYPE);

}

if(pevent->OSEventGrp){(2)

OSEventTaskRdy(pevent,(void*)0,OS_STAT_SEM);(3)

OS_EXIT_CRITICAL();

OSSched();(4)

return(OS_NO_ERR);

}else{

if(pevent->OSEventCnt<65535){

pevent->OSEventCnt++;(5)

OS_EXIT_CRITICAL();

return(OS_NO_ERR);

}else{

OS_EXIT_CRITICAL();

return(OS_SEM_OVF);

}

}

}

6.6.4 無等待地請求一個信號量,OSSemAccept()

當一個任務請求一個信號量時,如果該信號量暫時無效,也可以讓該任務簡單地返回,而不是進入睡眠等待狀態(tài)。這種情況下的操作是由OSSemAccept()函數(shù)完成的,其源代碼見程序清單L6.12。該函數(shù)在最開始也是檢查參數(shù)指針pevent指向的事件控制塊是否是由OSSemCreate()函數(shù)建立的[L6.12(1)],接著從該信號量的事件控制塊中取出當前計數(shù)值[L6.12(2)],并檢查該信號量是否有效(計數(shù)值是否為非0值)[L6.12(3)]。如果有效,則將信號量的計數(shù)值減1[L6.12(4)],然后將信號量的原有計數(shù)值返回給調用函數(shù)[L6.12(5)]。調用函數(shù)需要對該返回值進行檢查。如果該值是0,說明該信號量無效。如果該值大于0,說明該信號量有效,同時該值也暗示著該信號量當前可用的資源數(shù)。應該注意的是,這些可用資源中,已經被該調用函數(shù)自身占用了一個(該計數(shù)值已經被減1)。中斷服務子程序要請求信號量時,只能用OSSemAccept()而不能用OSSemPend(),因為中斷服務子程序是不允許等待的。

程序清單L6.12無等待地請求一個信號量

INT16UOSSemAccept(OS_EVENT*pevent)

{

INT16Ucnt;

OS_ENTER_CRITICAL();

if(pevent->OSEventType!=OS_EVENT_TYPE_SEM){(1)

OS_EXIT_CRITICAL();

return(0);

}

cnt=pevent->OSEventCnt;(2)

if(cnt>0){(3)

pevent->OSEventCnt--;(4)

}

OS_EXIT_CRITICAL();

return(cnt);(5)

}

6.6.5 查詢一個信號量的當前狀態(tài),OSSemQuery()

在應用程序中,用戶隨時可以調用函數(shù)OSSemQuery()[程序清單L6.13]來查詢一個信號量的當前狀態(tài)。該函數(shù)有兩個參數(shù):一個是指向信號量對應事件控制塊的指針pevent。該指針是在生產信號量時,由OSSemCreate()函數(shù)返回的;另一個是指向用于記錄信號量信息的數(shù)據(jù)結構OS_SEM_DATA(見uCOS_II.H)的指針pdata。因此,調用該函數(shù)前,用戶必須先定義該結構變量,用于存儲信號量的有關信息。在這里,之所以使用一個新的數(shù)據(jù)結構的原因在于,調用函數(shù)應該只關心那些和特定信號量有關的信息, 而不是象OS_EVENT數(shù)據(jù)結構包含的很全面的信息。

該數(shù)據(jù)結構只包含信號量計數(shù)值.OSCnt和等待任務列表.OSEventTbl[]、.OSEventGrp,而OS_EVENT中還包含了另外的兩個域.OSEventType和.OSEventPtr。

和其它與信號量有關的函數(shù)一樣,OSSemQuery()也是先檢查pevent指向的事件控制塊是否是OSSemCreate()產生的[L6.13(1)],然后將等待任務列表[L6.13(2)]和計數(shù)值[L6.13(3)]

從OS_EVENT結構拷貝到OS_SEM_DATA結構變量中去。

程序清單L6.13查詢一個信號量的狀態(tài)

INT8UOSSemQuery(OS_EVENT*pevent,OS_SEM_DATA*pdata)

{

INT8Ui;

INT8U*psrc;

INT8U*pdest;

OS_ENTER_CRITICAL();

if(pevent->OSEventType!=OS_EVENT_TYPE_SEM){(1)

OS_EXIT_CRITICAL();

return(OS_ERR_EVENT_TYPE);

}

pdata->OSEventGrp=pevent->OSEventGrp;(2)

psrc=&pevent->OSEventTbl[0];

pdest=&pdata->OSEventTbl[0];

for(i=0;i

*pdest++=*psrc++;

}

pdata->OSCnt=pevent->OSEventCnt;(3)

OS_EXIT_CRITICAL();

return(OS_NO_ERR);

}

6.7 郵箱

郵箱是μC/OS-II中另一種通訊機制, 它可以使一個任務或者中斷服務子程序向另一個任務發(fā)送一個指針型的變量。該指針指向一個包含了特定“消息”的數(shù)據(jù)結構。為了在μC/OS-II中使用郵箱,必須將OS_CFG.H中的OS_MBOX_EN常數(shù)置為1。

使用郵箱之前,必須先建立該郵箱。該操作可以通過調用OSMboxCreate()函數(shù)來完成(見下節(jié)),并且要指定指針的初始值。一般情況下,這個初始值是NULL,但也可以初始化一個郵

箱,使其在最開始就包含一條消息。如果使用郵箱的目的是用來通知一個事件的發(fā)生(發(fā)送一

條消息),那么就要初始化該郵箱為NULL,因為在開始時,事件還沒有發(fā)生。如果用戶用郵箱

來共享某些資源,那么就要初始化該郵箱為一個非NULL的指針。在這種情況下,郵箱被當成一

個二值信號量使用。

μC/OS-II提供了5種對郵箱的操作:OSMboxCreate(),OSMboxPend(),OSMboxPost(),

OSMboxAccept()和 OSMboxQuery()函數(shù)。圖F6.6描述了任務、中斷服務子程序和郵箱之間的關

系,這里用符號“I”表示郵箱。郵箱包含的內容是一個指向一條消息的指針。一個郵箱只能包

含一個這樣的指針(郵箱為滿時),或者一個指向NULL的指針(郵箱為空時)。從圖F6.6可以看出,任務或者中斷服務子程序可以調用函數(shù)OSMboxPost(),但是只有任務可以調用函數(shù)OSMboxPend()和OSMboxQuery()。

圖F6.6任務、中斷服務子程序和郵箱之間的關系

6.7.1 建立一個郵箱,OSMboxCreate()

程序清單L6.14是OSMboxCreate()函數(shù)的源代碼,基本上和函數(shù)OSSemCreate()相似。不同之處在于事件控制塊的類型被設置成OS_EVENT_TYPE_MBOX[L6.14(1)], 以及使用.OSEventPtr域來容納消息指針,而不是使用.OSEventCnt域[L6.14(2)]。[!--empirenews.page--]

OSMboxCreate()函數(shù)的返回值是一個指向事件控制塊的指針[L6.14(3)]。這個指針在調用函數(shù)OSMboxPend(),OSMboxPost(),OSMboxAccept()和OSMboxQuery()時使用。因此,該指針可以看作是對應郵箱的句柄。值得注意的是,如果系統(tǒng)中已經沒有事件控制塊可用,函數(shù)OSMboxCreate()將返回一個NULL指針。

郵箱一旦建立,是不能被刪除的。比如,如果有任務正在等待一個郵箱的信息,這時刪除該郵箱,將有可能產生災難性的后果。

程序清單L6.14建立一個郵箱

OS_EVENT*OSMboxCreate(void*msg)

{

OS_EVENT*pevent;

OS_ENTER_CRITICAL();

pevent=OSEventFreeList;

if(OSEventFreeList!=(OS_EVENT*)0){

OSEventFreeList=(OS_EVENT*)OSEventFreeList->OSEventPtr;

}

OS_EXIT_CRITICAL();

if(pevent!=(OS_EVENT*)0){

pevent->OSEventType=OS_EVENT_TYPE_MBOX;(1)

pevent->OSEventPtr=msg;(2)

OSEventWaitListInit(pevent);

}

return(pevent);(3)

}

6.7.2 等待一個郵箱中的消息,OSMboxPend()

程序清單L6.15是OSMboxPend()函數(shù)的源代碼。 同樣, 它和OSSemPend()也很相似,因此,在這里只講述其中的不同之處。OSMboxPend()首先檢查該事件控制塊是由 OSMboxCreate()函數(shù)建立的[L6.15(1)]。當.OSEventPtr域是一個非NULL的指針時,說明該郵箱中有可用的消息[L6.15(2)]。這種情況下,OSMboxPend()函數(shù)將該域的值復制到局部變量msg中,然后將.OSEventPtr置為NULL[L6.15(3)]。這正是我們所期望的,也是執(zhí)行 OSMboxPend()函數(shù)最快的路徑。

如果此時郵箱中沒有消息是可用的(.OSEventPtr域是NULL指針),OSMboxPend()函數(shù)檢查它的調用者是否是中斷服務子程序[L6.15(4)]。象OSSemPend()函數(shù)一樣,不能在中斷服務子程序中調用OSMboxPend(),因為中斷服務子程序是不能等待的。這里的代碼同樣是為了以防萬一。但是,如果郵箱中有可用的消息,即使從中斷服務子程序中調用OSMboxPend()函數(shù),也一樣是成功的。

如果郵箱中沒有可用的消息,OSMboxPend()的調用任務就被掛起,直到郵箱中有了消息或者等待超時[L6.15(5)]。當有其它的任務向該郵箱發(fā)送了消息后(或者等待時間超時),這時,該任務再一次成為最高優(yōu)先級任務,OSSched()返回。這時,OSMboxPend()函數(shù)要檢查是否有消息被放到該任務的任務控制塊中[L6.15(6)]。如果有,那么該次函數(shù)調用成功,對應的消息被返回到調用函數(shù)。

程序清單L6.15等待一個郵箱中的消息

void*OSMboxPend(OS_EVENT*pevent,INT16Utimeout,INT8U*err)

{

void*msg;

OS_ENTER_CRITICAL();

if(pevent->OSEventType!=OS_EVENT_TYPE_MBOX){(1)

OS_EXIT_CRITICAL();

*err=OS_ERR_EVENT_TYPE;

return((void*)0);

}

msg=pevent->OSEventPtr;

if(msg!=(void*)0){(2)

pevent->OSEventPtr=(void*)0;(3)

OS_EXIT_CRITICAL();

*err=OS_NO_ERR;

}elseif(OSIntNesting>0){(4)

OS_EXIT_CRITICAL();

*err=OS_ERR_PEND_ISR;

}else{

OSTCBCur->OSTCBStat|=OS_STAT_MBOX;(5)

OSTCBCur->OSTCBDly=timeout;

OSEventTaskWait(pevent);

OS_EXIT_CRITICAL();

OSSched();

OS_ENTER_CRITICAL();

if((msg=OSTCBCur->OSTCBMsg)!=(void*)0){(6)

OSTCBCur->OSTCBMsg=(void*)0;

OSTCBCur->OSTCBStat=OS_STAT_RDY;

OSTCBCur->OSTCBEventPtr=(OS_EVENT*)0;

OS_EXIT_CRITICAL();

*err=OS_NO_ERR;

}elseif(OSTCBCur->OSTCBStat&OS_STAT_MBOX){(7)

OSEventTO(pevent);(8)

OS_EXIT_CRITICAL();

msg=(void*)0;(9)

*err=OS_TIMEOUT;

}else{

msg=pevent->OSEventPtr;(10)

pevent->OSEventPtr=(void*)0;

(11)

OSTCBCur->OSTCBEventPtr=(OS_EVENT*)0;(12)

OS_EXIT_CRITICAL();

*err=OS_NO_ERR;

}

}

return(msg);

}

在OSMboxPend()函數(shù)中,通過檢查任務控制塊中的.OSTCBStat域中的OS_STAT_MBOX位,可以知道是否等待超時。如果該域被置1,說明任務等待已經超時[L6.15(7)]。這時,通過調用函數(shù)OSEventTo()可以將任務從郵箱的等待列表中刪除[L6.15(8)]。因為此時郵箱中沒有消息,所以返回的指針是NULL[L6.15(9)]。如果OS_STAT_MBOX位沒有被置1,說明所等待的消息已經被發(fā)出。OSMboxPend()的調用函數(shù)得到指向消息的指針[L6.15(10)]。此后,OSMboxPend()函數(shù)通過將郵箱事件控制塊的.OSEventPtr域置為NULL清空該郵箱,并且要將任務任務控制塊中指向郵箱事件控制塊的指針刪除[L6.15(12)]。

6.7.3 發(fā)送一個消息到郵箱中,OSMboxPost()

程序清單L6.16是OSMboxPost()函數(shù)的源代碼。檢查了事件控制塊是否是一個郵箱后[L6.16(1)],OSMboxPost()函數(shù)還要檢查是否有任務在等待該郵箱中的消息[L6.16(2)]。如果事件控制塊中的OSEventGrp域包含非零值,就暗示著有任務在等待該消息。這時,調用OSEventTaskRdy()將其中的最高優(yōu)先級任務從等待列表中刪除[見6.02節(jié),使一個任務進入就緒狀態(tài),OSEventTaskRdy()][L6.16(3)],加入系統(tǒng)的就緒任務列表中,準備運行。然后,調用OSSched()函數(shù)[L6.16(4)],檢查該任務是否是系統(tǒng)中最高優(yōu)先級的就緒任務。如果是,執(zhí)行任

務切換[僅當OSMboxPost()函數(shù)是由任務調用時],該任務得以執(zhí)行。如果該任務不是最高優(yōu)先

級的任務,OSSched()返回,OSMboxPost()的調用函數(shù)繼續(xù)執(zhí)行。如果沒有任何任務等待該消息,

指向消息的指針就被保存到郵箱中[L6.16(6)](假設此時郵箱中的指針不是非NULL的[L6.16(5)])。這樣,下一個調用OSMboxPend()函數(shù)的任務就可以立刻得到該消息了。

注意,如果OSMboxPost()函數(shù)是從中斷服務子程序中調用的,那么,這時并不發(fā)生上下文的切換。如果需要,中斷服務子程序引起的上下文切換只發(fā)生在中斷嵌套的最外層中斷服務子程序對OSIntExit()函數(shù)的調用時(見3.09節(jié),μC/OS-II中的中斷)。[!--empirenews.page--]

程序清單L6.16向郵箱中發(fā)送一條消息

INT8UOSMboxPost(OS_EVENT*pevent,void*msg)

{

OS_ENTER_CRITICAL();

if(pevent->OSEventType!=OS_EVENT_TYPE_MBOX){(1)

OS_EXIT_CRITICAL();

return(OS_ERR_EVENT_TYPE);

}

if(pevent->OSEventGrp){(2)

OSEventTaskRdy(pevent,msg,OS_STAT_MBOX);(3)

OS_EXIT_CRITICAL();

OSSched();(4)

return(OS_NO_ERR);

}else{

if(pevent->OSEventPtr!=(void*)0){(5)

OS_EXIT_CRITICAL();

return(OS_MBOX_FULL);

}else{

pevent->OSEventPtr=msg;(6)

OS_EXIT_CRITICAL();

return(OS_NO_ERR);

}

}

}

6.7.4 無等待地從郵箱中得到一個消息,OSMboxAccept()

應用程序也可以以無等待的方式從郵箱中得到消息。這可以通過程序清單L6.17中的OSMboxAccept()函數(shù)來實現(xiàn)。OSMboxAccept()函數(shù)開始也是檢查事件控制塊是否是由OSMboxCreate()函數(shù)建立的 [L6.17(1)]。接著,它得到郵箱中的當前內容[L6.17(2)],并判斷是否有消息是可用的[L6.17(3)]。如果郵箱中有消息,就把郵箱清空[L6.17(4)],而郵箱中原來指向消息的指針被返回給OSMboxAccept()的調用函數(shù)[L6.17(5)]。OSMboxAccept()函數(shù)的調用函數(shù)必須檢查該返回值是否為NULL。如果該值是NULL,說明郵箱是空的,沒有可用的消息。

如果該值是非NULL值,說明郵箱中有消息可用,而且該調用函數(shù)已經得到了該消息。中斷服務子程序在試圖得到一個消息時, 應該使用OSMboxAccept()函數(shù), 而不能使用OSMboxPend()函數(shù)。

OSMboxAccept()函數(shù)的另一個用途是,用戶可以用它來清空一個郵箱中現(xiàn)有的內容。

程序清單L6.17無等待地從郵箱中得到消息

void*OSMboxAccept(OS_EVENT*pevent)

{

void*msg;

OS_ENTER_CRITICAL();

if(pevent->OSEventType!=OS_EVENT_TYPE_MBOX){(1)

OS_EXIT_CRITICAL();

return((void*)0);

}

msg=pevent->OSEventPtr;(2)

if(msg!=(void*)0){(3)

pevent->OSEventPtr=(void*)0;(4)

}

OS_EXIT_CRITICAL();

return(msg);(5)

}

6.7.5 查詢一個郵箱的狀態(tài),OSMboxQuery()

OSMboxQuery()函數(shù)使應用程序可以隨時查詢一個郵箱的當前狀態(tài)。程序清單L6.18是該函數(shù)的源代碼。它需要兩個參數(shù):一個是指向郵箱的指針pevent。該指針是在建立該郵箱時,由OSMboxCreate()函數(shù)返回的;另一個是指向用來保存有關郵箱的信息的OS_MBOX_DATA(見

uCOS_II.H)數(shù)據(jù)結構的指針pdata。在調用OSMboxCreate()函數(shù)之前,必須先定義該結構變量,

用來保存有關郵箱的信息。之所以定義一個新的數(shù)據(jù)結構,是因為這里關心的只是和特定郵箱

有關的內容,而非整個OS_EVENT數(shù)據(jù)結構的內容。后者還包含了另外兩個域

(.OSEventCnt和.OSEventType),而OS_MBOX_DATA只包含郵箱中的消息指針(.OSMsg)和該郵箱現(xiàn)有的等待任務列表(.OSEventTbl[]和.OSEventGrp)。

和前面的所以函數(shù)一樣,該函數(shù)也是先檢查事件控制是否是郵箱[L6.18(1)]。然后,將郵箱中的等待任務列表[L6.18(2)]和郵箱中的消息[L6.18(3)]從OS_EVENT數(shù)據(jù)結構復制到OS_MBOX_DATA數(shù)據(jù)結構。

程序清單L6.18查詢郵箱的狀態(tài)

INT8UOSMboxQuery(OS_EVENT*pevent,OS_MBOX_DATA*pdata)

{

INT8Ui;

INT8U*psrc;

INT8U*pdest;

OS_ENTER_CRITICAL();

if(pevent->OSEventType!=OS_EVENT_TYPE_MBOX){(1)

OS_EXIT_CRITICAL();

return(OS_ERR_EVENT_TYPE);

}

pdata->OSEventGrp=pevent->OSEventGrp;(2)

psrc=&pevent->OSEventTbl[0];

pdest=&pdata->OSEventTbl[0];

for(i=0;i

*pdest++=*psrc++;

}

pdata->OSMsg=pevent->OSEventPtr;(3)

OS_EXIT_CRITICAL();

return(OS_NO_ERR);

}

6.7.6 用郵箱作二值信號量

一個郵箱可以被用作二值的信號量。 首先, 在初始化時, 將郵箱設置為一個非零的指針(如

void*1)。這樣,一個任務可以調用OSMboxPend()函數(shù)來請求一個信號量,然后通過調用

OSMboxPost()函數(shù)來釋放一個信號量。程序清單L6.19說明了這個過程是如何工作的。如果用

戶只需要二值信號量和郵箱,這樣做可以節(jié)省代碼空間。這時可以將OS_SEM_EN設置為0,只使

用郵箱就可以了。

程序清單L6.19使用郵箱作為二值信號量

OS_EVENT*MboxSem;

voIDTask1(void*pdata)

{

INT8Uerr;

for(;;){

OSMboxPend(MboxSem,0,&err);/*獲得對資源的訪問權*/

.

./*任務獲得信號量,對資源進行訪問*/

.

OSMboxPost(MboxSem,(void*)1);/*釋放對資源的訪問權*/

}

}

6.7.7 用郵箱實現(xiàn)延時,而不使用OSTimeDly()

郵箱的等待超時功能可以被用來模仿OSTimeDly()函數(shù)的延時,如程序清單L6.20所示。

如果在指定的時間段TIMEOUT內,沒有消息到來,Task1()函數(shù)將繼續(xù)執(zhí)行。這和OSTimeDly(TIMEOUT)功能很相似。但是,如果Task2()在指定的時間結束之前,向該郵箱發(fā)送了一個“啞”消息,Task1()就會提前開始繼續(xù)執(zhí)行。這和調用OSTimeDlyResume()函數(shù)的功能是一樣的。注意,這里忽略了對返回的消息的檢查,因為此時關心的不是得到了什么樣的消息。

程序清單L6.20使用郵箱實現(xiàn)延時

OS_EVENT*MboxTimeDly;

voidTask1(void*pdata)

{

INT8Uerr;

for(;;){

OSMboxPend(MboxTimeDly, TIMEOUT,&err);/*延時該任務*/

.

./*延時結束后執(zhí)行的代碼*/

.

}

}

voidTask2(void*pdata)[!--empirenews.page--]

{

INT8Uerr;

for(;;){

OSMboxPost(MboxTimeDly,(void*)1);/*取消任務1的延時*/

.

.

}

}

6.8 消息隊列

消息隊列是μC/OS-II中另一種通訊機制, 它可以使一個任務或者中斷服務子程序向另一個任務發(fā)送以指針方式定義的變量。因具體的應用有所不同,每個指針指向的數(shù)據(jù)結構變量也有所不同。為了使用μC/OS-II的消息隊列功能,需要在OS_CFG.H文件中,將OS_Q_EN常數(shù)設置為1,并且通過常數(shù)OS_MAX_QS來決定μC/OS-II支持的最多消息隊列數(shù)。

在使用一個消息隊列之前, 必須先建立該消息隊列。 這可以通過調用OSQCreate()函數(shù) (見

6.07.01節(jié)),并定義消息隊列中的單元數(shù)(消息數(shù))來完成。

μC/OS-II提供了7個對消息隊列進行操作的函數(shù):OSQCreate(),OSQPend(),OSQPost(),

OSQPostFront(),OSQAccept(),OSQFlush()和OSQQuery()函數(shù)。圖F6.7是任務、中斷服務子程序和消息隊列之間的關系。其中,消息隊列的符號很像多個郵箱。實際上,我們可以將消息隊列看作時多個郵箱組成的數(shù)組,只是它們共用一個等待任務列表。每個指針所指向的數(shù)據(jù)結構是由具體的應用程序決定的。N代表了消息隊列中的總單元數(shù)。當調用OSQPend()或者OSQAccept()之前,調用N次OSQPost()或者OSQPostFront()就會把消息隊列填滿。從圖F6.7中可以看出,一個任務或者中斷服務子程序可以調用OSQPost(),OSQPostFront(),OSQFlush()或者OSQAccept()函數(shù)。但是,只有任務可以調用OSQPend()和OSQQuery()函數(shù)。

圖F6.7任務、中斷服務子程序和消息隊列之間的關系——Figure6.7

圖F6.8是實現(xiàn)消息隊列所需要的各種數(shù)據(jù)結構。這里也需要事件控制塊來記錄等待任務列表[F6.8(1)],而且,事件控制塊可以使多個消息隊列的操作和信號量操作、郵箱操作相同的代碼。當建立了一個消息隊列時,一個隊列控制塊(OS_Q結構,見OS_Q.C文件)也同時被建立,并通過OS_EVENT中的.OSEventPtr域鏈接到對應的事件控制塊[F6.8(2)]。 在建立一個消息隊列之前,必須先定義一個含有與消息隊列最大消息數(shù)相同個數(shù)的指針數(shù)組[F6.8(3)]。數(shù)組的起始地址以及數(shù)組中的元素數(shù)作為參數(shù)傳遞給OSQCreate()函數(shù)。事實上,如果內存占用了連續(xù)的地址空間,也沒有必要非得使用指針數(shù)組結構。

文件OS_CFG.H中的常數(shù)OS_MAX_QS定義了在μC/OS-II中可以使用的最大消息隊列數(shù),這個值最小應為2。μC/OS-II在初始化時建立一個空閑的隊列控制塊鏈表,如圖F6.9所示。

圖F6.8用于消息隊列的數(shù)據(jù)結構——Figure6.8

圖F6.9空閑隊列控制塊鏈表——Figure6.9

隊列控制塊是一個用于維護消息隊列信息的數(shù)據(jù)結構,它包含了以下的一些域。這里,仍然在各個變量前加入一個[.]來表示它們是數(shù)據(jù)結構中的一個域。

.OSQPtr在空閑隊列控制塊中鏈接所有的隊列控制塊。一旦建立了消息隊列,該域就不再有用了。

.OSQStart是指向消息隊列的指針數(shù)組的起始地址的指針。用戶應用程序在使用消息隊列之前必須先定義該數(shù)組。

.OSQEnd是指向消息隊列結束單元的下一個地址的指針。該指針使得消息隊列構成一個循環(huán)的緩沖區(qū)。

.OSQIn 是指向消息隊列中插入下一條消息的位置的指針。當.OSQIn和.OSQEnd相等時,.OSQIn被調整指向消息隊列的起始單元。

.OSQOut 是指向消息隊列中下一個取出消息的位置的指針。當.OSQOut和.OSQEnd相等時,.OSQOut被調整指向消息隊列的起始單元。

.OSQSize 是消息隊列中總的單元數(shù)。該值是在建立消息隊列時由用戶應用程序決定的。在μC/OS-II中,該值最大可以是65,535。

.OSQEntries 是消息隊列中當前的消息數(shù)量。當消息隊列是空的時,該值為0。當消息隊列滿了以后,該值和.OSQSize值一樣。在消息隊列剛剛建立時,該值為0。

消息隊列最根本的部分是一個循環(huán)緩沖區(qū),如圖F6.10。其中的每個單元包含一個指針。

隊列未滿時,.OSQIn[F6.10(1)]指向下一個存放消息的地址單元。如果隊列已滿(.OSQEntries

與.OSQSize相等),.OSQIn[F6.10(3)]則與.OSQOut指向同一單元。如果在.OSQIn指向的單元

插入新的指向消息的指針,就構成 FIFO(First-In-First-Out)隊列。相反,如果在.OSQOut

指向的單元的下一個單元插入新的指針,就構成LIFO隊列(Last-In-First-Out)[F6.10(2)]。

當.OSQEntries和.OSQSize相等時,說明隊列已滿。消息指針總是從.OSQOut[F6.10(4)]指向

的單元取出。指針.OSQStart和.OSQEnd [F6.10(5)]定義了消息指針數(shù)組的頭尾,以便在.OSQIn

和.OSQOut到達隊列的邊緣時,進行邊界檢查和必要的指針調整,實現(xiàn)循環(huán)功能。

圖F6.10消息隊列是一個由指針組成的循環(huán)緩沖區(qū)——Figure6.10

6.8.1 建立一個消息隊列,OSQCreate()

程序清單L6.21是OSQCreate()函數(shù)的源代碼。該函數(shù)需要一個指針數(shù)組來容納指向各個消息的指針。該指針數(shù)組必須聲名為void類型。

OSQCreate()首先從空閑事件控制塊鏈表中取得一個事件控制塊(見圖F6.3)[L6.21(1)],并對剩下的空閑事件控制塊列表的指針做相應的調整,使它指向下一個空閑事件控制塊[L6.21(2)]。 接著, OSQCreate()函數(shù)從空閑隊列控制塊列表中取出一個隊列控制塊[L6.21(3)]。

如果有空閑隊列控制塊是可以的,就對其進行初始化[L6.21(4)]。然后該函數(shù)將事件控制塊的類型設置為OS_EVENT_TYPE_Q[L6.21(5)],并使其.OSEventPtr指針指向隊列控制塊[L6.21(6)]。OSQCreate()還要調用OSEventWaitListInit()函數(shù)對事件控制塊的等待任務列表初始化[見6.01節(jié),初始化一個事件控制塊,OSEventWaitListInit()][L6.21(7)]。因為此時消息隊列正在初始化,顯然它的等待任務列表是空的。最后,OSQCreate()向它的調用函數(shù)返回一個指向事件控制塊的指針[L6.21(9)]。該指針將在調用OSQPend(),OSQPost(),OSQPostFront(),OSQFlush(),OSQAccept()和OSQQuery()等消息隊列處理函數(shù)時使用。因此,該指針可以被看作是對應消息隊列的句柄。值得注意的是,如果此時沒有空閑的事件控制塊,OSQCreate()函數(shù)將返回一個NULL指針。如果沒有隊列控制塊可以使用,為了不浪費事件控制塊資源,OSQCreate()函數(shù)將把剛剛取得的事件控制塊重新返還給空閑事件控制塊列表[L6.21(8)]。[!--empirenews.page--]

另外,消息隊列一旦建立就不能再刪除了。試想,如果有任務正在等待某個消息隊列中的消息,而此時又刪除該消息隊列,將是很危險的。

程序清單L6.21建立一個消息隊列

OS_EVENT*OSQCreate(void**start,INT16Usize)

{

OS_EVENT*pevent;

OS_Q*pq;

OS_ENTER_CRITICAL();

pevent=OSEventFreeList;(1)

if(OSEventFreeList!=(OS_EVENT*)0){

OSEventFreeList=(OS_EVENT*)OSEventFreeList->OSEventPtr;(2)

}

OS_EXIT_CRITICAL();

if(pevent!=(OS_EVENT*)0){

OS_ENTER_CRITICAL();

pq=OSQFreeList;(3)

if(OSQFreeList!=(OS_Q*)0){

OSQFreeList=OSQFreeList->OSQPtr;

}

OS_EXIT_CRITICAL();

if(pq!=(OS_Q*)0){

pq->OSQStart=start;(4)

pq->OSQEnd=&start[size];

pq->OSQIn=start;

pq->OSQOut=start;

pq->OSQSize=size;

pq->OSQEntries=0;

pevent->OSEventType=OS_EVENT_TYPE_Q;(5)

pevent->OSEventPtr=pq;(6)

OSEventWaitListInit(pevent);(7)

}else{

OS_ENTER_CRITICAL();

pevent->OSEventPtr=(void*)OSEventFreeList;(8)

OSEventFreeList=pevent;

OS_EXIT_CRITICAL();

pevent=(OS_EVENT*)0;

}

}

return(pevent);(9)

}

6.8.2 等待一個消息隊列中的消息,OSQPend()

程序清單L6.22是OSQPend()函數(shù)的源代碼。OSQPend()函數(shù)首先檢查事件控制塊是否是由OSQCreate()函數(shù)建立的[L6.22(1)],接著,該函數(shù)檢查消息隊列中是否有消息可用(即.OSQEntries是否大于0) [L6.22(2)]。 如果有, OSQPend()函數(shù)將指向消息的指針復制到msg變量中, 并讓.OSQOut指針指向隊列中的下一個單元[L6.22(3)],然后將隊列中的有效消息數(shù)減1[L6.22(4)]。因為消息隊列是一個循環(huán)的緩沖區(qū),OSQPend()函數(shù)需要檢查.OSQOut是否超過了

隊列中的最后一個單元[L6.22(5)]。當發(fā)生這種越界時,就要將.OSQOut重新調整到指向隊列的起始單元[L6.22(6)]。這是我們調用OSQPend()函數(shù)時所期望的,也是執(zhí)行OSQPend()函數(shù)最快的路徑。

程序清單L6.22在一個消息隊列中等待一條消息

void*OSQPend(OS_EVENT*pevent,INT16Utimeout,INT8U*err)

{

void*msg;

OS_Q*pq;

OS_ENTER_CRITICAL();

if(pevent->OSEventType!=OS_EVENT_TYPE_Q){(1)

OS_EXIT_CRITICAL();

*err=OS_ERR_EVENT_TYPE;

return((void*)0);

}

pq=pevent->OSEventPtr;

if(pq->OSQEntries!=0){(2)

msg=*pq->OSQOut++;(3)

pq->OSQEntries--;(4)

if(pq->OSQOut==pq->OSQEnd){(5)

pq->OSQOut=pq->OSQStart;(6)

}

OS_EXIT_CRITICAL();

*err=OS_NO_ERR;

}elseif(OSIntNesting>0){(7)

OS_EXIT_CRITICAL();

*err=OS_ERR_PEND_ISR;

}else{

OSTCBCur->OSTCBStat|=OS_STAT_Q;(8)

OSTCBCur->OSTCBDly=timeout;

OSEventTaskWait(pevent);

OS_EXIT_CRITICAL();

OSSched();(9)

OS_ENTER_CRITICAL();

if((msg=OSTCBCur->OSTCBMsg)!=(void*)0){(10)

OSTCBCur->OSTCBMsg=(void*)0;

OSTCBCur->OSTCBStat=OS_STAT_RDY;

OSTCBCur->OSTCBEventPtr=(OS_EVENT*)0;(11)

OS_EXIT_CRITICAL();

*err=OS_NO_ERR;

}elseif(OSTCBCur->OSTCBStat&OS_STAT_Q){(12)

OSEventTO(pevent);(13)

OS_EXIT_CRITICAL();

msg=(void*)0;(14)

*err=OS_TIMEOUT;

}else{

msg=*pq->OSQOut++;(15)

pq->OSQEntries--;

if(pq->OSQOut==pq->OSQEnd){

pq->OSQOut=pq->OSQStart;

}

OSTCBCur->OSTCBEventPtr=(OS_EVENT*)0;(16)

OS_EXIT_CRITICAL();

*err=OS_NO_ERR;

}

}

return(msg);(17)

}

如果這時消息隊列中沒有消息(.OSEventEntries是0),OSQPend()函數(shù)檢查它的調用者是否是中斷服務子程序[L6.22(7)]。象OSSemPend()和OSMboxPend()函數(shù)一樣,不能在中斷服務子程序中調用OSQPend(), 因為中斷服務子程序是不能等待的。 但是, 如果消息隊列中有消息,即使從中斷服務子程序中調用OSQPend()函數(shù),也一樣是成功的。

如果消息隊列中沒有消息,調用OSQPend()函數(shù)的任務被掛起[L6.22(8)]。當有其它的任

務向該消息隊列發(fā)送了消息或者等待時間超時,并且該任務成為最高優(yōu)先級任務時,OSSched()

返回[L6.22(9)]。這時,OSQPend()要檢查是否有消息被放到該任務的任務控制塊中[L6.22(10)]。如果有,那么該次函數(shù)調用成功,把任務的任務控制塊中指向消息隊列的指針刪除[L6.22(17)],并將對應的消息被返回到調用函數(shù)[L6.22(17)]。

在OSQPend()函數(shù)中,通過檢查任務的任務控制塊中的.OSTCBStat域,可以知道是否等到時間超時。如果其對應的OS_STAT_Q位被置1,說明任務等待已經超時[L6.22(12)]。這時,通過調用函數(shù)OSEventTo()可以將任務從消息隊列的等待任務列表中刪除[L6.22(13)]。這時,因為消息隊列中沒有消息,所以返回的指針是NULL[L6.22(14)]。

如果任務控制塊標志位中的OS_STAT_Q位沒有被置1,說明有任務發(fā)出了一條消息。

OSQPend()函數(shù)從隊列中取出該消息[L6.22(15)]。然后,將任務的任務控制中指向事件控制塊的指針刪除[L6.22(16)]。

6.8.3 向消息隊列發(fā)送一個消息(FIFO),OSQPost()

程序清單L6.23是OSQPost()函數(shù)的源代碼。在確認事件控制塊是消息隊列后

[L6.23(1)],OSQPost()函數(shù)檢查是否有任務在等待該消息隊列中的消息[L6.23(2)]。當事件控[!--empirenews.page--]

制塊的.OSEventGrp域為非0值時,說明該消息隊列的等待任務列表中有任務。這時,調用

OSEventTaskRdy()函數(shù)[見6.02節(jié),使一個任務進入就緒狀態(tài),OSEventTaskRdy()]從列表中取出最高優(yōu)先級的任務[L6.23(3)], 并將它置于就緒狀態(tài)。 然后調用函數(shù)OSSched()[L6.23(4)]進行任務的調度。如果上面取出的任務的優(yōu)先級在整個系統(tǒng)就緒的任務里也是最高的,而且OSQPost()函數(shù)不是中斷服務子程序調用的,就執(zhí)行任務切換,該最高優(yōu)先級任務被執(zhí)行。否則的話,OSSched()函數(shù)直接返回,調用 OSQPost()函數(shù)的任務繼續(xù)執(zhí)行。

程序清單L6.23向消息隊列發(fā)送一條消息

INT8UOSQPost(OS_EVENT*pevent,void*msg)

{

OS_Q*pq;

OS_ENTER_CRITICAL();

if(pevent->OSEventType!=OS_EVENT_TYPE_Q){(1)

OS_EXIT_CRITICAL();

return(OS_ERR_EVENT_TYPE);

}

if(pevent->OSEventGrp){(2)

OSEventTaskRdy(pevent,msg,OS_STAT_Q);(3)

OS_EXIT_CRITICAL();

OSSched();

(4)

return(OS_NO_ERR);

}else{

pq=pevent->OSEventPtr;

if(pq->OSQEntries>=pq->OSQSize){(5)

OS_EXIT_CRITICAL();

return(OS_Q_FULL);

}else{

*pq->OSQIn++=msg;(6)

pq->OSQEntries++;

if(pq->OSQIn==pq->OSQEnd){

pq->OSQIn=pq->OSQStart;

}

OS_EXIT_CRITICAL();

}

return(OS_NO_ERR);

}

}

如果沒有任務等待該消息隊列中的消息,而且此時消息隊列未滿[L6.23(5)],指向該消息的指針被插入到消息隊列中[L6.23(6)]。這樣,下一個調用OSQPend()函數(shù)的任務就可以馬上得到該消息。注意,如果此時消息隊列已滿,那么該消息將由于不能插入到消息隊列中而丟失。

此外,如果OSQPost()函數(shù)是由中斷服務子程序調用的,那么即使產生了更高優(yōu)先級的任務,也不會在調用OSSched()函數(shù)時發(fā)生任務切換。這個動作一直要等到中斷嵌套的最外層中斷服務子程序調用OSIntExit()函數(shù)時才能進行(見3.09節(jié),μC/OS-II中的中斷)。

6.8.4 向消息隊列發(fā)送一個消息(后進先出LIFO),OSQPostFront()

OSQPostFront()函數(shù)和OSQPost()基本上是一樣的, 只是在插入新的消息到消息隊列中時,使用.OSQOut作為指向下一個插入消息的單元的指針,而不是.OSQIn。程序清單L6.24是它的源代碼。值得注意的是,.OSQOut指針指向的是已經插入了消息指針的單元,所以再插入新的消

息指針前,必須先將.OSQOut指針在消息隊列中前移一個單元。如果.OSQOut指針指向的當前單

元是隊列中的第一個單元[L6.24(1)],這時再前移就會發(fā)生越界,需要特別地將該指針指向隊

列的末尾[L6.24(2)]。由于.OSQEnd指向的是消息隊列中最后一個單元的下一個單元,因此.OSQOut必須被調整到指向隊列的有效范圍內[L6.24(3)]。因為QSQPend()函數(shù)取出的消息是

由OSQPend()函數(shù)剛剛插入的,因此OSQPostFront()函數(shù)實現(xiàn)了一個LIFO隊列。

程序清單L6.24向消息隊列發(fā)送一條消息(LIFO)

INT8UOSQPostFront(OS_EVENT*pevent,void*msg)

{

OS_Q*pq;

OS_ENTER_CRITICAL();

if(pevent->OSEventType!=OS_EVENT_TYPE_Q){

OS_EXIT_CRITICAL();

return(OS_ERR_EVENT_TYPE);

}

if(pevent->OSEventGrp){

OSEventTaskRdy(pevent,msg,OS_STAT_Q);

OS_EXIT_CRITICAL();

OSSched();

return(OS_NO_ERR);

}else{

pq=pevent->OSEventPtr;

if(pq->OSQEntries>=pq->OSQSize){

OS_EXIT_CRITICAL();

return(OS_Q_FULL);

}else{

if(pq->OSQOut==pq->OSQStart){(1)

pq->OSQOut=pq->OSQEnd;(2)

}

pq->OSQOut--;(3)

*pq->OSQOut=msg;

pq->OSQEntries++;

OS_EXIT_CRITICAL();

}

return(OS_NO_ERR);

}

}

6.8.5 無等待地從一個消息隊列中取得消息,OSQAccept()

如果試圖從消息隊列中取出一條消息,而此時消息隊列又為空時,也可以不讓調用任務等待而直接返回調用函數(shù)。這個操作可以調用OSQAccept()函數(shù)來完成。程序清單L6.25是該函數(shù)的源代碼。 OSQAccept()函數(shù)首先查看pevent指向的事件控制塊是否是由OSQCreate()函數(shù)建立的[L6.25(1)],然后它檢查當前消息隊列中是否有消息[L6.25(2)]。如果消息隊列中有至少一條消息,那么就從.OSQOut指向的單元中取出消息[L6.25(3)]。OSQAccept()函數(shù)的調用函數(shù)需要對OSQAccept()返回的指針進行檢查。如果該指針是NULL值,說明消息隊列是空的,其中沒有消息可以[L6.25(4)]。否則的話,說明已經從消息隊列中成功地取得了一條消息。當中斷服務子程序要從消息隊列中取消息時, 必須使用OSQAccept()函數(shù), 而不能使用OSQPend()函數(shù)。

程序清單L6.25無等待地從消息隊列中取一條消息

void*OSQAccept(OS_EVENT*pevent)

{

void*msg;

OS_Q*pq;

OS_ENTER_CRITICAL();

if(pevent->OSEventType!=OS_EVENT_TYPE_Q){(1)

OS_EXIT_CRITICAL();

return((void*)0);

}

pq=pevent->OSEventPtr;

if(pq->OSQEntries!=0){(2)

msg=*pq->OSQOut++;(3)

pq->OSQEntries--;

if(pq->OSQOut==pq->OSQEnd){

pq->OSQOut=pq->OSQStart;

}

}else{

msg=(void*)0;(4)

}

OS_EXIT_CRITICAL();

return(msg);

}

6.8.6 清空一個消息隊列,OSQFlush()

OSQFlush()函數(shù)允許用戶刪除一個消息隊列中的所有消息,重新開始使用。程序清單L6.26是該函數(shù)的源代碼。和前面的其它函數(shù)一樣,該函數(shù)首先檢查pevent指針是否是執(zhí)行一個消息隊列[L6.26(1)],然后將隊列的插入指針和取出指針復位,使它們都指向隊列起始單元,同時,將隊列中的消息數(shù)設為0[L6.26(2)]。這里,沒有檢查該消息隊列的等待任務列表是否為空,因為只要該等待任務列表不空,.OSQEntries就一定是0。唯一不同的是,指針.OSQIn和.OSQOut此時可以指向消息隊列中的任何單元,不一定是起始單元。[!--empirenews.page--]

程序清單L6.26清空消息隊列

INT8UOSQFlush(OS_EVENT*pevent)

{

OS_Q*pq;

OS_ENTER_CRITICAL();

if(pevent->OSEventType!=OS_EVENT_TYPE_Q){(1)

OS_EXIT_CRITICAL();

return(OS_ERR_EVENT_TYPE);

}

pq=pevent->OSEventPtr;

pq->OSQIn=pq->OSQStart;(2)

pq->OSQOut=pq->OSQStart;

pq->OSQEntries=0;

OS_EXIT_CRITICAL();

return(OS_NO_ERR);

}

6.8.7 查詢一個消息隊列的狀態(tài),OSQQuery()

OSQQuery()函數(shù)使用戶可以查詢一個消息隊列的當前狀態(tài)。 程序清單L6.27是該函數(shù)的源代碼。OSQQuery()需要兩個參數(shù):一個是指向消息隊列的指針pevent。它是在建立一個消息隊列時,由OSQCreate()函數(shù)返回的;另一個是指向OS_Q_DATA(見uCOS_II.H)數(shù)據(jù)結構的指針pdata。該結構包含了有關消息隊列的信息。在調用OSQQuery()函數(shù)之前,必須先定義該數(shù)據(jù)結構變量。OS_Q_DATA結構包含下面的幾個域:

.OSMsg 如果消息隊列中有消息,它包含指針.OSQOut所指向的隊列單元中的內容。如果隊列是空的,.OSMsg包含一個NULL指針。

.OSNMsgs是消息隊列中的消息數(shù)(.OSQEntries的拷貝)。

.OSQSize 是消息隊列的總的容量

.OSEventTbl[]和.OSEventGrp是消息隊列的等待任務列表。通過它們,OSQQuery()的調用函數(shù)可以得到等待該消息隊列中的消息的任務總數(shù)。

OSQQuery()函數(shù)首先檢查pevent指針指向的事件控制塊是一個消息隊列[L6.27(1)],然后復制等待任務列表[L6.27(2)]。如果消息隊列中有消息[L6.27(3)],.OSQOut指向的隊列單元中的內容被復制到OS_Q_DATA結構中[L6.27(4)], 否則的話, 就復制一個NULL指針[L6.27(5)]。

最后,復制消息隊列中的消息數(shù)和消息隊列的容量大小[L6.27(6)]。

程序清單L6.27程序消息隊列的狀態(tài)

INT8UOSQQuery(OS_EVENT*pevent,OS_Q_DATA*pdata)

{

OS_Q*pq;

INT8Ui;

INT8U*psrc;

INT8U*pdest;

OS_ENTER_CRITICAL();

if(pevent->OSEventType!=OS_EVENT_TYPE_Q){(1)

OS_EXIT_CRITICAL();

return(OS_ERR_EVENT_TYPE);

}

pdata->OSEventGrp=pevent->OSEventGrp;(2)

psrc=&pevent->OSEventTbl[0];

pdest=&pdata->OSEventTbl[0];

for(i=0;i

*pdest++=*psrc++;

}

pq=(OS_Q*)pevent->OSEventPtr;

if(pq->OSQEntries>0){(3)

pdata->OSMsg=pq->OSQOut;(4)

}else{

pdata->OSMsg=(void*)0;(5)

}

pdata->OSNMsgs=pq->OSQEntries;(6)

pdata->OSQSize=pq->OSQSize;

OS_EXIT_CRITICAL();

return(OS_NO_ERR);

}

6.8.8 使用消息隊列讀取模擬量的值

在控制系統(tǒng)中,經常要頻繁地讀取模擬量的值。這時,可以先建立一個定時任務OSTimeDly()[見5.00節(jié),延時一個任務,OSTimeDly()],并且給出希望的抽樣周期。然后,如圖F6.11所示,讓A/D采樣的任務從一個消息隊列中等待消息。該程序最長的等待時間就是抽樣周期。當沒有其它任務向該消息隊列中發(fā)送消息時,A/D采樣任務因為等待超時而退出等待狀態(tài)并進行執(zhí)行。這就模仿了OSTimeDly()函數(shù)的功能。

也許,讀者會提出疑問,既然OSTimeDly()函數(shù)能完成這項工作,為什么還要使用消息隊列呢?這是因為,借助消息隊列,我們可以讓其它的任務向消息隊列發(fā)送消息來終止A/D采樣任務等待消息,使其馬上執(zhí)行一次A/D采樣。此外,我們還可以通過消息隊列來通知A/D采樣程序具體對哪個通道進行采樣,告訴它增加采樣頻率等等,從而使得我們的應用更智能化。換句話說,我們可以告訴A/D采樣程序,“現(xiàn)在馬上讀取通道3的輸入值!”之后,該采樣任務將重新開始在消息隊列中等待消息,準備開始一次新的掃描過程。

圖F6.11讀模擬量輸入——Figure6.11

6.8.9 使用一個消息隊列作為計數(shù)信號量

在消息隊列初始化時,可以將消息隊列中的多個指針設為非NULL值(如void*1),來實現(xiàn)計數(shù)信號量的功能。這里,初始化為非NULL值的指針數(shù)就是可用的資源數(shù)。系統(tǒng)中的任務可以通過OSQPend()來請求“信號量”,然后通過調用OSQPost()來釋放“信號量”,如程序清單L6.28。如果系統(tǒng)中只使用了計數(shù)信號量和消息隊列,使用這種方法可以有效地節(jié)省代碼空間。

這時將OS_SEM_EN設為0,就可以不使用信號量,而只使用消息隊列。值得注意的是,這種方法

為共享資源引入了大量的指針變量。也就是說,為了節(jié)省代碼空間,犧牲了RAM空間。另外,

對消息隊列的操作要比對信號量的操作慢,因此,當用計數(shù)信號量同步的信號量很多時,這種

方法的效率是非常低的。

程序清單L6.28使用消息隊列作為一個計數(shù)信號量

OS_EVENT*QSem;

void*QMsgTbl[N_RESOURCES]

voidmain(void)

{

OSInit();

.

QSem=OSQCreate(&QMsgTbl[0],N_RESOURCES);

for(i=0;i

OSQPost(Qsem,(void*)1);

}

.

.

OSTaskCreate(Task1,..,..,..);

.

.

OSStart();

}

voidTask1(void*pdata)

{

INT8Uerr;

for(;;){

OSQPend(&QSem,0,&err);/*得到對資源的訪問權*/

.

./*任務獲得信號量,對資源進行訪問*/

.

OSMQPost(QSem,(void*)1);/*釋放對資源的訪問權*/

}

}

本站聲明: 本文章由作者或相關機構授權發(fā)布,目的在于傳遞更多信息,并不代表本站贊同其觀點,本站亦不保證或承諾內容真實性等。需要轉載請聯(lián)系該專欄作者,如若文章內容侵犯您的權益,請及時聯(lián)系本站刪除。
換一批
延伸閱讀

LED驅動電源的輸入包括高壓工頻交流(即市電)、低壓直流、高壓直流、低壓高頻交流(如電子變壓器的輸出)等。

關鍵字: 驅動電源

在工業(yè)自動化蓬勃發(fā)展的當下,工業(yè)電機作為核心動力設備,其驅動電源的性能直接關系到整個系統(tǒng)的穩(wěn)定性和可靠性。其中,反電動勢抑制與過流保護是驅動電源設計中至關重要的兩個環(huán)節(jié),集成化方案的設計成為提升電機驅動性能的關鍵。

關鍵字: 工業(yè)電機 驅動電源

LED 驅動電源作為 LED 照明系統(tǒng)的 “心臟”,其穩(wěn)定性直接決定了整個照明設備的使用壽命。然而,在實際應用中,LED 驅動電源易損壞的問題卻十分常見,不僅增加了維護成本,還影響了用戶體驗。要解決這一問題,需從設計、生...

關鍵字: 驅動電源 照明系統(tǒng) 散熱

根據(jù)LED驅動電源的公式,電感內電流波動大小和電感值成反比,輸出紋波和輸出電容值成反比。所以加大電感值和輸出電容值可以減小紋波。

關鍵字: LED 設計 驅動電源

電動汽車(EV)作為新能源汽車的重要代表,正逐漸成為全球汽車產業(yè)的重要發(fā)展方向。電動汽車的核心技術之一是電機驅動控制系統(tǒng),而絕緣柵雙極型晶體管(IGBT)作為電機驅動系統(tǒng)中的關鍵元件,其性能直接影響到電動汽車的動力性能和...

關鍵字: 電動汽車 新能源 驅動電源

在現(xiàn)代城市建設中,街道及停車場照明作為基礎設施的重要組成部分,其質量和效率直接關系到城市的公共安全、居民生活質量和能源利用效率。隨著科技的進步,高亮度白光發(fā)光二極管(LED)因其獨特的優(yōu)勢逐漸取代傳統(tǒng)光源,成為大功率區(qū)域...

關鍵字: 發(fā)光二極管 驅動電源 LED

LED通用照明設計工程師會遇到許多挑戰(zhàn),如功率密度、功率因數(shù)校正(PFC)、空間受限和可靠性等。

關鍵字: LED 驅動電源 功率因數(shù)校正

在LED照明技術日益普及的今天,LED驅動電源的電磁干擾(EMI)問題成為了一個不可忽視的挑戰(zhàn)。電磁干擾不僅會影響LED燈具的正常工作,還可能對周圍電子設備造成不利影響,甚至引發(fā)系統(tǒng)故障。因此,采取有效的硬件措施來解決L...

關鍵字: LED照明技術 電磁干擾 驅動電源

開關電源具有效率高的特性,而且開關電源的變壓器體積比串聯(lián)穩(wěn)壓型電源的要小得多,電源電路比較整潔,整機重量也有所下降,所以,現(xiàn)在的LED驅動電源

關鍵字: LED 驅動電源 開關電源

LED驅動電源是把電源供應轉換為特定的電壓電流以驅動LED發(fā)光的電壓轉換器,通常情況下:LED驅動電源的輸入包括高壓工頻交流(即市電)、低壓直流、高壓直流、低壓高頻交流(如電子變壓器的輸出)等。

關鍵字: LED 隧道燈 驅動電源
關閉