FreeRTOS 任務與排程器(1)
前言:
Task.c和Task.h檔案內是FreeRTOS的核心內容,所有任務和排程器相關的API函式都在這個檔案中,它包括下圖這些內容FreeRTOS檔案如下:
Task.c和Task.h檔案內是FreeRTOS的核心內容,所有任務和排程器相關的API函式都在這個檔案中,它包括下圖這些內容
在開始介紹函式之前,首先我們先簡單瞭解一下任務狀態:
• FreeRTOS的任務5種狀態:
- 執行狀態:當前正在執行的任務的狀態,只可能會一個當前正在執行的任務
- 就緒狀態:隨時可以執行的任務的狀態,就緒狀態任務隨時等待排程器排程
- 阻塞狀態:任務因為某些原因暫時不能被排程狀態,一般情況下正在等待某些事件的發生比如呼叫了xTaskDelay()在一段時間內任務會被阻塞,在這些事件達成後任務會自動回到就緒狀態。
- 掛起狀態:vTaskSuspend()函式會讓任務進入掛起狀態,這時候這個任務不會執行。呼叫xTaskResume()函式才能讓這些任務回到就緒狀態
- 刪除狀態:一個任務被使用vTaskDelete()函式後被刪除,處於刪除狀態。
他們之間的狀態切換如下示意圖:
這本篇中,主要介紹一下這6個部分:
一、建立任務:
- 顧名思義,這些函式的作用是建立一個任務,建立的任務會進入就緒狀態,如果沒其他更高優先順序的任務執行,則馬上進入執行狀態
- 這些函式可以在排程器啟動前或啟動後呼叫
1.1、vTaskCreate()
1.1.1、函式簡介
幾個比較重要的輸入引數介紹一下:
- pvTaskCode:直接指向函式的本體的指標,可以把任務函式名字直接貼過來
- usStackDepth:任務內申請的區域性變數會使用到任務的堆疊空間,(在32位系統中,這個引數的單位是word=4byte),例如這個引數設定為100,那麼這個任務將會申請到400byte的空間。
- uxPriority:任務優先順序,使用這個引數來設定任務優先順序(0是最低優先順序),在FreeRTOSConfig.h 中調整configMAX_PRIORITIES的定義可以設定最高可用的優先順序(最高可設定優先順序為configMAX_PRIORITIES-1)。高優先順序的任務可以搶斷低優先順序的任務,(主要:記得高優先順序的任務不需要用的時候將其阻塞或掛起或刪除,否則低優先順序的任務可能永遠無法得到執行權)
- pxCreatedTask:控制代碼的地址,以後使用其他API功能來索引這個任務時會需要用到(注意:這裡傳入的是控制代碼的地址!)
1.1.2、使用簡介:
以下是官方例子:
1.2、vTaskCreateStatic()
1.2.1、函式簡介
為了方便我們自己管理記憶體,有了靜態建立任務法,任務堆疊的建立和回收都要由程式設計者來處理,與vTaskCreate()對比,我們可以發現以下不同之處:
- puxStackBuffer引數:任務需要用到的堆疊陣列的地址,我們只需要建立一StackType_t型別個空的陣列,然後把陣列指標傳進來就好了(注意陣列的大小要大於ulStackDepth)
- pxTaskBuffer引數:存放任務資料結構(TCB)的變數,同樣的,我們建立一個StaticTask_t型別的變數,然後把他的指標傳進來
- 還有一處不同,輸入引數的控制代碼取消掉了,但是控制代碼還是存在的,只是變為了返回引數
1.2.2、使用簡介
官方例程如下:
- 建立一個StaticTask_t 型別的引數,稍後用於存放任務資料結構(TCB)
- 建立一個StackType_t型別陣列,稍後用於作為任務堆疊
- 建立一個控制代碼,稍後用作vTaskCode任務的控制代碼
- 使用xTaskCreateStatic()建立任務
- 使用vTaskSuspend()、並通過傳入控制代碼掛起剛剛建立的任務,目的是展示給我們看這個任務的控制代碼是可用的
二、刪除任務:
2.1、引數簡介
2.2、使用簡介
下面是官方例子:
- 在當前任務中,用xTaskCreate()建立另一個任務B
- 如果任務B建立成功,使用vTaskDelete(任務控制代碼)刪除掉任務B。
- 用vTaskDelete(NULL)刪除掉當前任務,目的是展示給我們看通過傳入NULL可以刪除當前任務
三、延時函式:
3.1、vTaskDelay()
3.1.1、函式簡介
xTaskDelay()
- 讓呼叫這個函式的任務在一定時間內進入阻塞狀態,時間到達後會切換回來這個任務。
- 如果輸入引數為0,那麼這個任務不會阻塞,但是會切換
*這個函式只有一個輸入引數,但需要注意一下它是以tick時鐘的中斷次數為單位的(並不是以毫秒為單位):
3.1.2、使用簡介
下面是官方的例子
其中兩處vTaskDelay()
- 延時20個tick時間片
- 延時20ms。(pdMS_TOTICKS()可以把ms時間換成tick為單位)
3.2、vTaskDelayUntil()
3.2.1、函式簡介
- 讓任務進入阻塞狀態等待實際那到達,是精確的絕對時間
- 週期性任務能夠使用vTaskDelayUntil()來達到連續的執行頻率
3.2.2、使用簡介
以下是官方的例子:
- 建立一個TickType_t型別的變數,用於記錄上一次系統時間
- 用pdMS_TO_TICKS()函式把50ms轉換為tick為單位,方便等下給vTaskDelayUntil呼叫
- 初始化第一步中的變數,在這一步後,這個變數不用再手動更新(vTaskDelayUntil()會更新它)
- 使用vTaskDelayUntil()、傳入剛剛的引數,製造50ms固定時間的迴圈
3.3、重要對比
vTaskDelay()和vTaskDelayUntil()的不同之處
我們可以直接翻譯一下官方手冊的描述:
舉個例子:
以下兩個任務分別用vTaskDelay()和vTaskDelayUntil()來實現延時功能:
思考一個問題: 任務A 和任務B都能實現LED閃爍,那麼A 和 B任務的LED埠多少毫秒翻轉一次 ?
任務A:
任務B:
- 任務A中,LED埠15毫秒翻轉一次
- 任務B中,LED埠10毫秒翻轉一次
*注意:Delay_MS()是一個自定義的函式,用來模擬任務中處理其他東西浪費了5ms。
兩個任務都是 TaskDelay(10毫秒) ,但是任務A中使用vTaskDelay(),在任務B中使用vTaskDelayUntil()。
在任務A中:vTaskDelay()是從呼叫的那一刻開始算,那麼這個任務本身在Delay_MS中佔用了5MS,LED翻轉的時間忽略不計,那麼加上vTaskDelay()的10MS,就是15MS。
在任務B中:vTaskDelayUntil()和任務本身執行時間無關,只要任務每次迴圈執行的總時間少於10ms,那麼這個任務就是10ms執行一次了。
最後提一下xTaskAbortDelay()這個函式,根據描述,他能讓正在阻塞狀態等待延時的函式馬上切出,進入就緒狀態。但由於我的庫版本比較舊,沒有這個函式,所以就不作更多的介紹了。
四、開啟排程器
4.1、函式簡介:
這個函式作用是開啟排程器,呼叫這個函式後任務就會開始執行。所以在整個程式中只需要呼叫一次,一般在main函式中呼叫就可以了。開啟成功的話,系統由排程器接管了,main函式中vTaskStartScheduler()後面的程式碼都不會被執行。
4.2、使用簡介:
官方的例子:
- 建立任務
- 開啟排程器,開啟後程序會跳轉到vATask()任務中
五、任務的掛起和恢復
5.1、vTaskSuspend() 和 vTaskResume()
5.1.1、函式簡介:
掛起/解除掛起單個任務:
- vTaskSuspend的函式是讓指定的任務進入掛起狀態
- xTaskResume的函式是讓指定的任務從掛起狀態換為就緒狀態。
- xTaskResumeFromISR()是xTaskResume()適合在中斷中呼叫的版本
5.1.2、使用簡介
使用很簡單,當不需要用某個任務的時候用vTaskResume(控制代碼) 把那個任務掛起,需要用的時候再開啟就行了,下面是官方的例程,實現了這三步:
- 使用xTaskCreate()建立任務
- 建立成功的話使用vTaskSuspend()把剛剛建立的任務轉換為掛起狀態(該任務將不會再得到執行)
- 使用vTaskResume()讓剛剛掛起的任務轉為就緒狀態
5.2、vTaskSuspendAll()和vTaskResumeAll()
5.2.1、函式簡介:
vTaskSuspendAll()掛起排程器 對應 xTaskResumeAll()解除掛起排程器:
• vTaskSuspendAll()掛起排程器後,只有當前任務在繼續執行,不會發生任務切換了。
• xTaskResumeAll()對應vTaskSuspendAll()恢復排程器。
這個函式的作用之一在於,可以保證一些不能被分的程式執行,因為掛起排程器保證了不會被高優先順序的任務強調(注意排程器掛起後中斷還是可以執行的,如果要保證時效,還得把中斷關閉)
注意:vTaskSuspendAll()是可以遞迴呼叫的,這意味著呼叫了多少次vTaskSuspendAll(),就必須有多少此vTaskResumeAll()的呼叫才能讓排程器恢復。這個情況以下的例子中很好地體現了。
5.2.2、使用簡介
- 在任務vTask1中第一此呼叫vTaskSuspendAll(),此時排程器被掛起,不會發生任務切換
- 呼叫另一個用作例子的vDemoFunction()
- 第二次呼叫vTaskSuspendAll(),此時排程器再次被掛起,而且掛起計數增加到2
- 第一次呼叫vTaskResumeAll(),此時排程器掛起計數減少為1,但是排程器仍然處於掛起狀態
- 第二次呼叫vTaskResumeAll(),排程器計數為0,排程器恢復執行,後面會發生任務切換了
六、任務切換
6.1、函式簡介
- 在一個執行的任務中呼叫taskYIELD(),那麼這個任務會被降級為就緒狀態,排程器會選擇另一個相同優先順序的就緒任務執行。(如果沒有相同優先順序的任務就緒,那麼這個任務將不會切換,會繼續執行。
6.2、使用簡介
我們來看官方例子:
- 在呼叫taskYIELD()後,vATask這個任務會馬上"讓步",進入就緒狀態等待,等待下次得到排程器排程的時候,會執行taskYIELD()下面的程式碼
在下一節中,我們會繼續介紹task中的通知和其他內容