說明
本文僅作為學習FreeRTOS的記錄文檔,作為初學者肯定很多理解不對甚至錯誤的地方,望網友指正。
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
6 內核控制函數
內核控制函數就是FreeRTOS內核所使用的函數,一般情況下應用程序不使用這些函數。
官網API說明:FreeRTOS - Task Control Functions and Macros for the Free Open Source RTOS FreeRTOS
6.1 任務切換
函數原型:
#include “FreeRTOS.h”
#include “task.h”
void taskYIELD( void );
函數描述:任務切換函數。如果沒有與當前任務同等優先級或高優先級的任務,則任務調度器會選擇當前任務繼續運行。必須在調度器初始化之后使用。
函數參數:無
返回值:無
測試代碼:創建兩個任務,任務task0優先級為2,任務函數中每次從0計數到2進行一次任務切換;任務task1優先級也為2,任務函數中每次打印之后就掛起自己。
configSTACK_DEPTH_TYPE Task0_STACK_SIZE = 5;
UBaseType_t Task0_Priority = 2;
TaskHandle_t Task0_xhandle;
configSTACK_DEPTH_TYPE Task1_STACK_SIZE = 5;
UBaseType_t Task1_Priority = 2;
TaskHandle_t Task1_xhandle;
void task0_code(void *para)
{
unsigned int i = 0;
for (;;)
{
for (i = 0; i < 4; i++) {
PRINT(" task0 cnt %u...", i);
if (i == 2) {
vTaskResume(Task1_xhandle);
taskYIELD();
}
}
vTaskDelay(2000);
}
}
void task1_code(void *para)
{
static unsigned int cnt = 0;
for (;;)
{
PRINT(" task1 cnt %u...", cnt);
cnt++;
vTaskSuspend(Task1_xhandle);
}
}
void creat_task(void)
{
taskENTER_CRITICAL();
if (xTaskCreate(task0_code, "task0 task",
Task0_STACK_SIZE, NULL, Task0_Priority,
&Task0_xhandle) != pdPASS)
{
PRINT("creat task failed!\n");
}
if (xTaskCreate(task1_code, "task1 task",
Task1_STACK_SIZE, NULL, Task1_Priority,
&Task1_xhandle) != pdPASS)
{
PRINT("creat task failed!\n");
}
taskEXIT_CRITICAL();
vTaskStartScheduler();
}
編譯、運行,結果符合預期,每次調度taskYIELD()之后執行一次任務切換,結果如下:
$ ./build/freertos-simulator
task0 cnt 0...
task0 cnt 1...
task0 cnt 2...
task1 cnt 0...
task0 cnt 3...
task0 cnt 0...
task0 cnt 1...
task0 cnt 2...
task1 cnt 1...
task0 cnt 3...
現在將任務task0的優先級改為3,大於任務task1的優先級:
UBaseType_t Task0_Priority = 3;
編譯、運行,結果符合預期,每次調度taskYIELD()之后不會任務切換,結果如下:
$ ./build/freertos-simulator
task0 cnt 0...
task0 cnt 1...
task0 cnt 2...
task0 cnt 3...
task1 cnt 0...
task0 cnt 0...
task0 cnt 1...
task0 cnt 2...
task0 cnt 3...
task1 cnt 1...
task0 cnt 0...
6.2 進入臨界區
函數原型:
#include “FreeRTOS.h”
#include “task.h”
void taskENTER_CRITICAL( void );
函數描述:進入臨界區,不能在中斷服務函數中調用。中斷服務函數中調用taskENTER_CRITICAL_FROM_ISR()進入臨界區。
函數參數:無
返回值:無
6.3 退出臨界區
函數原型:
#include “FreeRTOS.h”
#include “task.h”
void taskEXIT_CRITICAL( void );
函數描述:退出臨界區,不能在中斷服務函數中調用。中斷服務函數中調用taskEXIT_CRITICAL_FROM_ISR()退出臨界區。
函數參數:無
返回值:無
taskENTER_CRITICAL()和taskEXIT_CRITICAL()函數用於臨界段代碼保護(任務級)。
taskENTER_CRITICAL_FROM_ISR()和taskEXIT_CRITICAL_FROM_ISR()函數用於中斷級臨界段代碼保護。
臨界段代碼:也叫做臨界區,指那些必須完整運行,不能被打斷的代碼。比如某些外設的初始化。FreeRTOS在進入臨界段代碼的時候需要關閉中斷,當處理完臨界段代碼以后再打開中斷。
6.4 關閉可屏蔽中斷
函數原型:
#include “FreeRTOS.h”
#include “task.h”
void taskDISABLE_INTERRUPTS( void );
函數描述:關閉可屏蔽中斷。不可嵌套使用。
函數參數:無
返回值:無
6.4 打開可屏蔽中斷
函數原型:
#include “FreeRTOS.h”
#include “task.h”
void taskENABLE_INTERRUPTS( void );
函數描述:關閉可屏蔽中斷。不可嵌套使用。
函數參數:無
返回值:無
6.5 啟動任務調度器
函數原型:
#include “FreeRTOS.h”
#include “task.h”
void vTaskStartScheduler( void );
函數描述:啟動任務調度器。典型應用為:main()函數先於調度器使用,調度器啟動之后,執行任務及中斷函數。調度器啟動之后將選擇優先級最高的任務進行執行。調度器啟動之后,空閑任務(Idle task)將自動被創建。
函數參數:無
返回值:無
6.5 關閉任務調度器
函數原型:
#include “FreeRTOS.h”
#include “task.h”
void vTaskEndScheduler( void );
函數描述:關閉任務調度器。僅支持x86架構處理器。關閉任務調度器之后,內核時鍾將停止計數,所有創建的任務都會自動刪除。
函數參數:無
返回值:無
7 時間管理
FreeRTOS延時函數分為相對模式和絕對模式。vTaskDelay()是相對延時函數。vTaskDelayUntil()是絕對延時函數。
7.1 相對延時
函數原型:
#include "FreeRTOS.h"
#include "task.h"
void vTaskDelay( const TickType_t xTicksToDelay );
函數描述:調用該函數的任務將進入阻塞態,中斷一段固定的時鍾周期。使用這個函數必須將宏INCLUDE_vTaskDelay置1。
函數參數:xTicksToDelay表示調用函數的任務的阻塞態保持時間,單位為時鍾節拍數。真正的延時時間取決於時鍾節拍頻率。宏 portTICK_PERIOD_MS被用來根據時鍾節拍數來計算一個時鍾節拍的延時周期。
#define portTICK_PERIOD_MS ( ( TickType_t ) 1000 / configTICK_RATE_HZ )
#define configTICK_RATE_HZ ( 1000 )
可以看出,單個時鍾節拍計數時間為1ms,比如參數xTicksToDelay設為100,就表示延時100ms。
延時達到之后將進入就緒態。例如:當時鍾計數到10000時,函數調用了vTaskDelay(100),然后任務進入阻塞態,並且保持阻塞態直到時鍾計數到10100。
宏pdMS_TO_TICKS()可以被使用來延時毫秒。例如:調用vTaskDelay( pdMS_TO_TICKS(100) ),任務將進入阻塞態100毫秒。
如果參數xTicksToDelay為0,則等同於調用了一次taskYIELD()函數進行了一次任務切換。
返回值:無
7.2 絕對延時
函數原型:
#include “FreeRTOS.h”
#include “task.h”
void vTaskDelayUntil( TickType_t *pxPreviousWakeTime, TickType_t xTimeIncrement );
函數描述:調用該函數的任務將進入阻塞態直到一個絕對的時間到來。周期任務可以調用這個函數來實現一個固定的執行頻率。使用這個函數必須將宏INCLUDE_vTaskDelayUntil置1。
函數參數:pxPreviousWakeTime:上一次任務延時結束被喚醒的時間點,任務中第一次調用函數vTaskDelayUntil()需要將pxPreviousWakeTime初始化為進入任務的while()循環體的時間點值。在以后的運行中函數vTaskDelayUntil()會自動更新pxPreviousWakeTime。
xTimeIncrement:任務需要延時的節拍數(相對於pxPreviousWakeTime本次延時的節拍數),也就是任務在pxPreviousWakeTime+xTimeIncrementpd時鍾計數時從阻塞態恢復。MS_TO_TICKS()宏可用於延時毫秒。
(1)為任務主體,也就是任務執行的工作;(2)為任務調用vTaskDelayUntil()函數;(3)為其它任務執行。任務延時時間為xTimeIncrement,可看出任務總的執行時間一定小於任務的延時時間,也就是說使用vTaskDelayUntil()函數任務的執行周期永遠是xTimeIncrement,而任務一定要在這個時間內完成,這個延時值就是絕對延時時間。
上面圖中,xConstTickCount和xTimeToWake可能溢出,這些情況暫不討論,這里僅說明函數的用法。
測試代碼:創建一個任務,使用絕對延時函數延時50ms。
configSTACK_DEPTH_TYPE Task1_STACK_SIZE = 5;
UBaseType_t Task1_Priority = 2;
TaskHandle_t Task1_xhandle;
void task1_code(void *para)
{
unsigned int cnt = 0;
TickType_t xLastWakeTime;
const TickType_t xPeriod = pdMS_TO_TICKS(500);
xLastWakeTime = xTaskGetTickCount();
for (;;)
{
vTaskDelayUntil(&xLastWakeTime, xPeriod);
PRINT(" task1 cnt %u...", cnt);
cnt++;
}
}
void creat_task(void)
{
if (xTaskCreate(task1_code, "task1 task",
Task1_STACK_SIZE, NULL, Task1_Priority,
&Task1_xhandle) != pdPASS)
{
PRINT("creat task failed!\n");
}
}
7.3 系統時鍾節拍
xTickCount是FreeRTOS系統時鍾節拍計數器,每個滴答定時器中斷中xTickCount就會加1。xTickCount具體操作過程在xTaskIncrementTick()函數中進行,這個函數在時鍾計數器中斷函數中調用。