本文主要學習 FreeRTOS 事件組的相關知識,包括事件組概述、事件組特征、創建事件組、操作事件組、刪除事件組等知識 ...
1、準備材料
STM32CubeMX軟體(Version 6.10.0)
Keil µVision5 IDE(MDK-Arm)
2、學習目標
本文主要學習 FreeRTOS 事件組的相關知識,包括事件組概述、事件組特征、創建事件組、操作事件組、刪除事件組等知識
3、前提知識
3.1、什麼是事件組?
事件組(event group)也是FreeRTOS中另外一種進程間通信技術,事件組適用於多個事件觸發一個或多個任務運行,可以實現事件的廣播,還可以實現多個任務的同步運行,如下所述
- 事件組允許任務等待一個或多個事件的組合
- 事件組會解除所有等待同一事件的任務的阻塞狀態
3.1、事件組特征
3.1.1、事件組、事件標誌和事件位
事件 “標誌” 是一個布爾值(1 或 0),用於指示事件是否發生,事件 “組” 是一組事件標誌,事件標誌只能為 1 或 0 ,允許事件標誌的狀態存儲在單個位中,並且事件組中所有事件標誌的狀態存儲在單個變數中
事件組中每個事件標誌的狀態由 EventBits_t 類型變數中的單個位表示。因此,事件標誌也稱為事件 “位” ,如果 EventBits_t 變數中的某個位設置為 1 ,則該位表示的事件已發生,否則如果 EventBits_t 變數中的某個位設置為 0 ,則該位表示的事件尚未發生
如下圖所示顯示了各個事件標誌如何映射到 EventBits_t 類型變數中的各個位 (註釋1)
3.1.2、EventBits_t 數據類型
一個事件組對象有一個變數類型為 EventBits_t 的內部變數用於存儲事件標誌位,該變數可以設置為 16 位或 32 位,具體由參數 configUSE_16_BIT_TICKS 所決定,當參數設置為 1 時,那麼每個事件組包含 8 個可用的事件位(包括 8 個保留位),否則設置為 0 時,每個事件組包含 24 個可用的事件位(包括 8 個保留位)
3.1.3、多個任務訪問
事件組本身就是對象,任何知道其存在的任務或 ISR 都可以訪問它們。任意數量的任務可以在同一事件組中設置位,並且任意數量的任務可以從同一事件組中讀取位
3.2、創建事件組
一個事件組在使用之前必須先創建,如下所示為使用動態/靜態記憶體分配創建一個事件組的 API 函數
/**
* @brief 動態分配記憶體創建事件組函數
* @retval 返回成功創建的事件組的句柄,返回NULL表示因記憶體空間不足創建失敗
*/
EventGroupHandle_t xEventGroupCreate(void);
/**
* @brief 靜態分配記憶體創建事件組函數
* @param pxEventGroupBuffer:指向StaticEventGroup_t類型的變數,該變數用於存儲事件組數據結構體
* @retval 返回成功創建的事件組的句柄,返回NULL表示因pxEventGroupBuffer空間不足創建失敗
*/
EventGroupHandle_t xEventGroupCreateStatic(
StaticEventGroup_t *pxEventGroupBuffer);
3.3、操作事件組
FreeRTOS 提供了兩組 API 來對事件組的某些位進行置位和清零兩種操作,具體如下所示
/**
* @brief 將事件組某些位置位
* @param xEventGroup:要設置位的事件組
* @param uxBitsToSet:指定要在事件組中設置的一個或多個位的按位值,例如設置為0x09表示置位3 和位0
* @retval 調用 xEventGroupSetBits()返回時事件組的值
*/
EventBits_t xEventGroupSetBits(EventGroupHandle_t xEventGroup,
const EventBits_t uxBitsToSet);
/**
* @brief 將事件組某些位清零
* @param xEventGroup:要在其中清除位的事件組
* @param uxBitsToSet:表示要在事件組中清除一個或多個位的按位值
* @retval 返回清除指定位之前的事件組的值
*/
EventBits_t xEventGroupClearBits(EventGroupHandle_t xEventGroup,
const EventBits_t uxBitsToClear);
/**
* @brief 上述兩個函數的中斷安全版本
* @param pxHigherPriorityTaskWoken:用於通知應用程式編寫者是否應該執行上下文切換
* @retval 消息已發送到RTOS守護進程任務,則返回pdPASS,否則將返回pdFAIL
*/
BaseType_t xEventGroupSetBitsFromISR(EventGroupHandle_t xEventGroup,
const EventBits_t uxBitsToSet,
BaseType_t *pxHigherPriorityTaskWoken);
BaseType_t xEventGroupClearBitsFromISR(EventGroupHandle_t xEventGroup,
const EventBits_t uxBitsToClear);
/*example1: 將事件組 EventGroup_Test 的位 1 和 3 置位*/
EventBits_t return_value;
return_value = xEventGroupSetBits(EventGroup_Test, 0x0A);
/*example2: 將事件組 EventGroup_Test 的位 0 和 2 清零*/
EventBits_t return_value;
return_value = xEventGroupClearBits(EventGroup_Test, 0x05);
同時 FreeRTOS 也提供了查詢事件組當前值的 API 函數,具體如下所示
/**
* @brief 讀取事件組的當前值
* @param xEventGroup:正在查詢的事件組
* @retval 返回事件組當前的值
*/
EventBits_t xEventGroupGetBits(EventGroupHandle_t xEventGroup);
/**
* @brief 上述函數的中斷安全版本
*/
EventBits_t xEventGroupGetBitsFromISR(EventGroupHandle_t xEventGroup);
3.4、xEventGroupWaitBits() API 函數
FreeRTOS 關於事件組提出了等待事件組和事件組同步兩個比較重要的 API 函數,分別對應兩種不同的使用場景,等待事件組主要用於使用事件組進行事件的管理,而另外一主要用於使用事件組進行任務間的同步,接下來主要詳細介紹兩個函數的具體用法
xEventGroupWaitBits() API 函數允許任務讀取事件組的值,並且可以選擇在阻塞狀態下等待事件組中的一個或多個事件位被設置(如果事件位尚未設置),如下所示為其具體的函數聲明
/**
* @brief 等待事件組中多個事件位表示的事件成立
* @param xEventGroup:所操作事件組的句柄
* @param uxBitsToWaitFor:所等待事件位的掩碼,例如設置為0x05表示等待第0位和/或第2位
* @param xClearOnExit:pdTRUE表示事件組條件成立退出阻塞狀態時將掩碼指定的所有位清零;pdFALSE表示事件組條件成立退出阻塞狀態時不將掩碼指定的所有位清零
* @param xWaitForAllBits:pdTRUE表示等待掩碼中所有事件位都置1,條件才算成立(邏輯與);pdFALSE表示等待掩碼中所有事件位中一個置1,條件就成立(邏輯或)
* @param xTicksToWait:任務進入阻塞狀態等待時間成立的超時節拍數
* @retval 返回事件位等待完成設置或阻塞時間過期時的事件組值
*/
EventBits_t xEventGroupWaitBits(const EventGroupHandle_t xEventGroup,
const EventBits_t uxBitsToWaitFor,
const BaseType_t xClearOnExit,
const BaseType_t xWaitForAllBits,
TickType_t xTicksToWait);
3.4.1、uxBitsToWaitFor 和 xWaitForAllBits 參數
調度程式用來確定任務是否進入阻塞狀態以及任務何時離開阻塞狀態的條件稱為 “解除阻塞條件” 。解鎖條件由 uxBitsToWaitFor 和 xWaitForAllBits 參數值的組合指定:
- uxBitsToWaitFor 指定要測試事件組中的哪些事件位
- xWaitForAllBits 指定是使用按位 OR 測試還是按位 AND 測試
如果調用 xEventGroupWaitBits() 時滿足解鎖條件,任務將不會進入阻塞狀態,下表提供了導致任務進入阻塞狀態或退出阻塞狀態的條件示例。表中列出的值僅顯示事件組和 uxBitsToWaitFor 值的最低有效的四個二進位位,其他位均假定為零
現有事件組值 | uxBitsToWaitFor | xWaitForAllBits | 導致的結果 |
---|---|---|---|
0000 | 0101 | pdFALSE | 由於事件組中的位 0 或位 2 均未設置,調用任務將進入阻塞狀態,並且當事件組中的位 0 或位 2 被設置時,調用任務將離開阻塞狀態 |
0100 | 0101 | pdTRUE | 調用任務將進入阻塞狀態,因為事件組中的位 0 和位 2 未同時設置,並且當事件組中的位 0 和位 2 均設置時,調用任務將離開阻塞狀態 |
0100 | 0110 | pdFALSE | 調用任務不會進入阻塞狀態,因為 xWaitForAllBits 為 pdFALSE,並且 uxBitsToWaitFor 指定的兩個位之一已在事件組中設置 |
0100 | 0110 | pdTRUE | 調用任務將進入阻塞狀態,因為 xWaitForAllBits 為pdTRUE,並且事件組中僅已設置 uxBitsToWaitFor 指定的兩個位之一。 當事件組中的位 2 和位 3 均被設置時,任務將離開阻塞狀態 |
3.4.2、xClearOnExit 參數
調用任務使用 uxBitsToWaitFor 參數指定要測試的位,並且調用任務可能需要在滿足其解鎖條件後將這些位清零。可以使用 xEventGroupClearBits() API 函數清除事件位,但使用該函數手動清除事件位將導致應用程式代碼中出現競爭條件
因此提供 xClearOnExit 參數就是為了避免這些潛在的競爭條件。如果 xClearOnExit 設置為 pdTRUE,則事件位的測試和清除對於調用任務來說是一個原子操作(不能被其他任務或中斷中斷),簡單來說就是如果 xClearOnExit 設置為 pdTRUE,則調用任務退出後會將事件組所有位清零,否則不清零
如果 xEventGroupWaitBits() 由於滿足調用任務的解鎖條件而返回,則返回值是滿足調用任務的解鎖條件時事件組的值(如果 xClearOnExit 為 pdTRUE,則在自動清除任何位之前),在這種情況下,返回值也將滿足解鎖條件。如果 xEventGroupWaitBits() 因為 xTicksToWait 參數指定的退出阻塞時間到期而返回,則返回值為退出阻塞時間到期時事件組的值,在這種情況下,返回值將不滿足解鎖條件
3.5、xEventGroupSync() API 函數
提供 xEventGroupSync() 是為了允許兩個或多個任務使用事件組來相互同步。該函數允許任務設置事件組中的一個或多個事件位,然後等待同一事件組中指定的事件位組合被設置
如下所示為 xEventGroupSync() API 函數的具體聲明
/**
* @brief 事件組同步
* @param uxBitsToSet:設置和測試位的事件組
* @param uxBitsToWaitFor:指定事件組中要測試的一個或多個事件位的按位值
* @param xTicksToWait:任務進入阻塞狀態等待時間成立的超時節拍數
* @retval 返回函數退出時事件組的值
*/
EventBits_t xEventGroupSync(EventGroupHandle_t xEventGroup,
const EventBits_t uxBitsToSet,
const EventBits_t uxBitsToWaitFor,
TickType_t xTicksToWait);
3.5.1、函數返回值
xEventGroupSync() 函數返回函數退出時事件組的值,可能有以下兩種情況
-
xEventGroupSync() 函數的 uxBitsToWaitFor 參數指定了調用任務的解鎖條件,如果該函數由於滿足解鎖條件而返回,則 uxBitsToWaitFor 指定的事件位將在 xEventGroupSync() 返回之前清回零,並且在自動清為零之前會將事件組的值作為函數返回值返回
-
如果 xEventGroupSync() 由於 xTicksToWait 參數指定的阻塞時間到期而返回,則返回值為阻塞時間到期時事件組的值,在這種情況下,返回值將不滿足調用任務的解鎖條件
3.5.2、應用舉例
舉個簡單的例子就容易理解:
假設目前有兩個任務,分別為 TASK1 和 TASK2 ,如果 TASK1 被執行過程中因為延時等原因先於 TASK2 調用了 xEventGroupSync() 函數,參數 uxBitsToSet 被設置為 0x01(0000 0001),參數 uxBitsToWaitFor 被設置為 0x05(0000 0101),則 TASK1 執行到該函數時會將事件組中位 0 的值置 1 ,然後進入阻塞狀態,等待位 2 和位 0 同時被置 1 ;
如果 TASK2 與 TASK1 一樣,只不過落後於 TASK1 執行 xEventGroupSync() 函數,並且參數 uxBitsToSet 被設置為 0x04(0000 0100),當 TASK2 執行該函數時會將事件組中位 2 的值置 1 ,此時滿足解鎖條件,所以 TASK2 不會進入阻塞狀態,同時 TASK1 也滿足解鎖條件,從阻塞狀態中退出,這時候假設任務優先順序一致,則 TASK1 和 TASK2 會同時從同步點開始運行後續的程式代碼,從而達到同步的目的
3.5、刪除事件組
/**
* @brief 刪除事件組
* @param xEventGroup:要刪除事件組的句柄
* @retval None
*/
void vEventGroupDelete(EventGroupHandle_t xEventGroup);
4、實驗一:使用事件組進行事件管理
4.1、實驗目標
Mastering_the_FreeRTOS_Real_Time_Kernel-A_Hands-On_Tutorial_Guide.pdf 手冊第 “8.3” 小節最後介紹了關於事件組的事件管理示例 22 ,這裡我們來複現一下
- 創建一個用於演示本實驗的事件組 xEventGroup
- 創建一個負責將事件組 xEventGroup 位 0 和位 1 置位的任務 Task_SetBits
- 啟動 RTC 1s 周期喚醒,在 RTC 周期喚醒回調函數中負責將事件組 xEventGroup 位 0 置位
- 創建一個負責等待事件組位 0 或位 1 或位 2 滿足條件的任務 Task_ReadBits
4.2、CubeMX相關配置
首先讀者應按照 "FreeRTOS教程1 基礎知識 "章節配置一個可以正常編譯通過的 FreeRTOS 空工程,然後在此空工程的基礎上增加本實驗所提出的要求
本實驗需要初始化 USART1 作為輸出信息渠道,具體配置步驟請閱讀 “STM32CubeMX教程9 USART/UART 非同步通信” ,如下圖所示
本實驗需要配置 RTC 周期喚醒中斷,具體配置步驟和參數介紹讀者可閱讀”STM32CubeMX教程10 RTC 實時時鐘 - 周期喚醒、鬧鐘A/B事件和備份寄存器“實驗,此處不再贅述,這裡參數、時鐘配置如下圖所示
由於需要在 RTC 周期喚醒中斷中使用 FreeRTOS 的 API 函數,因此 RTC 周期喚醒中斷的優先順序應該設置在 15~5 之間,此處設置為 7 ,具體如下圖所示
單擊 Middleware and Software Packs/FREERTOS,在 Configuration 中單擊 Tasks and Queues 選項卡,雙擊預設任務按任務 Task_SetBits 修改其參數,然後增加另外一個 Task_ReadBits 任務,具體如下圖所示
然後在 Configuration 中單擊 Events 選項卡,單擊右下角的 Add 按鈕增加一個事件組 xEventGroup ,具體如下圖所示
配置 Clock Configuration 和 Project Manager 兩個頁面,接下來直接單擊 GENERATE CODE 按鈕生成工程代碼即可
4.3、添加其他必要代碼
按照 “STM32CubeMX教程9 USART/UART 非同步通信” 實驗 “6、串口printf重定向” 小節增加串口 printf 重定向代碼,具體不再贅述
首先應該在 freertos.c 中添加信號量的頭文件和定義需要用到的事件組位的巨集定義,如下所述
/*freertos.c中添加頭文件*/
#include "stdio.h"
#include "event_groups.h"
/*事件組位巨集定義*/
#define mainFIRST_TASK_BIT ( 1UL << 0UL ) /* 事件組位 0 */
#define mainSECOND_TASK_BIT ( 1UL << 1UL ) /* 事件組位 1 */
#define mainISR_BIT ( 1UL << 2UL ) /* 事件組位 2 */
然後在該文件中重新實現周期喚醒回調函數,該函數用於 1s 周期將事件組 xEventGroup 的位 2 置 1 ,具體如下所示
/*周期喚醒回調函數*/
void HAL_RTCEx_WakeUpTimerEventCallback(RTC_HandleTypeDef *hrtc)
{
/* 輸出信息提示 */
printf("Bit setting ISR -\t about to set bit 2.\r\n");
/* 從中斷中設置事件組位 2 為 1 */
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
xEventGroupSetBitsFromISR(xEventGroupHandle, mainISR_BIT, &xHigherPriorityTaskWoken);
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
}
最後仍然在該文件中實現任務 Task_SetBits 和任務 Task_ReadBits 兩個任務函數體即可,具體如下所示
/*事件組置位任務*/
void AppTask_SetBits(void *argument)
{
/* USER CODE BEGIN AppTask_SetBits */
/* 400ms延時變數 */
const TickType_t xDelay400ms = pdMS_TO_TICKS(400UL);
/* Infinite loop */
for(;;)
{
/* 在下次迴圈開始之前短延時 */
vTaskDelay(xDelay400ms);
/* 輸出事件組位 0 被置位任務置 1 信息 */
printf("Bit setting task -\t about to set bit 0.\r\n");
xEventGroupSetBits(xEventGroupHandle, mainFIRST_TASK_BIT);
/* 在置位下一位之前短延時 */
vTaskDelay(xDelay400ms);
/* 輸出事件組位 1 被置位任務置 1 信息 */
printf("Bit setting task -\t about to set bit 1.\r\n");
xEventGroupSetBits(xEventGroupHandle, mainSECOND_TASK_BIT);
}
/* USER CODE END AppTask_SetBits */
}
/*事件組讀取任務*/
void AppTask_ReadBits(void *argument)
{
/* USER CODE BEGIN AppTask_ReadBits */
/* 創建事件組 */
EventBits_t xEventGroupValue;
/* 設置要測試的位 */
const EventBits_t xBitsToWaitFor = (mainFIRST_TASK_BIT |
mainSECOND_TASK_BIT |
mainISR_BIT);
/* Infinite loop */
for(;;)
{
xEventGroupValue = xEventGroupWaitBits(
/* 被讀的事件組 */
xEventGroupHandle,
/* 要測試的位 */
xBitsToWaitFor,
/* 阻塞條件滿足退出時清除所有事件位 */
pdTRUE,
/* 不等待所有位. */
pdFALSE,
/* 永遠等待,不會超時 */
portMAX_DELAY);
/* 位 0 被置 1 */
if((xEventGroupValue & mainFIRST_TASK_BIT) != 0)
{
printf("Bit reading task -\t Event bit 0 was set\r\n");
}
/* 位 1 被置 1 */
if((xEventGroupValue & mainSECOND_TASK_BIT ) != 0 )
{
printf("Bit reading task -\t Event bit 1 was set\r\n");
}
/* 位 2 被置 1 */
if((xEventGroupValue & mainISR_BIT ) != 0 )
{
printf("Bit reading task -\t Event bit 2 was set\r\n");
}
}
/* USER CODE END AppTask_ReadBits */
}
4.4、燒錄驗證
燒錄程式,在 xEventGroupWaitBits() 函數 xWaitForAllBits 參數設置為 pdFALSE 的情況下串口產生的輸出信息如下圖所示
從圖中可可以看出,因為對 xEventGroupWaitBits() 的調用中的 xWaitForAllBits 參數設置為 pdFALSE, 每次設置任何事件位時,從事件組讀取的任務都會離開阻塞狀態並立即執行
4.5、測試 xWaitForAllBits 參數
將任務 AppTask_ReadBits() 調用的 xEventGroupWaitBits() 函數 xWaitForAllBits 參數設置為 pdTRUE,表示需要等待所有事件組測試位滿足才能離開阻塞狀態,這種情況下串口產生的輸出如下圖所示
在上圖中可以看出,由於 xWaitForAllBits 參數設置為 pdTRUE,從事件組讀取的任務僅在所有三個事件位均置 1 後才可以離開阻塞狀態
5、實驗二:使用事件組進行任務同步
5.1、實驗目標
- 創建一個用於演示本實驗的事件組 xEventGroup
- 創建三個任務通過延時模擬不同時間到達任務同步點
5.2、CubeMX相關配置
首先讀者應按照 "FreeRTOS教程1 基礎知識" 章節配置一個可以正常編譯通過的 FreeRTOS 空工程,然後在此空工程的基礎上增加本實驗所提出的要求
本實驗需要初始化 USART1 作為輸出信息渠道,具體配置步驟請閱讀 “STM32CubeMX教程9 USART/UART 非同步通信” ,如下圖所示
單擊 Middleware and Software Packs/FREERTOS,在 Configuration 中單擊 Tasks and Queues 選項卡,雙擊預設任務修改其參數,具體如下圖所示
然後在 Configuration 中單擊 Events 選項卡,單擊右下角的 Add 按鈕增加一個事件組 xEventGroup ,具體如下圖所示
配置 Clock Configuration 和 Project Manager 兩個頁面,接下來直接單擊 GENERATE CODE 按鈕生成工程代碼即可
5.3、添加其他必要代碼
按照 “STM32CubeMX教程9 USART/UART 非同步通信” 實驗 “6、串口printf重定向” 小節增加串口 printf 重定向代碼,具體不再贅述
首先應該在 freertos.c 中添加信號量的頭文件和定義需要用到的事件組位的巨集定義,如下所述
/*頭文件*/
#include "stdio.h"
#include "stdlib.h"
#include "event_groups.h"
/*事件組位巨集定義*/
#define mainFIRST_TASK_BIT ( 1UL << 0UL ) /* 事件組位 0 */
#define mainSECOND_TASK_BIT ( 1UL << 1UL ) /* 事件組位 1 */
#define mainTHIRD_TASK_BIT ( 1UL << 2UL ) /* 事件組位 2 */
修改 MX_FREERTOS_Init() 函數,將預設生成的創建一個任務程式註釋掉,然後利用一個任務回調函數通過不同的參數創建三個不同的任務,部分註釋已經刪除,具體如下所示
void MX_FREERTOS_Init(void) {
/* Create the thread(s) */
/* creation of Task_Syncing */
//Task_SyncingHandle = osThreadNew(AppTask_Syncing, NULL, &Task_Syncing_attributes);
/* USER CODE BEGIN RTOS_THREADS */
/* add threads, ... */
xTaskCreate(AppTask_Syncing, "Task 1", 1000, (void*)mainFIRST_TASK_BIT, 24, NULL);
xTaskCreate(AppTask_Syncing, "Task 2", 1000, (void*)mainSECOND_TASK_BIT, 24, NULL);
xTaskCreate(AppTask_Syncing, "Task 3", 1000, (void*)mainTHIRD_TASK_BIT, 24, NULL);
/* USER CODE END RTOS_THREADS */
/* Create the event(s) */
/* creation of xEventGroup */
xEventGroupHandle = osEventFlagsNew(&xEventGroup_attributes);
/* USER CODE BEGIN RTOS_EVENTS */
/* add events, ... */
/* USER CODE END RTOS_EVENTS */
}
最後實現任務入口函數 AppTask_Syncing() 的函數體即可,具體如下所述
/*事件組同步任務函數*/
void AppTask_Syncing(void *argument)
{
/* USER CODE BEGIN AppTask_Syncing */
/* 創建兩個延時用於合成隨機延時時間 */
const TickType_t xMaxDelay = pdMS_TO_TICKS(4000UL);
const TickType_t xMinDelay = pdMS_TO_TICKS(200UL);
/* 延時時間 */
TickType_t xDelayTime;
/* 任務要設置的事件組的位 */
EventBits_t uxThisTasksSyncBit;
/* 任務要等待的事件組的所有位 */
const EventBits_t uxAllSyncBits = ( mainFIRST_TASK_BIT |
mainSECOND_TASK_BIT |
mainTHIRD_TASK_BIT );
uxThisTasksSyncBit = ( EventBits_t )argument;
/* Infinite loop */
for(;;)
{
/* 合成隨機延時時間,模擬三個任務不同時間到達同步點 */
xDelayTime = (rand() % xMaxDelay) + xMinDelay;
vTaskDelay(xDelayTime);
printf("%s reached sync point\r\n", pcTaskGetTaskName(NULL));
xEventGroupSync(/* 被讀的事件組 */
xEventGroupHandle,
/* 測試的位 */
uxThisTasksSyncBit,
/* 需要等待的所有位 */
uxAllSyncBits,
/* 永遠等待,不會超時 */
portMAX_DELAY);
/* 任務會同時退出同步點,串口輸出需要時間,所以通過臨界段保護串口輸出 */
taskENTER_CRITICAL();
printf("%s exited sync point\r\n", pcTaskGetTaskName(NULL));
taskEXIT_CRITICAL();
}
/* USER CODE END AppTask_Syncing */
}
5.4、燒錄驗證
燒錄程式,打開串口助手,通過串口助手輸出的信息可以發現,三個任務在不同的(偽隨機)時間到達任務點,但是當其中最後一個任務到達同步點之後,三個任務會同時退出同步點,具體的串口輸出信息如下圖所示
6、註釋詳解
註釋1:圖片來源於 Mastering_the_FreeRTOS_Real_Time_Kernel-A_Hands-On_Tutorial_Guide.pdf
參考資料
Mastering_the_FreeRTOS_Real_Time_Kernel-A_Hands-On_Tutorial_Guide.pdf