前言:
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中的通知和其他內容