FreeRtos——多任務


官方資料整理測試:

多任務和單任務幾乎沒有差別。只用多創建一個或多個任務,其他地方和單任務時相同。

static void AppTaskCreate(void)
{
    xTaskCreate(vTaskLed1,           /* 任務函數名 */
                "Task Led1",         /* 任務名,字符串形式,方便調試 */
                 512,                /* 棧大小,單位為字,即4個字節 */
                 (void *)&task_led3,  // task_led1-task_led3可以切換   /* 任務形參 */
                 1,                  /* 優先級,數值越大,優先級越高 */
                 &xHandleTaskLED1);  /* 任務句柄 */
                             
    xTaskCreate(
                             vTaskBeep,
                             "Task Beep",
                             512,
                             NULL,
                             2,
                             &xHandleTaskBeep);
}

這里采用一個任務做LED燈閃爍,一個任務做蜂鳴器,可以看到兩個任務展現出似乎同時運行的現象。

但是,其中有一個地方需要測試

我們知道裸機中,優先級設置一樣時,會看硬件編號,小的優先級高。

freertos中,優先級數值越大的,優先級越高,那么,要是設置成優先級相同,那是以什么方式運行的呢?這個等到下一章,講串口調試時我們來驗證。

/*2017.6.22*/在官方手冊找到了說明。

任務優先級
xTaskCreate() API 函數的參數 uxPriority 為創建的任務賦予了一個初始優先級。這
個侁先級可以在調度器啟動后調用 vTaskPrioritySet() API 函數進行修改。
應 用 程 序 在 文 件 FreeRTOSConfig.h 中 設 定 的 編 譯 時 配 置 常 量
configMAX_PRIORITIES 的值,即是最多可具有的優先級數目。 FreeRTOS 本身並沒
有限定這個常量的最大值,但這個值越大,則內核花銷的內存空間就越多。所以總是建
議將此常量設為能夠用到的最小值。
對於如何為任務指定優先級, FreeRTOS 並沒有強加任何限制。任意數量的任務可
以共享同一個優先級——以保證最大設計彈性。當然,如果需要的話,你也可以為每個
任務指定唯一的優先級(就如同某些調度算法的要求一樣),但這不是強制要求的。
低優先級號表示任務的優先級低,優先級號 0 表示最低優先級。有效的優先級號范
圍從 0 (configMAX_PRIORITES – 1)

調度器保證總是在所有可運行的任務中選擇具有最高優先級的任務,並使其進入運
行態。如果被選中的優先級上具有不止一個任務,調度器會讓這些任務輪流執行。這種
行為方式在之前的例子中可以明顯看出來。兩個測試任務被創建在同一個優先級上,並
且一直是可運行的。所以每個任務都執行一個時間片,任務在時間片起始時刻進入運
行態,在時間片結束時刻又退出運行態。  3 t1 t2 之間的時段就等於一個時間片。

 

要能夠選擇下一個運行的任務,調度器需要在每個時間片的結束時刻運行自己本
身。一個稱為心跳(tick,有些地方被稱為時鍾滴答, 本文中一律稱為時鍾心跳)中斷的
周期性中斷用於此目的。時間片的長度通過心跳中斷的頻率進行設定,心跳中斷頻率由
FreeRTOSConfig.h 中的編譯時配置常量 configTICK_RATE_HZ 進行配置。比如說,
如果 configTICK_RATE_HZ 設為 100(HZ),則時間片長度為 10ms。可以將圖 3 進行
擴展,將調度器本身的執行時間在整個執行流程中體現出來。請參見圖 4
需要說明的是, FreeRTOS API 函數調用中指定的時間總是以心跳中斷為單位(通
常的提法為心跳”ticks”)。常量 portTICK_RATE_MS 用於將以心跳為單位的時間值轉化
為以毫秒為單位的時間值。有效精度依賴於系統心跳頻率。
心跳計數(tick count)值表示的是從調度器啟動開始,心跳中斷的總數,並假定心跳

計數器不會溢出。用戶程序在指定延遲周期時不必考慮心跳計數溢出問題,因為時間連
貫性在內核中進行管理。

調度器總是在可運行的任務中,選擇具有最高優級級的任務,並使其進入運行態。
到目前為止的示例程序中,兩個任務都創建在相同的優先級上。所以這兩個任務輪番進
入和退出運行態。本例將改變例 2 其中一個任務的優先級,看一下倒底會發生什么。現
在第一個任務創建在優先級 1 上,而另一個任務創建在優先級 2 上。創建這兩個任務的
代碼參見程序清單 10。這兩個任務的實現函數沒有任何改動,還是通過空循環產生延
遲來周期性打印輸出字符串 。

調度器總是選擇具有最高優先級的可運行任務來執行。任務 2 的優先級比任務 1
高,並且總是可運行,因此任務 2 是唯一一個一直處於運行態的任務。而任務 1 不可能
進入運行態,所以不可能輸出字符串。這種情況我們稱為任務 1 的執行時間被任務 2”
餓死(starved)”了。
任務 2 之所以總是可運行,是因為其不會等待任何事情——它要么在空循環里打
轉,要么往終端打印字符串。

 

這里顯示了為什么不加freertos的延時函數就無法發生輪轉。

注意,在優先級設置相同的情況下,不使用事件驅動(例如freertos的延時函數,即阻塞),多任務也是可以輪轉的,此時采用的時間輪轉片切換任務。

 

/******END*****/

