以下內容轉載自安富萊電子:http://forum.armfly.com/forum.php
了解任務管理的目的就是讓初學者從裸機的,單任務編程過渡到帶 OS 的,多任務編程上來。搞清楚了這一點,那么 FreeRTOS 學習就算入門了。
1 單 任 務 系統
學習多任務系統之前,我們先來回顧下單任務系統的編程框架,即裸機時的編程框架。裸機編程主要是采用超級循環(super-loops)系統,又稱前后台系統。應用程序是一個無限的循環,循環中調用相應的函數完成相應的操作,這部分可以看做后台行為;中斷服務程序處理異步事件,這部分可以看做是前台行為。后台也可以叫做任務級,前台也叫作中斷級。
對於前后台系統的編程思路主要有以下兩種方式:
1.1 查詢方式
對於一些簡單的應用,處理器可以查詢數據或者消息是否就緒,就緒后進行處理,然后再等待,如此循環下去。對於簡單的任務,這種方式簡單易處理。但大多數情況下,需要處理多個接口數據或者消息,那就需要多次處理,如下面的流程圖所示:
用查詢方式處理簡單的應用,效果比較好,但是隨着工程的復雜,采用查詢方式實現的工程就變得很難維護,同時,由於無法定義查詢任務的優先級,這種查詢方式會使得重要的接口消息得不到及時響應。比如程序一直在等待一個非緊急消息就緒,如果這個消息后面還有一個緊急的消息需要處理,那么就會使得緊急消息長時間得不到執行。
1.2 中斷方式
對於查詢方式無法有效執行緊急任務的情況,采用中斷方式就有效地解決了這個問題,下面是中斷方式簡單的流程圖:
采用中斷和查詢結合的方式可以解決大部分裸機應用,但隨着工程的復雜,裸機方式的缺點就暴露出來了:
2 多 任 務 系統
針對這些情況,使用多任務系統就可以解決這些問題了。下面是一個多任務系統的流程圖:
多任務系統或者說 RTOS 的實現,重點就在這個調度器上,而調度器的作用就是使用相關的調度算法來決定當前需要執行的任務。如上圖所示的那樣,創建了任務並完成 OS 初始化后,就可以通過調度器來決定任務 A,任務 B 和任務 C 的運行,從而實現多任務系統。另外需要初學者注意的是,這里所說的多任務系統同一時刻只能有一個任務可以運行,只是通過調度器的決策,看起來像所有任務同時運行一樣。為了更好的說明這個問題,再舉一個詳細的運行例子,運行條件如下:
下圖 10.2 所示是任務的運行過程,其中橫坐標是任務優先級由低到高排列,縱坐標是運行時間,時間刻度有小到大。
通過上面實例的講解,大家應該對多任務系統完整的運行過程有了一個全面的認識。隨着教程后面對調度器,任務切換等知識點的講解,大家會對這個運行過程有更深刻的理解。
FreeRTOS 就是一款支持多任務運行的實時操作系統,具有時間片,搶占式和合作式三種調度方法。通過 FreeRTOS 實時操作系統可以將程序函數分成獨立的任務,並為其提供合理的調度方式。
3 .FreeRTOS的 任 務 棧 設置
不管是裸機編程還是 RTOS 編程,棧的分配大小都非常重要。局部變量,函數調用時的現場保護和返回地址,函數的形參,進入中斷函數前和中斷嵌套等都需要棧空間,棧空間定義小了會造成系統崩潰。
裸機的情況下,可以在這里配置棧大小:
STM32F429 工程中棧大小的配置文件
不同於裸機編程,在 RTOS 下,每個任務都有自己的棧空間。對於 FreeRTOS 來說,任務棧空間是在任務創建的時候從 FreeRTOSConfig.h 文件中定義的 heap 空間中申請的
#define configTOTAL_HEAP_SIZE ( ( size_t ) ( 75 * 1024 ) ) /* 堆空間大小,內核在創建各種對象時需要用到,單位為字節 */
具體每個任務的棧大小是在創建 FreeRTOS 的任務時進行設置的:
4.FreeRTOS 的 系 統 棧 設置
上面講解了什么是任務棧,這里的系統棧又是什么呢?裸機的情況下,凡是用到棧空間的地方都是在這里配置的棧空間:
在 RTOS 下,上面兩個截圖中設置的棧大小有了一個新的名字叫系統棧空間,而任務棧是不使用這里的空間的。任務棧不使用這里的棧空間,哪里使用這里的棧空間呢?答案就在中斷函數和中斷嵌套。
對於這個問題,簡單的描述如下,
* * 由於 Cortex-M3 和 M4 內核具有雙堆棧指針,MSP 主堆棧指針和 PSP 進程堆棧指針,或者叫 PSP任務堆棧指針也是可以的。在 FreeRTOS 操作系統中,主堆棧指針 MSP 是給系統棧空間使用的,進程堆棧指針 PSP 是給任務棧使用的。也就是說,在 FreeRTOS 任務中,所有棧空間的使用都是通過PSP 指針進行指向的。一旦進入了中斷函數以及可能發生的中斷嵌套都是用的 MSP 指針。這個知識點要記住它,當前可以不知道這是為什么,但是一定要記住。
* * 實際應用中系統棧空間分配多大,主要是看可能發生的中斷嵌套層數,下面我們就按照最壞執行情況進行考慮,所有的寄存器都需要入棧,此時分為兩種情況:
5、FreeRTOS的 任 務 狀 態
下面是任務在各個狀態之間切換的關系圖,通過這個圖,基本可以對任務的運行狀態有了一個整體的認識。在后面章節的學習中會對各個狀態有一個深入的認識。
6 、FreeRTOS 啟 動
使用如下函數即可啟動 FreeRTOS:vTaskStartScheduler();
關於這個函數的講解及其使用方法可以看 FreeRTOS 在線版手冊:
7 、FreeRTOS 的 任務 創建
使用如下函數可以實現 FreeRTOS 的任務創建:xTaskCreate()
8、FreeRTOS 的 任 務 刪除
使用如下函數可以實現 FreeRTOS 的任務刪除:vTaskDelete()
9、FreeRTOS 的 任 務 掛 起
使用如下函數可以實現 FreeRTOS 的任務掛起:xTaskSuspend()
10、FreeRTOS 的 任務 恢復
使用如下函數可以實現 FreeRTOS 的任務恢復:xTaskResume()
11 、FreeRTOS 的 任 務 恢 復(中 斷 方 式)
使用如下函數可以實現 FreeRTOS 的任務恢復(中斷方式):xTaskResumeFromISR()
12 、FreeRTOS 的 空 閑 任 務
幾乎所有的小型 RTOS 中都會有一個空閑任務,空閑任務屬於系統任務,是必須要執行的,用戶程序不能將其關閉。不光小型系統中有空閑任務,大型的系統里面也有的,比如 WIN7,下面的截圖就是WIN7 中的空閑進程。
空閑任務主要有以下幾個作用:
用戶不能讓系統一直在執行各個應用任務,這樣的話系統利用率就是 100%,系統就會一直超負荷運行,所以空閑任務很有必要。
為了更好的實現低功耗,空閑任務也很有必要,用戶可以在空閑任務中實現睡眠,停機等低功耗措施。
FreeRTOS 任務調試信息(按 K1 按鍵,串口打印):
按下K2后LED任務被刪除:
K2的中斷函數:
void KEY2_IRQHandler(void) { //確保是否產生了EXTI Line中斷 if(EXTI_GetITStatus(KEY2_INT_EXTI_LINE) != RESET) { ucKeyCode = 2; EXTI_ClearITPendingBit(KEY2_INT_EXTI_LINE); } }
vTaskTaskUserIF()函數實現。
static void vTaskTaskUserIF(void *pvParameters) { uint8_t pcWriteBuffer[500]; while(1) { if(ucKeyCode != 0) { switch( ucKeyCode) { /* K1鍵按下 打印任務執行情況 */ case 1: printf("=================================================\r\n"); printf("任務名 任務狀態 優先級 剩余棧 任務序號\r\n"); vTaskList((char *)&pcWriteBuffer); printf("%s\r\n", pcWriteBuffer); printf("\r\n任務名 運行計數 使用率\r\n"); vTaskGetRunTimeStats((char *)&pcWriteBuffer); printf("%s\r\n", pcWriteBuffer); ucKeyCode = 0; break; /* K2鍵按下 刪除任務vTaskLED */ case 2: printf("K2鍵按下,刪除任務vTaskLED\r\n"); if(xHandleTaskLED1 != NULL) { vTaskDelete(xHandleTaskLED1); xHandleTaskLED1 = NULL; } ucKeyCode = 0; break; /* 其他的鍵值不處理 */ default: break; } vTaskDelay(20); } } }