1. 程式人生 > >FreeRTOS 任務與排程器(1)

FreeRTOS 任務與排程器(1)

 前言:

Task.c和Task.h檔案內是FreeRTOS的核心內容,所有任務和排程器相關的API函式都在這個檔案中,它包括下圖這些內容FreeRTOS檔案如下:

Task.c和Task.h檔案內是FreeRTOS的核心內容,所有任務和排程器相關的API函式都在這個檔案中,它包括下圖這些內容

在開始介紹函式之前,首先我們先簡單瞭解一下任務狀態:

• FreeRTOS的任務5種狀態:

  1. 執行狀態:當前正在執行的任務的狀態,只可能會一個當前正在執行的任務
  2. 就緒狀態:隨時可以執行的任務的狀態,就緒狀態任務隨時等待排程器排程
  3. 阻塞狀態:任務因為某些原因暫時不能被排程狀態,一般情況下正在等待某些事件的發生比如呼叫了xTaskDelay()在一段時間內任務會被阻塞,在這些事件達成後任務會自動回到就緒狀態。
  4. 掛起狀態:vTaskSuspend()函式會讓任務進入掛起狀態,這時候這個任務不會執行。呼叫xTaskResume()函式才能讓這些任務回到就緒狀態
  5. 刪除狀態:一個任務被使用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、使用簡介官方例程如下:

  1. 建立一個StaticTask_t 型別的引數,稍後用於存放任務資料結構(TCB)
  2. 建立一個StackType_t型別陣列,稍後用於作為任務堆疊
  3. 建立一個控制代碼,稍後用作vTaskCode任務的控制代碼
  4. 使用xTaskCreateStatic()建立任務
  5. 使用vTaskSuspend()、並通過傳入控制代碼掛起剛剛建立的任務,目的是展示給我們看這個任務的控制代碼是可用的

二、刪除任務:

2.1、引數簡介

2.2、使用簡介

下面是官方例子:

  1. 在當前任務中,用xTaskCreate()建立另一個任務B
  2. 如果任務B建立成功,使用vTaskDelete(任務控制代碼)刪除掉任務B。
  3. 用vTaskDelete(NULL)刪除掉當前任務,目的是展示給我們看通過傳入NULL可以刪除當前任務

 

三、延時函式:

3.1、vTaskDelay()

3.1.1、函式簡介

xTaskDelay()

  • 讓呼叫這個函式的任務在一定時間內進入阻塞狀態,時間到達後會切換回來這個任務。
  • 如果輸入引數為0,那麼這個任務不會阻塞,但是會切換

*這個函式只有一個輸入引數,但需要注意一下它是以tick時鐘的中斷次數為單位的(並不是以毫秒為單位):

3.1.2、使用簡介

下面是官方的例子其中兩處vTaskDelay()

  1. 延時20個tick時間片
  2. 延時20ms。(pdMS_TOTICKS()可以把ms時間換成tick為單位)

 3.2、vTaskDelayUntil()

3.2.1、函式簡介

  • 讓任務進入阻塞狀態等待實際那到達,是精確的絕對時間
  • 週期性任務能夠使用vTaskDelayUntil()來達到連續的執行頻率

3.2.2、使用簡介

以下是官方的例子:

  1. 建立一個TickType_t型別的變數,用於記錄上一次系統時間
  2. 用pdMS_TO_TICKS()函式把50ms轉換為tick為單位,方便等下給vTaskDelayUntil呼叫
  3. 初始化第一步中的變數,在這一步後,這個變數不用再手動更新(vTaskDelayUntil()會更新它)
  4. 使用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、使用簡介:

官方的例子:

  1. 建立任務
  2. 開啟排程器,開啟後程序會跳轉到vATask()任務中

五、任務的掛起和恢復

5.1、vTaskSuspend() 和 vTaskResume()

5.1.1、函式簡介:

掛起/解除掛起單個任務:

  • vTaskSuspend的函式是讓指定的任務進入掛起狀態
  • xTaskResume的函式是讓指定的任務從掛起狀態換為就緒狀態
  • xTaskResumeFromISR()是xTaskResume()適合在中斷中呼叫的版本

5.1.2、使用簡介使用很簡單,當不需要用某個任務的時候用vTaskResume(控制代碼) 把那個任務掛起,需要用的時候再開啟就行了,下面是官方的例程,實現了這三步:

  1. 使用xTaskCreate()建立任務
  2. 建立成功的話使用vTaskSuspend()把剛剛建立的任務轉換為掛起狀態(該任務將不會再得到執行)
  3. 使用vTaskResume()讓剛剛掛起的任務轉為就緒狀態

5.2、vTaskSuspendAll()和vTaskResumeAll()

5.2.1、函式簡介:

vTaskSuspendAll()掛起排程器 對應 xTaskResumeAll()解除掛起排程器: • vTaskSuspendAll()掛起排程器後,只有當前任務在繼續執行,不會發生任務切換了。 • xTaskResumeAll()對應vTaskSuspendAll()恢復排程器。這個函式的作用之一在於,可以保證一些不能被分的程式執行,因為掛起排程器保證了不會被高優先順序的任務強調(注意排程器掛起後中斷還是可以執行的,如果要保證時效,還得把中斷關閉)

注意:vTaskSuspendAll()是可以遞迴呼叫的,這意味著呼叫了多少次vTaskSuspendAll(),就必須有多少此vTaskResumeAll()的呼叫才能讓排程器恢復。這個情況以下的例子中很好地體現了。

5.2.2、使用簡介

  1. 在任務vTask1中第一此呼叫vTaskSuspendAll(),此時排程器被掛起,不會發生任務切換
  2. 呼叫另一個用作例子的vDemoFunction()
  3. 第二次呼叫vTaskSuspendAll(),此時排程器再次被掛起,而且掛起計數增加到2
  4. 第一次呼叫vTaskResumeAll(),此時排程器掛起計數減少為1,但是排程器仍然處於掛起狀態
  5. 第二次呼叫vTaskResumeAll(),排程器計數為0,排程器恢復執行,後面會發生任務切換了

六、任務切換

6.1、函式簡介

  • 在一個執行的任務中呼叫taskYIELD(),那麼這個任務會被降級為就緒狀態,排程器會選擇另一個相同優先順序的就緒任務執行。(如果沒有相同優先順序的任務就緒,那麼這個任務將不會切換,會繼續執行。

 

6.2、使用簡介

我們來看官方例子:

  1. 在呼叫taskYIELD()後,vATask這個任務會馬上"讓步",進入就緒狀態等待,等待下次得到排程器排程的時候,會執行taskYIELD()下面的程式碼

 在下一節中,我們會繼續介紹task中的通知和其他內容