擴充“非運行態”
到目前為止所有用到的示例中,創建的每個任務都只顧不停地處理自己的事情而沒
有其它任何事情需要等待——由於它們不需要等待所以總是能夠進入運行態。這種
停處理類型的任務限制了其有用性,因為它們只可能被創建在最低優先級上。如果它
們運行在其它任何優先級上,那么比它們優先級更低的任務將永遠沒有執行的機會。
為了使我們的任務切實有用,我們需要通過某種方式來進行事件驅動。一個事件驅
動任務只會在事件發生后觸發后工作(處理),而在事件沒有發生時是不能進入運行態的。
調度器總是選擇所有能夠進入運行態的任務中具有最高優先級的任務。一個高優先級但
不能夠運行的任務意味着不會被調度器選中,而代之以另一個優先級雖然更低但能夠運
行的任務。因此,采用事件驅動任務的意義就在於任務可以被創建在許多不同的優先級
上,並且最高優先級任務不會把所有的低優先級任務餓死。

阻塞狀態
如果一個任務正在等待某個事件,則稱這個任務處於阻塞態(blocked)”。阻塞態是
非運行態的一個子狀態。
任務可以進入阻塞態以等待以下兩種不同類型的事件:
1. 定時(時間相關)事件——這類事件可以是延遲到期或是絕對時間到點。比如
說某個任務可以進入阻塞態以延遲 10ms
2. 同步事件——源於其它任務或中斷的事件。比如說,某個任務可以進入阻塞
態以等待隊列中有數據到來。同步事件囊括了所有板級范圍內的事件類型。
FreeRTOS 的隊列,二值信號量,計數信號量,互斥信號量(recursive semaphore,
遞歸信號量,本文一律稱為互斥信號量, 因為其主要用於實現互斥訪問)和互斥量都可
以用來實現同步事件。
任務可以在進入阻塞態以等待同步事件時指定一個等待超時時間,這樣可以有效地
實現阻塞狀態下同時等待兩種類型的事件。比如說,某個任務可以等待隊列中有數據到
來,但最多只等 10ms。如果 10ms 內有數據到來,或是 10ms 過去了還沒有數據到來,
這兩種情況下該任務都將退出阻塞態。
掛起狀態
掛起(suspended)”也是非運行狀態的子狀態。處於掛起狀態的任務對調度器而言
是不可見的。讓一個任務進入掛起狀態的唯一辦法就是調用 vTaskSuspend() API 函數;
而 把 一 個 掛 起 狀 態 的 任 務 喚 醒 的 唯 一 途 徑 就 是 調 用 vTaskResume()
vTaskResumeFromISR() API 函數。大多數應用程序中都不會用到掛起狀態。
就緒狀態
如果任務處於非運行狀態,但既沒有阻塞也沒有掛起,則這個任務處於就緒(ready
准備或就緒)狀態。處於就緒態的任務能夠被運行,但只是准備(ready)”運行,而當前
尚未運行。

 

通過調用 vTaskDelay() API 函數來代替空循環,對這種不良行為
進行糾正。 vTaskDelay()的函數原型見程序清單 11,而新的任務實現見程序清單 12

 

 

 有了這個函數之后,就可以在優先級不同的任務間切換了。

其中idle是空閑任務,因為首字母大寫了,有點誤導成小寫l。

vTaskDelayUntil() API 函數
vTaskDelayUntil()類似於 vTaskDelay()。和范例中演示的一樣,函數 vTaskDelay()
的參數用來指定任務在調用 vTaskDelay()到切出阻塞態整個過程包含多少個心跳周期。
任務保持在阻塞態的時間量由 vTaskDelay()的入口參數指定,但任務離開阻塞態的時刻
實際上是相對於 vTaskDelay()被調用那一刻的。 vTaskDelayUntil()的參數就是用來指定
任務離開阻塞態進入就緒態那一刻的精確心跳計數值。 API 函數 vTaskDelayUntil()可以
用於實現一個固定執行周期的需求(當你需要讓你的任務以固定頻率周期性執行的時
)。由於調用此函數的任務解除阻塞的時間是絕對時刻,比起相對於調用時刻的相對
時間更精確(即比調用 vTaskDelay()可以實現更精確的周期性)
void vTaskDelayUntil( portTickType * pxPreviousWakeTime, portTickType xTimeIncrement );
程序清單 13 vTaskDelayUntil() API 函數原型

 

 上個例子中的兩個任務是周期性任務,但是使用 vTaskDelay()無法保證它們具有固定的
執行頻率,因為這兩個任務退出阻塞態的時刻相對於調用 vTaskDelay()的時刻。通過調
vTaskDelayUntil()代替 vTaskDelay(),把這兩個任務進行轉換,以解決這個潛在的問
題。

 

void vTaskFunction( void *pvParameters )
{
char *pcTaskName;
portTickType xLastWakeTime;
/* The string to print out is passed in via the parameter. Cast this to a
character pointer. */
pcTaskName = ( char * ) pvParameters;
/* 變量xLastWakeTime需要被初始化為當前心跳計數值。說明一下,這是該變量唯一一次被顯式賦值。之后,
xLastWakeTime將在函數vTaskDelayUntil()中自動更新。 */
xLastWakeTime = xTaskGetTickCount();
/* As per most tasks, this task is implemented in an infinite loop. */
for( ;; )
{
/* Print out the name of this task. */
vPrintString( pcTaskName );
/* 本任務將精確的以250毫秒為周期執行。同vTaskDelay()函數一樣,時間值是以心跳周期為單位的,
可以使用常量portTICK_RATE_MS將毫秒轉換為心跳周期。變量xLastWakeTime會在
vTaskDelayUntil()中自動更新,因此不需要應用程序進行顯示更新。 */
vTaskDelayUntil( &xLastWakeTime, ( 250 / portTICK_RATE_MS ) );
}
}
程序清單 14 使用 vTaskDelayUntil()實現示例任務

 

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM