UCOS學習筆記
鉤子函數
空閒任務函數OSIdleTaskHook()
函數代碼如下:
void OSIdleTaskHook (void)
{
#ifOS CFG APP HOOKS EN> Ou
if (OS_ AppIdleTaskHookPtr != (OS_ APP HOOK_ VOID)0) {
(*OS_ AppIdleTaskHookPtr)();
}
#endif
}
從上面的函數代碼中可以看出要使用空閒任務鉤子函數的話需要將宏
OS_CFG_APP_HOOKSEN置1,即允許使用空閒任務的鉤子函數。當時使能空閒任務的鉤子函數以後每次進入空閒任務就會調用指針OS_ AppIdleTaskHookPtr所指向的函數。
App_ OS_ SetAllHooks()函數代碼如下:
void App_ OS_ SetAllHooks (void)
{
#ifOS CFG APP HOOKS EN> Ou
CPU_ SR_ ALLOCO);
CPU_CRITICAL_ENTERO;
OS_AppTaskCreateHookPtr = App_ OS_ TaskCreateHook;
OS_AppTaskDelHookPtr = App_OS_TaskDelHook;
OS_AppTaskReturnHookPtr = App_OS_TaskReturnHook;
OS_AppIdleTaskHookPtr = App_OS_IdleTaskHook;
OS_AppStatTaskHookPtr = App_OS_StatTaskHook;
OS_AppTaskSwHookPtr = App_OS_TaskSwHook;
OS_AppTimeTickHookPtr = App_OS_TimeTickHook;
CPU_ CRITICAL_ EXIT();
#endif
}
OS_AppIdleTaskHookPtr = App_OS_IdleTaskHook該語句是將App_OS_IdleTaskHook複製給OS_AppIdleTaskHookPtr,
而OS_AppIdleTaskHookPtr是一個函數:
void App_OS_IdleTaskHook(void)
{
}
空閒任務的鉤子函數OSIdleTaskHook()工作原理:OSIdleTaskHook中最終調用的是函數App_ OS_ Idle TaskHook(),也就是說如果我們要想在空閒任務的鉤子函數中做一些其他處理的話就需要將程序代碼寫在App_OS_IdleTaskHook()函數中
注意!:
在空閒任務的鉤子函數中不能調用任何可以是空閒進入等待態的代碼,原因很簡單,CPU總是在不停的運行,需要一直工作,不能讓CPU停下來,哪怕是執行一些對應用沒有任何用的代碼,比如簡單的將-一個變量加一。在UCOS中爲了讓CPU一直工作,在所有應用任務都進入等待態的時候CPU會執行空閒任務,我們可以從空閒任務的任務函數OS_ IdleTask()看出,在OS_ IdleTask(中 沒有任何可以讓空閒任務進入等待態的代碼。如果在OS_ IdleTask()中有可以讓空閒任務進入等待態的代碼的話有可能會在同一時刻所有任務(應用任務和空閒任務)同時進入等待態,此時CPU就會無所事事了,所以在空閒任務的鉤子函數OSIdle TaskHook()
中不能出現可以讓空閒任務進入等待態的代碼!
中斷管理
中斷處理過程
在STM32中是支持中斷的,中斷是一個硬件機制,主要用來向CPU通知一個異步事件發生了,這時CPU就會將當前CPU寄存器值入棧,然後轉而執行中斷服務程序,在CPU執行中斷服務程序的時候有可能有更高優先級的任務就緒,那麼當退出中斷服務程序的時候,CPU就會直接執行這個高優先級的任務。
UCOSII是支持中斷嵌套的,既高優先級的中斷可以打斷低優先級的中斷,在UCOSIII中使用OSIntNestingCtr來記錄中斷嵌套次數,最大支持250級的中斷嵌套,每進入一-次中斷服務函數OSIntNestingCtr就會加1,當退出中斷服務函數的時候OSIntNestingCtr就會減1。
我們在編寫UCOSIII 的中斷服務程序的時候需要使用到兩個函數OSIntEnter()和
OSIntExit(),OSIntExit()函數是中斷級任務調度器,OSIntEnter()的函數代碼如下:
void OSIntEnter (void)
{
if (OSRunning!=OS_ STATE OS_ RUNNING) {//判斷UCOSII是否運行,
return;
if (OSIntNestingCtr >= (OS_ NESTING CTR)250u) { //判斷中斷嵌套次數 是否大於250
return;
}
OSIntNestingCtr++;//中斷嵌套次數加1
}
從上面代碼中可以看出OSIntEnter()函數其實就是將OSIntNestingCtr 進行簡單的加一操作,用來中斷嵌套的次數而已。
那麼我們在UCOSIII環境中如何編寫中斷服務函數呢我們按照下面所示代碼編寫中斷服務函數:
void XXX_ Handler(void) (1)
{
OSIntEnter();//進入中斷 (2)
用戶自行編寫的中斷服務程序;//這部分就是我們的中斷服務程序 (3)
OSIntExit();//觸發任務切換軟中斷 (4)
}
(1)中斷服務程序,XXX爲不同中斷源的中斷函數名字。
(2)首先調用OSIntEnter()函數來標記進入中斷服務函數,並且記錄中斷嵌套次數。
(3)這部分就是我們需要自行編寫的中斷服務程序了,也就是我們平時不使用UCOSIII時的中斷服務程序。
(4)退出中斷服務函數的時候調用OSIntExit(),發起一.次中斷級任務切換。
直接發佈與延遲發佈
相比UCOSII,UCOSIII對從中斷髮布消息或者信號的處理有兩種模式:直接發佈和延遲發佈兩種方式。我們可以通過宏Os_ CFG ISR_ POST_ DEFERRED_ EN來選擇使用直接發佈還是延遲發佈。宏OS_ CFG_ ISR_ POST DEFERRED EN在os_ cfg.h 文件中有定義,當定義爲0時使用直接發佈模式,定義爲1的時候使用延遲發佈模式。不管使用那種方式,我們的應用程序不需要做出任何的修改,編譯器會根據不同的設置編譯相應的代碼。
直接發佈
在UCOSII中使用的是直接發佈,直接發佈如圖8.1.1所示:
(1)外設產生中斷請求。
(2)中斷服務程序開始運行,該中斷服務程序中可能會包含有發送信號量、消息、事件標誌組等事件。那麼等待這個事件的任務的優先級要麼比當前被中斷的任務高,要麼比其低。
(3)如果中斷對應的事件使得某個比被中斷的任務優先級低的任務進入就緒態,則中斷退出後仍恢復運行被中斷的任務。
(4)如果中斷對應的事件使得某個比被中斷的任務優先級更高的任務進入就緒態,則UCOSII將進行任務切換,中斷服務程序推出後就執行更高優先級的任務。
(5)如果使用直接發佈模式的話,則UCOSII必須關中斷以保護臨界段代碼,防止中斷處理程序訪問這些臨界段代碼。
使用直接發佈模式的話,UCOSII會對臨界段代碼採用關閉中斷的保護措施,這樣就會延長中斷的響應時間。雖然UCOSII已經採用了所有可能的措施來降低中斷關閉時間,但仍然有一些複雜的功能會使得中斷關閉相對較長的時間。
延遲發佈
當設置宏OS_ CFG_ ISR_ POST_ DEFERRED_ EN爲1的時候,UCOSIII 不是通過關中斷,而是通過給任務調度器上鎖的方法來保護臨界段代碼,在延遲發佈模式下基本不存在關閉中斷的情況,延遲發佈如圖8.1.2所示。
(1)外設產生中斷請求。
(2)中斷服務程序開始運行,該中斷服務程序中可能會包含有發送信號量、消息、事件標誌組等事件。那麼等待這個事件的任務的優先級要麼比當前被中斷的任務高,要麼比其低。
(3)中斷服務程序通過調用系統的發佈服務函數向任務發佈消息或信號,在延遲發佈模式下,這個過程不是直接進行發佈操作,而是將這個發佈函數調用和相應的參數寫入到專用隊列中,該隊列稱爲中斷隊列。然後使中斷隊列處理任務進入就緒態,這個任務是UCOSIII的內部任務,並且具有最高優先級(0)。
(4)中斷服務程序處理結束時,UCOSIII 切換執行中斷隊列處理任務,該任務從中斷隊列中提取出發佈函數調用信息,此時仍需要關閉中斷以防止中斷服務程序同時對中斷隊列進行訪問。中斷隊列處理任務提取出發佈函數調用的信息後重新開中斷,鎖定任務調度器,然後進行發佈函數調用,相當於發佈函數調用一直是在任務級代碼中進行的,這樣本來應該在臨界段中處理的代碼就被放到了任務級完成。
(5)中斷隊列處理任務將中斷隊列處理完,將自身掛起,並重新啓動任務調度來運行處於最高優先級的就緒任務。如果原先被中斷的任務仍然是最高優先級的就緒任務,則UCOSIII恢復運行這個任務。
(6)由於中斷隊列處理任務的發佈操作使得更重要的任務進入就緒態,內核將切換到更高優先級的任務運行。
在使用延遲發佈模式額外增加的操作都是爲了避免使用關中斷來保護臨界段代碼。這些額外增加的操作僅包括將發佈調用及其參數複製到中斷隊列中、從中斷隊列提取發佈調用和相關參數以及一-次額外的任務切換。
直接發佈與延遲發佈對比
直接發佈模式 | 延遲發佈模式 | |
---|---|---|
保護臨界段代碼 | UCOSIII通過關閉中斷 | UCOSII通過鎖定任務調度來保護臨界段代碼。 |
訪問中斷隊列 | 如果應用中存在非常快速的中斷請求源,則當UCOSIII在直接發佈模式下的中斷關閉時間不能滿足要求的時候,可以使用延遲發佈模式來降低中斷關閉時間。 | 仍需要關閉中斷(時間短) |
OSTimeTick()函數
就像人的心臟一-樣,UCOSIII 需要-一個系統時鐘節拍,作爲系統心跳,這個時鐘我們一般都使用MCU的硬件定時器。Cortex-M 內核提供了一個定時器用於產生系統時鐘節拍,這個定時器就是Systick。UCOSIII通過時鐘節拍來對任務進行整個節拍的延遲,併爲等待事件的任務提供超時判斷。時鐘節拍中斷必須調用OlSTimeTick()函數,我們使用Systick來爲系統提供時鐘,因此在Systick的中斷服務程序中就必須調用OSTimeTick()。
使用注意事項:
(1)時鐘節拍中斷服務程序中首先會調用鉤子函數OSTimeTickHook(),這個函數中用戶可以放置一些代碼。
(2)如果使用了延遲發佈模式,則UCOSII讀取當前的時間戳信息,並在中斷隊列中放入發佈函數調用請求和相關參數,延遲向時鍾節拍任務發信號的操作。然後,中斷隊列處理任務根據中斷隊列向時鍾節拍任務發信號。
(3)向時鍾節拍任務(OS_ TickTask))發送-一個信號量。
(4)如果UCOSIII使用了時間片輪轉調度機制,判斷當前任務分配的運行時間片是否已經用完。
(5)如果使用定時器的話就向定時器任務(OS _TmrTask))發送信號量。
臨界段代碼保護
有一些代碼我們需要保證其完成運行,不能被打斷,這些不能被打斷的代碼就是臨界段代碼,也叫臨界區。我們在進入臨界段代碼的時候使用宏OS_ CRITICAL ENTER(), 退出臨界區的時候使用宏OS_ CRITICAL_ EXIT0或者OS_ CRITICAL_ EXIT_ NO_ SCHED()。當宏OS_ CFG_ ISR_ POST_ DEFERRED EN定義爲0的時候,進入臨界區的時候UCOSIII會使用關中斷的方式,退出臨界區以後重新打開中斷。當OS_ [CFG ISR POST DEFERRED EN定義爲1的時候進入臨界區前是給調度器上鎖,並在退出臨界區的時候給調度器解鎖。
使用注意事項:
時間管理
OSTimeDly()函數
當我們對某一個任務需要進行延時的時候就調用該函數。函數原型如下:
void OSTimeDly(OS_ TICK dly,OS_ OPT opt,OS_ ERR *p_err)
dly | 指定延時的時間長度,這裏單位爲時間節拍數。 |
opt | 指定延遲使用的選項,有四種選項。 |
OS OPT TIME DLY | 相對模式 |
OS OPT TIME TIMEOUT | 和OS_ OPT_ TIME_ DLY一樣 |
OS OPT TIME MATCH | 絕對模式 |
OS_ OPT TIME_ PERIODIC | 週期模式 |
P_ err | 指向調用該函數後返回的錯誤碼 |
“相對模式”在系統負荷較重時有可能延時會少-一個節拍,甚至偶爾差多個節拍,在週期模式下,任務仍然可能會被推遲執行,但它總會和預期的“匹配值”同步。因此,推薦使用“週期模式”來實現長時間運行的週期性延時。
“絕對模式”可以用來在上電後指定的時間執行具體的動作,比如可以規定,上電N秒後關閉某個外設。
OSTimeDlyHMSM()函數
函數原型:
對應參數的解釋說明: