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

posted @ 2018-11-03 16:39 HongYi_Liang 閱讀(...) 評論(...) 編輯 收藏
耐克篮球多少钱