1 說明
本文僅作為學習FreeRTOS的記錄文件,作為初學者肯定很多理解不對甚至錯誤的地方,望網友指正。
1.1 簡介
FreeRTOS是一個RTOS(實時作業系統)系統,支援搶佔式、合作式和時間片排程。適用於微處理器或小型微處理器的實時應用。
本文件使用的FreeRTOS版本:FreeRTOS Kernel V10.4.1
參考文件:《FreeRTOS_Reference_Manual_V10.0.0.pdf》、《FreeRTOS_Real_Time_Kernel-A_Hands-On_Tutorial_Guide.pdf》、《STM32F4 FreeRTOS開發手冊_V1.1.pdf》
參考視訊:正點原子FreeRTOS手把手教學-基於STM32_嗶哩嗶哩_bilibili
1.2 測試工程說明
測試環境為在Linux進行模擬跑FreeRTOS,關於如何實現在Linux下的FreeRTOS模擬說明如下:
網上在linux下跑FreeRTOS的教程很多,這裡我直接check了一份別人已經建立了的程式碼。
程式碼路徑:freertos-simulator: FreeRTOS simulator on linux platform (gitee.com)
或者去官網,按照官網說明建立也可以。
官網:FreeRTOS simulator for Posix/Linux
編譯之前需要先安裝pcap開發包,不同的系統安裝方式也不同,官網也給出了安裝方式。
To install on ubuntu run
$ sudo apt-get install libpcap-dev
To install on rpm based system run
$ sudo yum install libpcap-devel
or
$ sudo dnf install libpcap-devel
To install on MacOS run
$ brew install libpcap
編譯方式:
進入simulator目錄,直接執行make即可。
執行方式:
$ ./build/freertos-simulator
2 FreeRTOS任務基礎知識
主要介紹任務管理。從以下幾個方面進行介紹:多工系統、任務狀態、任務優先順序、任務實現、任務控制塊、任務堆疊。
2.1 多工系統
FreeRTOS是一個搶佔式的實時多工系統。高優先順序的任務可以打斷低優先順序的任務而取得CPU的使用權。
2.2 任務的特性
任務是獨立的,每個任務都有自己的執行環境,不依賴於系統中的其他任務或者RTOS排程器。任何一個時間點只能有一個任務執行,具體執行哪個任務由排程器決定。RTOS排程器的職責是確保當一個任務開始執行的時候其上下文環境(暫存器、堆疊內容等)和任務上一次退出的時候相同。所以每個任務都有堆疊,當任務切換的時候將上下文環境存放在堆疊中,這樣當任務再次執行的時候可以從堆疊中取出上下文環境,任務恢復執行。
任務有以下特性:簡單、沒有使用限制、支援搶佔、支援優先順序、任務必須有堆疊、使用搶佔必須考慮重入的問題。
2.3 任務狀態
FreeRTOS中的任務永遠處在下面幾種狀態中的某一種。
1)執行態:當一個任務執行時,則該任務處於執行態,處於執行態的任務就是當前使用處理器的任務。單核處理器在任何時刻只有一個任務處於執行態。
2)就緒態:任務已經準備就緒可以執行,但還沒有執行,因為有一個同優先順序或者更高優先順序的任務正在執行。
3)阻塞態:任務當前正在等待某個外部事件,則任務處於阻塞態,比如說某個任務使用了vTaskDelay()函式的話就會進入阻塞態,直到延時週期完成。任務在等待佇列、訊號量、事件組、通知或互斥訊號量的時候也會進入阻塞態。任務進入阻塞態會有一個超時時間,當超過這個時間任務會退出阻塞態,即使所等待的事件還沒有來臨。
4)掛起態:任務進入掛起態後不能被排程器呼叫進入執行態,但是進入掛起態的任務沒有超時時間。
任務狀態之間的轉換如下圖所示:
2.4 任務優先順序
每個任務都可以分配一個從0~configMAX_PRIORITIES-1(定義在FreeRTOSConfig.h)的優先順序。優先順序數字越低表示任務的優先順序越低,0的優先順序最低,configMAX_PRIORITIES-1的優先順序最高。空閒任務的優先順序最低,為0。
FreeRTOS排程器確保處於就緒態或執行態最高優先順序的任務獲取處理器使用權。當巨集configUSE_TIME_SLICING(預設在FreeRTOS.h中定義)定義為1的時候多個任務可以共用一個優先順序,數量不限。此時處於就緒態的優先順序相同的任務就會使用時間片輪轉排程器獲取執行時間。
2.5 任務控制塊
FreeRTOS的每個任務都有一些屬性需要儲存,這些屬性集合在一個結構體來表示,這個結構體就叫任務控制塊:TCB_t,這個結構體定義在task.c,如下:
typedef struct tskTaskControlBlock /* The old naming convention is used to prevent breaking kernel aware debuggers. */
{
volatile StackType_t * pxTopOfStack; /*< Points to the location of the last item placed on the tasks stack. THIS MUST BE THE FIRST MEMBER OF THE TCB STRUCT. */
#if ( portUSING_MPU_WRAPPERS == 1 )
xMPU_SETTINGS xMPUSettings; /*< The MPU settings are defined as part of the port layer. THIS MUST BE THE SECOND MEMBER OF THE TCB STRUCT. */
#endif
ListItem_t xStateListItem; /*< The list that the state list item of a task is reference from denotes the state of that task (Ready, Blocked, Suspended ). */
ListItem_t xEventListItem; /*< Used to reference a task from an event list. */
UBaseType_t uxPriority; /*< The priority of the task. 0 is the lowest priority. */
StackType_t * pxStack; /*< Points to the start of the stack. */
char pcTaskName[ configMAX_TASK_NAME_LEN ]; /*< Descriptive name given to the task when created. Facilitates debugging only. */ /*lint !e971 Unqualified char types are allowed for strings and single characters only. */
#if ( ( portSTACK_GROWTH > 0 ) || ( configRECORD_STACK_HIGH_ADDRESS == 1 ) )
StackType_t * pxEndOfStack; /*< Points to the highest valid address for the stack. */
#endif
#if ( portCRITICAL_NESTING_IN_TCB == 1 )
UBaseType_t uxCriticalNesting; /*< Holds the critical section nesting depth for ports that do not maintain their own count in the port layer. */
#endif
#if ( configUSE_TRACE_FACILITY == 1 )
UBaseType_t uxTCBNumber; /*< Stores a number that increments each time a TCB is created. It allows debuggers to determine when a task has been deleted and then recreated. */
UBaseType_t uxTaskNumber; /*< Stores a number specifically for use by third party trace code. */
#endif
#if ( configUSE_MUTEXES == 1 )
UBaseType_t uxBasePriority; /*< The priority last assigned to the task - used by the priority inheritance mechanism. */
UBaseType_t uxMutexesHeld;
#endif
#if ( configUSE_APPLICATION_TASK_TAG == 1 )
TaskHookFunction_t pxTaskTag;
#endif
#if ( configNUM_THREAD_LOCAL_STORAGE_POINTERS > 0 )
void * pvThreadLocalStoragePointers[ configNUM_THREAD_LOCAL_STORAGE_POINTERS ];
#endif
#if ( configGENERATE_RUN_TIME_STATS == 1 )
uint32_t ulRunTimeCounter; /*< Stores the amount of time the task has spent in the Running state. */
#endif
#if ( configUSE_NEWLIB_REENTRANT == 1 )
/* Allocate a Newlib reent structure that is specific to this task.
* Note Newlib support has been included by popular demand, but is not
* used by the FreeRTOS maintainers themselves. FreeRTOS is not
* responsible for resulting newlib operation. User must be familiar with
* newlib and must provide system-wide implementations of the necessary
* stubs. Be warned that (at the time of writing) the current newlib design
* implements a system-wide malloc() that must be provided with locks.
*
* See the third party link http://www.nadler.com/embedded/newlibAndFreeRTOS.html
* for additional information. */
struct _reent xNewLib_reent;
#endif
#if ( configUSE_TASK_NOTIFICATIONS == 1 )
volatile uint32_t ulNotifiedValue[ configTASK_NOTIFICATION_ARRAY_ENTRIES ];
volatile uint8_t ucNotifyState[ configTASK_NOTIFICATION_ARRAY_ENTRIES ];
#endif
/* See the comments in FreeRTOS.h with the definition of
* tskSTATIC_AND_DYNAMIC_ALLOCATION_POSSIBLE. */
#if ( tskSTATIC_AND_DYNAMIC_ALLOCATION_POSSIBLE != 0 ) /*lint !e731 !e9029 Macro has been consolidated for readability reasons. */
uint8_t ucStaticallyAllocated; /*< Set to pdTRUE if the task is a statically allocated to ensure no attempt is made to free the memory. */
#endif
#if ( INCLUDE_xTaskAbortDelay == 1 )
uint8_t ucDelayAborted;
#endif
#if ( configUSE_POSIX_ERRNO == 1 )
int iTaskErrno;
#endif
} tskTCB;
2.6 任務堆疊
FreeRTOS 之所以能正確的恢復一個任務的執行就是因為有任務堆疊在保駕護航,任務調
度器在進行任務切換的時候會將當前任務的現場(CPU 暫存器值等)儲存在此任務的任務堆疊中,
等到此任務下次執行的時候就會先用堆疊中儲存的值來恢復現場,恢復現場以後任務就會接著
從上次中斷的地方開始執行。
建立任務的時候需要給任務指定堆疊,如果使用的函式 xTaskCreate()建立任務(動態方法)
的話那麼任務堆疊就會由函式 xTaskCreate()自動建立,後面分析 xTaskCreate()的時候會講解 。
堆疊大小:任務堆疊的資料型別為 StackType_t, StackType_t 本質上是 unsigned long,在 portmacro.h 中有定義。
#define portSTACK_TYPE unsigned long
typedef portSTACK_TYPE StackType_t;
可以看出 StackType_t 型別的變數為 4 個位元組,那麼任務的實際堆疊大小就應該是我們所定義的 4 倍(32位處理器)或8倍(64位處理器) 。
3 任務相關API函式
任務相關函式如下:
任務建立和刪除API函式
任務建立和刪除實驗(動態方法)
任務建立和刪除實驗(靜態方法)
任務掛起和恢復API函式
任務掛起和恢復實驗
3.1 任務建立API函式(動態方法)
函式原型:
#include "FreeRTOS.h"
#include "task.h"
BaseType_t xTaskCreate( TaskFunction_t pxTaskCode,
const char * const pcName,
const configSTACK_DEPTH_TYPE usStackDepth,
void * const pvParameters,
UBaseType_t uxPriority,
TaskHandle_t * const pxCreatedTask );
描述:使用動態方法建立一個任務,任務控制塊和任務堆疊在函式內建立。最新建立的任務初始化為就緒態,如果當前沒有更高優先順序的任務執行,則立刻變為執行態。
函式引數說明:
引數名 | 說明 |
---|---|
pxTaskCode | 任務函式,通常為一個無限迴圈。 |
pcName | 任務名字,名稱長度有限制,在FreeRTOSConfig.h中有定義configMAX_TASK_NAME_LEN。 |
usStackDepth | 任務堆疊大小,實際申請到的堆疊是usStackDepth的4倍。configMINIMAL_STACK_SIZE定義的是空閒任務堆疊大小。 |
pvParameters | 傳遞給任務函式的引數 |
uxPriority | 任務優先順序,範圍0~configMAX_PRIORITIES-1。 |
pxCreatedTask | 任務控制程式碼,任務建立成功以後會返回此任務的任務控制程式碼, 這個控制程式碼其實就是 任務的任務堆疊。 此引數就用來儲存這個任務控制程式碼。其他 API 函式可能會使 用到這個控制程式碼。如果任務控制程式碼不需要使用,可以被設定為NULL。 |
返回值:
pdPASS:任務建立成功。
errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY: 任務建立失敗,因為堆記憶體不足!
3.2 任務建立函式(靜態方法)
函式原型:
#include "FreeRTOS.h"
#include "task.h"
TaskHandle_t xTaskCreateStatic( TaskFunction_t pxTaskCode,
const char * const pcName,
const uint32_t ulStackDepth,
void * const pvParameters,
UBaseType_t uxPriority,
StackType_t * const puxStackBuffer,
StaticTask_t * const pxTaskBuffer );
描述:使用靜態方法建立一個任務。任務所需要的RAM需要使用者來提供。
函式引數說明:
引數名 | 說明 |
---|---|
pxTaskCode | 任務函式,通常為一個無限迴圈。 |
pcName | 任務名字,名稱長度有限制,在FreeRTOSConfig.h中有定義configMAX_TASK_NAME_LEN。 |
usStackDepth | 任務堆疊大小,靜態建立任務的堆疊由使用者給出,通常為一個陣列,這個引數就是陣列的大小。 |
pvParameters | 傳遞給任務函式的引數 |
uxPriority | 任務優先順序,範圍0~configMAX_PRIORITIES-1。 |
puxStackBuffer | 任務堆疊,一般為陣列,陣列型別為StackType_t型別。 |
pxTaskBuffer | 任務控制塊 |
返回值:
NULL:任務建立失敗,puxStackBuffer或pxTaskBuffer為空。
其他值: 任務建立成功,返回任務的任務控制程式碼。
3.3 任務刪除API函式
函式原型:
void vTaskDelete( TaskHandle_t xTaskToDelete ) PRIVILEGED_FUNCTION;
描述:刪除任務,刪除之後的任務不再存在,也不能再使用此函式的控制程式碼。如果任務使用的是xTaskCreate()建立的,此任務被刪除後此任務之前申請的堆疊和控制塊記憶體會在任務中被釋放掉。
函式引數:xTaskToDelete要刪除的任務的任務控制程式碼。
返回值:無
3.4 任務建立和刪除實驗(動態方法)
建立兩個任務,這兩個任務的功能如下:
task00:此任務每個1000ms列印一次字串,呼叫5次之後呼叫vTaskDelete()函式刪除task01。
task01:此任務為普通任務,間隔500ms列印一次字串,和task00同樣的任務優先順序。
task00任務建立程式碼:
configSTACK_DEPTH_TYPE Task00_STACK_SIZE = 5;
UBaseType_t Task00_Priority = 1;
TaskHandle_t Task00_xHandle;
void vTask00_Code(void *para)
{
static unsigned int cnt = 0;
for (;;)
{
PRINT(" task00 cnt %u...", cnt);
if (cnt == 4)
vTaskDelete(Task01_xHandle);
cnt++;
vTaskDelay(1000);
}
}
xTaskCreate(vTask00_Code, "task00", Task00_STACK_SIZE, NULL, Task00_Priority, &Task00_xHandle);
task01任務建立程式碼:
configSTACK_DEPTH_TYPE Task01_STACK_SIZE = 5;
UBaseType_t Task01_Priority = 1;
TaskHandle_t Task01_xHandle;
void vTask01_Code(void *para)
{
static unsigned int cnt = 0;
for (;;)
{
PRINT(" task01 cnt %u...", cnt);
cnt++;
vTaskDelay(500);
}
}
xTaskCreate(vTask01_Code, "task01", Task01_STACK_SIZE, NULL, Task01_Priority, &Task01_xHandle);
編譯,執行,測試結果符合預期,task00執行5秒之後刪除了task01:
3.5 任務建立和刪除實驗(靜態方法)
使用靜態方式建立一個任務,該任務每個1秒列印一個字串:
#define STATIC_STACK_SIZE 5
UBaseType_t Static_Task_Priority = 1;
StaticTask_t Static_xTaskBuffer;
StackType_t Static_xStack[STATIC_STACK_SIZE];
TaskHandle_t Static_xhandle = NULL; //任務控制程式碼
void static_task_code(void *para)
{
static unsigned int cnt = 0;
for (;;)
{
PRINT(" static task cnt %u...", cnt);
cnt++;
vTaskDelay(1000);
}
}
Static_xhandle = xTaskCreateStatic (static_task_code,
"static task",
STATIC_STACK_SIZE,
NULL,
Static_Task_Priority,
Static_xStack,
&Static_xTaskBuffer);
3.6 vTaskDelay()
函式原型:
#include "FreeRTOS.h"
#include "task.h"
void vTaskDelay( const TickType_t xTicksToDelay );
描述:呼叫該函式的任務將進入阻塞態,中斷一段固定的時鐘週期。
函式引數:xTicksToDelay表示呼叫函式的任務的阻塞態保持時間。延時達到之後將進入就緒態。例如:當時鍾計數到10000時,函式呼叫了vTaskDelay(100),然後任務進入阻塞態,並且保持阻塞態直到時鐘計數到10100。
巨集pdMS_TO_TICKS()可以被使用來延時毫秒。例如:呼叫vTaskDelay( pdMS_TO_TICKS(100) ),任務將進入阻塞態100毫秒。