第十一章 FreeRTOS事件標志組
1. 事件標志組簡介
事件標志組與信號量一樣屬于任務間同步的機制,但是信號量一般用于任務間的單事件同步,對于任務間的多事件同步,僅使用信號量就顯得力不從心了。 FreeRTOS 提供的事件標志組可以很好的處理多事件情況下的任務同步。
- 事件標志
事件標志是一個用于指示事件是否發生的布爾值,一個事件標志只有 0 或 1 兩種狀態, FreeRTOS 將多個事件標志儲存在一個變量類型為 EventBits_t 變量中, 這個變量就是事件組。
- 事件組
事件組是一組事件標志的集合,一個事件組就包含了一個 EventBites_t 數據類型的變量,變量類型 EventBits_t 的定義如下所示:
typedef TickType_t EventBits_t;
#if ( configUSE_16_BIT_TICKS == 1 )
typedef uint16_t TickType_t;
#else
typedef uint32_t TickType_t;
#endif
#define configUSE_16_BIT_TICKS 0
從上面可以看出, EventBits_t 實際上是一個 16 位或 32 位無符號的數據類型。當configUSE_16_BIT_TICKS 配置為 0 時, EventBits_t 是一個 32 位無符號的數據類型;當configUSE_16_BIT_TICKS 配置為 1 時, EventBits_t 是一個 16 為無符號的數據類型。在本套教程的所有配套例程中,都將配置項 configUSE_16_BIT_TICKS 配置為 0,因此本文就以EventBits_t 為 32 位無符號數據類型為例進行講解,對于另外一種情況,也是大同小異的。
雖然說使用了 32 位無符號的數據類型變量來存儲事件標志,但這并不意味著,一個EventBits_t 數據類型的變量能夠存儲 32 個事件標志, FreeRTOS 將這個 EventBits_t 數據類型的變量拆分成兩部分,其中低 24 位[23:0] (configUSE_16_BIT_TICKS 配置位 1 時,是低 8 位[7:0])用于存儲事件標志,而高 8 位[31:24](configUSE_16_BIT_TICKS 配置位 1 時,依然是高 8 位[15:8]) 用作存儲事件標志組的一些控制信息,也就是說一個事件組最多可以存儲 24 個事件標志。 EventBits_t 數據類型變量的位使用情況如下圖所示:
從上圖中可以看到,變量中低 24 位中的每一位都是一個事件標志,當某一位被置一時,就表示這一位對應的事件發生了。
2. 事件標志組相關函數
函數 | 描述 |
---|---|
xEventGroupCreate() | 使用動態方式創建事件標志組 |
xEventGroupCreateStatic() | 使用靜態方式創建事件標志組 |
vEventGroupDelete() | 刪除事件標志組 |
xEventGroupWaitBits() | 等待事件標志位 |
xEventGroupSetBits() | 設置事件標志位 |
xEventGroupSetBitsFromISR() | 在中斷中設置事件標志位 |
xEventGroupClearBits() | 清零事件標志位 |
xEventGroupClearBitsFromISR() | 在中斷中清零事件標志位 |
xEventGroupGetBits() | 獲取事件組中各事件標志位的值 |
xEventGroupGetBitsFromISR() | 在中斷中獲取事件組中各事件標志位的值 |
xEventGroupSync() | 設置事件標志位,并等待事件標志位 |
2.1 創建事件標志組
-
xEventGroupCreate()
- 描述: 動態方式創建事件標志組。事件標志組所需的內存從 FreeRTOS 堆中分配。創建時,所有事件標志位都被初始化為 0。
- 函數原型:
EventGroupHandle_t xEventGroupCreate( void );
- 返回值: 如果事件標志組成功創建,則返回一個句柄(
EventGroupHandle_t
類型);否則返回NULL
。 - 注意事項: 要使此函數可用,
configSUPPORT_DYNAMIC_ALLOCATION
需在FreeRTOSConfig.h
中定義為 1。
-
xEventGroupCreateStatic()
- 描述: 靜態方式創建事件標志組。事件標志組所需的內存由用戶在編譯時或運行時提供。創建時,所有事件標志位都被初始化為 0。
- 函數原型:
EventGroupHandle_t xEventGroupCreateStatic( StaticEventGroup_t *pxEventGroupBuffer );
- 參數:
pxEventGroupBuffer
- 指向用戶提供的StaticEventGroup_t
結構體變量的指針,該結構體將用作事件標志組的控制塊。 - 返回值: 如果事件標志組成功創建,則返回一個句柄;否則返回
NULL
。 - 注意事項: 要使此函數可用,
configSUPPORT_STATIC_ALLOCATION
需在FreeRTOSConfig.h
中定義為 1。用戶需要自行管理pxEventGroupBuffer
所指向的內存生命周期。
2.2 等待事件標志位
xEventGroupWaitBits()
- 描述: 任務阻塞等待一個或多個事件標志位被設置。可以配置為等待所有指定位(邏輯 AND)或任意一個位(邏輯 OR)。還可以選擇在成功接收后自動清除這些位。
- 函數原型:
EventBits_t xEventGroupWaitBits( EventGroupHandle_t xEventGroup, const EventBits_t uxBitsToWaitFor, const BaseType_t xClearOnExit, const BaseType_t xWaitForAllBits, TickType_t xTicksToWait );
- 參數:
xEventGroup
: 目標事件標志組的句柄。uxBitsToWaitFor
: 要等待的事件標志位的掩碼(例如(1 << 0) | (1 << 2)
)。xClearOnExit
: 如果設置為pdTRUE
,當等待的位被滿足后,它們會被自動清除;如果設置為pdFALSE
,則不會自動清除。xWaitForAllBits
: 如果設置為pdTRUE
,任務將等待所有uxBitsToWaitFor
中的位都設置;如果設置為pdFALSE
,任務將等待uxBitsToWaitFor
中任意一個位設置。xTicksToWait
: 如果事件標志位未被設置,任務愿意阻塞等待的節拍數。
- 返回值: 調用函數時事件組中所有標志位的當前值。可以通過檢查返回值來確定哪些位被設置。
2.3 設置事件標志位
-
xEventGroupSetBits()
- 描述: 設置事件標志組中的一個或多個標志位。設置位可能會導致一個或多個等待該事件組的任務解除阻塞。
- 函數原型:
EventBits_t xEventGroupSetBits( EventGroupHandle_t xEventGroup, const EventBits_t uxBitsToSet );
- 參數:
xEventGroup
: 目標事件標志組的句柄。uxBitsToSet
: 要設置的事件標志位的掩碼。
- 返回值: 調用函數后事件組中所有標志位的當前值。
-
xEventGroupSetBitsFromISR()
- 描述: 在中斷中設置事件標志組中的一個或多個標志位。此函數不會阻塞。
- 函數原型:
BaseType_t xEventGroupSetBitsFromISR( EventGroupHandle_t xEventGroup, const EventBits_t uxBitsToSet, BaseType_t *pxHigherPriorityTaskWoken );
- 參數:
xEventGroup
: 目標事件標志組的句柄。uxBitsToSet
: 要設置的事件標志位的掩碼。pxHigherPriorityTaskWoken
: 指向BaseType_t
變量的指針。如果調用此函數導致更高優先級的任務解除阻塞,則該變量將被設置為pdTRUE
。
- 注意事項: 在 ISR 結束時,應檢查
*pxHigherPriorityTaskWoken
的值。如果為pdTRUE
,則調用portYIELD_FROM_ISR()
。
2.4 清零事件標志位
-
xEventGroupClearBits()
- 描述: 清零(設置為 0)事件標志組中的一個或多個標志位。
- 函數原型:
EventBits_t xEventGroupClearBits( EventGroupHandle_t xEventGroup, const EventBits_t uxBitsToClear );
- 參數:
xEventGroup
: 目標事件標志組的句柄。uxBitsToClear
: 要清零的事件標志位的掩碼。
- 返回值: 調用函數后事件組中所有標志位的當前值。
-
xEventGroupClearBitsFromISR()
- 描述: 在中斷中清零事件標志組中的一個或多個標志位。
- 函數原型:
BaseType_t xEventGroupClearBitsFromISR( EventGroupHandle_t xEventGroup, const EventBits_t uxBitsToClear );
- 參數:
xEventGroup
: 目標事件標志組的句柄。uxBitsToClear
: 要清零的事件標志位的掩碼。
- 返回值: 總是返回
pdPASS
。此函數不會導致任務切換,因此沒有pxHigherPriorityTaskWoken
參數。
2.5 獲取事件標志位的值
-
xEventGroupGetBits()
- 描述: 獲取事件組中所有事件標志位的當前值。此函數不會阻塞。
- 函數原型:
EventBits_t xEventGroupGetBits( EventGroupHandle_t xEventGroup );
- 參數:
xEventGroup
- 目標事件標志組的句柄。 - 返回值: 事件組中所有標志位的當前值。
-
xEventGroupGetBitsFromISR()
- 描述: 在中斷中獲取事件組中所有事件標志位的當前值。
- 函數原型:
EventBits_t xEventGroupGetBitsFromISR( EventGroupHandle_t xEventGroup );
- 參數:
xEventGroup
- 目標事件標志組的句柄。 - 返回值: 事件組中所有標志位的當前值。
2.6 同步任務:xEventGroupSync()
- 描述:
xEventGroupSync()
是一個同步原語,它結合了設置位和等待位的功能。一個或多個任務可以調用此函數來同步它們的執行。當所有參與的任務都設置了各自指定的位后,它們才會被解除阻塞。- 函數原型:
EventBits_t xEventGroupSync( EventGroupHandle_t xEventGroup, const EventBits_t uxBitsToSet, const EventBits_t uxBitsToWaitFor, TickType_t xTicksToWait );
- 參數:
xEventGroup
: 目標事件標志組的句柄。uxBitsToSet
: 調用任務要設置的標志位。uxBitsToWaitFor
: 要等待的所有任務共同設置的標志位。xTicksToWait
: 如果等待的位未被滿足,任務愿意阻塞等待的節拍數。
- 返回值: 成功返回
uxBitsToWaitFor
中所有位都被設置時的事件組值;超時則返回 0。 - 注意事項: 這是一個非常強大的同步機制,可以用于任務屏障(Task Barrier)的實現。
- 函數原型:
2.7 刪除事件標志組
vEventGroupDelete()
- 描述: 刪除一個事件標志組,并釋放其占用的內存(如果它是動態創建的)。
- 函數原型:
void vEventGroupDelete( EventGroupHandle_t xEventGroup );
- 參數:
xEventGroup
- 要刪除的事件標志組句柄。 - 注意事項: 只能刪除動態創建的事件標志組。刪除后,不應再使用該句柄。
3. 事件標志組測試
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 4
#define TASK2_STK_SIZE 128
TaskHandle_t Task2Task_Handler;
void task2(void *pvParameters);// TASK3配置
#define TASK3_PRIO 3
#define TASK3_STK_SIZE 128
TaskHandle_t Task3Task_Handler;
void task3(void *pvParameters);EventGroupHandle_t Event_handle;
#define EVENT0_BIT (1 << 0)
#define EVENT1_BIT (1 << 1)
#define EVENTALL_BIT (EVENT0_BIT | EVENT1_BIT)
/*---------------------------------------------*/
3.2 任務實現
/*------------------任務實現區------------------*/
void freertos_demo(void)
{lcd_show_string(10, 47, 220, 24, 24, "Event Group", RED);lcd_draw_rectangle(5, 130, 234, 314, BLACK); lcd_show_string(30, 110, 220, 16, 16, "Event Group Value: 0", 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(); // 進入臨界區// 創建事件標志組Event_handle = xEventGroupCreate();// 創建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(); // 退出臨界區
}void task1(void *pvParameters)
{uint8_t key_value;while(1){key_value = key_scan(0);switch(key_value){case KEY0_PRES:{xEventGroupSetBits((EventGroupHandle_t)Event_handle, (EventBits_t)EVENT0_BIT);break;}case KEY1_PRES:{xEventGroupSetBits((EventGroupHandle_t)Event_handle, (EventBits_t)EVENT1_BIT);break;}default:break;}vTaskDelay(10);}
}// task2函數實現
void task2(void *pvParameters)
{uint32_t task2_value;while(1){xEventGroupWaitBits((EventGroupHandle_t)Event_handle,// 等待的事件標志組(EventBits_t)EVENTALL_BIT, // 等待的事件標志(BaseType_t)pdTRUE, // 函數退出時清零等待的事件(BaseType_t)pdTRUE, // 等待 等待事件中的所有事件(TickType_t)portMAX_DELAY); // 等待事件lcd_fill(6,131,233,313,lcd_discolor[++task2_value%11]);vTaskDelay(10);}
}// task3函數實現
void task3(void *pvParameters)
{EventBits_t event_value;while(1){event_value = xEventGroupGetBits((EventGroupHandle_t)Event_handle); /* 獲取的事件標志組句柄 */lcd_show_xnum(132,110,event_value,1,16,0,BLUE);vTaskDelay(10);}
}/*---------------------------------------------*/
3.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. 任務通知
在 FreeRTOS 中,每一個任務都有兩個用于任務通知功能的數組,分別為任務通知數組和任務通知狀態數組。其中任務通知數組中的每一個元素都是一個 32 位無符號類型的通知值;而任務通知狀態數組中的元素則表示與之對應的任務通知的狀態。
任務通知數組中的 32 位無符號通知值,用于任務到任務或中斷到任務發送通知的“媒介”。當通知值為 0 時,表示沒有任務通知;當通知值不為 0 時,表示有任務通知,并且通知值就是通知的內容。
任務通知狀態數組中的元素,用于標記任務通知數組中通知的狀態,任務通知有三種狀態,分別為未等待通知狀態、等待通知狀態和等待接收通知狀態。其中未等待通知狀態為任務通知的復位狀態;當任務在沒有通知的時候接收通知時,在任務阻塞等待任務通知的這段時間內,任務所等待的任務通知就處于等待通知狀態;當有其他任務向任務發送通知,但任務還未接收這一通知的這段期間內,任務通知就處于等待接收通知狀態。
任務通知功能所使用到的任務通知數組和任務通知狀態數組為任務控制塊中的成員變量,因此任務通知的傳輸是直接傳出到任務中的,不同通過任務的通訊對象(隊列、事件標志組和信號量就屬于通訊對象) 這個間接的方式。 間接通訊示意圖如下所示:
任務通知則是直接地往任務中發送通知,直接通訊示意圖如下所示:
1.1 任務通知的優勢
使用任務通知向任務發送事件或數據比使用隊列、事件標志組或信號量快得多;并且使用任務通知代替隊列、事件標志組或信號量,可以節省大量的內存,這是因為每個通訊對象在使用之前都需要被創建,而任務通知功能中的每個通知只需要在每個任務中占用固定的 5 字節內存
1.2 任務通知的缺點
雖然任務通知功能相比通訊對象,有著更快、占用內存少的優點,但是任務通知功能并不能適用于所有情況,例如以下列出的幾種情況:
- 發送事件或數據到中斷
通訊對象可以發送事件或數據從中斷到任務,或從任務到中斷,但是由于任務通知依賴于任務控制塊中的兩個成員變量,并且中斷不是任務,因此任務通知功能并不適用于從任務往中斷發送事件或數據的這種情況,但是任務通知功能可以在任務之間或從中斷到任務發送事件或數據。
- 存在多個接收任務
通訊對象可以被已知通訊對象句柄的任意多個任務或中斷訪問(發送或接收),但任務通知是直接發送事件或數據到指定接收任務的,因傳輸的事件或數據只能由接收任務處理。然而在實際中很少受到這種情況的限制,因為,雖然多個任務和中斷發送事件或數據到一個通訊對象是很常見的,但很少出現多個任務或中斷接收同一個通訊對象的情況。
- 緩沖多個數據項
通訊對象中的隊列是可以一次性保存多個已經被發送到隊列,但還未被接收的事件或數據的,也就是說,通訊對象有著一定的緩沖多個數據的能力,但是任務通知是通過更新任務通知值來發送事件或數據的,一個任務通知值只能保存一次。
- 廣播到多個任務
通訊對象中的事件標志組是可以將一個事件同時發送到多個任務中的,但任務通知只能是被指定的一個接收任務接收并處理。
- 阻塞等待接收任務
當通訊對象處于暫時無法寫入的狀態(例如隊列已滿,此時無法再向隊列寫入消息)時,發送任務是可以選擇阻塞等待接收任務接收,但是任務因嘗試發送任務通知到已有任務通知但還未處理的任務而進行阻塞等待的。但是任務通知也很少在實際情況中收到這種情況的限制。
3. 任務通知相關函數
3.1 基本任務通知相關函數
這些函數操作任務的默認(索引為 0)通知值。
函數 | 描述 |
---|---|
xTaskNotify() | 發送任務通知 |
xTaskNotifyAndQuery() | 發送任務通知,并獲取舊的通知值 |
xTaskNotifyGive() | 遞增接收任務的通知值 |
xTaskNotifyFromISR() | 在中斷中發送任務通知 |
xTaskNotifyAndQueryFromISR() | 在中斷中發送任務通知,并獲取舊的通知值 |
vTaskNotifyGiveFromISR() | 在中斷中遞增接收任務的通知值 |
ulTaskNotifyTake() | 接收任務通知 (作為二值信號量或計數信號量) |
xTaskNotifyWait() | 接收任務通知 (作為事件標志組或直接值) |
1. 發送任務通知
-
xTaskNotify()
- 描述: 向指定任務發送通知,并可以對接收任務的通知值執行不同的操作(如覆蓋、設置位、遞增)。
- 函數原型:
BaseType_t xTaskNotify( TaskHandle_t xTaskToNotify, uint32_t ulValue, eNotifyAction eAction );
- 參數:
xTaskToNotify
: 接收通知的任務句柄。ulValue
: 要發送的值,其具體含義取決于eAction
。eAction
: 指定如何使用ulValue
更新接收任務的通知值(例如:eSetBits
設置位,eIncrement
遞增,eSetValueWithOverwrite
覆蓋等)。
- 返回值:
pdPASS
: 成功發送通知。pdFAIL
: 通常不會失敗,除非xTaskToNotify
為NULL
或通知機制未使能。
-
xTaskNotifyAndQuery()
- 描述: 與
xTaskNotify()
類似,但它會返回接收任務通知值被更新前的值。 - 函數原型:
BaseType_t xTaskNotifyAndQuery( TaskHandle_t xTaskToNotify, uint32_t ulValue, eNotifyAction eAction, uint32_t *pulPreviousNotificationValue );
- 參數:
- 前三個參數與
xTaskNotify()
相同。 pulPreviousNotificationValue
: 指向uint32_t
變量的指針,用于接收通知值被更新前的值。如果不需要舊值,可以傳入NULL
。
- 前三個參數與
- 返回值: 同
xTaskNotify()
。
- 描述: 與
-
xTaskNotifyGive()
- 描述: 遞增接收任務的通知值。這模擬了信號量的
Give
操作,每次調用都會使通知值加 1,直到其達到最大值 (portMAX_DELAY
)。 - 函數原型:
BaseType_t xTaskNotifyGive( TaskHandle_t xTaskToNotify );
- 參數:
xTaskToNotify
- 接收通知的任務句柄。 - 返回值:
pdPASS
表示成功遞增通知值。
- 描述: 遞增接收任務的通知值。這模擬了信號量的
2. 中斷中發送任務通知
這些函數是其對應非ISR版本的變體,用于在中斷上下文安全地發送通知。它們通過 pxHigherPriorityTaskWoken
參數指示是否需要進行上下文切換。
-
xTaskNotifyFromISR()
- 描述: 在中斷中向指定任務發送通知,并對接收任務的通知值執行操作。
- 函數原型:
BaseType_t xTaskNotifyFromISR( TaskHandle_t xTaskToNotify, uint32_t ulValue, eNotifyAction eAction, BaseType_t *pxHigherPriorityTaskWoken );
- 參數:
xTaskToNotify
,ulValue
,eAction
: 同xTaskNotify()
。pxHigherPriorityTaskWoken
: 指向BaseType_t
變量的指針。如果調用此函數導致更高優先級的任務解除阻塞,則該變量將被設置為pdTRUE
。
- 注意事項: 在 ISR 結束時,應檢查
*pxHigherPriorityTaskWoken
的值。如果為pdTRUE
,則調用portYIELD_FROM_ISR()
。
-
xTaskNotifyAndQueryFromISR()
- 描述: 在中斷中向指定任務發送通知,并獲取通知值被更新前的值。
- 函數原型:
BaseType_t xTaskNotifyAndQueryFromISR( TaskHandle_t xTaskToNotify, uint32_t ulValue, eNotifyAction eAction, uint32_t *pulPreviousNotificationValue, BaseType_t *pxHigherPriorityTaskWoken );
- 參數、返回值及注意事項 同
xTaskNotifyFromISR()
和xTaskNotifyAndQuery()
的組合。
-
vTaskNotifyGiveFromISR()
- 描述: 在中斷中遞增接收任務的通知值。
- 函數原型:
BaseType_t vTaskNotifyGiveFromISR( TaskHandle_t xTaskToNotify, BaseType_t *pxHigherPriorityTaskWoken );
- 參數:
xTaskToNotify
: 接收通知的任務句柄。pxHigherPriorityTaskWoken
: 指向BaseType_t
變量的指針。如果調用此函數導致更高優先級的任務解除阻塞,則該變量將被設置為pdTRUE
。
- 注意事項: 在 ISR 結束時,應檢查
*pxHigherPriorityTaskWoken
的值。如果為pdTRUE
,則調用portYIELD_FROM_ISR()
。
3. 接收任務通知
-
ulTaskNotifyTake()
- 描述: 接收任務通知,并可以使其表現為二值信號量或計數信號量。它會清零通知計數器,并返回其舊值。
- 函數原型:
uint32_t ulTaskNotifyTake( BaseType_t xClearCountOnExit, TickType_t xTicksToWait );
- 參數:
xClearCountOnExit
: 如果設置為pdTRUE
,則在返回前將通知值重置為 0。如果設置為pdFALSE
,則僅將通知值減 1(類似于計數信號量)。xTicksToWait
: 如果沒有待處理的通知,任務愿意阻塞等待的節拍數。
- 返回值: 接收通知前任務的通知值。
-
xTaskNotifyWait()
- 描述: 接收任務通知,并可以使其表現為事件標志組或直接值。
- 函數原型:
BaseType_t xTaskNotifyWait( uint32_t ulBitsToClearOnEntry, uint32_t ulBitsToClearOnExit, uint32_t *pulNotificationValue, TickType_t xTicksToWait );
- 參數:
ulBitsToClearOnEntry
: 進入函數時,哪些位將被清零(設置為 0)。ulBitsToClearOnExit
: 退出函數時(收到通知后),哪些位將被清零。pulNotificationValue
: 指向uint32_t
變量的指針,用于存儲接收到的通知值。xTicksToWait
: 如果沒有待處理的通知,任務愿意阻塞等待的節拍數。
- 返回值:
pdPASS
表示成功接收通知;pdFALSE
表示超時未收到通知。
3.2 帶索引的任務通知相關函數
FreeRTOS v10.0.0 引入了帶索引的任務通知,允許每個任務擁有一個通知值數組(由 configTASK_NOTIFICATION_ARRAY_ENTRIES
定義數組大小,默認為 1),而不是單個通知值。這使得一個任務可以同時等待來自多個源的通知,而無需創建多個信號量或隊列。
這些函數的命名通常在基本函數名后加上 Indexed
后綴。它們都多了一個 uxIndexToNotify
或 uxIndexToWait
參數,用于指定操作哪個通知值數組元素。
xTaskNotifyIndexed()
xTaskNotifyAndQueryIndexed()
xTaskNotifyGiveIndexed()
xTaskNotifyIndexedFromISR()
xTaskNotifyAndQueryIndexedFromISR()
vTaskNotifyGiveIndexedFromISR()
ulTaskNotifyTakeIndexed()
xTaskNotifyWaitIndexed()
這些帶索引的函數的功能和參數與它們對應的基本任務通知函數類似,只是多了一個 uxIndex
參數來指定操作哪個通知槽。
示例:xTaskNotifyIndexed()
- 函數原型:
BaseType_t xTaskNotifyIndexed( TaskHandle_t xTaskToNotify, UBaseType_t uxIndexToNotify, uint32_t ulValue, eNotifyAction eAction );
- 參數:
xTaskToNotify
: 接收通知的任務句柄。uxIndexToNotify
: 要通知的通知值數組的索引。ulValue
: 要發送的值,其具體含義取決于eAction
。eAction
: 指定如何更新通知值。
示例:xTaskNotifyWaitIndexed()
- 函數原型:
BaseType_t xTaskNotifyWaitIndexed( UBaseType_t uxIndexToWait, uint32_t ulBitsToClearOnEntry, uint32_t ulBitsToClearOnExit, uint32_t *pulNotificationValue, TickType_t xTicksToWait );
- 參數:
uxIndexToWait
: 要等待的通知值數組的索引。- 其他參數同
xTaskNotifyWait()
。
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 4
#define TASK2_STK_SIZE 128
TaskHandle_t Task2Task_Handler;
void task2(void *pvParameters);
/*---------------------------------------------*/
4.2 任務實現
/*------------------任務實現區------------------*/
void freertos_demo(void)
{lcd_show_string(10,47,220,24,24,"Task Notify Demo",RED);lcd_draw_rectangle(5, 130, 234, 314, BLACK); // 創建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);vTaskDelete(StartTask_Handler); // 開始任務已經完成自己使命,刪除自己taskEXIT_CRITICAL(); // 退出臨界區
}void task1(void *pvParameters)
{uint8_t key_value =0;while(1){if(Task2Task_Handler != NULL){key_value = key_scan(0);switch(key_value){case KEY0_PRES:{xTaskNotifyGive((TaskHandle_t)Task2Task_Handler); // 發送通知給TASK2break;}default:break;}}vTaskDelay(10);}
}// task2函數實現
void task2(void *pvParameters)
{uint32_t nofify_value = 0;uint32_t task2_value = 0;while(1){nofify_value = ulTaskNotifyTake((BaseType_t)pdTRUE, (TickType_t)portMAX_DELAY); // 等待通知if(nofify_value != 0){lcd_fill(6,131,233,313,lcd_discolor[++task2_value%11]);}}
}
/*---------------------------------------------*/
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例程 */
}
5. 任務通知模擬消息郵箱測試
5.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);
/*---------------------------------------------*/
5.2 任務實現
/*------------------任務實現區------------------*/
void freertos_demo(void)
{lcd_show_string(10,47,220,24,24,"Task Notify Demo",RED);lcd_draw_rectangle(5, 125, 234, 314, BLACK);// 創建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);vTaskDelete(StartTask_Handler); // 開始任務已經完成自己使命,刪除自己taskEXIT_CRITICAL(); // 退出臨界區
}void task1(void *pvParameters)
{uint8_t key_value =0;while(1){key_value = key_scan(0);if((Task2Task_Handler != NULL) && (key_value != 0)){// 接受任務通知的句柄 要更新的bit位 更新方式為覆寫xTaskNotify((TaskHandle_t)Task2Task_Handler, (uint32_t)key_value, (eNotifyAction)eSetValueWithOverwrite);}vTaskDelay(10);}
}// task2函數實現
void task2(void *pvParameters)
{uint32_t nofify_value = 0;uint32_t task2_value = 0;while(1){xTaskNotifyWait((uint32_t)0x00000000, // 進入函數時,不清除任務通知值(uint32_t)0xFFFFFFFF, // 函數退出時,清除所有任務通知值(uint32_t*)&nofify_value, // 等待接收通知值(TickType_t)portMAX_DELAY);switch(nofify_value){case KEY0_PRES:{lcd_fill(6, 126, 233, 313, lcd_discolor[++task2_value % 11]);break;}case KEY1_PRES:{LED0_TOGGLE();break;}case WKUP_PRES:{LED1_TOGGLE();break;}default:break;}}
}
/*---------------------------------------------*/
5.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例程 */
}
6. 任務通知模擬計數信號量測試
6.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);
/*---------------------------------------------*/
6.2 任務實現
/*------------------任務實現區------------------*/
void freertos_demo(void)
{lcd_show_string(10,47,220,24,24,"Task Notify Demo",RED);lcd_show_string(54, 111, 200, 16, 16, "Notify Value: 0", BLUE);lcd_draw_rectangle(5, 110, 234, 314, BLACK);lcd_draw_line(5, 130, 234, 130, BLACK);// 創建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);vTaskDelete(StartTask_Handler); // 開始任務已經完成自己使命,刪除自己taskEXIT_CRITICAL(); // 退出臨界區
}void task1(void *pvParameters)
{uint8_t key_value =0;while(1){key_value = key_scan(0);if(Task2Task_Handler != NULL){switch(key_value){case KEY0_PRES:{xTaskNotifyGive((TaskHandle_t)Task2Task_Handler); // 發送通知給TASK2break;}default:break;}}vTaskDelay(10);}
}// task2函數實現
void task2(void *pvParameters)
{uint32_t nofify_value = 0;uint32_t task2_value = 0;while(1){nofify_value = ulTaskNotifyTake((BaseType_t)pdTRUE, (TickType_t)portMAX_DELAY); // 等待通知lcd_show_xnum(166,111,nofify_value-1,2,16,0,BLUE);lcd_fill(6,131,233,313,lcd_discolor[++task2_value%11]);vTaskDelay(1000);}
}
/*---------------------------------------------*/
6.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例程 */
}
7. 任務通知模擬事件標志組
7.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);#define EventBit_0 (1<<0)
#define EventBit_1 (1<<1)
#define EventBit_ALL (EventBit_0 | EventBit_1)
/*---------------------------------------------*/
7.2 任務實現
/*------------------任務實現區------------------*/
void freertos_demo(void)
{lcd_show_string(10,47,220,24,24,"Task Notify Demo",RED);lcd_show_string(30,110,220,16,16,"Event Group Value: 0",BLUE);lcd_draw_rectangle(5,130,234,314,BLACK); // 創建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);vTaskDelete(StartTask_Handler); // 開始任務已經完成自己使命,刪除自己taskEXIT_CRITICAL(); // 退出臨界區
}void task1(void *pvParameters)
{uint8_t key_value =0;while(1){if(Task1Task_Handler != NULL){key_value = key_scan(0);switch(key_value){case KEY0_PRES:{xTaskNotify((TaskHandle_t)Task2Task_Handler, (uint32_t)EventBit_0, (eNotifyAction)eSetBits);break;}case KEY1_PRES:{xTaskNotify((TaskHandle_t)Task2Task_Handler, (uint32_t)EventBit_1, (eNotifyAction)eSetBits);break;}default:break;}}vTaskDelay(10);}
}// task2函數實現
void task2(void *pvParameters)
{uint32_t notify_value = 0;uint32_t event_value = 0;uint32_t task2_value = 0;while(1){xTaskNotifyWait((uint32_t)0x00000000, // 進入函數時,不清除任務通知值(uint32_t)0xFFFFFFFF, // 函數退出時,清除所有任務通知值(uint32_t*)¬ify_value, // 等待接收通知值(TickType_t)portMAX_DELAY);if(notify_value & EventBit_0){event_value |= EventBit_0;}else if(notify_value & EventBit_1){event_value |= EventBit_1;}lcd_show_xnum(182,110,event_value,1,16,0,BLUE);if(event_value == EventBit_ALL){event_value = 0;lcd_fill(6, 131, 233, 313, lcd_discolor[++task2_value % 11]);}}
}
/*---------------------------------------------*/
7.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低功耗Tickless模式
1. 低功耗 Tickless 模式簡介
在整個系統的運行過程中,其實大部分的時間是在執行空閑任務的,而空閑任務之所及叫做空閑任務,是因為空閑任務是在系統中的所有其他都阻塞或被掛起時才運行的,因此可以在本該空閑任務執行的期間,讓MCU 進入相應的低功耗模式,接著在其他任務因被解除阻塞或其他原因,而準備運行的時候,讓 MCU 退出相應的低功耗模式,去執行相應的任務。 在以上這一過程中,主要的難點在于, MCU 進入相應的低功耗模式后,如何判斷有除空閑任務外的其他任務就緒,并退出相應的空閑模式去執行就緒任務,也就是如何計算 MCU 進入相應低功耗模式的時間,而 FreeRTOS 的低功耗 Tickless 模式機制已經處理好了這個問題。
2. 低功耗 Tickless 模式相關配置項
在前面對 FreeRTOS 低功耗 Tickless 模式的簡介中提到了 FreeRTOS 中針對該模式的幾個配置,如下表所示:
- configUSE_TICKLESS_IDLE
此宏用于使能低功耗 Tickless 模式,當此宏定義為 1 時,系統會在進入空閑任務期間進入相應的低功耗模式大于 configEXPECTED_IDLE_TIME_BEFORE_SLEEP 的時長。
- configEXPECTED_IDLE_TIME_BEFORE_SLEEP
此宏用于定義系統進入相應低功耗模式的最短時長,如果系統在進入相應低功耗模式前,計算出系統將進入相應低功耗的時長小于 configEXPECTED_IDLE_TIME_BEFORE_SLEEP 定義的最小時長,則系統不進入相應的低功耗模式,要注意的是,此宏的值不能小于 2。
- configPRE_SLEEP_PROCESSING(x)
此宏用于定義一些需要在系統進入相應低功耗模式前執行的事務,例如可以在進入低功耗模式前關閉一些 MCU 片上外設的時鐘,以達到降低功耗的目的。
- configPOSR_SLEEP_PROCESSING(x)
此宏用于定義一些需要在系統退出相應低功耗模式后執行的事務,例如開啟在系統在進入相應低功耗模式前關閉的 MCU 片上外設的時鐘,以是系統能夠正常運行。
3. 低功耗 Tickless測試
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);
/*---------------------------------------------*/
3.2 任務實現
/*------------------任務實現區------------------*/
void freertos_demo(void)
{lcd_show_string(10,47,220,24,24,"FreeRTOS Tickless",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(); // 進入臨界區lcd_display_off();LCD_BL(0);// 創建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(); // 退出臨界區
}void task1(void *pvParameters)
{while(1){LED0(1); // LED0滅,代表退出低功耗模式delay_ms(3000); // 模擬任務運行,此時不會進入低功耗模式LED0(0); // LED0亮,代表進入低功耗模式vTaskDelay(3000);}
}
3.3 低功耗處理
#if (configUSE_TICKLESS_IDLE == 1)
// 進入低功耗模式執行的操作,只做演示,以實際要求為準
void PRE_SLEEP_PROCESSING(void)
{__HAL_RCC_GPIOA_CLK_DISABLE();__HAL_RCC_GPIOB_CLK_DISABLE();__HAL_RCC_GPIOC_CLK_DISABLE();__HAL_RCC_GPIOD_CLK_DISABLE();__HAL_RCC_GPIOE_CLK_DISABLE();__HAL_RCC_GPIOF_CLK_DISABLE();__HAL_RCC_GPIOG_CLK_DISABLE();
}
void POST_SLEEP_PROCESSING(void)
{__HAL_RCC_GPIOA_CLK_ENABLE();__HAL_RCC_GPIOB_CLK_ENABLE();__HAL_RCC_GPIOC_CLK_ENABLE();__HAL_RCC_GPIOD_CLK_ENABLE();__HAL_RCC_GPIOE_CLK_ENABLE();__HAL_RCC_GPIOF_CLK_ENABLE();__HAL_RCC_GPIOG_CLK_ENABLE();
}
#endif
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例程 */
}
第十四章 FreeRTOS空閑任務
1. 空閑任務簡介
在前面章節分析 vTaskStartScheduler()函數啟動任務調度器的時候,就了解到, FreeRTOS 會自動創建一個空閑任務,這樣就可以確保系統中永遠都至少有一個正在被執行的任務。空閑任務是以系統中最低的任務優先級被創建的,這樣可以確保空閑任務不會占用其他就緒態任務的被執行時間。
當有任務被函數 vTaskDelete()函數刪除時,如果函數 vTaskDelete()的調用者與被刪除任務不是同一個任務,那么被刪除任務的刪除工作可以由函數 vTaskDelete()的調用者完成。如果vTaskDelete()的調用者和被刪除任務為同一個任務,即一個任務調用函數 vTaskDelete()刪除了自身,那么刪除工作是不能完全由任務本身完成的, 因此這項刪除任務自身的工作就交給了空閑任務,也正因如此,如果在任務中調用了函數 vTaskDelete()刪除了自身,那么就必須要保證最低任務優先級的空閑任務能夠被分配到運行時間。
2. 空閑任務鉤子函數
2.1 FreeRTOS 中的鉤子函數
FreeRTOS 提供了多種鉤子函數,當系統運行到某個功能或函數的某個位置時,就會調用相應的鉤子函數,至于鉤子函數具體要實現什么功能,可有由用戶自行編寫。當然,鉤子函數是一項可選功能,用戶如果不需要使用相應的鉤子函數,那就無需編寫相應的鉤子函數。在FreeRTOSConfig.h 文件中就可以看到啟用鉤子函數的相關配置項,具體的代碼如下所示:
/* 鉤子函數相關定義 */
#define configUSE_IDLE_HOOK 0 /* 空閑任務鉤子函數 */
#define configUSE_TICK_HOOK 0 /* 系統時鐘節拍中斷鉤子函數 */
#define configUSE_MALLOC_FAILED_HOOK 0 /* 動態內存申請失敗鉤子函數 */
#define configUSE_DAEMON_TASK_STARTUP_HOOK 0 /* 首次執行定時器服務任務鉤子函數 */
如果要啟用相應的鉤子函數,只需將對應的配置項配置為 1 即可,當然也不要忘了編寫相應的鉤子函數。
2.2 FreeRTOS 空閑任務鉤子函數
如果將宏 configUSE_IDLE_HOOK 配置為1,那么在空閑任務的每一個運行周期中,都會調用一次函數 vApplicationIdleHook(),此函數就是空閑任務的鉤子函數。
如果想在空閑任務相同的優先級中處理某些事務,那么有兩種選擇:
- 在空閑任務的鉤子函數中處理需要處理的任務
在這種情況下,需要特別注意,因為不論在什么時候,都應該保證系統中有一個正在被執行的任務,因此在空閑任務的鉤子函數中,不能夠調用會到時空閑任務被阻塞或掛起的函數,例如函數 vTaskDelay()。
- 在和空閑任務相同任務優先級的任務中處理需要處理的事務
創建一個和空閑任務相同優先級的任務來處理需要處理的事務是一個比較好的方法,但是這會導致消耗更多的 RAM。
通常在空閑任務的鉤子函數中設置處理器進入相應的低功耗模式,以達到降低整體功率的目的,為了與 FreeRTOS 自帶的低功耗 Tickless 模式做區分,這里暫且將這種使用空閑任務鉤子函數的低功耗模式成為通用低功耗模式,這是因為,幾乎所有的 RTOS 都可以使用這種方式實現低功耗。
通用的低功耗模式會使處理器在每次進入空閑任務函數時,進入相應的低功耗模式,并且在每次 SysTick 中斷發生的時候都會被喚醒, 可見通用方式實現的低功耗效果遠不如 FreeRTOS自帶的低功耗 Tickless 模式,但是這種方式更加通用。
3. 空閑任務鉤子函數測試
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);
/*---------------------------------------------*/
3.2 任務實現
/*------------------任務實現區------------------*/
void freertos_demo(void)
{lcd_show_string(10,47,220,24,24,"HOOK",RED); delay_ms(2000); // 創建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(); // 進入臨界區lcd_display_off();LCD_BL(0);// 創建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(); // 退出臨界區
}void task1(void *pvParameters)
{while(1){LED0(1); // LED0滅,代表退出低功耗模式delay_ms(3000); // 模擬任務運行,此時不會進入低功耗模式LED0(0); // LED0亮,代表進入低功耗模式vTaskDelay(3000);}
}
3.3 鉤子函數
void before_sleep(void)
{__HAL_RCC_GPIOA_CLK_DISABLE();__HAL_RCC_GPIOB_CLK_DISABLE();__HAL_RCC_GPIOC_CLK_DISABLE();__HAL_RCC_GPIOD_CLK_DISABLE();__HAL_RCC_GPIOE_CLK_DISABLE();__HAL_RCC_GPIOF_CLK_DISABLE();__HAL_RCC_GPIOG_CLK_DISABLE();
}void after_wakeup(void)
{__HAL_RCC_GPIOA_CLK_ENABLE();__HAL_RCC_GPIOB_CLK_ENABLE();__HAL_RCC_GPIOC_CLK_ENABLE();__HAL_RCC_GPIOD_CLK_ENABLE();__HAL_RCC_GPIOE_CLK_ENABLE();__HAL_RCC_GPIOF_CLK_ENABLE();__HAL_RCC_GPIOG_CLK_ENABLE();
}// 空閑任務鉤子函數
void vApplicationIdleHook(void)
{__disable_irq();__dsb(0);__isb(0);before_sleep();// 進入睡眠模式(等待中斷喚醒)__WFI();after_wakeup();__dsb(0);__isb(0);__enable_irq();
}
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例程 */
}
第十五章 FreeRTOS內存管理
1. 內存管理簡介
在使用 FreeRTOS 創建任務、隊列、信號量等對象的時候, FreeRTOS 一般都提供了兩種方法,一種是動態地申請創建對象時所需要的內存,這種方法也叫動態方法;一種是由用戶自定義對象,在編譯器編譯程序的時候,會為已經在程序中定義好的對象分配一定的內存空間,這種方法也叫靜態方法。
靜態方法創建任務、隊列、信號量等對象的 API 函數一般是以“Static”結尾的,例如靜態創建任務的 API 函數 xTaskCreateStatic()。使用靜態方式創建各種對象時,需要用戶提供各種內存空間,例如任務的棧空間、任務控制塊所用內存空間等等, 并且使用靜態方式占用的內存空間一般固定下來了,即使任務、隊列等被刪除后,這些被占用的內存空間也沒有其他用途。
在使用動態方式管理內存的時候, FreeRTOS 就能夠在創建任務、隊列、信號量等對象的時候,自動地從 FreeRTOS 管理的內存堆中申請所創建對象所需的內存,在對象被刪除后,又可以將這塊內存釋放會 FreeRTOS 管理的內存堆,這樣看來,動態方式管理內存相比與靜態方式,顯得靈活許多。
除了 FreeRTOS 提供的動態內存管理方法,標準的 C 庫也提供了函數 malloc()和函數 free()來實現動態地申請和釋放內存,但是標準 C 庫的動態內存管理方法有如下幾個缺點:
-
并不適用于所有嵌入式系統。
-
占用大量的代碼空間。
-
沒有線程安全的相關機制。
-
具有不確定性,體現在每次執行的時間不同。
-
……
為此, FreeRTOS 提供了動態方式管理內存的方法。不同的嵌入式系統對于動態內存管理的需求不同,因此 FreeRTOS 提供了多種內存管理算法選項,并將其作為 FreeRTOS 移植層的一部分,這樣一來, FreeRTOS 的使用者就能夠根據自己實際的需求選的何時的動態內存管理算法,并將其移植到系統中。
FreeRTOS 一共提供了 5 種動態內存管理算法,這 5 種動態內存管理算法本別對應了 5 個C 源文件,分別為: heap_1.c、 heap_2.c、 heap_3.c、 heap_4.c、 heap_5.c, 在后面小節中將會講解這 5 種動態內存管理算法的異同。
2. FreeRTOS 內存管理算法
FreeRTOS提供了5種動態內存管理算法,分別為heap_1、heap_2、heap_3、heap_4和heap_5,這 5 種動態內存管理算法各自的特點如下所示:
2.1 heap_1.c
- 特點:
- 最簡單的實現。
- 只支持內存分配 (
pvPortMalloc()
),不支持內存釋放 (vPortFree()
)。 - 因此,不會產生內存碎片。
- 分配速度快,是確定性的。
- 適用場景:
- 內存分配只在系統初始化階段進行,或者應用程序中分配的內存永遠不會被釋放。
- 適用于資源非常有限、對內存管理開銷要求極高且可以預先確定所有內存需求的簡單嵌入式系統。
2.2 heap_2.c
- 特點:
- 支持內存分配 (
pvPortMalloc()
) 和內存釋放 (vPortFree()
)。 - 內存分配是確定性的,但內存釋放不是確定性的,因為涉及搜索最佳匹配塊。
- 使用“最佳適配”(best fit)算法來查找分配塊。
- 釋放內存時,不會自動合并相鄰的空閑塊,因此可能導致內存碎片。
- 支持內存分配 (
- 適用場景:
- 早期的 FreeRTOS 版本中經常使用。
- 適用于分配和釋放操作數量有限,并且對內存碎片化容忍度較高,或者可以通過重啟來解決碎片化問題的系統。
- 相對于
heap_4.c
,它在某些特定場景下可能會更快,但通常不作為首選。
2.3 heap_3.c
- 特點:
- 支持內存分配 (
pvPortMalloc()
) 和內存釋放 (vPortFree()
)。 - 包裝了標準庫的
malloc()
和free()
函數。 - 線程安全:通過互斥量(Mutex)保護對
malloc()
和free()
的調用,使其在多任務環境中是安全的。 - 性能和碎片化特性取決于底層標準庫的實現。
- 支持內存分配 (
- 適用場景:
- 當你的嵌入式系統有充足的 RAM 并且已經集成了標準 C 庫的
malloc()
和free()
,并且希望利用這些成熟的實現時。 - 開發者不希望自己管理 FreeRTOS 堆的復雜性。
- 需要注意:標準庫的
malloc()
和free()
通常不是為實時系統設計的,可能存在非確定性行為和性能瓶頸。
- 當你的嵌入式系統有充足的 RAM 并且已經集成了標準 C 庫的
2.4 heap_4.c
- 特點:
- 支持內存分配 (
pvPortMalloc()
) 和內存釋放 (vPortFree()
)。 - 最常用且推薦的通用內存管理方案。
- 使用“首次適配”(first fit)算法來查找分配塊。
- 釋放內存時,會自動合并相鄰的空閑塊,從而有效減少內存碎片。
- 分配和釋放操作都不是嚴格確定性的(因為需要遍歷空閑列表),但通常在大多數應用中性能良好。
- 支持內存分配 (
- 適用場景:
- 大多數需要動態內存分配和釋放的 FreeRTOS 應用程序。
- 對內存碎片化有一定關注,希望堆空間能夠高效重用的系統。
- 需要相對平衡的性能和內存效率。
2.5 heap_5.c
- 特點:
- 支持內存分配 (
pvPortMalloc()
) 和內存釋放 (vPortFree()
)。 - 與
heap_4.c
類似,也支持合并相鄰空閑塊以減少碎片。 - 主要區別在于,
heap_5.c
允許將堆空間分布在多個不連續的內存區域。
- 支持內存分配 (
- 適用場景:
- 當硬件架構中 RAM 是非連續的,例如,部分 RAM 在內部,部分 RAM 在外部,或者 RAM 分布在不同的地址空間中。
- 需要整合多個獨立的內存塊形成一個邏輯上的大堆。
2.6 總結選擇指南
特性 / 算法 | heap_1.c | heap_2.c | heap_3.c | heap_4.c | heap_5.c |
---|---|---|---|---|---|
內存分配 | 支持 | 支持 | 支持 | 支持 | 支持 |
內存釋放 | 不支持 | 支持 | 支持 | 支持 | 支持 |
碎片化 | 無 | 存在可能 | 取決于StdLib | 較少(合并) | 較少(合并) |
確定性 | 分配確定 | 分配確定 | 取決于StdLib | 非確定 | 非確定 |
自動合并 | N/A | 否 | 取決于StdLib | 是 | 是 |
不連續內存 | 否 | 否 | 否 | 否 | 是 |
推薦度 | 特殊場景 | 不推薦 | 僅限特定場景 | 最常用 | 特殊硬件場景 |
在大多數 FreeRTOS 應用程序中,heap_4.c
是最常用且推薦的通用內存管理算法,因為它在內存碎片化和效率之間提供了很好的平衡。只有當有非常特定的需求(如不連續內存、絕對零碎片化或利用現有標準庫)時,才考慮其他選項。
3. 內存管理測試
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);
/*---------------------------------------------*/
3.2 任務實現
/*------------------任務實現區------------------*/
void freertos_demo(void)
{lcd_show_string(10,47,220,24,24,"Memory Management",RED); lcd_show_string(30, 118, 200, 16, 16, "Total Mem: Bytes", RED);lcd_show_string(30, 139, 200, 16, 16, "Free Mem: Bytes", RED);lcd_show_string(30, 160, 200, 16, 16, "Malloc Addr:", 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(); // 退出臨界區
}void task1(void *pvParameters)
{uint8_t key_value = 0;uint8_t *buf = NULL;size_t free_size = 0;while(1){key_value = key_scan(0);switch(key_value){case KEY0_PRES:{buf = pvPortMalloc(30); // 分配30字節內存sprintf((char*)buf, "0x%p",buf);lcd_show_string(130,160,200,16,16,(char*)buf,BLUE);break;}case KEY1_PRES:{if(NULL != buf){vPortFree(buf); // 釋放內存buf = NULL;}break;}default:break;}lcd_show_xnum(114,118,configTOTAL_HEAP_SIZE,5,16,0,BLUE); // 顯示總內存大小free_size = xPortGetFreeHeapSize(); // 獲取空閑內存大小lcd_show_xnum(114,139,free_size,5,16,0,BLUE);vTaskDelay(10);}
}
/*---------------------------------------------*/
3.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例程 */
}
文中完整工程下載:https://github.com/hazy1k/FreeRTOS-Quick-Start-Guide/tree/main/2.code