第一章 FreeRTOS系統配置
1. FreeRTOSConfig.h文件
針對 FreeRTOSConfig.h 文件,在 FreeRTOS 官方的在線文檔中有詳細的說明,網址為: https://www.freertos.org/a00110.html
FreeRTOS 使用 FreeRTOSConfig.h 文件進行配置和裁剪。 FreeRTOSConfig.h 文件中有幾十個配置項,這使得用戶能夠很好地配置和裁剪 FreeRTOS。
FreeRTOSConfig.h 文件中的配置項可分為三大類:“config”配置項、“INCLUDE”配置項和其他配置項,下面就為讀者詳細地講解這三類配置項。
2. "config"配置項
“config”配置項按照配置的功能分類,可分為十類,分別為基礎配置項、內存分配相關定義、鉤子函數相關定義、運行時間和任務狀態統計相關定義、協程相關定義、軟件定時器相關定義、中斷嵌套行為配置、斷言、 FreeRTOS MPU 特殊定義和 ARMv8-M 安全側端口相關定義。下面就分別介紹這寫“config”配置項。
2.1 基礎配置項
- configUSE_PREEMPTION
此宏用于設置系統的調度方式。 當宏 configUSE_PREEMPTION 設置為 1 時,系統使用搶占式調度; 當宏 configUSE_PREEMPTION 設置為 0 時,系統使用協程式調度。搶占式調度和協程式調度的區別在于, 協程式調度是正在運行的任務主動釋放 CPU 后才能切換到下一個任務,任務切換的時機完全取決于正在運行的任務。 協程式的優點在于可以節省開銷,但是功能比較有限,現在的 MCU 性能都比較強大,建議使用搶占式調度。
- configUSE_PORT_OPTIMISED_TASK_SELECTION
FreeRTOS 支持兩種方法來選擇下一個要執行的任務,分別為通用方法和特殊方法。
當宏 configUSE_PORT_OPTIMISED_TASK_SELECTION 設置為 0 時,使用通用方法。 通用方法是完全使用 C 實現的軟件算法,因此支持所用硬件,并且不限制任務優先級的最大值,但效率相較于特殊方法低。
當宏 configUSE_PORT_OPTIMISED_TASK_SELECTION 設置為 1 時,使用特殊方法。特殊方法的效率相較于通用方法高,但是特殊方法依賴于一個或多個特定架構的匯編指令(一般是類似計算前導零[CLZ]的指令),因此特殊方法并不支持所有硬件,并且對任務優先級的最大值一般也有限制,通常為 32。
- configUSE_TICKLESS_IDLE
當宏 configUSE_TICKLESS_IDLE 設置為 1 時,使能 tickless 低功耗模式;設置為 0 時, tick 中斷則會移植運行。 tickless 低功耗模式并不適用于所有硬件。
- configCPU_CLOCK_HZ
此宏應設置為 CPU 的內核時鐘頻率,單位為 Hz。
- configSYSTICK_CLOCK_HZ
此宏應設置為 SysTick 的時鐘頻率,當 SysTick 的時鐘源頻率與內核時鐘頻率不同時才可以定義,單位為 Hz。
- configTICK_RATE_HZ
此宏用于設置 FreeRTOS 系統節拍的中斷頻率,單位為 Hz。
- configMAX_PRIORITIES
此 宏 用 于 定 義 系 統 支 持 的 最 大 任 務 優 先 級 數 量 , 最 大 任 務 優 先 級 數 值 為configMAX_PRIORITIES-1。
- configMINIMAL_STACK_SIZE
此宏用于設置空閑任務的棧空間大小,單位為 word。
- configMAX_TASK_NAME_LEN
此宏用于設置任務名的最大字符數。
- configUSE_16_BIT_TICKS
此宏用于定義系統節拍計數器的數據類型, 當宏 configUSE_16_BIT_TICKS 設置為 1 時,系統節拍計數器的數據類型為 16 位無符號整形; 當宏 configUSE_16_BIT_TICKS 設置為 0 時,系統節拍計數器的數據類型為 32 為無符號整型。
- configIDLE_SHOULD_YIELD
當宏 configIDLE_SHOULD_YIELD 設置為 1 時,在搶占調度下,同等優先級的任務可搶占空閑任務,并延用空閑任務剩余的時間片。
- configUSE_TASK_NOTIFICATIONS
當宏 configUSE_TASK_NOTIFICATIONS 設置為 1 時,開啟任務通知功能。 當開啟任務通知功能后,每個任務將多占用 8 字節的內存空間。
- configTASK_NOTIFICATION_ARRAY_ENTRIES
此宏用于定義任務通知數組的大小。
- configUSE_MUTEXES
此宏用于使能互斥信號量, 當宏 configUSE_MUTEXS 設置為 1 時,使能互斥信號量; 當宏configUSE_MUTEXS 設置為 0 時,則不使能互斥信號量。
- configUSE_RECURSIVE_MUTEXES
此宏用于使能遞歸互斥信號量,當宏 configUSE_RECURSIVE_MUTEXES 設置為 1 時,使能遞歸互斥信號量;當宏 configUSE_RECURSIVE_MUTEXES 設置為 0 時,則不使能遞歸互斥信號量。
- configUSE_COUNTING_SEMAPHORES
此宏用于使能計數型信號量, 當宏 configUSE_COUNTING_SEMAPHORES 設置為 1 時,使能計數型信號量; 當宏 configUSE_COUNTING_SEMAPHORES 設置為 0 時,則不使能計數型信號量。
- configUSE_ALTERNATIVE_API
此宏在 FreeRTOS V9.0.0 之后已棄用。
- configQUEUE_REGISTRY_SIZE
此宏用于定義可以注冊的隊列和信號量的最大數量。此宏定義僅用于調試使用。
- configUSE_QUEUE_SETS
此宏用于使能隊列集, 當宏 configUSE_QUEUE_SETS 設置為 1 時,使能隊列集; 當宏configUSE_QUEUE_SETS 設置為 0 時,則不使能隊列集。
- configUSE_TIME_SLICING
此宏用于使能時間片調度, 當宏 configUSE_TIMER_SLICING 設置為 1 且使用搶占式調度時,使能時間片調度; 當宏 configUSE_TIMER_SLICING 設置為 0 時,則不使能時間片調度。
- configUSE_NEWLIB_REENTRANT
此宏用于為每個任務分配一個 NewLib 重入結構體,當宏configUSE_NEWLIB_REENTRANT 設置為 1 時, FreeRTOS 將為每個創建的任務的任務控制塊中分配一個 NewLib 重入結構體。
- configENABLE_BACKWARD_COMPATIBILITY
此宏用于兼容 FreeRTOS 老版本的 API 函數。
- configNUM_THREAD_LOCAL_STORAGE_POINTERS
此宏用于在任務控制塊中分配一個線程本地存儲指著數組,當此宏被定義為大于 0 時, configNUM_THREAD_LOCAL_STORAGE_POINTERS 為線程本地存儲指針數組的元素個數;當宏 configNUM_THREAD_LOCAL_STORAGE_POINTERS 為 0 時,則禁用線程本地存儲指針數組。
- configSTACK_DEPTH_TYPE
此宏用于定義任務堆棧深度的數據類型,默認為 uint16_t。
- configMESSAGE_BUFFER_LENGTH_TYPE
此宏用于定義消息緩沖區中消息長度的數據類型,默認為 size_t。
2.2 內存分配相關定義
- configSUPPORT_STATIC_ALLOCATION
當宏 configSUPPORT_STSTIC_ALLOCATION 設置為 1 時, FreeRTOS 支持使用靜態方式管理內存,此宏默認設置為 0。 如果將 configSUPPORT_STATIC_ALLOCATION 設置為 1,用戶還 需 要 提 供 兩 個 回 調 函 數 : vApplicationGetIdleTaskMemory() 和vApplicationGetTimerTaskMemory()
- configSUPPORT_DYNAMIC_ALLOCATION
當宏 configSUPPORT_DYNAMIC_ALLOCATION 設置為 1 時, FreeRTOS 支持使用動態方式管理內存,此宏默認設置為 1。
- configTOTAL_HEAP_SIZE
此宏用于定義用于 FreeRTOS 動態內存管理的內存大小,即 FreeRTOS 的內存堆,單位為Byte。
- configAPPLICATION_ALLOCATED_HEAP
此宏用于自定義 FreeRTOS 的內存堆, 當宏 configAPPLICATION_ALLOCATED_HEAP 設置為 1 時,用戶需要自行創建 FreeRTOS 的內存堆,否則 FreeRTOS 的內存堆將由編譯器進行分配。利用此宏定義,可以使用 FreeRTOS 動態管理外擴內存。
- configSTACK_ALLOCATION_FROM_SEPARATE_HEAP
此宏用于自定義動態創建和刪除任務時,任務棧內存的申請與釋放函數pvPortMallocStack()和vPortFreeStack(), 當宏configSTACK_ALLOCATION_FROM_SEPARATE_HEAP 設置為1是,用戶需提供 pvPortMallocStack()和 vPortFreeStack()函數。
2.3 鉤子函數相關定義
- configUSE_IDLE_HOOK
此宏用于使能使用空閑任務鉤子函數, 當宏 configUSE_IDLE_HOOK 設置為 1 時,使能使用空閑任務鉤子函數,用戶需自定義相關鉤子函數; 當宏 configUSE_IDLE_HOOK 設置為 0 時,則不使能使用空閑任務鉤子函數。
- configUSE_TICK_HOOK
此宏用于使能使用系統時鐘節拍中斷鉤子函數, 當宏 configUSE_TICK_HOOK 設置為 1 時,使能使用系統時鐘節拍中斷鉤子函 數,用戶需自定義相關鉤子函數;當 宏configUSE_TICK_HOOK 設置為 0 時, 則不使能使用系統時鐘節拍中斷鉤子函數。
- configCHECK_FOR_STACK_OVERFLOW
此宏用于使能棧溢出檢測, 當宏 configCHECK_FOR_STACK_OVERFLOW 設置為 1 時,使用棧溢出檢測方法一; 當宏 configCHECK_FOR_STACK_OVERFLOW 設置為 2 時,棧溢出檢測方法二; 當宏 configCHECK_FOR_STACK_OVERFLOW 設置為 0 時,不使能棧溢出檢測。
- configUSE_MALLOC_FAILED_HOOK
此宏用于使能使用動態內存分配失敗鉤子函數,當宏configUSE_MALLOC_FAILED_HOOK 設置為 1 時,使能使用動態內存分配失敗鉤子函數,用戶需自定義相關鉤子函數; 當宏 configUSE_MALLOC_FAILED_HOOK 設置為 0 時,則不使能使用動態內存分配失敗鉤子函數。
- configUSE_DAEMON_TASK_STARTUP_HOOK
此宏用于使能使用定時器服務任務首次執行前的鉤子函數,當 宏configUSE_DEAMON_TASK_STARTUP_HOOK 設置為 1 時,使能使用定時器服務任務首次執行前的鉤子函數, 此時用戶需定義定時器服務任務首次執行的相關鉤子函數; 當宏configUSE_DEAMON_TASK_STARTUP_HOOK 設置為 0 時,則不使能使用定時器服務任務首次執行前的鉤子函數。
2.4 運行時間和任務狀態統計相關定義
- configGENERATE_RUN_TIME_STATS
此宏用于使能任務運行時間統計功能, 當宏 configGENERATE_RUN_TIME_STATS 設置為1 時,使能任務運行時間統計功能,此時用戶需要提供兩個函數,一個是用于配置任務運行時間統計功能的函數 portCONFIGURE_TIMER_FOR_RUN_TIME_STATS(),一般是完成定時器的初始化,另一個函數是 portGET_RUN_TIME_COUNTER_VALUE(),該函數用于獲取定時器的計時值; 當宏 configGENERATE_RUN_TIME_STATS 設置為 0 時,則不使能任務運行時間統計功能。
- configUSE_TRACE_FACILITY
此宏用于使能可視化跟蹤調試, 當宏 configUSE_TRACE_FACILITY 設置為 1 時,使能可視化跟蹤調試; 當宏 configUSE_TRACE_FACILITY 設置為 0 時,則不使能可視化跟蹤調試。
- configUSE_STATS_FORMATTING_FUNCTIONS
當此宏與 configUSE_TRACE_FACILITY 同時設置為 1 時,將編譯函數 vTaskList()和函數vTaskGetRunTimeStats(),否則將忽略編譯函數 vTaskList()和函數 vTaskGetRunTimeStats()。
2.5 協程相關定義
- configUSE_CO_ROUTINES
此宏用于啟用協程, 當宏 configUSE_CO_ROUTINES 設置為 1 時,啟用協程; 當宏configUSE_CO_ROUTINES 設置為 0 時,則不啟用協程。
- configMAX_CO_ROUTINE_PRIORITIES
此宏用于設置協程的最大任務優先級數量,協程的最大任務優先級數值為configMAX_CO_ROUTINE_PRIORITIES-1
2.6 軟件定時器相關定義
- configUSE_TIMERS
此宏用于啟用軟件定時器功能, 當宏 configUSE_TIMERS 設置為 1 時,啟用軟件定時器功能; 當宏 configUSE_TIMERS 設置為 0 時,則不啟用軟件定時器功能。
- configTIMER_TASK_PRIORITY
此宏用于設置軟件定時器處理任務的優先級,當啟用軟件定時器功能時,系統會創建一個用于處理軟件定時器的軟件定時器處理任務。
- configTIMER_QUEUE_LENGTH
此宏用于定義軟件定時器隊列的長度, 軟件定時器的開啟、停止與銷毀等操作都是通過隊列實現的。
- configTIMER_TASK_STACK_DEPTH
此宏用于設置軟件定時器處理任務的棧空間大小,當啟用軟件定時器功能時,系統會創建一個用于處理軟件定時器的軟件定時器處理任務。
2.7 中斷嵌套行為配置
- configPRIO_BITS
此宏應定義為 MCU 的 8 位優先級配置寄存器實際使用的位數。
- configLIBRARY_LOWEST_INTERRUPT_PRIORITY
此宏應定義為 MCU 的最低中斷優先等級,對于 STM32,在使用 FreeRTOS 時,建議將中斷優先級分組設置為組 4,此時中斷的最低優先級為 15。 此宏定義用于輔助配置宏configKERNEL_INTERRUPT_PRIORITY。
- configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY
此宏定義用于設置 FreeRTOS 可管理中斷的最高優先級,當中斷的優先級數值小于configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY 時,此中斷不受 FreeRTOS 管理。此宏定義用于輔助配置宏 configMAX_SYSCALL_INTERRUPT_PRIORITY。
- configKERNEL_INTERRUPT_PRIORITY
此宏應定義為 MCU 的最低中斷優先等級在中斷優先級配置寄存器中的值,對于 STM32,即宏 configLIBRARY_LOWEST_INTERRUPT_PRIORITY 偏移 4bit 的值。
- configMAX_SYSCALL_INTERRUPT_PRIORITY
此宏應定義為 FreeRTOS 可管理中斷的最高優先等級在中斷優先級配置寄存器中的值,對于 STM32,即宏 configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY 偏移 4bit 的值。
- configMAX_API_CALL_INTERRUPT_PRIORITY
此宏為宏 configMAX_SYSCALL_INTERRUPT_PRIORITY 的新名稱,只被用在 FreeRTOS官方一些新的移植當中,此宏與宏 configMAX_SYSCALL_INTERRUPT_PRIORITY 是等價的。
2.8 斷言
- vAssertCalled(char, int)
此宏用于輔助配置宏 configASSERT( x )以通過串口打印相關信息。
- configASSERT( x )
此宏為 FreeRTOS 操作系統中的斷言,斷言會對表達式 x 進行判斷,當 x 為假時,斷言失敗,表明程序出錯,于是使用宏 vAssertCalled(char, int)通過串口打印相關的錯誤信息。斷言常用于檢測程序中的錯誤,使用斷言將增加程序的代碼大小和執行時間,因此建議在程序調試通過后將宏 configASSERT( x )進行注釋,以較少額外的開銷。
3. “INCLUDE”配置項
FreeRTOS 使用“INCLUDE”配置項對部分 API 函數進行條件編譯,當“INCLUDE”配置項被定義為 1 時,其對應的 API 函數則會加入編譯。對于用不到的 API 函數,用戶則可以將其對應的“INCLUDE”配置項設置為 0,那么這個 API 函數就不會加入編譯,以減少不必要的系統開銷。“INCLUDE”配置項與其對應 API 函數的功能描述如下表所示:
4. 其他配置項
- ureconfigMAX_SECURE_CONTEXTS
此宏為 ARMv8-M 安全側端口的相關配置項,本文暫不涉及 ARMv8-M 安全側端口的相關內容,感興趣的讀者可自行查閱相關資料。
- endSVHandler 和 vPortSVCHandler
這兩個宏為 PendSV 和 SVC 的中斷服務函數,主要用于 FreeRTOS 操作系統的任務切換,有關 FreeRTOS 操作系統中任務切換的相關內容
第二章 FreeRTOS中斷管理
1. FreeRTOS中斷配置項
- configPRIO_BITS
此宏是用于輔助配置的宏,主要用于輔助配置宏 configKERNEL_INTERRUPT_PRIORITY和宏 configMAX_SYSCALL_INTERRUPT_PRIORITY 的, 此宏應定義為 MCU 的 8 位優先級配置寄存器實際使用的位數,因為 STM32 只使用到了中斷優先級配置寄存器的高 4 位,因此,此宏應配置為 4。
- configLIBRARY_LOWEST_INTERRUPT_PRIORITY
此宏是用于輔助配置宏 configKERNEL_INTERRUPT_PRIORITY 的,此宏應設置為 MCU的最低優先等級,因為 STM32 只使用了中斷優先級配置寄存器的高 4 位,因此 MCU 的最低優先等級就是 2^4-1=15,因此,此宏應配置為 15。
- configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY
此宏是用于輔助配置宏 configMAX_SYSCALL_INTERRUPT_PRIORITY 的,此宏適用于配置 FreeRTOS 可管理的最高優先級的中斷,此功能就是操作 BASEPRI 寄存器來實現的。此宏的值可以根據用戶的實際使用場景來決定,本教程的配套例程源碼全部將此宏配置為 5,即中斷優先級高于 5 的中斷不受 FreeRTOS 影響,如下圖所示:
- configKERNEL_INTERRUPT_PRIORITY
此宏應配置為 MCU 的最低優先級在中斷優先級配置寄存器中的值,在 FreeRTOS 的源碼中,使用此宏將 SysTick 和 PenSV 的中斷優先級設置為最低優先級。 因為 STM32 只使用了中斷優先級配置寄存器的高 4 位,因此,此宏應配置為最低中斷優先級在中斷優先級配置寄存器高 4 位的表示,即(configLIBRARY_LOWEST_INTERRUPT_PRIORITY<<(8-configPRIO_BITS))。
- configMAX_SYSCALL_INTERRUPT_PRIORITY
此宏用于配置 FreeRTOS 可管理的最高優先級的中斷,在 FreeRTOS 的源碼中,使用此宏來打開和關閉中斷。 因為 STM32 只使用了中斷優先級配置寄存器的高 4 位,因此,此宏應配置為(configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY<<(8-configPRIO_BITS))。
- configMAX_API_CALL_INTERRUPT_PRIORITY
此宏為宏 configMAX_SYSCALL_INTERRUPT_PRIORITY 的新名稱,只被用在 FreeRTOS官方一些新的移植當中,此宏于宏 configMAX_SYSCALL_INTERRUPT_PRIORITY 是等價的。
2. 臨界區
臨界區是指那些必須完整運行的區域,在臨界區中的代碼必須完整運行,不能被打斷。例如一些使用軟件模擬的通信協議,通信協議在通信時,必須嚴格按照通信協議的時序進行,不能被打斷。 FreeRTOS 在進出臨界區的時候,通過關閉和打開受 FreeRTOS 管理的中斷,以保護臨界區中的代碼。 FreeRTOS 的源碼中就包含了許多臨界區的代碼,這部分代碼都是用臨界區進行保護,用戶在使用 FreeRTOS 編寫應用程序的時候,也要注意一些不能被打斷的操作,并為這部分代碼加上臨界區進行保護。
對于進出臨界區,FreeRTOS 的源碼中有四個相關的宏定義,分別為 taskENTER_CRITICAL() 、 taskENTER_CRITICAL_FROM_ISR() 、 taskEXIT_CRITICAL() 、taskEXIT_CRITICAL_FROM_ISR(x), 這四個宏定義分別用于在中斷和非中斷中進出臨界區, 定義代碼如下所示:
/* 進入臨界區 */
#define taskENTER_CRITICAL() portENTER_CRITICAL()
#define portENTER_CRITICAL() vPortEnterCritical()
/* 中斷中進入臨界區 */
#define taskENTER_CRITICAL_FROM_ISR() portSET_INTERRUPT_MASK_FROM_ISR()
#define portSET_INTERRUPT_MASK_FROM_ISR() ulPortRaiseBASEPRI()
/* 退出臨界區 */
#define taskEXIT_CRITICAL() portEXIT_CRITICAL()
#define portEXIT_CRITICAL() vPortExitCritical()
/* 中斷中退出臨界區 */
#define taskEXIT_CRITICAL_FROM_ISR(x) portCLEAR_INTERRUPT_MASK_FROM_ISR(x)
#define portCLEAR_INTERRUPT_MASK_FROM_ISR(x) vPortSetBASEPRI(x)
3. FreeRTOS中斷測試
3.1 程序流程圖
本實驗主要用于測試 FreeRTOS 打開和關閉中斷對中斷的影響,本實驗設計了兩個任務,這兩個任務的功能如下表所示:
本實驗的程序流程圖,如下圖所示:
3.2 FreeRTOS函數解析
3.2.1 portDISABLE_INTERRUPTS()
功能:禁用處理器的全局中斷,防止中斷服務程序(ISR)打斷當前正在執行的代碼。
3.2.2 portENABLE_INTERRUPTS()
功能:重新啟用處理器的全局中斷。
3.2 代碼分析
3.2.1 任務參數宏定義
/*----------------任務函數配置區-----------------*/
// START_TASK配置
#define START_TASK_PRIO 1 // 任務優先級
#define START_STK_SIZE 128 // 任務堆棧大小
TaskHandle_t StartTask_Handler; // 任務句柄
void start_task(void *pvParameters);// 任務函數 // TASK1配置
#define TASK1_PRIO 2
#define TASK1_STK_SIZE 128
TaskHandle_t Task1Task_Handler;
void task1(void *pvParameters);
/*---------------------------------------------*/
3.2.2 start_task 任務實現
// start_task函數實現
void start_task(void *pvParameters)
{taskENTER_CRITICAL(); // 進入臨界區btim_tim3_int_init(10000-1, 24000-1);btim_tim5_int_init(10000-1, 24000-1);// 創建TASK1任務xTaskCreate((TaskFunction_t)task1, (const char*)"task1", (uint16_t)TASK1_STK_SIZE, (void*)NULL, (UBaseType_t)TASK1_PRIO, (TaskHandle_t*)&Task1Task_Handler);vTaskDelete(StartTask_Handler); // 開始任務已經完成自己使命,刪除自己taskEXIT_CRITICAL(); // 退出臨界區
}
3.2.3 task1 任務實現
// task1函數實現
void task1(void *pvParameters)
{uint32_t task1_count = 0;while(1){if(++task1_count == 5){printf("FreeRTOS關閉它能影響的中斷,TIM3不受影響");portDISABLE_INTERRUPTS(); // 關閉中斷LED0(0);delay_ms(5000);printf("FreeRTOS重新打開中斷,TIM3繼續工作");LED0(1);portENABLE_INTERRUPTS(); // 重新打開中斷}vTaskDelay(1000);}
}
3.2.4 主函數
#include "sys.h"
#include "delay.h"
#include "usart.h"
#include "led.h"
#include "mpu.h"
#include "lcd.h"
#include "key.h"
#include "malloc.h"
#include "freertos_demo.h"int main(void)
{sys_cache_enable(); /* 打開L1-Cache */HAL_Init(); /* 初始化HAL庫 */sys_stm32_clock_init(240, 2, 2, 4); /* 設置時鐘, 480Mhz */delay_init(480); /* 延時初始化 */usart_init(115200); /* 串口初始化為115200 */led_init(); /* 初始化LED */mpu_memory_protection(); /* 保護相關存儲區域 */lcd_init(); /* 初始化LCD */key_init(); /* 初始化按鍵 */my_mem_init(SRAMIN); /* 初始化內部內存池(AXI) */freertos_demo(); /* 運行FreeRTOS例程 */
}
第三章 FreeRTOS任務基礎
1. 創建和刪除任務
函數 | 描述 |
---|---|
xTaskCreate() | 動態方式創建任務 |
xTaskCreateStatic() | 靜態方式創建任務 |
xTaskCreateRestricted() | 動態方式創建使用 MPU 限制的任務 |
xTaskCreateRestrictedStatic() | 靜態方式創建使用 MPU 限制的任務 |
vTaskDelete() | 刪除任務 |
1.1 動態方式創建任務
xTaskCreate()
函數用于創建一個新的 FreeRTOS 任務并將其添加到就緒任務列表中,以便調度器進行管理和運行。
函數原型:
BaseType_t xTaskCreate( TaskFunction_t pvTaskCode,const char * const pcName,unsigned short usStackDepth,void *pvParameters,UBaseType_t uxPriority,TaskHandle_t *pxCreatedTask );
參數說明:
pvTaskCode
: 指向任務入口函數的指針。任務函數通常實現為一個無限循環,并且不應該返回或退出。pcName
: 任務的描述性名稱。主要用于調試,最大長度由configMAX_TASK_NAME_LEN
定義 (默認為 16)。usStackDepth
: 任務堆棧的大小,以 字 為單位,而不是字節。例如,如果堆棧是 16 位寬,usStackDepth
為 100,則分配 200 字節的堆棧。pvParameters
: 指向將作為參數傳遞給被創建任務的指針。uxPriority
: 任務的優先級。pxCreatedTask
: 用于將創建的任務句柄傳回的指針。這個句柄可以用來引用任務,例如在vTaskDelete()
中刪除任務。 此參數是可選的,可以設置為NULL
。
返回值:
pdPASS
:任務成功創建并添加到就緒列表。- 其他錯誤代碼:定義在
errors.h
文件中。
注意事項:
- 使用
xTaskCreate()
創建任務時,所需的 RAM 會自動從 FreeRTOS 堆中分配。 - 如果系統支持 MPU,可以使用
xTaskCreateRestricted()
來創建受 MPU 約束的任務。 - 如果
configSUPPORT_DYNAMIC_ALLOCATION
未定義或設置為 1,則xTaskCreate()
函數可用。
1.2 靜態方式創建任務
與 xTaskCreate()
動態分配內存不同,xTaskCreateStatic()
允許用戶在編譯時或運行時提供任務堆棧和任務控制塊(TCB)所需的內存,從而避免了運行時的堆內存分配。這在對內存管理有嚴格要求或不能使用動態內存分配的嵌入式系統中非常有用。
函數原型:
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 );
參數說明:
pxTaskCode
: 指向任務入口函數的指針。pcName
: 任務的描述性名稱。ulStackDepth
: 任務堆棧的大小,以 字 為單位。pvParameters
: 將作為參數傳遞給被創建任務的指針。uxPriority
: 任務的優先級。puxStackBuffer
: 指向用戶提供的、用于任務堆棧的uint32_t
數組的指針。pxTaskBuffer
: 指向用戶提供的、用于任務控制塊 (TCB) 的StaticTask_t
變量的指針。
返回值:
TaskHandle_t
: 任務創建成功返回任務句柄,否則返回NULL
。
注意事項:
- 要使此函數可用,
configSUPPORT_STATIC_ALLOCATION
必須在FreeRTOSConfig.h
中定義為 1。 - 用戶需要自行管理
puxStackBuffer
和pxTaskBuffer
所指向的內存生命周期。
1.3 動態方式創建使用 MPU 限制的任務
xTaskCreateRestricted()
用于創建具有內存保護單元 (MPU) 限制的任務。MPU 允許為任務定義獨立的內存訪問權限,增強系統的安全性、隔離性和穩定性。
函數原型:
BaseType_t xTaskCreateRestricted( const TaskParameters_t * const pxTaskDefinition,TaskHandle_t *pxCreatedTask );
參數說明:
pxTaskDefinition
: 指向TaskParameters_t
結構體的指針,該結構體包含任務的配置信息,包括任務代碼、堆棧大小、優先級以及 MPU 區域定義等。pxCreatedTask
: 用于傳回創建的任務句柄的指針。
返回值:
pdPASS
:任務成功創建。- 其他錯誤代碼。
注意事項:
- 要使此函數可用,
configENABLE_MPU
必須在FreeRTOSConfig.h
中定義為 1。 - 需要處理器支持 MPU 功能。
1.4 靜態方式創建使用 MPU 限制的任務
xTaskCreateRestrictedStatic()
結合了 xTaskCreateRestricted()
的 MPU 限制功能和 xTaskCreateStatic()
的靜態內存分配特性。它允許用戶在編譯時或運行時提供內存,并同時為任務設置 MPU 權限。
函數原型:
TaskHandle_t xTaskCreateRestrictedStatic( const TaskParameters_t * const pxTaskDefinition,TaskHandle_t *pxCreatedTask );
參數說明:
pxTaskDefinition
: 指向TaskParameters_t
結構體的指針,其中包含任務的配置信息,包括 MPU 區域定義以及指向用戶提供的堆棧和 TCB 緩沖區的指針。pxCreatedTask
: 用于傳回創建的任務句柄的指針。
返回值:
TaskHandle_t
: 任務創建成功返回任務句柄,否則返回NULL
。
注意事項:
- 要使此函數可用,
configENABLE_MPU
和configSUPPORT_STATIC_ALLOCATION
都必須在FreeRTOSConfig.h
中定義為 1。 - 需要處理器支持 MPU 功能。
1.5 刪除任務:vTaskDelete()
vTaskDelete()
函數用于將任務從 FreeRTOS 實時內核的管理中移除。
函數原型:
void vTaskDelete( TaskHandle_t xTask );
參數說明:
xTask
: 要刪除的任務的句柄。如果傳入NULL
,則會刪除調用此函數的任務自身。
注意事項:
- 要使此函數可用,
INCLUDE_vTaskDelete
必須在FreeRTOSConfig.h
中定義為 1。 - 被刪除的任務將從所有就緒、阻塞、掛起和事件列表中移除。
- 如果任務刪除其他任務,FreeRTOS 內核分配的內存會在 API 中立即釋放。如果任務刪除自身,則由空閑任務 (
idle task
) 負責釋放內核分配的內存。因此,如果應用程序調用vTaskDelete()
,確保空閑任務有足夠的處理時間來釋放內存非常重要。 - 任務代碼中分配的內存不會自動釋放,應在任務刪除之前手動釋放。
- 刪除任務主要有兩種情況:自刪除(在任務本身的 TaskCode 中調用
vTaskDelete(NULL)
)和強制刪除(在其他任務中刪除另一個任務)。 - 對于使用
xTaskCreate()
創建的任務,刪除后存儲資源將在空閑任務中自動釋放。對于使用xTaskCreateStatic()
靜態創建的任務,存儲資源不會被釋放,需要手動釋放。 - 在刪除任務時,需要考慮該任務是否占用了共享資源、是否申請了新資源,以及是否與關聯任務或中斷服務例程 (ISR) 存在通信關系,以避免后遺癥。
2. FreeRTOS任務創建與刪除測試
2.1 動態方法
2.1.1 任務配置
/*----------------任務函數配置區-----------------*/
// START_TASK配置
#define START_TASK_PRIO 1 // 任務優先級
#define START_STK_SIZE 128 // 任務堆棧大小
TaskHandle_t StartTask_Handler; // 任務句柄
void start_task(void *pvParameters);// 任務函數 // TASK1配置
#define TASK1_PRIO 2
#define TASK1_STK_SIZE 128
TaskHandle_t Task1Task_Handler;
void task1(void *pvParameters);// TASK2配置
#define TASK2_PRIO 3
#define TASK2_STK_SIZE 128
TaskHandle_t Task2Task_Handler;
void task2(void *pvParameters);// TASK3配置
#define TASK3_PRIO 4
#define TASK3_STK_SIZE 128
TaskHandle_t Task3Task_Handler;
void task3(void *pvParameters);
/*---------------------------------------------*/
2.1.2 任務實現
/*----------------任務函數實現區----------------*/
void freertos_demo(void)
{// 初始化LCD顯示lcd_show_string(10, 47, 220, 30, 24, "Task Create&Delete", RED);lcd_draw_rectangle(5, 110, 115, 314, BLACK);lcd_draw_rectangle(125, 110, 234, 314, BLACK);lcd_draw_line(5, 130, 115, 130, BLACK);lcd_draw_line(125, 130, 234, 130, BLACK);lcd_show_string(15, 111, 110, 16, 16, "Task1: 000", BLUE);lcd_show_string(135, 111, 110, 16, 16, "Task2: 000", BLUE);// 創建START_TASK任務xTaskCreate((TaskFunction_t)start_task, // 任務函數(const char*)"start_task", // 任務名稱(uint16_t)START_STK_SIZE, // 任務堆棧大小(void*)NULL, // 傳遞給任務函數的參數(UBaseType_t)START_TASK_PRIO, // 任務優先級(TaskHandle_t*)&StartTask_Handler);// 任務句柄// 開始任務調度vTaskStartScheduler();
}
2.1.3 主函數
#include "sys.h"
#include "delay.h"
#include "usart.h"
#include "led.h"
#include "mpu.h"
#include "lcd.h"
#include "key.h"
#include "malloc.h"
#include "freertos_demo.h"int main(void)
{sys_cache_enable(); /* 打開L1-Cache */HAL_Init(); /* 初始化HAL庫 */sys_stm32_clock_init(240, 2, 2, 4); /* 設置時鐘, 480Mhz */delay_init(480); /* 延時初始化 */usart_init(115200); /* 串口初始化為115200 */led_init(); /* 初始化LED */mpu_memory_protection(); /* 保護相關存儲區域 */lcd_init(); /* 初始化LCD */key_init(); /* 初始化按鍵 */my_mem_init(SRAMIN); /* 初始化內部內存池(AXI) */freertos_demo(); /* 運行FreeRTOS例程 */
}
2.2 靜態方法
2.2.1 提供空閑任務和軟件定時器服務任務
/*---------------FreeRTOS系統配置區-------------*/
static StackType_t IdleTaskStack[configMINIMAL_STACK_SIZE]; // 空閑任務棧
static StaticTask_t IdleTaskTCB; // 空閑任務控制塊
static StackType_t TimerTaskStack[configTIMER_TASK_STACK_DEPTH]; // 定時器任務棧
static StaticTask_t TimerTaskTCB; // 定時器任務控制塊
/*---------------------------------------------*//*----------------任務函數配置區-----------------*/
/*** @brief 獲取空閑任務地任務堆棧和任務控制塊內存,因為本例程使用的靜態內存,因此空閑任務的任務堆棧和任務控制塊的內存就應該有用戶來提供,FreeRTOS提供了接口函數vApplicationGetIdleTaskMemory()實現此函數即可。* @param ppxIdleTaskTCBBuffer:任務控制塊內存ppxIdleTaskStackBuffer:任務堆棧內存pulIdleTaskStackSize:任務堆棧大小* @retval 無*/
void vApplicationGetIdleTaskMemory(StaticTask_t **ppxIdleTaskTCBBuffer, StackType_t **ppxIdleTaskStackBuffer, uint32_t *pulIdleTaskStackSize)
{*ppxIdleTaskTCBBuffer = &IdleTaskTCB;*ppxIdleTaskStackBuffer = IdleTaskStack;*pulIdleTaskStackSize = configMINIMAL_STACK_SIZE;
}/*** @brief 獲取定時器服務任務的任務堆棧和任務控制塊內存* @param ppxTimerTaskTCBBuffer:任務控制塊內存ppxTimerTaskStackBuffer:任務堆棧內存pulTimerTaskStackSize:任務堆棧大小* @retval 無*/
void vApplicationGetTimerTaskMemory(StaticTask_t **ppxTimerTaskTCBBuffer,StackType_t **ppxTimerTaskStackBuffer,uint32_t *pulTimerTaskStackSize)
{*ppxTimerTaskTCBBuffer = &TimerTaskTCB;*ppxTimerTaskStackBuffer= TimerTaskStack;*pulTimerTaskStackSize = configTIMER_TASK_STACK_DEPTH;
}
2.2.2 任務配置
// START_TASK配置
#define START_TASK_PRIO 1 // 任務優先級
#define START_STK_SIZE 128 // 任務堆棧大小
StackType_t StartTaskStack[START_STK_SIZE]; // 任務堆棧
StaticTask_t StartTaskTCB; // 任務控制塊
TaskHandle_t StartTask_Handler; // 任務句柄
void start_task(void *pvParameters);// TASK1配置
#define TASK1_PRIO 2
#define TASK1_STK_SIZE 128
StackType_t Task1TaskStack[TASK1_STK_SIZE];
StaticTask_t Task1TaskTCB;
TaskHandle_t Task1Task_Handler;
void task1(void *pvParameters);// TASK2配置
#define TASK2_PRIO 3
#define TASK2_STK_SIZE 128
StackType_t Task2TaskStack[TASK2_STK_SIZE];
StaticTask_t Task2TaskTCB;
TaskHandle_t Task2Task_Handler;
void task2(void *pvParameters);// TASK3配置
#define TASK3_PRIO 4
#define TASK3_STK_SIZE 128
StackType_t Task3TaskStack[TASK3_STK_SIZE];
StaticTask_t Task3TaskTCB;
TaskHandle_t Task3Task_Handler;
void task3(void *pvParameters);
2.2.3 任務實現
*----------------任務函數實現區----------------*/
void freertos_demo(void)
{// 初始化LCD顯示lcd_show_string(10, 47, 220, 30, 24, "Task Create&Delete", RED);lcd_draw_rectangle(5, 110, 115, 314, BLACK);lcd_draw_rectangle(125, 110, 234, 314, BLACK);lcd_draw_line(5, 130, 115, 130, BLACK);lcd_draw_line(125, 130, 234, 130, BLACK);lcd_show_string(15, 111, 110, 16, 16, "Task1: 000", BLUE);lcd_show_string(135, 111, 110, 16, 16, "Task2: 000", BLUE);// 創建START_TASK任務StartTask_Handler = xTaskCreateStatic((TaskFunction_t)start_task, // 任務函數(const char*)"start_task", // 任務名稱(uint16_t)START_STK_SIZE, // 任務堆棧大小(void*)NULL, // 傳遞給任務函數的參數(UBaseType_t)START_TASK_PRIO, // 任務優先級(StackType_t*)StartTaskStack, // 任務堆棧(StaticTask_t*)&StartTaskTCB);// 任務控制塊// 開始任務調度vTaskStartScheduler();
}// start_task函數實現
void start_task(void *pvParameters)
{taskENTER_CRITICAL(); // 進入臨界區// 創建TASK1任務Task1Task_Handler = xTaskCreateStatic((TaskFunction_t)task1, (const char*)"task1", (uint16_t)TASK1_STK_SIZE, (void*)NULL, (UBaseType_t)TASK1_PRIO, (StackType_t*)Task1TaskStack,(StaticTask_t*)&Task1TaskTCB);// 創建TASK2任務Task2Task_Handler = xTaskCreateStatic((TaskFunction_t)task2, (const char*)"task2", (uint16_t)TASK2_STK_SIZE, (void*)NULL, (UBaseType_t)TASK2_PRIO, (StackType_t*)Task2TaskStack,(StaticTask_t*)&Task2TaskTCB);// 創建TASK3任務Task3Task_Handler = xTaskCreateStatic((TaskFunction_t)task3, (const char*)"task3", (uint16_t)TASK3_STK_SIZE, (void*)NULL, (UBaseType_t)TASK3_PRIO, (StackType_t*)Task3TaskStack,(StaticTask_t*)&Task3TaskTCB);vTaskDelete(StartTask_Handler); // 刪除START_TASK任務taskEXIT_CRITICAL(); // 退出臨界區
}// task1函數實現
// 實現功能:記錄自己的運行次數,每次運行時在LCD顯示不同顏色
void task1(void *pvParameters)
{uint32_t task1_count = 0;while(1){lcd_fill(6,131,114,313,lcd_discolor[++task1_count % 11]);lcd_show_xnum(71,111,task1_count,3,16,0x80,BLUE);vTaskDelay(500);}
}// task2函數實現
// 實現功能:記錄自己的運行次數,每次運行時在LCD顯示不同顏色
void task2(void *pvParameters)
{uint32_t task2_count = 0;while(1){lcd_fill(126,131,233,313,lcd_discolor[11-(++task2_count % 11)]);lcd_show_xnum(191,111,task2_count,3,16,0x80,BLUE);vTaskDelay(500);}
}// task3函數實現
// 實現功能:按下KEY0刪除task1任務,按下KEY1刪除task2任務
void task3(void *pvParameters)
{uint8_t key_value;while(1){key_value = key_scan(0);if(key_value == KEY0_PRES){if(Task1Task_Handler != NULL){vTaskDelete(Task1Task_Handler);Task1Task_Handler = NULL;lcd_show_string(15, 111, 110, 16, 16, "Task1: Del", RED); }}if(key_value == KEY1_PRES){if(Task2Task_Handler != NULL){vTaskDelete(Task2Task_Handler);Task2Task_Handler = NULL;lcd_show_string(135, 111, 110, 16, 16, "Task2: Del", RED);}}vTaskDelay(10);}
}
/*---------------------------------------------*/
2.2.4 主函數
#include "sys.h"
#include "delay.h"
#include "usart.h"
#include "led.h"
#include "mpu.h"
#include "lcd.h"
#include "key.h"
#include "malloc.h"
#include "freertos_demo.h"int main(void)
{sys_cache_enable(); /* 打開L1-Cache */HAL_Init(); /* 初始化HAL庫 */sys_stm32_clock_init(240, 2, 2, 4); /* 設置時鐘, 480Mhz */delay_init(480); /* 延時初始化 */usart_init(115200); /* 串口初始化為115200 */led_init(); /* 初始化LED */mpu_memory_protection(); /* 保護相關存儲區域 */lcd_init(); /* 初始化LCD */key_init(); /* 初始化按鍵 */my_mem_init(SRAMIN); /* 初始化內部內存池(AXI) */freertos_demo(); /* 運行FreeRTOS例程 */
}
3. 掛起和恢復任務
函數 | 描述 |
---|---|
vTaskSuspend() | 掛起任務 |
vTaskResume() | 恢復被掛起的任務 |
xTaskResumeFromISR() | 在中斷中恢復被掛起的任務 |
3.1 掛起任務
vTaskSuspend()
函數用于將一個任務置于掛起狀態。當任務處于掛起狀態時,它將不會被 FreeRTOS 調度器調度執行,即使它的優先級很高。
函數原型:
void vTaskSuspend( TaskHandle_t xTaskToSuspend );
參數說明:
xTaskToSuspend
: 要掛起的任務的句柄。如果傳入NULL
,則會掛起調用此函數的任務自身。
注意事項:
- 要使此函數可用,
INCLUDE_vTaskSuspend
必須在FreeRTOSConfig.h
中定義為 1。 - 被掛起的任務將從所有就緒、阻塞和事件列表中移除,并放置在掛起列表中。
- 被掛起的任務在被恢復之前不會消耗任何 CPU 時間。
- 如果一個任務被掛起,即使有外部事件(如信號量、隊列消息等)發生,它也不會被解除阻塞,直到它被明確地恢復。
3.2 恢復被掛起的任務
vTaskResume()
函數用于將一個處于掛起狀態的任務恢復到就緒狀態。一旦任務被恢復,它就可以被 FreeRTOS 調度器調度執行。
函數原型:
void vTaskResume( TaskHandle_t xTaskToResume );
參數說明:
xTaskToResume
: 要恢復的任務的句柄。
注意事項:
- 要使此函數可用,
INCLUDE_vTaskSuspend
必須在FreeRTOSConfig.h
中定義為 1。 - 此函數不能從中斷服務例程 (ISR) 中調用。在 ISR 中恢復任務應使用
xTaskResumeFromISR()
。 - 如果恢復一個已經處于就緒或阻塞狀態的任務,
vTaskResume()
將不執行任何操作。
3.3 在中斷中恢復被掛起的任務
xTaskResumeFromISR()
函數是 vTaskResume()
的中斷安全版本,用于在中斷服務函數 (ISR) 中恢復一個被掛起的任務。
函數原型:
BaseType_t xTaskResumeFromISR( TaskHandle_t xTaskToResume );
參數說明:
xTaskToResume
: 要恢復的任務的句柄。
返回值:
pdTRUE
:如果調用xTaskResumeFromISR()
導致更高優先級的任務被解除阻塞(即需要進行上下文切換),則返回pdTRUE
。pdFALSE
:否則返回pdFALSE
。
注意事項:
- 要使此函數可用,
INCLUDE_vTaskSuspend
必須在FreeRTOSConfig.h
中定義為 1。 - 此函數通常在中斷處理結束時用于檢查是否需要進行任務切換。如果返回
pdTRUE
,通常會調用portYIELD_FROM_ISR()
或類似的宏來觸發上下文切換,從而立即執行被恢復的高優先級任務。
4. 掛起和恢復任務測試
4.1 任務配置
/*----------------任務函數配置區-----------------*/
// START_TASK配置
#define START_TASK_PRIO 1 // 任務優先級
#define START_STK_SIZE 128 // 任務堆棧大小
TaskHandle_t StartTask_Handler; // 任務句柄
void start_task(void *pvParameters);// 任務函數 // TASK1配置
#define TASK1_PRIO 2
#define TASK1_STK_SIZE 128
TaskHandle_t Task1Task_Handler;
void task1(void *pvParameters);// TASK2配置
#define TASK2_PRIO 3
#define TASK2_STK_SIZE 128
TaskHandle_t Task2Task_Handler;
void task2(void *pvParameters);// TASK3配置
#define TASK3_PRIO 4
#define TASK3_STK_SIZE 128
TaskHandle_t Task3Task_Handler;
void task3(void *pvParameters);
/*---------------------------------------------*/
4.2 任務實現
/*----------------任務函數實現區----------------*/
void freertos_demo(void)
{// 初始化LCD顯示lcd_show_string(10, 47, 220, 30, 24, "Task Create&Delete", RED);lcd_draw_rectangle(5, 110, 115, 314, BLACK);lcd_draw_rectangle(125, 110, 234, 314, BLACK);lcd_draw_line(5, 130, 115, 130, BLACK);lcd_draw_line(125, 130, 234, 130, BLACK);lcd_show_string(15, 111, 110, 16, 16, "Task1: 000", BLUE);lcd_show_string(135, 111, 110, 16, 16, "Task2: 000", BLUE);// 創建START_TASK任務xTaskCreate((TaskFunction_t)start_task, // 任務函數(const char*)"start_task", // 任務名稱(uint16_t)START_STK_SIZE, // 任務堆棧大小(void*)NULL, // 傳遞給任務函數的參數(UBaseType_t)START_TASK_PRIO, // 任務優先級(TaskHandle_t*)&StartTask_Handler);// 任務句柄// 開始任務調度vTaskStartScheduler();
}// start_task函數實現
void start_task(void *pvParameters)
{taskENTER_CRITICAL(); // 進入臨界區// 創建TASK1任務xTaskCreate((TaskFunction_t)task1, (const char*)"task1", (uint16_t)TASK1_STK_SIZE, (void*)NULL, (UBaseType_t)TASK1_PRIO, (TaskHandle_t*)&Task1Task_Handler);// 創建TASK2任務xTaskCreate((TaskFunction_t)task2, (const char*)"task2", (uint16_t)TASK2_STK_SIZE, (void*)NULL, (UBaseType_t)TASK2_PRIO, (TaskHandle_t*)&Task2Task_Handler);// 創建TASK3任務xTaskCreate((TaskFunction_t)task3, (const char*)"task3", (uint16_t)TASK3_STK_SIZE, (void*)NULL, (UBaseType_t)TASK3_PRIO, (TaskHandle_t*)&Task3Task_Handler);vTaskDelete(StartTask_Handler); // 刪除START_TASK任務taskEXIT_CRITICAL(); // 退出臨界區
}// task1函數實現
// 實現功能:記錄自己的運行次數,每次運行時在LCD顯示不同顏色
void task1(void *pvParameters)
{uint32_t task1_count = 0;while(1){lcd_fill(6,131,114,313,lcd_discolor[++task1_count % 11]);lcd_show_xnum(71,111,task1_count,3,16,0x80,BLUE);vTaskDelay(500);}
}// task2函數實現
// 實現功能:記錄自己的運行次數,每次運行時在LCD顯示不同顏色
void task2(void *pvParameters)
{uint32_t task2_count = 0;while(1){lcd_fill(126,131,233,313,lcd_discolor[11-(++task2_count % 11)]);lcd_show_xnum(191,111,task2_count,3,16,0x80,BLUE);vTaskDelay(500);}
}// task3函數實現
// 實現功能:按下KEY0刪除task1任務,按下KEY1刪除task2任務
void task3(void *pvParameters)
{uint8_t key_value;while(1){key_value = key_scan(0);if(key_value == KEY0_PRES){if(Task1Task_Handler != NULL){vTaskDelete(Task1Task_Handler);Task1Task_Handler = NULL;lcd_show_string(15, 111, 110, 16, 16, "Task1: Del", RED); }}if(key_value == KEY1_PRES){if(Task2Task_Handler != NULL){vTaskSuspend(Task2Task_Handler); // 掛起task2任務}}if(key_value == WKUP_PRES){if(Task2Task_Handler != NULL){vTaskResume(Task2Task_Handler); // 恢復task2任務}}vTaskDelay(10);}
}
/*---------------------------------------------*/
4.3 主函數
#include "sys.h"
#include "delay.h"
#include "usart.h"
#include "led.h"
#include "mpu.h"
#include "lcd.h"
#include "key.h"
#include "malloc.h"
#include "freertos_demo.h"int main(void)
{sys_cache_enable(); /* 打開L1-Cache */HAL_Init(); /* 初始化HAL庫 */sys_stm32_clock_init(240, 2, 2, 4); /* 設置時鐘, 480Mhz */delay_init(480); /* 延時初始化 */usart_init(115200); /* 串口初始化為115200 */led_init(); /* 初始化LED */mpu_memory_protection(); /* 保護相關存儲區域 */lcd_init(); /* 初始化LCD */key_init(); /* 初始化按鍵 */my_mem_init(SRAMIN); /* 初始化內部內存池(AXI) */freertos_demo(); /* 運行FreeRTOS例程 */
}
第四章 FreeRTOS列表與列表項
1. 列表與列表項
函數 | 描述 |
---|---|
vListInitialise() | 初始化列表 |
vListInitialiseItem() | 初始化列表項 |
vListInsertEnd() | 列表末尾插入列表項 |
vListInsert() | 列表插入列表項 |
uxListRemove() | 列表移除列表項 |
1.1 初始化列表
vListInitialise()
函數用于初始化一個 FreeRTOS 列表。在使用任何列表操作之前,必須先調用此函數對列表進行初始化。
函數原型:
void vListInitialise( List_t * const pxList );
參數說明:
pxList
: 指向要初始化的List_t
結構體變量的指針。
注意事項:
List_t
結構體通常在外部定義,例如在任務控制塊(TCB)或事件組中。- 此函數將列表的成員(如鏈表頭指針、列表項計數器等)設置為其初始狀態,使其成為一個空列表。
1.2 初始化列表項
vListInitialiseItem()
函數用于初始化一個 FreeRTOS 列表項。每個要插入到 FreeRTOS 列表中的數據結構都必須包含一個 ListItem_t
或 MiniListItem_t
類型的列表項成員,并且在使用前需要通過此函數進行初始化。
函數原型:
void vListInitialiseItem( ListItem_t * const pxListItem );
參數說明:
pxListItem
: 指向要初始化的ListItem_t
結構體變量的指針。
注意事項:
ListItem_t
或MiniListItem_t
通常是用戶定義結構體的一部分,用于將該結構體鏈接到 FreeRTOS 列表中。- 此函數將列表項的成員(如指向其所屬列表的指針、節點值等)設置為其初始狀態。
1.3 列表末尾插入列表項
vListInsertEnd()
函數用于將一個已初始化的列表項插入到指定列表的末尾。
函數原型:
void vListInsertEnd( List_t * const pxList, ListItem_t * const pxNewListItem );
參數說明:
pxList
: 指向目標列表的List_t
結構體變量的指針。pxNewListItem
: 指向要插入的列表項的ListItem_t
結構體變量的指針。
注意事項:
- 此函數不會考慮列表項的值,而是簡單地將其添加到列表的末尾。
1.4 列表插入列表項(按排序順序)
vListInsert()
函數用于將一個已初始化的列表項插入到指定列表的正確位置,以保持列表的排序順序。列表項的排序基于其“值”(xItemValue
成員)。
函數原型:
void vListInsert( List_t * const pxList, ListItem_t * const pxNewListItem );
參數說明:
pxList
: 指向要插入列表項的List_t
結構體變量的指針。pxNewListItem
: 指向要插入的ListItem_t
結構體變量的指針。列表項的xItemValue
成員將用于排序。
注意事項:
- 此函數通常在 FreeRTOS 內部使用,例如將一個任務添加到就緒列表或阻塞列表中,并根據任務優先級進行排序。
- 應用程序通常不需要直接調用此函數。
1.5 列表移除列表項
uxListRemove()
函數用于將一個列表項從其當前所在的列表中移除。
函數原型:
UBaseType_t uxListRemove( ListItem_t * const pxItemToRemove );
參數說明:
pxItemToRemove
: 指向要移除的ListItem_t
結構體變量的指針。
返回值:
UBaseType_t
: 返回移除列表項后,列表中剩余的列表項數量。
注意事項:
- 此函數通常在 FreeRTOS 內部使用,例如當任務從阻塞狀態變為就緒狀態時,將其從阻塞列表中移除。
- 應用程序通常不需要直接調用此函數
2. FreeRTOS列表項的插入與刪除測試
2.1 任務配置
/*----------------任務配置區-----------------*/
// START_TASK配置
#define START_TASK_PRIO 1 // 任務優先級
#define START_STK_SIZE 128 // 任務堆棧大小
TaskHandle_t StartTask_Handler; // 任務句柄
void start_task(void *pvParameters);// 任務函數 // TASK1配置
#define TASK1_PRIO 2
#define TASK1_STK_SIZE 128
TaskHandle_t Task1Task_Handler;
void task1(void *pvParameters);
/*-------------------------------------------*/
2.2 列表配置
/*----------------列表配置區------------------*/
List_t TestList; // 測試列表
ListItem_t ListItem1; // 測試列表項1
ListItem_t ListItem2; // 測試列表項2
ListItem_t ListItem3; // 測試列表項3
/*-------------------------------------------*/
2.3 任務實現
/*------------------任務實現區----------------*/
void freertos_demo(void)
{lcd_show_string(10,47,220,24,24,"List Insert Demo",RED);// 創建START_TASK任務xTaskCreate((TaskFunction_t)start_task, // 任務函數(const char*)"start_task", // 任務名稱(uint16_t)START_STK_SIZE, // 任務堆棧大小(void*)NULL, // 傳遞給任務函數的參數(UBaseType_t)START_TASK_PRIO, // 任務優先級(TaskHandle_t*)&StartTask_Handler);// 任務句柄// 開始任務調度vTaskStartScheduler();
}// start_task函數實現
void start_task(void *pvParameters)
{taskENTER_CRITICAL(); // 進入臨界區// 創建TASK1任務xTaskCreate((TaskFunction_t)task1, (const char*)"task1", (uint16_t)TASK1_STK_SIZE, (void*)NULL, (UBaseType_t)TASK1_PRIO, (TaskHandle_t*)&Task1Task_Handler);vTaskDelete(StartTask_Handler); // 開始任務已經完成自己使命,刪除自己taskEXIT_CRITICAL(); // 退出臨界區
}// task1函數實現
void task1(void *pvParameters)
{// 初始化列表和列表項vListInitialise(&TestList);vListInitialiseItem(&ListItem1);vListInitialiseItem(&ListItem2);vListInitialiseItem(&ListItem3);// 打印列表和列表項的地址printf("-----------------打印列表和列表項的地址-------------------\r\n");printf("項目\t\t\t地址\r\n");printf("TestList\t\t0x%p\t\r\n", &TestList);printf("TestList->pxIndex\t0x%p\t\r\n", TestList.pxIndex); // 列表頭printf("TestList->xListEnd\t0x%p\t\r\n", (&TestList.xListEnd)); // 列表尾printf("ListItem1\t\t0x%p\t\r\n", &ListItem1);printf("ListItem2\t\t0x%p\t\r\n", &ListItem2);printf("ListItem3\t\t0x%p\t\r\n", &ListItem3);printf("-----------------------STEP1 END------------------------\r\n");printf("按下KEY0以繼續\r\n");while(key_scan(0) != KEY0_PRES){vTaskDelay(10);}// 列表項1插入列表中printf("--------------------列表項1插入列表中---------------------\r\n");vListInsert((List_t*)&TestList, (ListItem_t*)&ListItem1);printf("項目\t\t\t\t地址\r\n");printf("TestList->xListEnd->pxNext\t0x%p\r\n", (TestList.xListEnd.pxNext));printf("ListItem1->pxNext\t\t0x%p\r\n", (ListItem1.pxNext));printf("TestList->xListEnd->pxPrevious\t0x%p\r\n", (TestList.xListEnd.pxPrevious));printf("ListItem1->pxPrevious\t\t0x%p\r\n", (ListItem1.pxPrevious));printf("-----------------------STEP2 END------------------------\r\n");printf("按下KEY0以繼續\r\n");while(key_scan(0) != KEY0_PRES){vTaskDelay(10);}// 列表項2插入列表中printf("--------------------列表項2插入列表中---------------------\r\n");vListInsert((List_t*)&TestList, (ListItem_t*)&ListItem2);printf("項目\t\t\t\t地址\r\n");printf("TestList->xListEnd->pxNext\t0x%p\r\n", (TestList.xListEnd.pxNext));printf("ListItem1->pxNext\t\t0x%p\r\n", (ListItem1.pxNext));printf("ListItem2->pxNext\t\t0x%p\r\n", (ListItem2.pxNext));printf("TestList->xListEnd->pxPrevious\t0x%p\r\n", (TestList.xListEnd.pxPrevious));printf("ListItem1->pxPrevious\t\t0x%p\r\n", (ListItem1.pxPrevious));printf("ListItem2->pxPrevious\t\t0x%p\r\n", (ListItem2.pxPrevious));printf("-----------------------STEP3 END------------------------\r\n");printf("按下KEY0以繼續\r\n");while(key_scan(0) != KEY0_PRES){vTaskDelay(10);}// 列表項3插入列表中printf("--------------------列表項3插入列表中---------------------\r\n");vListInsert((List_t*)&TestList, (ListItem_t*)&ListItem3);printf("項目\t\t\t\t地址\r\n");printf("TestList->xListEnd->pxNext\t0x%p\r\n", (TestList.xListEnd.pxNext));printf("ListItem1->pxNext\t\t0x%p\r\n", (ListItem1.pxNext));printf("ListItem2->pxNext\t\t0x%p\r\n", (ListItem2.pxNext));printf("ListItem3->pxNext\t\t0x%p\r\n", (ListItem3.pxNext));printf("TestList->xListEnd->pxPrevious\t0x%p\r\n", (TestList.xListEnd.pxPrevious));printf("ListItem1->pxPrevious\t\t0x%p\r\n", (ListItem1.pxPrevious));printf("ListItem2->pxPrevious\t\t0x%p\r\n", (ListItem2.pxPrevious));printf("ListItem3->pxPrevious\t\t0x%p\r\n", (ListItem3.pxPrevious));printf("-----------------------STEP4 END------------------------\r\n");printf("按下KEY0以繼續\r\n");while(key_scan(0) != KEY0_PRES){vTaskDelay(10);}// 移除列表項2printf("------------------------移除列表項2-----------------------\r\n");uxListRemove((ListItem_t*)&ListItem2);printf("項目\t\t\t\t地址\r\n");printf("TestList->xListEnd->pxNext\t0x%p\r\n", (TestList.xListEnd.pxNext));printf("ListItem1->pxNext\t\t0x%p\r\n", (ListItem1.pxNext));printf("ListItem3->pxNext\t\t0x%p\r\n", (ListItem3.pxNext));printf("TestList->xListEnd->pxPrevious\t0x%p\r\n", (TestList.xListEnd.pxPrevious));printf("ListItem1->pxPrevious\t\t0x%p\r\n", (ListItem1.pxPrevious));printf("ListItem3->pxPrevious\t\t0x%p\r\n", (ListItem3.pxPrevious));printf("按下KEY0以繼續\r\n");printf("-----------------------STEP5 END------------------------\r\n");while(key_scan(0) != KEY0_PRES){vTaskDelay(10);}// 在列表末尾添加列表項2printf("-------------------在列表末尾添加列表項2-------------------\r\n");vListInsertEnd((List_t*)&TestList, (ListItem_t*)&ListItem2);printf("項目\t\t\t\t地址\r\n");printf("TestList->pxIndex\t\t0x%p\r\n", TestList.pxIndex);printf("TestList->xListEnd->pxNext\t0x%p\r\n", (TestList.xListEnd.pxNext));printf("ListItem1->pxNext\t\t0x%p\r\n", (ListItem1.pxNext));printf("ListItem2->pxNext\t\t0x%p\r\n", (ListItem2.pxNext));printf("ListItem3->pxNext\t\t0x%p\r\n", (ListItem3.pxNext));printf("TestList->xListEnd->pxPrevious\t0x%p\r\n", (TestList.xListEnd.pxPrevious));printf("ListItem1->pxPrevious\t\t0x%p\r\n", (ListItem1.pxPrevious));printf("ListItem2->pxPrevious\t\t0x%p\r\n", (ListItem2.pxPrevious));printf("ListItem3->pxPrevious\t\t0x%p\r\n", (ListItem3.pxPrevious));printf("-----------------------------END-------------------------\r\n");while(1){vTaskDelay(10);}
}
/*-------------------------------------------*/
2.4 主函數
#include "sys.h"
#include "delay.h"
#include "usart.h"
#include "led.h"
#include "mpu.h"
#include "lcd.h"
#include "key.h"
#include "malloc.h"
#include "freertos_demo.h"int main(void)
{sys_cache_enable(); /* 打開L1-Cache */HAL_Init(); /* 初始化HAL庫 */sys_stm32_clock_init(240, 2, 2, 4); /* 設置時鐘, 480Mhz */delay_init(480); /* 延時初始化 */usart_init(115200); /* 串口初始化為115200 */led_init(); /* 初始化LED */mpu_memory_protection(); /* 保護相關存儲區域 */lcd_init(); /* 初始化LCD */key_init(); /* 初始化按鍵 */my_mem_init(SRAMIN); /* 初始化內部內存池(AXI) */freertos_demo(); /* 運行FreeRTOS例程 */
}
第五章 FreeRTOS任務切換
1. PendSV異常
PendSV(Pended Service Call,可掛起服務調用),是一個對 RTOS 非常重要的異常。 PendSV的中斷優先級是可以編程的,用戶可以根據實際的需求,對其進行配置。 PendSV 的中斷由將中斷控制狀態寄存器(ICSR)中 PENDSVSET 為置一觸發。PendSV 與 SVC 不同, PendSV 的中斷是非實時的,即 PendSV 的中斷可以在更高優先級的中斷中觸發,但是在更高優先級中斷結束后才執行。
利用 PendSV 的這個可掛起特性,在設計 RTOS 時,可以將 PendSV 的中斷優先級設置為最低的中斷優先級,這么一來, PendSV 的中斷服務函數就會在其他所有中斷處理完成后才執行。任務切換時,就需要用到 PendSV 的這個特性。
首先,來看一下任務切換的一些基本概念,在典型的 RTOS 中,任務的處理時間被分為多個時間片, OS 內核的執行可以有兩種觸發方式,一種是通過在應用任務中通過 SVC 指令觸發,例如在應用任務在等待某個時間發生而需要停止的時候,那么就可以通過 SVC 指令來觸發 OS內核的執行,以切換到其他任務;第二種方式是, SysTick 周期性的中斷,來觸發 OS 內核的執行。 下圖演示了只有兩個任務的 RTOS 中,兩個任務交替執行的過程:
在操作系統中,任務調度器決定是否切換任務。圖中的任務及切換都是在 SysTick 中斷中完成的, SysTick 的每一次中斷都會切換到其他任務。
如果一個中斷請求(IRQ)在 SysTick 中斷產生之前產生,那么 SysTick 就可能搶占該中斷請求,這就會導致該中斷請求被延遲處理,這在實時操作系統中是不允許的,因為這將會影響到實時操作系統的實時性,如下圖所示:
并且,當 SysTick 完成任務的上下文切換,準備返回任務中運行時,由于存在中斷請求, ARM Cortex-M 不允許返回線程模式,因此,將會產生用法錯誤異常(Usage Fault)。在一些 RTOS 的設計中,會通過判斷是否存在中斷請求,來決定是否進行任務切換。雖然可以通過檢查 xPSR 或 NVIC 中的中斷活躍寄存器來判斷是否存在中斷請求,但是這樣可能會影響系統的性能,甚至可能出現中斷源在 SysTick 中斷前后不斷產生中斷請求,導致系統無法進行任務切換的情況。PendSV 通過延遲執行任務切換,直到處理完所有的中斷請求,以解決上述問題。為了達到這樣的效果,必須將 PendSV 的中斷優先級設置為最低的中斷優先等級。如果操作系統決定切換任務,那么就將 PendSV 設置為掛起狀態,并在 PendSV 的中斷服務函數中執行任務切換,如下圖所示:
-
任務一觸發 SVC 中斷以進行任務切換(例如,任務一正等待某個事件發生)。
-
系統內核接收到任務切換請求,開始準備任務切換,并掛起 PendSV 異常。
-
當退出 SVC 中斷的時候,立刻進入 PendSV 異常處理,完成任務切換。
-
當 PendSV 異常處理完成,返回線程模式,開始執行任務二。
-
中斷產生,并進入中斷處理函數。
-
當運行中斷處理函數的時候, SysTick 異常(用于內核時鐘節拍) 產生。
-
操作系統執行必要的操作,然后掛起 PendSV 異常,準備進行任務切換。
-
當 SysTick 中斷處理完成,返回繼續處理中斷。
-
當中斷處理完成,立馬進入 PendSV 異常處理,完成任務切換。
-
當 PendSV 異常處理完成,返回線程模式,繼續執行任務一。
PendSV在RTOS的任務切換中,起著至關重要的作用, FreeRTOS的任務切換就是在PendSV中完成的。
2. 時間片調度測試
2.1 任務配置
/*----------------任務配置區-----------------*/
// START_TASK配置
#define START_TASK_PRIO 1 // 任務優先級
#define START_STK_SIZE 128 // 任務堆棧大小
TaskHandle_t StartTask_Handler; // 任務句柄
void start_task(void *pvParameters);// 任務函數 // TASK1配置
#define TASK1_PRIO 2
#define TASK1_STK_SIZE 128
TaskHandle_t Task1Task_Handler;
void task1(void *pvParameters);// TASK2配置
#define TASK2_PRIO 2 // 相同任務優先級
#define TASK2_STK_SIZE 128
TaskHandle_t Task2Task_Handler;
void task2(void *pvParameters);
/*-------------------------------------------*/
2.2 任務實現
/*------------------任務實現區----------------*/
void freertos_demo(void)
{lcd_show_string(10,47,220,24,24,"Task Demo",RED);// 創建START_TASK任務xTaskCreate((TaskFunction_t)start_task, // 任務函數(const char*)"start_task", // 任務名稱(uint16_t)START_STK_SIZE, // 任務堆棧大小(void*)NULL, // 傳遞給任務函數的參數(UBaseType_t)START_TASK_PRIO, // 任務優先級(TaskHandle_t*)&StartTask_Handler);// 任務句柄// 開始任務調度vTaskStartScheduler();
}// start_task函數實現
void start_task(void *pvParameters)
{taskENTER_CRITICAL(); // 進入臨界區// 創建TASK1任務xTaskCreate((TaskFunction_t)task1, (const char*)"task1", (uint16_t)TASK1_STK_SIZE, (void*)NULL, (UBaseType_t)TASK1_PRIO, (TaskHandle_t*)&Task1Task_Handler);xTaskCreate((TaskFunction_t)task2, (const char*)"task2", (uint16_t)TASK2_STK_SIZE, (void*)NULL, (UBaseType_t)TASK2_PRIO, (TaskHandle_t*)&Task2Task_Handler);vTaskDelete(StartTask_Handler); // 開始任務已經完成自己使命,刪除自己taskEXIT_CRITICAL(); // 退出臨界區
}// task1函數實現
void task1(void *pvParameters)
{uint32_t task1_num = 0;while(1){taskENTER_CRITICAL(); // 進入臨界區printf("任務1運行次數:%d\r\n",++task1_num);taskEXIT_CRITICAL(); // 退出臨界區vTaskDelay(10);}
}// task2函數實現
void task2(void *pvParameters)
{uint32_t task2_num = 0;while(1){taskENTER_CRITICAL(); // 進入臨界區printf("任務2運行次數:%d\r\n",++task2_num);taskEXIT_CRITICAL(); // 退出臨界區vTaskDelay(10);}
}
2.3 主函數
#include "sys.h"
#include "delay.h"
#include "usart.h"
#include "led.h"
#include "mpu.h"
#include "lcd.h"
#include "key.h"
#include "malloc.h"
#include "freertos_demo.h"int main(void)
{sys_cache_enable(); /* 打開L1-Cache */HAL_Init(); /* 初始化HAL庫 */sys_stm32_clock_init(240, 2, 2, 4); /* 設置時鐘, 480Mhz */delay_init(480); /* 延時初始化 */usart_init(115200); /* 串口初始化為115200 */led_init(); /* 初始化LED */mpu_memory_protection(); /* 保護相關存儲區域 */lcd_init(); /* 初始化LCD */key_init(); /* 初始化按鍵 */my_mem_init(SRAMIN); /* 初始化內部內存池(AXI) */freertos_demo(); /* 運行FreeRTOS例程 */
}
第六章 FreeRTOS任務相關API函數介紹
1. 任務相關函數
函數 | 描述 |
---|---|
uxTaskPriorityGet() | 獲取任務優先級 |
vTaskPrioritySet() | 設置任務優先級 |
uxTaskGetSystemState() | 獲取所有任務的狀態信息 |
vTaskGetInfo() | 獲取單個任務的狀態信息 |
xTaskGetApplicationTaskTag() | 獲取任務 Tag |
xTaskGetCurrentTaskHandle() | 獲取當前任務的任務句柄 |
xTaskGetHandle() | 獲取指定任務的任務句柄 |
xTaskGetIdleTaskHandle() | 獲取空閑任務的任務句柄 |
uxTaskGetStackHighWaterMark() | 獲取任務的棧歷史剩余最小值 |
eTaskGetState() | 獲取任務狀態 |
pcTaskGetName() | 獲取任務名 |
xTaskGetTickCount() | 獲取系統時鐘節拍計數器的值 |
xTaskGetTickCountFromISR() | 中斷中獲取系統使用節拍計數器的值 |
xTaskGetSchedulerState() | 獲取任務調度器狀態 |
uxTaskGetNumberOfTasks() | 獲取系統中任務的數量 |
vTaskList() | 以“表格”形式獲取所有任務的信息 |
vTaskGetRunTimeStats() | 獲取任務的運行時間等信息 |
vTaskSetApplicationTaskTag() | 設置任務 Tag |
SetThreadLocalStoragePointer() | 設置任務的獨有數據記錄數組指針 |
GetThreadLocalStoragePointer() | 獲取任務的獨有數據記錄數組指針 |
1.1 任務優先級相關函數
-
uxTaskPriorityGet()
- 描述: 獲取指定任務的當前優先級。
- 函數原型:
UBaseType_t uxTaskPriorityGet( TaskHandle_t xTask );
- 參數:
xTask
- 任務句柄。如果傳入NULL
,則返回調用此函數的任務的優先級。 - 返回值: 任務的優先級。
- 注意事項: 要使此函數可用,
INCLUDE_uxTaskPriorityGet
需在FreeRTOSConfig.h
中定義為 1。
-
vTaskPrioritySet()
- 描述: 設置指定任務的優先級。
- 函數原型:
void vTaskPrioritySet( TaskHandle_t xTask, UBaseType_t uxNewPriority );
- 參數:
xTask
- 任務句柄。如果傳入NULL
,則設置調用此函數的任務的優先級。uxNewPriority
- 新的優先級。
- 注意事項: 要使此函數可用,
INCLUDE_vTaskPrioritySet
需在FreeRTOSConfig.h
中定義為 1。改變任務優先級可能會導致立即的上下文切換。
1.2 任務信息獲取函數
-
uxTaskGetSystemState()
- 描述: 獲取系統中所有任務的狀態信息,包括任務句柄、任務名稱、優先級、堆棧使用情況等。這些信息存儲在一個數組中。
- 函數原型:
UBaseType_t uxTaskGetSystemState( TaskStatus_t *pxTaskStatusArray, UBaseType_t uxArraySize, uint32_t *pulTotalRunTime );
- 參數:
pxTaskStatusArray
- 指向TaskStatus_t
結構體數組的指針,用于存儲任務狀態信息。uxArraySize
-pxTaskStatusArray
數組的大小(即可以容納多少個任務的狀態信息)。pulTotalRunTime
- 可選參數,指向一個uint32_t
變量的指針,用于返回自啟動以來所有任務的總運行時間(如果configGENERATE_RUNTIME_STATS
為 1)。
- 返回值: 獲取到的任務數量。
- 注意事項: 要使此函數可用,
configGENERATE_RUNTIME_STATS
需在FreeRTOSConfig.h
中定義為 1。此函數會占用較長時間,因為它需要遍歷所有任務。
-
vTaskGetInfo()
- 描述: 獲取單個任務的詳細狀態信息。
- 函數原型:
void vTaskGetInfo( TaskHandle_t xTask, TaskStatus_t *pxTaskStatus, BaseType_t xGetStackHighWaterMark, eTaskState eGetState );
- 參數:
xTask
- 要獲取信息的任務句柄。pxTaskStatus
- 指向TaskStatus_t
結構體的指針,用于存儲任務信息。xGetStackHighWaterMark
- 如果為pdTRUE
,則計算并返回任務堆棧的歷史剩余最小值。eGetState
- 如果為pdTRUE
,則返回任務的當前狀態。
- 注意事項: 要使此函數可用,
INCLUDE_vTaskGetInfo
需在FreeRTOSConfig.h
中定義為 1。
-
xTaskGetApplicationTaskTag()
- 描述: 獲取任務的應用層 Tag 值。Tag 是一個用戶定義的指針,可以與任務關聯,用于存儲自定義數據或上下文。
- 函數原型:
TaskHookFunction_t xTaskGetApplicationTaskTag( TaskHandle_t xTask );
- 參數:
xTask
- 任務句柄。如果傳入NULL
,則返回當前任務的 Tag。 - 返回值: 任務的應用層 Tag 值。
- 注意事項: 要使此函數可用,
configUSE_APPLICATION_TASK_TAG
需在FreeRTOSConfig.h
中定義為 1。
-
xTaskGetCurrentTaskHandle()
- 描述: 獲取當前正在運行的任務的句柄。
- 函數原型:
TaskHandle_t xTaskGetCurrentTaskHandle( void );
- 返回值: 當前任務的句柄。
- 注意事項: 總是可用的,不需要額外配置。
-
xTaskGetHandle()
- 描述: 根據任務名稱獲取任務的句柄。
- 函數原型:
TaskHandle_t xTaskGetHandle( const char *pcNameToQuery );
- 參數:
pcNameToQuery
- 要查詢的任務名稱。 - 返回值: 如果找到匹配的任務,則返回任務句柄;否則返回
NULL
。 - 注意事項: 要使此函數可用,
configINCLUDE_QUERY_HEAP_INFO
需在FreeRTOSConfig.h
中定義為 1。任務名稱必須是唯一的。
-
xTaskGetIdleTaskHandle()
- 描述: 獲取空閑任務的句柄。
- 函數原型:
TaskHandle_t xTaskGetIdleTaskHandle( void );
- 返回值: 空閑任務的句柄。
- 注意事項: 總是可用的,不需要額外配置。
-
uxTaskGetStackHighWaterMark()
- 描述: 獲取指定任務的堆棧歷史剩余最小值(“高水位線”)。這個值表示任務運行期間堆棧的最小空閑空間。
- 函數原型:
UBaseType_t uxTaskGetStackHighWaterMark( TaskHandle_t xTask );
- 參數:
xTask
- 任務句柄。如果傳入NULL
,則返回調用此函數的任務的堆棧高水位線。 - 返回值: 堆棧歷史剩余最小值,以字為單位。
- 注意事項: 要使此函數可用,
INCLUDE_uxTaskGetStackHighWaterMark
需在FreeRTOSConfig.h
中定義為 1。這個值對于調試堆棧溢出非常有用。
-
eTaskGetState()
- 描述: 獲取指定任務的當前狀態(例如:就緒、運行、阻塞、掛起、刪除)。
- 函數原型:
eTaskState eTaskGetState( TaskHandle_t xTask );
- 參數:
xTask
- 任務句柄。 - 返回值: 任務的當前狀態,類型為
eTaskState
枚舉。 - 注意事項: 要使此函數可用,
INCLUDE_eTaskGetState
需在FreeRTOSConfig.h
中定義為 1。
-
pcTaskGetName()
- 描述: 獲取指定任務的名稱。
- 函數原型:
char *pcTaskGetName( TaskHandle_t xTaskToQuery );
- 參數:
xTaskToQuery
- 任務句柄。如果傳入NULL
,則返回當前任務的名稱。 - 返回值: 指向任務名稱字符串的指針。
- 注意事項: 任務名稱字符串是只讀的。要使此函數可用,
INCLUDE_pcTaskGetName
需在FreeRTOSConfig.h
中定義為 1。
1.3 系統時間與調度器狀態相關函數
-
xTaskGetTickCount()
- 描述: 獲取 FreeRTOS 系統自啟動以來的時鐘節拍計數器的值。
- 函數原型:
TickType_t xTaskGetTickCount( void );
- 返回值: 系統時鐘節拍計數器的當前值。
- 注意事項: 此函數通常用于實現延時和超時機制。
-
xTaskGetTickCountFromISR()
- 描述: 在中斷服務例程 (ISR) 中獲取 FreeRTOS 系統自啟動以來的時鐘節拍計數器的值。這是
xTaskGetTickCount()
的中斷安全版本。 - 函數原型:
TickType_t xTaskGetTickCountFromISR( void );
- 返回值: 系統時鐘節拍計數器的當前值。
- 注意事項: 只能在 ISR 中調用。
- 描述: 在中斷服務例程 (ISR) 中獲取 FreeRTOS 系統自啟動以來的時鐘節拍計數器的值。這是
-
xTaskGetSchedulerState()
- 描述: 獲取 FreeRTOS 調度器的當前狀態。
- 函數原型:
BaseType_t xTaskGetSchedulerState( void );
- 返回值: 調度器狀態(例如:調度器已啟動、調度器已掛起)。
- 注意事項: 總是可用的,不需要額外配置。
-
uxTaskGetNumberOfTasks()
- 描述: 獲取系統中當前存在的任務數量(包括空閑任務)。
- 函數原型:
UBaseType_t uxTaskGetNumberOfTasks( void );
- 返回值: 系統中任務的總數量。
- 注意事項: 總是可用的,不需要額外配置。
1.4 調試與統計相關函數
-
vTaskList()
- 描述: 以格式化的字符串(“表格”形式)獲取所有任務的信息,包括任務名稱、狀態、優先級、堆棧使用情況等,以便于調試和監控。
- 函數原型:
void vTaskList( char *pcWriteBuffer );
- 參數:
pcWriteBuffer
- 指向字符緩沖區的指針,用于存儲格式化后的任務信息。 - 注意事項: 要使此函數可用,
configUSE_STATS_FORMATTING_FUNCTIONS
和configSUPPORT_DYNAMIC_ALLOCATION
需在FreeRTOSConfig.h
中定義為 1。此函數會占用較長時間,且需要足夠大的緩沖區。
-
vTaskGetRunTimeStats()
- 描述: 獲取所有任務的運行時間統計信息,前提是已配置了運行時間統計功能。
- 函數原型:
void vTaskGetRunTimeStats( char *pcWriteBuffer );
- 參數:
pcWriteBuffer
- 指向字符緩沖區的指針,用于存儲格式化后的運行時間統計信息。 - 注意事項: 要使此函數可用,
configGENERATE_RUNTIME_STATS
和configUSE_STATS_FORMATTING_FUNCTIONS
需在FreeRTOSConfig.h
中定義為 1,并且需要一個硬件定時器來提供運行時間計數。
1.5 任務本地存儲與 Tag 相關函數
-
vTaskSetApplicationTaskTag()
- 描述: 設置指定任務的應用層 Tag 值。
- 函數原型:
void vTaskSetApplicationTaskTag( TaskHandle_t xTask, TaskHookFunction_t pxHookFunction );
- 參數:
xTask
- 任務句柄。如果傳入NULL
,則設置當前任務的 Tag。pxHookFunction
- 要設置的 Tag 值(通常是一個函數指針或任意指針)。
- 注意事項: 要使此函數可用,
configUSE_APPLICATION_TASK_TAG
需在FreeRTOSConfig.h
中定義為 1。
-
SetThreadLocalStoragePointer()
- 描述: 設置任務的線程本地存儲 (TLS) 指針。FreeRTOS 為每個任務提供了一個獨立的 TLS 數組,每個元素可以存儲一個指向任意數據的指針。
- 函數原型:
void SetThreadLocalStoragePointer( TaskHandle_t xTaskToSet, BaseType_t xIndex, void *pvValue );
- 參數:
xTaskToSet
- 要設置 TLS 指針的任務句柄。如果傳入NULL
,則設置當前任務的 TLS 指針。xIndex
- TLS 數組的索引。pvValue
- 要存儲的指針值。
- 注意事項: 要使此函數可用,
configTHREAD_LOCAL_STORAGE_POINTERS
需在FreeRTOSConfig.h
中定義為大于 0。
-
GetThreadLocalStoragePointer()
- 描述: 獲取任務的線程本地存儲 (TLS) 指針。
- 函數原型:
void *GetThreadLocalStoragePointer( TaskHandle_t xTaskToQuery, BaseType_t xIndex );
- 參數:
xTaskToQuery
- 要查詢 TLS 指針的任務句柄。如果傳入NULL
,則查詢當前任務的 TLS 指針。xIndex
- TLS 數組的索引。
- 返回值: 存儲在指定 TLS 索引處的指針值。
- 注意事項: 要使此函數可用,
configTHREAD_LOCAL_STORAGE_POINTERS
需在FreeRTOSConfig.h
中定義為大于 0。
2. 任務狀態與信息查詢測試
2.1 任務配置
/*----------------任務配置區-----------------*/
// START_TASK配置
#define START_TASK_PRIO 1 // 任務優先級
#define START_STK_SIZE 128 // 任務堆棧大小
TaskHandle_t StartTask_Handler; // 任務句柄
void start_task(void *pvParameters);// 任務函數 // TASK1配置
#define TASK1_PRIO 2
#define TASK1_STK_SIZE 128
TaskHandle_t Task1Task_Handler;
void task1(void *pvParameters);
/*-------------------------------------------*/
2.2 任務實現
/*------------------任務實現區----------------*/
void freertos_demo(void)
{lcd_show_string(10,47,220,24,24,"Task Demo",RED);// 創建START_TASK任務xTaskCreate((TaskFunction_t)start_task, // 任務函數(const char*)"start_task", // 任務名稱(uint16_t)START_STK_SIZE, // 任務堆棧大小(void*)NULL, // 傳遞給任務函數的參數(UBaseType_t)START_TASK_PRIO, // 任務優先級(TaskHandle_t*)&StartTask_Handler);// 任務句柄// 開始任務調度vTaskStartScheduler();
}// start_task函數實現
void start_task(void *pvParameters)
{taskENTER_CRITICAL(); // 進入臨界區// 創建TASK1任務xTaskCreate((TaskFunction_t)task1, (const char*)"task1", (uint16_t)TASK1_STK_SIZE, (void*)NULL, (UBaseType_t)TASK1_PRIO, (TaskHandle_t*)&Task1Task_Handler);vTaskDelete(StartTask_Handler); // 開始任務已經完成自己使命,刪除自己taskEXIT_CRITICAL(); // 退出臨界區
}// task1函數實現
void task1(void *pvParameters)
{uint32_t i = 0;UBaseType_t task_num = 0;TaskStatus_t *status_array = NULL;TaskHandle_t task_handle = NULL;TaskStatus_t *task_info = NULL;eTaskState task_state = eInvalid;char *task_state_str = NULL;char *task_info_buf = NULL;/*--------------------函數uxTaskGetSystemState()的使用------==--------------*/printf("-------------Step1 函數uxTaskGetSystemState()的使用-------------\r\n");task_num = uxTaskGetNumberOfTasks(); // 獲取系統中任務數量status_array = mymalloc(SRAMIN, task_num*sizeof(TaskStatus_t)); // 內存分配task_num = uxTaskGetSystemState((TaskStatus_t*)status_array, // 任務狀態信息(UBaseType_t)task_num, // buffer大小(uint32_t*)NULL); // 不獲取任務運行時間信息printf("任務名\t\t優先級\t\t任務編號\r\n");for(i=0;i<task_num;i++){printf("%s\t%s%ld\t\t%ld\r\n",status_array[i].pcTaskName,strlen(status_array[i].pcTaskName) > 7 ? "": "\t",status_array[i].uxCurrentPriority,status_array[i].xTaskNumber);} myfree(SRAMIN, status_array); printf("-----------------------------END-------------------------------\r\n");printf("按下KEYO以繼續!!!\r\n");while(key_scan(0) != KEY0_PRES){vTaskDelay(10);}/*-------------------------函數vTaskGetInfo()的使用--------------------------*/printf("-----------------Step2 函數vTaskGetInfo()的使用------------------\r\n");task_info = mymalloc(SRAMIN, sizeof(TaskStatus_t)); // 內存分配task_handle = xTaskGetHandle("task1"); // 獲取任務句柄vTaskGetInfo((TaskHandle_t)task_handle, // 任務句柄(TaskStatus_t*)task_info, // 任務狀態信息(BaseType_t)pdTRUE, // 允許統計任務堆棧歷史最小值(eTaskState)eInvalid); // 任務狀態printf("任務名:\t\t\t%s\r\n", task_info->pcTaskName);printf("任務編號:\t\t%ld\r\n", task_info->xTaskNumber);printf("任務壯態:\t\t%d\r\n", task_info->eCurrentState);printf("任務當前優先級:\t\t%ld\r\n", task_info->uxCurrentPriority);printf("任務基優先級:\t\t%ld\r\n", task_info->uxBasePriority);printf("任務堆棧基地址:\t\t0x%p\r\n", task_info->pxStackBase);printf("任務堆棧歷史剩余最小值:\t%d\r\n", task_info->usStackHighWaterMark);myfree(SRAMIN, task_info);printf("-----------------------------END-------------------------------\r\n");printf("按下KEYO以繼續!!!\r\n");while(key_scan(0) != KEY0_PRES){vTaskDelay(10);}/*-------------------------函數eTaskGetState()的使用--------------------------*/printf("-----------------Step3 函數eTaskGetState()的使用------------------\r\n");task_state_str = mymalloc(SRAMIN, 10); // 內存分配task_handle = xTaskGetHandle("task1");task_state = eTaskGetState(task_handle);sprintf(task_state_str, task_state == eRunning ? "Runing" :task_state == eReady ? "Ready" :task_state == eBlocked ? "Blocked" :task_state == eSuspended ? "Suspended" :task_state == eDeleted ? "Deleted" :task_state == eInvalid ? "Invalid" :"");printf("任務狀態值: %d,對應狀態為: %s\r\n", task_state, task_state_str);myfree(SRAMIN, task_state_str);printf("-----------------------------END-------------------------------\r\n");printf("按下KEYO以繼續!!!\r\n");while(key_scan(0) != KEY0_PRES){vTaskDelay(10);}/*---------------------------函數vTaskList()的使用--------------------------*/printf("------------------Step4 函數vTaskList()的使用-------------------\r\n");task_info_buf = mymalloc(SRAMIN, 500);vTaskList(task_info_buf); // 打印任務列表printf("任務名\t\t狀態\t優先級\t剩余棧\t任務序號\r\n");printf("%s\r\n", task_info_buf);myfree(SRAMIN, task_info_buf);printf("-----------------------------END-------------------------------\r\n");while(1){vTaskDelay(10);}
}
/*-------------------------------------------*/
2.3 主函數
#include "sys.h"
#include "delay.h"
#include "usart.h"
#include "led.h"
#include "mpu.h"
#include "lcd.h"
#include "key.h"
#include "malloc.h"
#include "freertos_demo.h"int main(void)
{sys_cache_enable(); /* 打開L1-Cache */HAL_Init(); /* 初始化HAL庫 */sys_stm32_clock_init(240, 2, 2, 4); /* 設置時鐘, 480Mhz */delay_init(480); /* 延時初始化 */usart_init(115200); /* 串口初始化為115200 */led_init(); /* 初始化LED */mpu_memory_protection(); /* 保護相關存儲區域 */lcd_init(); /* 初始化LCD */key_init(); /* 初始化按鍵 */my_mem_init(SRAMIN); /* 初始化內部內存池(AXI) */freertos_demo(); /* 運行FreeRTOS例程 */
}
3. 任務運行時間統計測試
3.1 定時器配置
#include "btim.h"
#include "FreeRTOS.h"TIM_HandleTypeDef g_tim3_handle; /* 定時器3句柄 */uint32_t FreeRTOSRunTimeTicks; /* FreeRTOS時間統計所用的節拍計數器 */void ConfigureTimeForRunTimeStats(void)
{FreeRTOSRunTimeTicks = 0; /* 節拍計數器初始化為0 */btim_tim3_int_init(10-1, 2400-1); /* 初始化TIM3 */
}/*** @brief 基本定時器TIM3定時中斷初始化函數* @note* 基本定時器的時鐘來自APB1,當D2PPRE1≥2分頻的時候* 基本定時器的時鐘為APB1時鐘的2倍, 而APB1為120M, 所以定時器時鐘 = 240Mhz* 定時器溢出時間計算方法: Tout = ((arr + 1) * (psc + 1)) / Ft us.* Ft=定時器工作頻率,單位:Mhz** @param arr: 自動重裝值。* @param psc: 時鐘預分頻數* @retval 無*/
void btim_tim3_int_init(uint16_t arr, uint16_t psc)
{g_tim3_handle.Instance = BTIM_TIM3_INT; /* 通用定時器3 */g_tim3_handle.Init.Prescaler = psc; /* 分頻 */g_tim3_handle.Init.CounterMode = TIM_COUNTERMODE_UP; /* 向上計數器 */g_tim3_handle.Init.Period = arr; /* 自動裝載值 */g_tim3_handle.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1; /* 時鐘分頻因子 */HAL_TIM_Base_Init(&g_tim3_handle);HAL_TIM_Base_Start_IT(&g_tim3_handle); /* 使能定時器3和定時器3更新中斷 */
}/*** @brief 定時器底層驅動,開啟時鐘,設置中斷優先級此函數會被HAL_TIM_Base_Init()函數調用* @param 無* @retval 無*/
void HAL_TIM_Base_MspInit(TIM_HandleTypeDef *htim)
{if (htim->Instance == BTIM_TIM3_INT){BTIM_TIM3_INT_CLK_ENABLE(); /* 使能TIM3時鐘 */HAL_NVIC_SetPriority(BTIM_TIM3_INT_IRQn, 4, 0); /* 設置中斷優先級,搶占優先級4,子優先級0 */HAL_NVIC_EnableIRQ(BTIM_TIM3_INT_IRQn); /* 開啟ITM3中斷 */}
}/*** @brief 定時器中斷服務函數* @param 無* @retval 無*/
void BTIM_TIM3_INT_IRQHandler(void)
{HAL_TIM_IRQHandler(&g_tim3_handle);
}/*** @brief 定時器更新中斷回調函數* @param htim:定時器句柄指針* @retval 無*/
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{if (htim == (&g_tim3_handle)){FreeRTOSRunTimeTicks++;}
}
3.1 任務配置
/*----------------任務配置區-----------------*/
// START_TASK配置
#define START_TASK_PRIO 1 // 任務優先級
#define START_STK_SIZE 128 // 任務堆棧大小
TaskHandle_t StartTask_Handler; // 任務句柄
void start_task(void *pvParameters);// 任務函數 // TASK1配置
#define TASK1_PRIO 2
#define TASK1_STK_SIZE 128
TaskHandle_t Task1Task_Handler;
void task1(void *pvParameters);// TASK2配置
#define TASK2_PRIO 3
#define TASK2_STK_SIZE 128
TaskHandle_t Task2Task_Handler;
void task2(void *pvParameters);// TASK3配置
#define TASK3_PRIO 4
#define TASK3_STK_SIZE 128
TaskHandle_t Task3Task_Handler;
void task3(void *pvParameters);
/*-------------------------------------------*/
3.3 任務實現
/*------------------任務實現區----------------*/
void freertos_demo(void)
{lcd_show_string(10,47,220,24,24,"Get Run Time Stats",RED);lcd_draw_rectangle(5, 110, 115, 314, BLACK);lcd_draw_rectangle(125, 110, 234, 314, BLACK);lcd_draw_line(5, 130, 115, 130, BLACK);lcd_draw_line(125, 130, 234, 130, BLACK);lcd_show_string(15, 111, 110, 16, 16, "Task1: 000", BLUE);lcd_show_string(135, 111, 110, 16, 16, "Task2: 000", BLUE);// 創建START_TASK任務xTaskCreate((TaskFunction_t)start_task, // 任務函數(const char*)"start_task", // 任務名稱(uint16_t)START_STK_SIZE, // 任務堆棧大小(void*)NULL, // 傳遞給任務函數的參數(UBaseType_t)START_TASK_PRIO, // 任務優先級(TaskHandle_t*)&StartTask_Handler);// 任務句柄// 開始任務調度vTaskStartScheduler();
}// start_task函數實現
void start_task(void *pvParameters)
{taskENTER_CRITICAL(); // 進入臨界區// 創建TASK1任務xTaskCreate((TaskFunction_t)task1, (const char*)"task1", (uint16_t)TASK1_STK_SIZE, (void*)NULL, (UBaseType_t)TASK1_PRIO, (TaskHandle_t*)&Task1Task_Handler);// 創建TASK2任務xTaskCreate((TaskFunction_t)task2, (const char*)"task2", (uint16_t)TASK2_STK_SIZE, (void*)NULL, (UBaseType_t)TASK2_PRIO, (TaskHandle_t*)&Task2Task_Handler);// 創建TASK3任務xTaskCreate((TaskFunction_t)task3, (const char*)"task3", (uint16_t)TASK3_STK_SIZE, (void*)NULL, (UBaseType_t)TASK3_PRIO, (TaskHandle_t*)&Task3Task_Handler);vTaskDelete(StartTask_Handler); // 開始任務已經完成自己使命,刪除自己taskEXIT_CRITICAL(); // 退出臨界區
}// task1函數實現
void task1(void *pvParameters)
{uint32_t task1_num = 0;while(1){lcd_fill(6,131,114,313,lcd_discolor[++task1_num%11]);lcd_show_xnum(71,111,task1_num,3,16,0x80,BLUE);vTaskDelay(1000);}
}// task2函數實現
void task2(void *pvParameters)
{uint32_t task2_num = 0;while(1){lcd_fill(126,131,233,313,lcd_discolor[11-(++task2_num%11)]);lcd_show_xnum(191,111,task2_num,3,16,0x80,BLUE);vTaskDelay(1000);}
}// task3函數實現
void task3(void *pvParameters)
{uint8_t key_value = 0;char *runtime_info = NULL;while(1){key_value = key_scan(0);switch(key_value){case KEY0_PRES:{runtime_info = mymalloc(SRAMIN ,100);vTaskGetRunTimeStats(runtime_info);printf("任務名\t\t運行時間\t運行所占百分比\r\n");printf("%s\r\n", runtime_info);myfree(SRAMIN, runtime_info);break;}default:{break;}}vTaskDelay(10);}
}
/*-------------------------------------------*/
3.4 主函數
#include "sys.h"
#include "delay.h"
#include "usart.h"
#include "led.h"
#include "mpu.h"
#include "lcd.h"
#include "key.h"
#include "malloc.h"
#include "freertos_demo.h"int main(void)
{sys_cache_enable(); /* 打開L1-Cache */HAL_Init(); /* 初始化HAL庫 */sys_stm32_clock_init(240, 2, 2, 4); /* 設置時鐘, 480Mhz */delay_init(480); /* 延時初始化 */usart_init(115200); /* 串口初始化為115200 */led_init(); /* 初始化LED */mpu_memory_protection(); /* 保護相關存儲區域 */lcd_init(); /* 初始化LCD */key_init(); /* 初始化按鍵 */my_mem_init(SRAMIN); /* 初始化內部內存池(AXI) */freertos_demo(); /* 運行FreeRTOS例程 */
}
文中完整工程下載:https://github.com/hazy1k/FreeRTOS-Quick-Start-Guide/tree/main/2.code