FreeRTOS學習筆記(一)基礎篇
我的FreeRTOS學習,是從朱工的FreeRTOS系列部落格開始的,感謝朱工的悉心整理,文章很不錯,適合學習。
根據朱工的部落格,按照我自己的習慣和思路,把最關鍵的資訊抓取下來,以備快速理清思路。
具體展開和豐富,需要移步朱工的文章,或者參考官方網站的資料。
1. 什麼是FreeRTOS
開源,免費,使用者量大,核心簡練的一個實時作業系統。
因為開源免費,所以用的人多,所有bug少。
核心是任務排程器,比較短小,3個C檔案,學習壓力相對小。
官網 www.freertos.org
2. 簡單入門
文件
- 指南
- 手冊
- demo文件, 官網左側Supported Devices & Demos
原始碼
- 核心原始碼
- 移植包
結構
- 頂層
- FreeRTOS-Plus
- FreeRTOS 主要關注這個
FreeRTOS
FreeRTOS
|+-- Demo 包含演示例程工程;
|+-- Source 包含實時核心原始檔。
tasks.c
queue.c
list.c 以上3個是主要的檔案,下面3個是可選的
timers.c
event_group.c
croutine.c
FreeRTOS/Source
FreeRTOS
| +-- Source FreeRTOS核心程式碼檔案
| |+-- include FreeRTOS核心程式碼標頭檔案
| |+-- Portable 處理器特定程式碼
| | |+--Compiler x 支援編譯器x的所有移植包
| | |+--Compiler y 支援編譯器y的所有移植包
| | |+--MemMang 記憶體堆實現範例
- 處理器架構相關程式碼
- FreeRTOS/Source/Portable/[相應編譯器]/[相應CPU架構]子目錄
- 堆疊設計
- FreeRTOS/Source/portable/MemMang目錄下heap_x.c
演示例程
FreeRTOS
|+-- Demo
| |+-- Common 所有例程都可以使用的演示例程檔案
| |+-- Dir x 用於x平臺的演示例程工程檔案
| |+-- Dir y 用於y平臺的演示例程工程檔案
如果有演示例程中相同的裝置,就可以開始編譯和檢視結果。
3. 移植
演示例程都是針對特定的物件
- 特定的微控制器
- 開發工具(編譯器,偵錯程式)
- 特定的平臺(開發板,樣機)
官方例程沒有的,就需要移植。
最簡單的上前兩條都相同,只是不同的開發板。直接開始編譯就可以,target都是一樣的。
但是GPIO管腳什麼的肯定不一樣,所以即使能編譯過,一些功能測試函式肯定也是功能錯誤的。
partest.c裡邊的vParTestInitialise()用於IO埠配置,vParTestSetLED和vParTestToggleLED用來測試GPIO,這些需要根據當前的target改改,然後在main裡邊呼叫測試
int main( void )
{
volatile unsigned long ul; /* 禁止編譯器優化此變數 */
/* 初始化LED IO為輸出-注:prvSetupHardware()也可能會被呼叫*/
vParTestInitialise();
/*不斷開啟LED */
for( ;; )
{
/* 我們暫時不使用RTOS,這裡只是使用一個非常粗糙的延時*/
for( ul = 0; ul < 0xfffff; ul++ )
{
}
/* 開啟4個LED */
vParTestToggleLED( 0 );
vParTestToggleLED( 1 );
vParTestToggleLED( 2 );
vParTestToggleLED( 3 );
}
return 0;
}
LED點燈測試沒有問題了,就可以開始測試任務排程了
使用vStartLEDFlashTasks() 測試任務排程,如果例程不包含,就新增FreeRTOS/Demo/Common/Minimal/Flash.c到工程中,功能是讓幾個LED等以不公的頻率閃。
int main( void )
{
/* 設定用於演示的微控制器硬體 */
prvSetupHardware();
/* 留下這個函式 */
vCreateFlashTasks();
/* 所有的其它建立任務的函式統統註釋掉
vCreatePollQTasks();
vCreateComTestTasks();
//等等…
xTaskCreate( vCheckTask,"check", STACK_SIZE, NULL, TASK_PRIORITY, NULL );
*/
/*啟動RTOS排程器. */
vTaskStartScheduler();
/* 永遠不會執行到這裡! */
return 0;
}
能看到主要的函式就3個
- prvSetupHardware() 設定硬體
- vCreateFlashTasks() 建立閃燈任務
- vTaskStartScheduler() 啟動排程
特定target的核心檔案
特定平臺,大多數檔案位於
- FreeRTOS/source/portable/[編譯器]/[微控制器/port.c
- FreeRTOS/source/portable/[編譯器]/[微控制器]/portmacro.h
- 可能還有portasm.s或者portasm.asm
4. 編碼標準
FreeRTOS的核心原始碼遵從MISRA編碼標準指南。
不展開了。
5. CM3的移植
準備
- 開發板
- FreeRTOS程式包
- CMSIS-M3,core_cm3.h
過程
- 核心程式碼加入工程
- taks.c, queue.c, list.c
- port.c
- heap_1.c
- 標頭檔案路徑
- …\FreeRTOS\Source\portable\RVDS\ARM_CM3
- …\FreeRTOS\Source\include
- 編寫FreeRTOSConfig.h檔案,下一節介紹
- 編寫鉤子函式
- 如果配置了configUSE_TICK_HOOK=1,需要編寫voidvApplicationTickHook( void )
- 如果配置了configCHECK_FOR_STACK_OVERFLOW=1或=2,需要編寫voidvApplicationStackOverflowHook( xTaskHandle pxTask, signed char *pcTaskName )
- 檢查硬體,亮一下LED或者UART串列埠發個字元
#include"task.h"
#include"queue.h"
#include"list.h"
#include"portable.h"
#include"debug.h"
int main(void)
{
init_rtos_debug(); //初始化除錯串列埠
MAP_UARTCharPut('A'); //傳送一個字元
while(1);
}
5. 掛接中斷,在startup.s中,使用IMPORT關鍵字宣告要掛接的異常中斷服務函式名
DCD SVC_Handler 換成: DCD vPortSVCHandler
DCD PendSV_Handler 換成: DCD xPortPendSVHandler
DCD SysTick_Handler 換成: DCD xPortSysTickHandler
6. 建立第一個任務,每隔一秒鐘傳送一個字元
voidvTask(void *pvParameters)
{
while(1)
{
MAP_UARTCharPut(0x31);
vTaskDelay(1000/portTICK_RATE_MS);
}
}
其中vTaskDelay()是API函式。
7. 設定節拍時鐘,每10ms產生一箇中斷,只需要在FreeRTOSConfig.h裡邊做下配置
- configCPU_CLOCK_HZ (/你的硬體平臺CPU系統時鐘,Fcclk/)
- configTICK_RATE_HZ ((portTickType)100)
在prot.c中,函式vPortSetupTimerInterrupt()設定節拍時鐘,由函式vTaskStartScheduler()呼叫,這個函式用於啟動排程器。
8. 設定中斷優先順序巨集,在FreeRTOSConfig.h中
#ifdef __NVIC_PRIO_BITS
#defineconfigPRIO_BITS __NVIC_PRIO_BITS
#else
#defineconfigPRIO_BITS 5 /*lpc177x_8x微處理器使用優先順序暫存器的5位*/
#endif
/*設定核心使用的中斷優先順序*/
#define configKERNEL_INTERRUPT_PRIORITY ( 31 << (8 - configPRIO_BITS) )
/*定義RTOS可以遮蔽的最大中斷優先順序,大於這個優先順序的中斷,不受RTOS控制*/
#defineconfigMAX_SYSCALL_INTERRUPT_PRIORITY ( 5<< (8 - configPRIO_BITS) )
9. 設定其他巨集,在FreeRTOSConfig.h裡邊
#define configUSE_PREEMPTION 1 //配置為1使用搶佔式核心,配置為0使用時間片
#define configUSE_IDLE_HOOK 0 //設定為1使用空閒鉤子;設定為0不使用空閒鉤子
#define configMAX_PRIORITIES ( 5 ) //應用程式任務中可用優先順序數目
#define configUSE_TICK_HOOK 0 //就設定為1使用時間片鉤子,設定為0不使用
#define configMINIMAL_STACK_SIZE ( ( unsigned short ) 80 ) //最小空閒堆疊
#define configTOTAL_HEAP_SIZE ( ( size_t ) ( 5 * 1024 ) ) //核心總共可用RAM
9. 建立任務
xTaskCreate(vTask,"Task1",50,NULL,1,NULL);
10. 開啟排程器
vTaskStartScheduler();
此時main函式的程式碼如下
int main(void)
{
init_rtos_debug(); //初始化除錯串列埠
xTaskCreate(vTask,"Task1",50,NULL,1,NULL);
vTaskStartScheduler();
while(1);
}
6. 核心配置
FreeRTOS的定製體現在FreeRTOSConfig.h的定製上。每一個FreeRTOS應用都必須包含這個檔案,這個檔案針對使用者,不針對核心,放在應用程式目錄下。
- configUSE_PREEMPTION, 1:搶佔式排程,0:協作式排程
- configUSE_PORT_OPTIMISED_TASK_SELECTION, 選擇下一個要執行的任務,1:特殊方法,0:通用方法
- configUSE_TICKLESS_IDLE, 1:使能低功耗tickless模式,0:tick中斷一直執行
- configUSE_IDLE_HOOK, 1:使用空閒鉤子,0:忽略空閒鉤子
- configUSE_MALLOC_FAILED_HOOK, 1:必須定義一個malloc()失敗鉤子函式,0:malloc()失敗鉤子函式不會被呼叫
- configUSE_TICK_HOOK, 1:使用時間片鉤子函式,0:忽略時間片鉤子函式
- configCPU_CLOCK_HZ, CPU核心時鐘頻率 FCLK
- configTICK_RATE_HZ, 系統街拍中斷頻率,每秒中斷次數
- configMAX_PRIORITIES, 有效優先順序數目
- configMINIMAL_STACK_SIZE, 空閒任務使用的堆疊大小
- configTOTAL_HEAP_SIZE, FreeRTOS核心使用的有效RAM大小
- configMAX_TASK_NAME_LEN, 任務描述資訊字串最大長度
- configUSE_TRACE_FACILITY, 1:啟動視覺化跟蹤除錯
- configUSE_STATS_FORMATTING_FUNCTIONS, 編譯vTaskList()和vTaskGetRunTimeStats()函式,需要configUSE_TRACE_FACILITY也設定成1
- configUSE_16_BIT_TICKS, 定義portTickType是表示16位變數還是32位變數
- configIDLE_SHOULD_YIELD, 如果使用搶佔式排程,使用者任務使用空閒優先順序,1:共享空閒任務優先順序的使用者任務就緒時,空閒任務立即讓出CPU,0:空閒任務結束時才會讓出CPU
- configUSE_TASK_NOTIFICATIONS, 1:開啟任務通知,0:關閉通知功能
- configUSE_MUTEXES, 1:使用互斥量,0:忽略互斥量
- configUSE_RECURSIVE_MUTEXES, 1:使用遞迴互斥量,0:不使用
- configUSE_COUNTING_SEMAPHORES, 1:使用計數訊號量,0:不使用
- configUSE_ALTERNATIVE_API, 1:使用“替代”佇列函式,0:不使用
- configCHECK_FOR_STACK_OVERFLOW, 0:使用者提供棧溢位鉤子函式,1:任務切換時核心檢查棧指標是否指向堆疊,2:任務切換時核心檢查堆疊結尾是否被覆蓋
- configQUEUE_REGISTRY_SIZE, 記錄佇列和訊號量的最大數目
- configUSE_QUEUE_SETS, 1:使能佇列集功能,0:取消佇列集功能
- configUSE_TIME_SLICING, 1:基於時間片的優先順序搶佔排程,節拍器中斷時相同優先順序任務切換,0:節拍器中斷時相同優先順序任務不切換
- configUSE_NEWLIB_REENTRANT, 1:每一個建立的任務分配一個neblib reent結構
- configENABLE_BACKWARD_COMPATIBILITY, 1:通過巨集定義確保v8.0.0之前版本的程式碼相容性,0:去掉這些巨集定義
- configNUM_THREAD_LOCAL_STORAGE_POINTERS, 每個任務的執行緒本地儲存指標大小
- configGENERATE_RUN_TIME_STATS, 1:使能時間統計功能
- configUSE_CO_ROUTINES, 1:使用協程,0:不使用(協程主要用於資源非常受限的嵌入式系統,RAM非常少,通常不會用於32位微處理器,FreeRTOS開發者停止開發協程了)
- configMAX_CO_ROUTINE_PRIORITIES, 協程的有效優先順序數目
- configUSE_TIMERS, 1:使用軟甲定時器,0:不使用
- configTIMER_TASK_PRIORITY, 軟體定時服務/守護程序的優先順序
- configTIMER_QUEUE_LENGTH, 軟體定時器命令佇列長度
- configTIMER_TASK_STACK_DEPTH, 軟體定時器服務/守護程序的堆疊深度
- configKERNEL_INTERRUPT_PRIORITY、configMAX_SYSCALL_INTERRUPT_PRIORITY和configMAX_API_CALL_INTERRUPT_PRIORITY
- configMAX_SYSCALL_INTERRUPT_PRIORITY 使用FreeRTOS API函式的中斷例程,其優先順序數值需要大於此定義,即邏輯優先順序低於它(CM3的情況),不能設定成0
- configASSERT, 定義斷言的巨集
- INCLUDE Parameters, 允許使用者不編譯那些應用程式不需要的核心元件(函式)
/*需要使用vTaskDelete函式*/
#define INCLUDE_vTaskDelete 1
/*不需要使用vTaskDelete函式*/
#define INCLUDE_vTaskDelete 0
7. 記憶體管理
建立任務、佇列、互斥量、軟體定時器、訊號量或事件組時,RTOS核心會為它們分配RAM。標準函式庫中的malloc()和free()函式有些時候能夠用於完成這個任務,但是:
- 在嵌入式系統中,它們並不總是可以使用的;
- 它們會佔用更多寶貴的程式碼空間;
- 它們沒有執行緒保護;
- 它們不具有確定性(每次呼叫執行的時間可能會不同)
FreeRTOS把記憶體分配放到了移植層,可以有不同的實現,通過API來完成。當RTOS核心需要RAM時,呼叫pvPortMallo()函式來代替malloc()函式。當RAM要被釋放時,呼叫vPortFree()函式來代替free()函式。
FreeRTOS提供了不同的方案,位於\FreeRTOS\Source\portable\MemMang
- heap_1.c
- heap_2.c
- heap_3.c
- heap_4.c
- heap_5.c
heap_1
- 用於從不會刪除任務、佇列、訊號量、互斥量等的應用程式(實際上大多數使用FreeRTOS的應用程式都符合這個條件)
- 執行時間是確定的並且不會產生記憶體碎片
- 實現和分配過程非常簡單,需要的記憶體是從一個靜態陣列中分配的,意味著這種記憶體分配通常只是適用於那些不進行動態記憶體分配的應用
將一個大陣列細分出一個子集來,大陣列的容量大小通過FreeRTOSConfig.h檔案中的configTOTAL_HEAP_SIZE巨集來設定
API函式xPortGetFreeHeapSize()返回未分配的堆疊空間總大小
heap_2
heap_2.c適用於需要動態建立任務的大多數小型實時系統(smallreal time)
- 可以用於重複的分配和刪除具有相同堆疊空間的任務、佇列、訊號量、互斥量等等,並且不考慮記憶體碎片的應用程式
- 不能用在分配和釋放隨機位元組堆疊空間的應用程式(因為會產生碎片,碎片過多會倒置分配失敗)
- 不具有確定性,但是它比標準庫中的malloc函式具有高得多的效率
和方案1不同,這個方案使用一個最佳匹配演算法,它允許釋放之前分配的記憶體塊。它不會把相鄰的空閒塊合成一個更大的塊(換句話說,這會造成記憶體碎片)
有效的堆疊空間大小由位於FreeRTOSConfig.h檔案中的configTOTAL_HEAP_SIZE巨集來定義
API函式xPortGetFreeHeapSize()返回剩下的未分配堆疊空間的大小
heap_3
heap_3.c簡單的包裝了標準庫中的malloc()和free()函式,包裝後的malloc()和free()函式具備執行緒保護
- 需要連結器設定一個堆疊,並且編譯器庫提供malloc()和free()函式
- 不具有確定性
- 可能明顯的增大RTOS核心的程式碼大小
用heap_3時,FreeRTOSConfig.h檔案中的configTOTAL_HEAP_SIZE巨集定義沒有作用
heap_4
使用一個最佳匹配演算法,但不像方案2那樣。它會將相鄰的空閒記憶體塊合併成一個更大的塊(包含一個合併演算法)
- 可用於重複分配、刪除任務、佇列、訊號量、互斥量等等的應用程式
- 可以用於分配和釋放隨機位元組記憶體的情況,並不像heap_2.c那樣產生嚴重碎片
- 不具有確定性,但是它比標準庫中的malloc函式具有高得多的效率
特別適用於移植層程式碼,可以直接使用pvPortMalloc()和 vPortFree()函式來分配和釋放記憶體
heap_5
同樣實現了heap_4.c中的合併演算法,並且允許堆疊跨越多個非連續的記憶體區
- 通過呼叫vPortDefineHeapRegions()函式實現初始化,在該函式執行完成前不允許使用記憶體分配和釋放
- 建立RTOS物件(任務、佇列、訊號量等等)會隱含的呼叫pvPortMalloc(),因此必須注意:使用heap_5建立任何物件前,要先執行vPortDefineHeapRegions()函式
typedef struct HeapRegion
{
/* 用於記憶體堆的記憶體塊起始地址*/
uint8_t *pucStartAddress;
/* 記憶體塊大小 */
size_t xSizeInBytes;
} HeapRegion_t;
/* 在記憶體中為記憶體堆分配兩個記憶體塊.第一個記憶體塊0x10000位元組,起始地址為0x80000000,
第二個記憶體塊0xa0000位元組,起始地址為0x90000000.起始地址為0x80000000的記憶體塊的
起始地址更低,因此放到了陣列的第一個位置.*/
const HeapRegion_t xHeapRegions[] =
{
{ ( uint8_t * ) 0x80000000UL, 0x10000 },
{ ( uint8_t * ) 0x90000000UL, 0xa0000 },
{ NULL, 0 } /* 陣列結尾. */
};
/* 向函式vPortDefineHeapRegions()傳遞陣列引數. */
vPortDefineHeapRegions( xHeapRegions );
8. 任務
8.1 概述
任務和協程
使用不同的API函式,兩者不能使用同一個佇列或者訊號量傳遞資料。
協程不討論。
任務的概念
- 每個RTOS應用程式,都是若干獨立任務的集合
- 每個任務有自己的環境
- RTOS排程器負責停止,啟動任務,任務的上下文切換也由排程器完成,看起來所有的任務都在推進
- 任務的上下文儲存在自己的堆疊裡
- 任務應該簡單
- 任務沒有使用限制
- 任務有優先順序
- 任務堆疊獨立,RAM有消耗
- 使用搶佔的話,重入問題需要考慮
任務狀態
- 執行
- 就緒
- 阻塞
- 掛起
執行
正在執行,佔用CPU。
就緒
具備執行能力,沒有阻塞和掛起,但是同優先順序或者更高優先順序的任務在執行,所以還沒有真正執行
阻塞
等待某個時序或者外部中斷。通常有“超時”週期,超時後解除阻塞。
掛起
排程器不再管理。通過API進入和退出掛起狀態:vTaskSuspend() 和xTaskResume()
優先順序
0~configMAX_PRIORITIES,每個任務都有
- 數值低表示優先順序低,空閒任務優先順序0
- 排程器確保處於最高優先順序的就緒或者執行任務獲得CPU
- 可以共享優先順序,無數量限制
實現
void vATaskFunction( voidvoid *pvParameters )
{
for( ;; )
{
/*-- 應用程式程式碼放在這裡. --*/
}
/* 任務不可以從這個函式返回或退出。在較新的FreeRTOS移植包中,如果
試圖從一個任務中返回,將會呼叫configASSERT()(如果定義的話)。
如果一個任務確實要退出函式,那麼這個任務應呼叫vTaskDelete(NULL)
函式,以便處理一些清理工作。*/
vTaskDelete( NULL );
}
- 返回void
- 入參是void *
- 任務函式絕不應該返回,通常就是死迴圈
- 任務由xTaskCreate()函式建立,由vTaskDelete()函式刪除
空閒任務
- 啟動排程器時核心自動建立的任務,確保至少有一個任務在執行
- 是由空閒任務來釋放被刪除的任務的資源,因此vTaskDelete()函式後確保空閒任務能獲得處理器時間就很重要了
- 使用者任務可以共享空閒任務的優先順序
空閒任務鉤子
- 鉤子函式每一個空閒任務週期呼叫一次
- 可以用來實現任務程式執行在空閒任務優先順序上,在鉤子函式中實現即可
- 限制:鉤子函式不可以呼叫能夠陰氣阻塞的API函式,例如vTaskDelay()或者帶有超時事件的佇列或訊號量函式
- 建立步驟
- 設定configUSE_IDLE_HOOK為1
- 定義一個函式,名字和引數原型如下所示
void vApplicationIdleHook( void );
通常,使用這個空閒鉤子函式設定CPU進入低功耗模式
8.2 建立和刪除
1. 建立
1.1 建立函式
BaseType_t xTaskCreate(
TaskFunction_t pvTaskCode,
const charchar * const pcName,
unsigned short usStackDepth,
voidvoid *pvParameters,
UBaseType_t uxPriority,
TaskHandle_t * pvCreatedTask
);
果使用FreeRTOS-MPU(在官方下載包中,為Cortex-M3核心寫了兩個移植方案,一個是普通的FreeRTOS移植層,還有一個是FreeRTOS-MPU移植層。後者包含完整的記憶體保護),那麼推薦使用函式xTaskCreateRestricted()來代替xTaskCreate()
1.2 引數
pvTaskCode
指標,指向任務函式的入口。
TaskFunction_t定義在檔案projdefs.h中,定義為:typedefvoid (TaskFunction_t)( void )
pcName
任務描述。主要用於除錯。
usStackDepth
指定任務堆疊大小,能夠支援的堆疊變數數量,而不是位元組數。
pvParameters
指標,當任務建立時,作為一個引數傳遞給任務。
uxPriority
任務的優先順序。
比如,建立一個優先順序為2的特權任務,引數uxPriority可以設定為( 2 | portPRIVILEGE_BIT )
pvCreatedTask
用於回傳一個控制代碼(ID),建立任務後可以使用這個控制代碼引用任務。
1.3 返回值
如果任務成功建立並加入就緒列表函式返回pdPASS,否則函式返回錯誤碼,具體參見projdefs.h
1.4 舉例
/* 建立任務. */
void vTaskCode( voidvoid * pvParameters )
{
for( ;; )
{
/* 任務程式碼放在這裡 */
}
}
/* 建立任務函式 */
void vOtherFunction( void )
{
static unsigned char ucParameterToPass;
xTaskHandlexHandle;
/* 建立任務,儲存控制代碼。注:傳遞的引數ucParameterToPass必須和任務具有相同的生存週期,
因此這裡定義為靜態變數。如果它只是一個自動變數,可能不會有太長的生存週期,因為
中斷和高優先順序任務可能會用到它。 */
xTaskCreate( vTaskCode, "NAME", STACK_SIZE,&ucParameterToPass, tskIDLE_PRIORITY, &xHandle );
/* 使用控制代碼刪除任務. */
if( xHandle !=NULL )
{
vTaskDelete( xHandle );
}
}
2. 刪除
2.1 任務描述
voidvTaskDelete( TaskHandle_t xTask );
任務刪除後將會從就緒、阻塞、暫停和事件列表中移除。
FreeRTOSConfig.h中,必須定義巨集INCLUDE_vTaskDelete 為1,本函式才有效
2.2 引數
- xTask:被刪除任務的控制代碼。為NULL表示刪除當前任務
8.3 任務控制
1. 相對延時
- 呼叫vTaskDelay()函式後,任務會進入阻塞狀態
- 持續時間由vTaskDelay()函式的引數xTicksToDelay指定,單位是系統節拍時鐘週期
- 延時時間是從呼叫vTaskDelay()後開始計算的相對時間
- 其它任務和中斷活動,會影響到vTaskDelay()的呼叫(比如呼叫前高優先順序任務搶佔了當前任務)
- API函式vTaskDelayUntil()可用於固定頻率的延時
void vTaskDelay( portTickTypexTicksToDelay )
/*xTicksToDelay:延時時間總數,單位是系統時鐘節拍週期*/
/*舉例*/
voidvTaskFunction( voidvoid * pvParameters )
{
/* 阻塞500ms. */
constportTickType xDelay = 500 / portTICK_RATE_MS;
for( ;; )
{
/* 每隔500ms觸發一次LED, 觸發後進入阻塞狀態 */
vToggleLED();
vTaskDelay( xDelay );
}
}
2. 絕對延時
- 週期性任務可以使用此函式,以確保一個恆定的頻率執行
- INCLUDE_vTaskDelayUntil 必須設定成1,此函式才有效
- vTaskDelayUntil()指定一個絕對時間,每當時間到達,則解除任務阻塞
- 呼叫vTaskSuspendAll()函式掛起RTOS排程器時,不可以使用此函式
/*原型*/
void vTaskDelayUntil( TickType_t *pxPreviousWakeTime,
const TickType_txTimeIncrement );
/*引數說明*/
/*pxPreviousWakeTime:指標,指向一個變數,該變數儲存任務最後一次解除阻塞的時間。第一次使用前,該變數必須初始化為當前時間。之後這個變數會在vTaskDelayUntil()函式內自動更新*/
/*xTimeIncrement:週期迴圈時間。當時間等於(*pxPreviousWakeTime + xTimeIncrement)時,任務解除阻塞。如果不改變引數xTimeIncrement的值,呼叫該函式的任務會按照固定頻率執行*/
/*舉例*/
//每10次系統節拍執行一次
void vTaskFunction( voidvoid * pvParameters )
{
static portTickType xLastWakeTime;
const portTickType xFrequency = 10;
// 使用當前時間初始化變數xLastWakeTime
xLastWakeTime = xTaskGetTickCount();
for( ;; )
{
//等待下一個週期
vTaskDelayUntil( &xLastWakeTime,xFrequency );
// 需要週期性執行程式碼放在這裡
}
}
3. 獲取任務優先順序
- 獲取指定任務的優先順序
- INCLUDE_vTaskPriorityGet必須設定成1,此函式才有效
/*原型*/
UBaseType_t uxTaskPriorityGet(TaskHandle_t xTask );
/*引數說明*/
/*xTask:任務控制代碼。NULL表示獲取當前任務的優先順序*/
/*舉例*/
voidvAFunction( void )
{
xTaskHandlexHandle;
// 建立任務,儲存任務控制代碼
xTaskCreate( vTaskCode, "NAME",STACK_SIZE, NULL, tskIDLE_PRIORITY, &xHandle );
// ...
// 使用控制代碼獲取建立的任務的優先順序
if( uxTaskPriorityGet( xHandle ) !=tskIDLE_PRIORITY )
{
// 任務可以改變自己的優先順序
}
// ...
// 當前任務優先順序比建立的任務優先順序高?
if( uxTaskPriorityGet( xHandle ) <uxTaskPriorityGet( NULL ) )
{
// 當前優先順序較高
}
}
4. 設定任務優先順序
- 設定指定任務的優先順序。如果設定的優先順序高於當前執行的任務,在函式返回前會進行一次上下文切換
- INCLUDE_vTaskPrioritySet 必須設定成1,此函式才有效
/*原型*/
void vTaskPrioritySet( TaskHandle_txTask, UBaseType_tuxNewPriority );
/*引數*/
/*xTask:要設定優先順序任務的控制代碼,為NULL表示設定當前執行的任務*/
/*uxNewPriority:要設定的新優先順序*/
/*舉例*/
voidvAFunction( void )
{
xTaskHandlexHandle;
// 建立任務,儲存任務控制代碼。
xTaskCreate( vTaskCode, "NAME",STACK_SIZE, NULL, tskIDLE_PRIORITY, &xHandle );
// ...
// 使用控制代碼來提高建立任務的優先順序
vTaskPrioritySet( xHandle,tskIDLE_PRIORITY + 1 );
// ...
// 使用NULL引數來提高當前任務的優先順序,設定成和建立的任務相同。
vTaskPrioritySet( NULL, tskIDLE_PRIORITY +1 );
}
5. 任務掛起
- 掛起指定任務
- 被掛起的任務絕不會得到處理器時間,不管該任務具有什麼優先順序
- 多次呼叫vTaskSuspend ()函式將一個任務掛起,也只需呼叫一次vTaskResume ()函式就能使掛起的任務解除掛起狀態
- INCLUDE_vTaskSuspend必須設定成1,此函式才有效
/*原型*/
void vTaskSuspend( TaskHandle_txTaskToSuspend );
/*引數*/
/*xTaskToSuspend:要掛起的任務控制代碼。為NULL表示掛起當前任務*/
/*舉例*/
void vAFunction( void )
{
xTaskHandlexHandle;
// 建立任務,儲存任務控制代碼.
xTaskCreate( vTaskCode, "NAME",STACK_SIZE, NULL, tskIDLE_PRIORITY, &xHandle );
// ...
// 使用控制代碼掛起建立的任務.
vTaskSuspend( xHandle );
// ...
// 任務不再執行,除非其它任務呼叫了vTaskResume(xHandle )
//...
// 掛起本任務.
vTaskSuspend( NULL );
// 除非另一個任務使用handle呼叫了vTaskResume,否則永遠不會執行到這裡
}
6. 恢復掛起任務
- INCLUDE_vTaskSuspend必須置1,此函式才有效
/*原型*/
void vTaskResume( TaskHandle_txTaskToResume );
/*引數*/
/*xTaskToResume:要恢復執行的任務控制代碼*/
/*舉例*/
void vAFunction( void )
{
xTaskHandle xHandle;
// 建立任務,儲存任務控制代碼
xTaskCreate( vTaskCode, "NAME",STACK_SIZE, NULL, tskIDLE_PRIORITY, &xHandle );
// ...
```
// 使用控制代碼掛起建立的任務
vTaskSuspend( xHandle );
// ...
//任務不再執行,除非其它任務呼叫了vTaskResume(xHandle )
//...
// 恢復掛起的任務.
vTaskResume( xHandle );
// 任務再一次得到處理器時間
// 任務優先順序與之前相同
}
7. 恢復掛起的任務(在中斷服務函式中使用)
- 用於恢復一個掛起的任務,用在ISR中
- 不可用於任務和中斷間的同步,如果中斷恰巧在任務被掛起之前到達,這就會導致一次中斷丟失(用訊號量同步)
- INCLUDE_xTaskResumeFromISR 必須設定成1
/*原型*/
BaseType_t xTaskResumeFromISR(TaskHandle_t xTaskToResume );
/*引數*/
/*xTaskToResume:要恢復執行的任務控制代碼*/
/*舉例*/
xTaskHandlexHandle; //注意這是一個全域性變數
void vAFunction( void )
{
// 建立任務並儲存任務控制代碼
xTaskCreate( vTaskCode, "NAME",STACK_SIZE, NULL, tskIDLE_PRIORITY, &xHandle );
// 剩餘程式碼.
}
void vTaskCode( voidvoid *pvParameters )
{
for( ;; )
{
// ... 在這裡執行一些其它功能
// 掛起自己
vTaskSuspend( NULL );
//直到ISR恢復它之前,任務會一直掛起
}
}
void vAnExampleISR( void )
{
portBASE_TYPExYieldRequired;
// 恢復被掛起的任務
xYieldRequired = xTaskResumeFromISR(xHandle );
if( xYieldRequired == pdTRUE )
{
// 我們應該進行一次上下文切換
// 注: 如何做取決於你具體使用,可檢視說明文件和例程
portYIELD_FROM_ISR();
}
}
8.4 任務應用函式
1. 獲取任務系統狀態
UBaseType_t uxTaskGetSystemState(
TaskStatus_t * constpxTaskStatusArray,
const UBaseType_tuxArraySize,
unsigned longlong * constpulTotalRunTime );
2. 獲取當前任務控制代碼
TaskHandle_t xTaskGetCurrentTaskHandle(void );
3. 獲取空閒任務控制代碼
TaskHandle_t xTaskGetIdleTaskHandle(void );
4. 獲取任務堆疊最大使用深度
UBaseType_t uxTaskGetStackHighWaterMark( TaskHandle_t xTask );
5. 獲取任務狀態
eTaskState eTaskGetState( TaskHandle_txTask );
6. 獲取任務描述內容
char * pcTaskGetTaskName( TaskHandle_txTaskToQuery );
7. 獲取系統節拍次數
volatile TickType_t xTaskGetTickCount(void );
8. 獲取排程器狀態
BaseType_t xTaskGetSchedulerState( void);
9. 獲取任務總數
UBaseType_t uxTaskGetNumberOfTasks(void );
10. 獲取所有任務詳情
void vTaskList( char *pcWriteBuffer );
11. 獲取任務執行時間
void vTaskGetRunTimeStats( char*pcWriteBuffer );
12. 設定任務標籤值
voidvTaskSetApplicationTaskTag( TaskHandle_t xTask, TaskHookFunction_tpxTagValue);
13. 獲取任務標籤值
TaskHookFunction_txTaskGetApplicationTaskTag( TaskHandle_t xTask );
14. 執行任務的應用鉤子函式
BaseType_txTaskCallApplicationTaskHook(TaskHandle_txTask,
void*pvParameter);
15. 設定執行緒本地儲存指標
void vTaskSetThreadLocalStoragePointer(TaskHandle_t xTaskToSet,
BaseType_t xIndex,
void*pvValue )
16. 讀取執行緒本地儲存指標
void*pvTaskGetThreadLocalStoragePointer(TaskHandle_txTaskToQuery,
BaseType_txIndex );
17. 設定超時狀態
void vTaskSetTimeOutState( TimeOut_t *const pxTimeOut );
18. 超時檢測
BaseType_t xTaskCheckForTimeOut(TimeOut_t * const pxTimeOut,
TickType_t* const pxTicksToWait );
8.5 核心控制
核心控制的一些功能需要移植層提供,為了方便移植,這些API函式用巨集來實現
強制上下文切換
- taskYIELD
在中斷服務程式中的等價版本為portYIELD_FROM_ISR,這也是個巨集,其實現取決於移植層
進入臨界區巨集
- taskENTER_CRITICAL
在臨界區中不會發生上下文切換,對於Cortex-M3硬體,先禁止所有RTOS可遮蔽中斷,這可以通過向basepri 暫存器寫入configMAX_SYSCALL_INTERRUPT_PRIORITY來實現
退出臨界區巨集
- taskEXIT_CRITICAL
對於Cortex-M3硬體,先將臨界區巢狀計數器減1,如果臨界區計數器為零,則使能所有RTOS可遮蔽中斷,這可以通過向basepri 暫存器寫入0來實現
禁止可遮蔽中斷巨集
- taskDISABLE_INTERRUPTS
在呼叫巨集taskENTER_CRITICAL進入臨界區時,也會間接呼叫該巨集禁止所有RTOS可遮蔽中斷
使能可遮蔽中斷巨集
- taskENABLE_INTERRUPTS
在呼叫巨集taskEXIT_CRITICAL退出臨界區時,也會間接呼叫該巨集使能所有RTOS可遮蔽中斷
啟動排程器
void vTaskStartScheduler( void );
如果vTaskStartScheduler()成功執行,則該函式不會返回,直到有任務呼叫了vTaskEndScheduler()。如果因為RAM不足而無法建立空閒任務,該函式也可能執行失敗,並會立刻返回呼叫處
停止排程器
僅用於x86硬體架構中
void vTaskEndScheduler( void );
掛起排程器
- 掛起排程器,但不禁止中斷
- 正在執行的任務會一直繼續執行,核心不再排程
- 核心排程器掛起期間,那些可以引起上下文切換的API函式(如vTaskDelayUntil()、xQueueSend()等)決不可使用
void vTaskSuspendAll( void );
恢復被掛起的排程器
- 不會恢復那些被vTaskSuspend()函式掛起的任務
BaseType_t xTaskResumeAll( void );
調整系統節拍
/* 首先定義巨集portSUPPRESS_TICKS_AND_SLEEP()。巨集引數指定要進入低功耗(睡眠)的時間,單位是系統節拍週期。*/
#defineportSUPPRESS_TICKS_AND_SLEEP( xIdleTime ) vApplicationSleep( xIdleTime )
/* 定義被巨集portSUPPRESS_TICKS_AND_SLEEP()呼叫的函式 */
void vApplicationSleep(TickType_t xExpectedIdleTime )
{
unsigned long ulLowPowerTimeBeforeSleep,ulLowPowerTimeAfterSleep;
/* 從時鐘源獲取當前時間,當微控制器進入低功耗的時候,這個時鐘源必須在執行 */
ulLowPowerTimeBeforeSleep =ulGetExternalTime();
/*停止系統節拍時鐘中斷。*/
prvStopTickInterruptTimer();
/* 配置一箇中斷,當指定的睡眠時間達到後,將處理器從低功耗中喚醒。這個中斷源必須在微控制器進入低功耗時也可以工作。*/
vSetWakeTimeInterrupt( xExpectedIdleTime );
/*進入低功耗 */
prvSleep();
/* 確定微控制器進入低功耗模式持續的真正時間。因為其它中斷也可能使得微處理器退出低功耗模式。注意:在呼叫巨集portSUPPRESS_TICKS_AND_SLEEP()之前,排程器應該被掛起,portSUPPRESS_TICKS_AND_SLEEP()返回後,再將排程器恢復。因此,這個函式未完成前,不會執行其它任務。*/
ulLowPowerTimeAfterSleep =ulGetExternalTime();
/*調整核心系統節拍計數器。*/
vTaskStepTick( ulLowPowerTimeAfterSleep –ulLowPowerTimeBeforeSleep );
/*重新啟動系統節拍時鐘中斷。*/
prvStartTickInterruptTimer();
}
8.6 任務通知
- 每個RTOS任務都有一個32位的通知值,任務建立時,這個值被初始化為0
- RTOS任務通知相當於直接向任務傳送一個事件,接收到通知的任務可以解除阻塞狀態,前提是這個阻塞事件是因等待通知而引起的
- 傳送通知的同時,也可以可選的改變接收任務的通知值
- 相比於使用訊號量解除任務阻塞,使用任務通知可以快45%、使用更少的RAM
API函式xTaskNotify()和xTaskNotifyGive()傳送通知
接收RTOS任務呼叫API函式xTaskNotifyWait()或ulTaskNotifyTake()之前,這個通知都被保持著
限制
- 只能有一個任務接收通知事件
- 接收通知的任務可以因為等待通知而進入阻塞狀態,但是傳送通知的任務即便不能立即完成通知傳送也不能進入阻塞狀態
傳送通知
方法1
BaseType_t xTaskNotify( TaskHandle_txTaskToNotify,
uint32_t ulValue,
eNotifyAction eAction);
方法2
BaseType_t xTaskNotifyGive(TaskHandle_t xTaskToNotify );
獲取通知
uint32_t ulTaskNotifyTake( BaseType_txClearCountOnExit,
TickType_txTicksToWait );
等待通知
BaseType_t xTaskNotifyWait( uint32_tulBitsToClearOnEntry,
uint32_tulBitsToClearOnExit,
uint32_t*pulNotificationValue,
TickType_txTicksToWait );
任務通知並查詢
BaseType_t xTaskNotifyAndQuery(TaskHandle_t xTaskToNotify,
uint32_tulValue,
eNotifyActioneAction,
uint32_t*pulPreviousNotifyValue );
9. 佇列
9.1 佇列
任務間主要的通訊方式,任務和任務,中斷和任務之間傳送資訊。
- 圖中能儲存5個專案
- 任務A使用API函式xQueueSendToBack()向佇列傳送資料
- 任務B使用API函式xQueueReceive()將資料從佇列取出
- FIFO,LIFO都可以
FreeRTOS佇列特性
- C變數(整形、簡單結構體等等)中的簡單資訊可以直接傳送到佇列
- 佇列是通過拷貝傳遞資料的,但這並不妨礙佇列通過引用來傳遞資料
- 佇列記憶體區域分配由核心完成
- 變長訊息可以通過定義儲存一個結構體變數的佇列實現,結構體一個成員指向要入隊的快取,另一個成員儲存快取資料的大小
- 單個佇列可以接收不同型別資訊,並且資訊可以來自不同的位置
- 在中斷函式中使用獨立的API
- API函式很簡單
佇列阻塞
- 每當任務企圖從一個空的佇列讀取資料時,任務會進入阻塞狀態,直到佇列中出現有效資料或者阻塞時間到期
- 每當任務企圖向一個滿的佇列寫資料時,任務會進入阻塞狀態,直到佇列中出現有效空間或者阻塞時間到期
- 如果多個任務阻塞在一個佇列上,那麼最高優先級別的任務會第一個解除阻塞
- 中斷程式中絕不可以使用不帶“FromISR”結尾的API函式
佇列用法總結
- 定義一個佇列控制代碼變數,用於儲存建立的佇列:xQueueHandle xQueue1
- 使用API函式xQueueCreate()建立一個佇列
- FIFO API函式xQueueSend()或xQueueSendToBack()向佇列投遞佇列項
- LIFO API函式xQueueSendToFront()向佇列投遞佇列項
- API函式xQueueReceive()從佇列讀取佇列項
9.2 佇列API
1.獲取佇列入隊資訊數目
UBaseType_t uxQueueMessagesWaiting( QueueHandle_t xQueue );
2.獲取佇列的空閒數目
UBaseType_t uxQueueMessagesWaiting( QueueHandle_t xQueue );
3.刪除佇列
void vQueueDelete( QueueHandle_t xQueue );
4.復位佇列
BaseType_t xQueueReset( QueueHandle_t xQueue );
5.建立佇列
QueueHandle_t xQueueCreate (UBaseType_t uxQueueLength, UBaseType_t uxItemSize);
6.向佇列投遞佇列項
BaseType_t xQueueSend(QueueHandle_t xQueue, const void * pvItemToQueue, TickType_t xTicksToWait );
7.向佇列投遞佇列項(帶中斷保護)
BaseType_t xQueueSendFromISR (QueueHandle_t xQueue,
const voidvoid *pvItemToQueue, BaseType_t *pxHigherPriorityTaskWoken);
8.向佇列尾部投遞佇列項
BaseType_t xQueueSendToBack(QueueHandle_t xQueue,
const voidvoid * pvItemToQueue, TickType_t xTicksToWait );
9. 向佇列尾部投遞佇列項(帶中斷保護)
BaseType_t xQueueSendToBackFromISR (QueueHandle_t xQueue,
const voidvoid *pvItemToQueue, BaseType_t *pxHigherPriorityTaskWoken );
10. 向佇列首部投遞佇列項
BaseType_t xQueueSendToFront(QueueHandle_t xQueue,
const voidvoid * pvItemToQueue,TickType_t xTicksToWait);
11. 向佇列首部投遞佇列項(帶中斷保護)
BaseType_t xQueueSendToFrontFromISR (QueueHandle_t xQueue,
const voidvoid *pvItemToQueue,BaseType_t *pxHigherPriorityTaskWoken);
12.讀取並移除佇列項
BaseType_t xQueueReceive(QueueHandle_t xQueue,
voidvoid *pvBuffer,TickType_t xTicksToWait);
13讀取並移除佇列項(帶中斷保護)
BaseType_t xQueueReceiveFromISR (QueueHandle_t xQueue,
voidvoid *pvBuffer, BaseType_t *pxHigherPriorityTaskWoken);
14.讀取但不移除佇列項
BaseType_t xQueuePeek(QueueHandle_t xQueue,
voidvoid *pvBuffer, TickType_t xTicksToWait);
15.讀取但不移除佇列項(帶中斷保護)