FreeRTOS 任務間通信機制:隊列、信號量、事件標志組詳解與實驗

1. FreeRTOS 消息隊列

1.1 簡介

? 隊列是 任務間通信的主要形式,可用于在任務之間以及中斷與任務之間傳遞消息。隊列在 FreeRTOS 中具有以下關鍵特點:

  • 隊列默認采用 先進先出 FIFO 方式,也可以使用 xQueueSendToFront()實現 LIFO。
  • FreeRTOS 確保隊列操作是原子的,不會因為任務切換而導致數據損壞。
  • 如果隊列 滿了(寫入時)或 空了(讀取時),任務 可以選擇阻塞(等待消息) 或 非阻塞(立即返回)。
  • 高優先級任務更快地執行隊列操作,如果多個任務在相同優先級,按照 時間片輪轉 處理隊列。
  • 消息大小固定,但可以存儲指向可變數據的指針。

1.2 隊列相關 API 函數介紹

FreeRTOS 隊列管理 API 速覽

1. 隊列的創建 API
API 函數描述
xQueueCreate()動態創建隊列
xQueueCreateStatic()靜態創建隊列
2. 隊列消息的寫入 API
API 函數描述
xQueueSend()往隊列尾部寫入消息
xQueueSendToBack()xQueueSend(),往隊列尾部寫入消息
xQueueSendToFront()往隊列頭部寫入消息
xQueueOverwrite()覆寫隊列消息(僅限隊列長度為 1)
xQueueSendFromISR()在中斷中往隊列尾部寫入消息
xQueueSendToBackFromISR()xQueueSendFromISR(),往隊列尾部寫入
xQueueSendToFrontFromISR()在中斷中往隊列頭部寫入消息
xQueueOverwriteFromISR()在中斷中覆寫隊列消息(僅限隊列長度為 1)
3. 隊列消息的讀取 API
API 函數描述
xQueueReceive()從隊列頭部讀取消息,并刪除消息
xQueuePeek()從隊列頭部讀取消息(但不刪除)
xQueueReceiveFromISR()在中斷中從隊列頭部讀取消息,并刪除消息
xQueuePeekFromISR()在中斷中從隊列頭部讀取消息(但不刪除)

1.3 實驗

  • start_task:用來創建其他的 3 個任務。
  • task1:當按鍵 key1 或 key2 按下,將鍵值拷貝到隊列 queue1(入隊);當按鍵 key3 按下,將傳輸大數據,這里拷貝大數據的地址到隊列 big_queue 中。
  • task2:讀取隊列 queue1 中的消息(出隊),打印出接收到的鍵值。
  • task3:從隊列 big_queue 讀取大數據地址,通過地址訪問大數據。

1 ) 創建隊列程序:

QueueHandle_t queue1;    /* 小數據句柄 */
QueueHandle_t big_queue; /* 大數據句柄 */
char buff[100] = {"dioajdiaj fdahjk324hjkhfjksdahjk#$@!@#jfaskdfhjka"};void task_start(void *pvParameters)
{/* 進入臨界區:保護臨界區里的代碼不會被打斷 */taskENTER_CRITICAL();/* 創建 queue1 隊列 */queue1 = xQueueCreate(2, sizeof(uint8_t));if (queue1 != NULL){printf("queue1 隊列創建成功\r\n");}else{printf("queue1 隊列創建失敗\r\n");}/* 創建 big_queue 隊列 */big_queue = xQueueCreate(1, sizeof(char *));if (big_queue != NULL){printf("big_queue 隊列創建成功\r\n");}else{printf("big_queue 隊列創建失敗\r\n");}/* 創建三個任務 */xTaskCreate((TaskFunction_t)task1,(char *)"task1",(configSTACK_DEPTH_TYPE)TASK1_STACK_SIZE,(void *)NULL,(UBaseType_t)TASK1_PRIORITY,(TaskHandle_t *)&Task1_Handler);xTaskCreate((TaskFunction_t)task2,(char *)"task2",(configSTACK_DEPTH_TYPE)TASK2_STACK_SIZE,(void *)NULL,(UBaseType_t)TASK2_PRIORITY,(TaskHandle_t *)&Task2_Handler);xTaskCreate((TaskFunction_t)task3,(char *)"task3",(configSTACK_DEPTH_TYPE)TASK3_STACK_SIZE,(void *)NULL,(UBaseType_t)TASK3_PRIORITY,(TaskHandle_t *)&Task3_Handler);/* 啟動任務只需要執行一次即可,用完就刪除自己 */vTaskDelete(NULL);/* 退出臨界區 */taskEXIT_CRITICAL();
}

2 ) task1:

void task1(void *pvParameters)
{uint8_t key_value = 0;char *buf;BaseType_t err = 0;buf = &buff[0];char task_info[200];while (1){if (key[0].flag == 1 || key[1].flag == 1){key_value = key[0].flag;err = xQueueSendToBack(queue1, &key_value, portMAX_DELAY);if (err != pdTRUE){printf("queue1 隊列發送失敗\r\n");}key[0].flag = 0;key[1].flag = 0;}else if (key[2].flag == 1){err = xQueueSendToBack(big_queue, &buf, portMAX_DELAY);if (err != pdTRUE){printf("big_queue 隊列發送失敗\r\n");}key[2].flag = 0;}}
}

3 ) task2:

void task2(void *pvParameters)
{uint8_t key = 0;BaseType_t err = 0;while (1){err = xQueueReceive(queue1, &key, portMAX_DELAY);if (err != pdTRUE){printf("queue1 隊列讀取失敗\r\n");}else{printf("queue1 讀取隊列成功,數據:%d\r\n", key);}}
}

4 ) task3:

void task3(void *pvParameters)
{char *buf;BaseType_t err = 0;while (1){for (int i = 0; i < 16; i++){if (key[i].flag == 1){err = xQueueReceive(big_queue, &buf, portMAX_DELAY);if (err != pdTRUE){printf("big_queue 隊列讀取失敗\r\n");}else{printf("data:%s\r\n", buf);}}}}
}

2. FreeRTOS 隊列集

? 隊列集(Queue Set)是 FreeRTOS 中的一種數據結構,用于管理多個隊列。它提供了一種有效的方式,通過單個 API 調用來操作和訪問一組相關的隊列。在多任務系統中,任務之間可能需要共享數據,而這些數據可能存儲在不同的隊列中。隊列集的作用就是為了更方便地管理這些相關隊列,使得任務能夠輕松地訪問和處理多個隊列的數據。

2.1 隊列集相關 API 函數介紹

函數描述
xQueueCreateSet()創建隊列集,用于管理多個隊列或信號量
xQueueAddToSet()向隊列集添加隊列或信號量
xQueueRemoveFromSet()從隊列集中移除隊列或信號量
xQueueSelectFromSet()獲取隊列集中有消息的隊列(阻塞)
xQueueSelectFromSetFromISR()在中斷中獲取隊列集中有消息的隊列(非阻塞)

2.2 實驗

  • start_task:用來創建其他 2 個任務,并創建隊列集、倆個隊列,將這倆個隊列添加到隊列集中。
  • task1:用于掃描按鍵,當 KEY1 按下,往隊列1中寫入數據,當 KEY2 按下,往隊列2中寫入數據。
  • task2:讀取隊列集中的消息,并打印。

1 ) 創建隊列集

QueueHandle_t queue1, queue2;
QueueSetHandle_t queue_set;void task_start(void *pvParameters)
{/* 進入臨界區:保護臨界區里的代碼不會被打斷 */taskENTER_CRITICAL();/* 創建 queue1 隊列 */queue1 = xQueueCreate(1, sizeof(uint8_t));if (queue1 != NULL){printf("queue1 隊列創建成功\r\n");}else{printf("queue1 隊列創建失敗\r\n");}/* 創建 queue2 隊列 */queue2 = xQueueCreate(1, sizeof(char *));if (queue2 != NULL){printf("queue2 隊列創建成功\r\n");}else{printf("queue2 隊列創建失敗\r\n");}/* 創建 queue2 隊列 */queue_set = xQueueCreateSet(2);if (queue_set != NULL){printf("queue_set 隊列創建成功\r\n");}else{printf("queue_set 隊列創建失敗\r\n");}xQueueAddToSet(queue1, queue_set);xQueueAddToSet(queue2, queue_set);/* 創建任務 */xTaskCreate((TaskFunction_t)task1,(char *)"task1",(configSTACK_DEPTH_TYPE)TASK1_STACK_SIZE,(void *)NULL,(UBaseType_t)TASK1_PRIORITY,(TaskHandle_t *)&Task1_Handler);xTaskCreate((TaskFunction_t)task2,(char *)"task2",(configSTACK_DEPTH_TYPE)TASK2_STACK_SIZE,(void *)NULL,(UBaseType_t)TASK2_PRIORITY,(TaskHandle_t *)&Task2_Handler);/* 啟動任務只需要執行一次即可,用完就刪除自己 */vTaskDelete(NULL);/* 退出臨界區 */taskEXIT_CRITICAL();
}

2 ) task1:

void task1(void *pvParameters)
{char *buf;char text[20];BaseType_t err = 0;while (1){if (key[0].flag == 1){sprintf((char *)text, "queue1 數據");buf = &text[0];err = xQueueSendToBack(queue1, &buf, portMAX_DELAY);if (err != pdTRUE){printf("queue1 隊列寫入數據錯誤\r\n");}key[0].flag = 0;}else if (key[1].flag == 1){sprintf((char *)text, "queue2 數據");buf = &text[0];err = xQueueSendToBack(queue2, &buf, portMAX_DELAY);if (err != pdTRUE){printf("queue2 隊列寫入數據錯誤\r\n");}key[1].flag = 0;}vTaskDelay(100);}
}

3 ) task2:

void task2(void *pvParameters)
{char *buf;QueueSetMemberHandle_t queue = NULL;while (1){queue = xQueueSelectFromSet(queue_set, portMAX_DELAY);if (queue == queue1){xQueueReceive(queue, &buf, portMAX_DELAY);printf("queue1 讀取到數據為: %s\r\n", buf);}if (queue == queue2){xQueueReceive(queue, &buf, portMAX_DELAY);printf("queue2 讀取到數據為: %s\r\n", buf);}vTaskDelay(100);}
}

3. FreeRTOS 信號量

3.1 簡介

信號量(Semaphore)是一種用于 任務同步任務間資源互斥 的機制,主要用于:

  • 任務同步:協調任務之間的執行順序,如等待某個事件發生后再執行任務。
  • 互斥訪問:保護共享資源,防止多個任務同時訪問而引起競爭問題。

FreeRTOS 提供了 二值信號量計數信號量互斥信號量 三種類型:

信號量類型作用
二值信號量(Binary Semaphore)只能取 0 或 1,常用于任務同步或簡單的互斥訪問
計數信號量(Counting Semaphore)可取多個值,適用于多個資源的同步管理
互斥信號量(Mutex)具備 優先級繼承 機制,主要用于任務間的互斥訪問

3.2 常用 API 函數

API 函數作用
xSemaphoreCreateBinary()創建二值信號量(初始值為 0,需要 xSemaphoreGive() 釋放一次后才能使用)
xSemaphoreCreateBinaryStatic()使用靜態方式創建二值信號量
xSemaphoreCreateCounting()創建計數信號量
xSemaphoreCreateMutex()創建互斥信號量
xSemaphoreGive()釋放信號量(+1)
xSemaphoreTake()獲取信號量(-1),如果沒有可用信號量則阻塞
uxSemaphoreGetCount()獲取信號量的計數值
xSemaphoreGiveFromISR()在中斷中釋放信號量
xSemaphoreTakeFromISR()在中斷中獲取信號量

? 信號量 API 函數允許指定阻塞時間。 阻塞時間表示當一個任務試圖“獲取”信號量時,如果信號不是立即可用,那么該任務進入阻塞狀態的最大 “tick” 數。 如果多個任務在同一個信號量上阻塞,那么具有最高優先級的任務將在下次信號量可用時最先解除阻塞

在 FreeRTOS 中,信號量 實際上是一個特殊的隊列,本質上 創建信號量就是創建一個只存儲一個元素的隊列,區別在于:

  • 二值信號量(Binary Semaphore):隊列長度固定為 1,每次只能存儲 一個項目,用于 任務同步
  • 計數信號量(Counting Semaphore):隊列長度可以大于 1,用于 多個資源的管理
  • 互斥信號量(Mutex):基于二值信號量,但增加了 優先級繼承 機制,用于資源互斥

對于二值信號量 FreeRTOS 通過 xQueueGenericCreate() 這個通用隊列創建函數來創建信號量,但是這個函數在 semphr.h 被宏定義為:

/*
(UBaseType_t) 1 → 隊列長度 = 1(二值信號量只能存儲一個信號量)
semSEMAPHORE_QUEUE_ITEM_LENGTH → 項目大小 = 0(不存儲實際數據,僅表示可用信號量)
queueQUEUE_TYPE_BINARY_SEMAPHORE → 隊列類型 = 信號量隊列
*/
#define xSemaphoreCreateBinary()    xQueueGenericCreate( ( UBaseType_t ) 1, semSEMAPHORE_QUEUE_ITEM_LENGTH, queueQUEUE_TYPE_BINARY_SEMAPHORE )

信號量的作用本質是管理這個隊列是否為空。

而計數信號量創建函數等效為:

/*
uxMaxCount  → 隊列長度 (計數信號量可以為任何整數)
queueSEMAPHORE_QUEUE_ITEM_LENGTH → 項目大小 = 0(不存儲實際數據,僅表示可用信號量)
queueQUEUE_TYPE_BINARY_SEMAPHORE → 隊列類型 = 信號量隊列
*/
xQueueGenericCreate( uxMaxCount, queueSEMAPHORE_QUEUE_ITEM_LENGTH, queueQUEUE_TYPE_COUNTING_SEMAPHORE );

互斥信號量創建函數等效為:

/*
uxMutexLength 1  → 隊列長度 = 1(互斥信號量只能存儲一個信號量)
uxMutexSize → 項目大小 = 0(不存儲實際數據,僅表示可用信號量)
queueQUEUE_TYPE_MUTEX → 隊列類型 = 信號量隊列
*/
xQueueGenericCreate( uxMutexLength, uxMutexSize, queueQUEUE_TYPE_MUTEX )

3.3 實驗

3.3.1 二值信號量實驗

  • start_task:用來創建其他的 2 個任務。
  • task1:用于按鍵掃描,當檢測到按鍵 KEY1 被按下時,釋放二值信號量。
  • task2:獲取二值信號量,當成功獲取后打印提示信息。

1 ) 創建二值信號量:

QueueHandle_t semaphore_handle;void task_start(void *pvParameters)
{/* 進入臨界區:保護臨界區里的代碼不會被打斷 */taskENTER_CRITICAL();/* 創建信號量 */semaphore_handle = xSemaphoreCreateBinary();if (semaphore_handle != NULL){printf("二值信號量創建成功\r\n");}else{printf("二值信號量創建失敗\r\n");}/* 創建任務 */xTaskCreate((TaskFunction_t)task1,(char *)"task1",(configSTACK_DEPTH_TYPE)TASK1_STACK_SIZE,(void *)NULL,(UBaseType_t)TASK1_PRIORITY,(TaskHandle_t *)&Task1_Handler);xTaskCreate((TaskFunction_t)task2,(char *)"task2",(configSTACK_DEPTH_TYPE)TASK2_STACK_SIZE,(void *)NULL,(UBaseType_t)TASK2_PRIORITY,(TaskHandle_t *)&Task2_Handler);/* 啟動任務只需要執行一次即可,用完就刪除自己 */vTaskDelete(NULL);/* 退出臨界區 */taskEXIT_CRITICAL();
}

2 ) task1:

void task1(void *pvParameters)
{BaseType_t err;while (1){if (key[0].flag == 1){err = xSemaphoreGive(semaphore_handle);if (err != pdTRUE){printf("釋放信號量失敗\r\n");}led[0].state = !led[0].state;HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13, (GPIO_PinState)led[0].state);key[0].flag = 0;}}
}

3 ) task2:

void task2(void *pvParameters)
{BaseType_t err = 0;while (1){err = xSemaphoreTake(semaphore_handle,portMAX_DELAY);if (err != pdTRUE){printf("獲取信號量失敗\r\n");}else{printf("成功獲取信號量\r\n");}}
}

3.3 .2 計數信號量實驗

  • start_task:用來創建其他的 2 個任務。
  • task1:用于按鍵掃描,當檢測到按鍵 KEY1 被按下時,釋放計數型信號量。
  • task2:每過一秒獲取一次計數型信號量,當成功獲取后打印信號量計數值。

1 ) 創建計數信號量:

QueueHandle_t semaphore_handle;void task_start(void *pvParameters)
{/* 進入臨界區:保護臨界區里的代碼不會被打斷 */taskENTER_CRITICAL();/* 創建計數信號量 */UBaseType_t uxMaxCount = 4;UBaseType_t uxInitialCount = 0;semaphore_handle = xSemaphoreCreateCounting(uxMaxCount,uxInitialCount);if (semaphore_handle != NULL){printf("計數信號量創建成功\r\n");}else{printf("計數信號量創建失敗\r\n");}/* 創建任務 */xTaskCreate((TaskFunction_t)task1,(char *)"task1",(configSTACK_DEPTH_TYPE)TASK1_STACK_SIZE,(void *)NULL,(UBaseType_t)TASK1_PRIORITY,(TaskHandle_t *)&Task1_Handler);xTaskCreate((TaskFunction_t)task2,(char *)"task2",(configSTACK_DEPTH_TYPE)TASK2_STACK_SIZE,(void *)NULL,(UBaseType_t)TASK2_PRIORITY,(TaskHandle_t *)&Task2_Handler);/* 啟動任務只需要執行一次即可,用完就刪除自己 */vTaskDelete(NULL);/* 退出臨界區 */taskEXIT_CRITICAL();
}

2 ) task1:

void task1(void *pvParameters)
{BaseType_t err;while (1){if (key[0].flag == 1){err = xSemaphoreGive(semaphore_handle);if (err != pdTRUE){printf("釋放信號量失敗\r\n");}else{printf("釋放信號量\r\n");}led[0].state = !led[0].state;HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13, (GPIO_PinState)led[0].state);key[0].flag = 0;}}
}

3 ) task2:

void task2(void *pvParameters)
{BaseType_t err = 0;while (1){if(uxSemaphoreGetCount(semaphore_handle) != 0){err = xSemaphoreTake(semaphore_handle,portMAX_DELAY);if (err != pdTRUE){printf("獲取信號量失敗\r\n");}else{printf("成功獲取信號量\r\n");}}else{printf("無空閑資源分配\r\n");}vTaskDelay(1000);}
}

3.4 任務優先級翻轉 bug 問題

? 優先級翻轉是一個在實時系統中可能出現的問題,特別是在多任務環境中。該問題指的是一個較低優先級的任務阻塞了一個較高優先級任務的執行,從而導致高優先級任務無法及時完成。

典型的優先級翻轉場景如下:

  • 任務 A(高優先級):擁有高優先級,需要訪問共享資源,比如一個關鍵數據結構。
  • 任務 B(低優先級):擁有低優先級,目前正在訪問該共享資源。
  • 任務 C(中優先級):位于任務 A 和任務 B 之間,具有介于兩者之間的優先級。

具體流程如下:

(1)任務 A 開始執行,但由于任務 B 正在訪問共享資源,任務 A 被阻塞等待。

(2)任務 C 獲得執行權,由于優先級高于任務 B,它可以搶占任務 B。

(3)任務 C 執行完成后,任務 B 被解除阻塞,開始執行,完成后釋放了共享資源。

(4)任務 A 重新獲取執行權,繼續執行。

? 這個過程中,任務 A 因為資源被占用而被阻塞,而任務 B 卻被中優先級的任務 C 搶占,導致任務 B 無法及時完成。這種情況稱為優先級翻轉,因為任務 C 的介入翻轉了高優先級任務 A 的執行順序。
請添加圖片描述

實驗模擬

模擬優先級翻轉,觀察對搶占式內核的影響:

  • start_task:用來創建其他的 3 個任務。
  • task1:低優先級任務,同高優先級一樣的操作,不同的是低優先級任務占用信號
  • task2:中等優先級任務,簡單的應用任務。
  • task3:高優先級任務,會獲取二值信號量,獲取成功以后打印提示信息,處理完后釋放信號量。

1 ) 創建隊列程序:

QueueHandle_t semaphore_handle;/*** @brief : 啟動任務函數,創建三個任務,并刪除自己** @param pvParameters*/
void task_start(void *pvParameters)
{/* 進入臨界區:保護臨界區里的代碼不會被打斷 */taskENTER_CRITICAL();/* 創建計數信號量 */UBaseType_t uxMaxCount = 1;UBaseType_t uxInitialCount = 1;semaphore_handle = xSemaphoreCreateCounting(uxMaxCount, uxInitialCount);if (semaphore_handle != NULL){printf("計數信號量創建成功\r\n");}else{printf("計數信號量創建失敗\r\n");}/* 創建三個任務 */xTaskCreate((TaskFunction_t)task1,(char *)"task1",(configSTACK_DEPTH_TYPE)TASK1_STACK_SIZE,(void *)NULL,(UBaseType_t)TASK1_PRIORITY,(TaskHandle_t *)&Task1_Handler);xTaskCreate((TaskFunction_t)task2,(char *)"task2",(configSTACK_DEPTH_TYPE)TASK2_STACK_SIZE,(void *)NULL,(UBaseType_t)TASK2_PRIORITY,(TaskHandle_t *)&Task2_Handler);xTaskCreate((TaskFunction_t)task3,(char *)"task3",(configSTACK_DEPTH_TYPE)TASK3_STACK_SIZE,(void *)NULL,(UBaseType_t)TASK3_PRIORITY,(TaskHandle_t *)&Task3_Handler);/* 啟動任務只需要執行一次即可,用完就刪除自己 */vTaskDelete(NULL);/* 退出臨界區 */taskEXIT_CRITICAL();
}

2 ) task1:

void task1(void *pvParameters)
{while (1){printf("低優先級 Task1 獲取信號量\r\n");xSemaphoreTake(semaphore_handle, portMAX_DELAY);printf("低優先級 Task1 正在運行\r\n");HAL_Delay(3000);printf("低優先級 Task1 釋放信號量\r\n");xSemaphoreGive(semaphore_handle);vTaskDelay(1000);}
}

3 ) task2:

void task2(void *pvParameters)
{while (1){printf("中優先級的 Task2 正在執行\r\n");HAL_Delay(1500);printf("Task2 執行完成一次.....\r\n");vTaskDelay(1000);}
}

4 ) task3:

void task3(void *pvParameters)
{while (1){printf("高優先級 Task3 獲取信號量\r\n");xSemaphoreTake(semaphore_handle, portMAX_DELAY);printf("高優先級 Task3 正在運行\r\n");HAL_Delay(1000);printf("高優先級 Task3 釋放信號量\r\n");xSemaphoreGive(semaphore_handle);vTaskDelay(1000);}
}

5 ) 實驗結果

請添加圖片描述

可以觀察到 Task1 獲取信號量后,Task2 通過搶占 Task1 實現了翻轉優先級,實現了優于 Task3 的執行優先。

3.5 互斥信號量

? 互斥信號量是包含優先級繼承機制的二進制信號量。二進制信號量能更好實現同步(任務間或任務與中斷之間), 而互斥信號量有助于更好實現簡單互斥(即相互排斥)。優先級繼承是一種解決實時系統中任務調度引起的優先級翻轉問題的機制。在具體的任務調度中,當一個高優先級任務等待一個低優先級任務所持有的資源時,系統會提升低優先級任務的優先級,以避免高優先級任務長時間等待的情況。
請添加圖片描述

互斥信號量的獲取和釋放函數與二值信號量的相應函數相似,但有一個重要的區別:互斥信號量不支持在中斷服務程序中直接調用。注意,當創建互斥信號量時,系統會自動進行一次信號量的釋放操作。

通過互斥信號量來解決優先級翻轉實驗

1 ) 將二值信號量改為互斥信號量:

    semaphore_handle = xSemaphoreCreateMutex();if (semaphore_handle != NULL){printf("互斥信號量創建成功\r\n");}else{printf("互斥信號量創建失敗\r\n");}

2 ) 實驗結果:

可以觀察到 Task2 只能在 Task3 信號量釋放的后才可以搶占運行,不會發生任務優先級的翻轉(在 Task1 釋放信號量后 Task2 立馬搶占執行,而是 Task3 立刻占用信號量,開始執行 Task3 再執行 Task2)
請添加圖片描述

4. FreeRTOS 事件標志組

4.1 簡介

? 事件標志組(Event Groups)是 FreeRTOS 提供的一種輕量級任務間同步機制,允許任務或中斷通過 設置或清除 “事件標志(Event Bits)” 來實現 事件通知、同步和狀態監控。事件標志組類似于 二進制標志位集合,每個標志位可以單獨操作,多個任務可以等待多個標志位滿足特定條件后再執行。

事件標志組特點
  • 每個事件標志組是一個 8/24 位獨立的二進制標志位(Event Bits)。
  • 任務可以等待多個事件標志位,并指定所有滿足或任意一個滿足時觸發任務執行。
  • 事件標志位可由任務或中斷設置/清除,支持 ISR 操作。
  • 比隊列(Queue)和信號量(Semaphore)更高效,適用于簡單事件同步。
事件標志組的適用場景
應用場景適合事件標志組適合隊列或信號量
多任務同步?? 多任務需要等待某些事件發生?
中斷通知任務?? 事件觸發后可直接通知多個任務?? 但隊列或信號量只能通知一個任務
狀態監測?? 可使用不同事件位表示不同狀態?
任務間數據傳輸??? 隊列適合傳輸數據

? 事件標志組和信號量都是 FreeRTOS 中用于任務同步和通信的機制,但它們適用于不同的場景,主要區別如下:

對比項事件標志組(Event Flags Group)信號量(Semaphore)
用途用于任務間事件通知和同步,標志位代表某個事件狀態用于任務間資源控制和同步,確保安全訪問共享資源
狀態表示每個標志位只有 已設置/未設置 兩種狀態信號量是一個計數器,可用于 計數、互斥、同步
任務等待任務可等待 多個事件同時滿足任意一個事件發生任務等待信號量計數變為 非零,然后繼續執行
適用場景適用于任務間 事件同步,如 數據準備完成、狀態變更通知適用于 資源訪問控制、同步、互斥,防止多個任務同時訪問共享資源
信號傳遞可存儲多個事件狀態,即使任務不在等待,事件狀態仍然保留不可存儲狀態,如果任務未在等待,信號量會直接丟失
示例任務 A 設置事件標志 → 任務 B 等待該事件標志并繼續執行任務 A 釋放信號量 → 任務 B 獲取信號量并訪問共享資源

4.2 事件標志組和事件位數據類型

? 可以將事件組視為一個二進制標志集合,其中的每一位表示一個事件。任務可以設置(Set)、清(Clear)或等待(Wait)某些特定的事件位,從而實現任務間的 同步與信號觸發。事件組的大小(即支持的 事件位數)受 FreeRTOS 配置項的影響,由 configUSE_16_BIT_TICKS 控制:

#define configUSE_16_BIT_TICKS 	1 // 事件組內可用標志數位為8
#define configUSE_16_BIT_TICKS 	0 // 事件組內可用標志數位為24

? 在 FreeRTOS 中,事件組的最大位數EventBits_t 變量的大小決定,而 EventBits_t 的大小受 TickType_t(用于計時的變量類型)影響。所以最高為32位,但是 FreeRTOS 保留了 8 位 供系統內部使用

4.3 事件標志組相關 API 函數介紹

API 函數功能
xEventGroupCreate()創建事件標志組
xEventGroupCreateStatic()使用靜態地創建事件標志組
xEventGroupSetBits()設置一個或多個事件標志位
xEventGroupClearBits()清除一個或多個事件標志位
xEventGroupWaitBits()等待一個或多個事件標志位被置位
xEventGroupGetBits()獲取當前事件標志狀態
xEventGroupSetBitsFromISR()在中斷中設置事件標志位
vEventGroupDelete()刪除事件標志組
xEventGroupSync()多個任務同步等待所有事件標志

4.4 實驗

  • start_task:用來創建其他 3 個任務,并創建事件標志組。
  • task1:讀取按鍵按下鍵值,根據不同鍵值將事件標志組相應事件位置一,模擬事件發生。
  • task2:同時等待事件標志組中的多個事件位,當這些事件位都置 1 的話就執行相應的處理。
  • task3:等待事件標志組中的多個任一事件位,當這些事件位任意置 1 的話就執行相應的處理。

1 ) 創建事件標志組程序:

EventGroupHandle_t event_group;/*** @brief : 啟動任務函數,創建三個任務,并刪除自己** @param pvParameters*/
void task_start(void *pvParameters)
{/* 進入臨界區:保護臨界區里的代碼不會被打斷 */taskENTER_CRITICAL();/* 創建事件標志組 */event_group = xEventGroupCreate();if (event_group == NULL){printf("事件標志組創建失敗\r\n");}else{printf("事件標志組創建成功\r\n");}/* 創建任務 */xTaskCreate((TaskFunction_t)task1,(char *)"task1",(configSTACK_DEPTH_TYPE)TASK1_STACK_SIZE,(void *)NULL,(UBaseType_t)TASK1_PRIORITY,(TaskHandle_t *)&Task1_Handler);xTaskCreate((TaskFunction_t)task2,(char *)"task2",(configSTACK_DEPTH_TYPE)TASK2_STACK_SIZE,(void *)NULL,(UBaseType_t)TASK2_PRIORITY,(TaskHandle_t *)&Task2_Handler);xTaskCreate((TaskFunction_t)task3,(char *)"task3",(configSTACK_DEPTH_TYPE)TASK3_STACK_SIZE,(void *)NULL,(UBaseType_t)TASK3_PRIORITY,(TaskHandle_t *)&Task3_Handler);/* 啟動任務只需要執行一次即可,用完就刪除自己 */vTaskDelete(NULL);/* 退出臨界區 */taskEXIT_CRITICAL();
}

2 ) task1:

void task1(void *pvParameters)
{while (1){if (key[0].flag == 1){xEventGroupSetBits(event_group, 0x01 << 0);printf("事件0置位\r\n");key[0].flag = 0;}else if (key[1].flag == 1){xEventGroupSetBits(event_group, 0x01 << 1);printf("事件1置位\r\n");key[1].flag = 0;}else if (key[2].flag == 1){xEventGroupSetBits(event_group, 0x01 << 2);printf("事件2置位\r\n");key[2].flag = 0;}else if (key[3].flag == 1){xEventGroupSetBits(event_group, 0x01 << 3);printf("事件3置位\r\n");key[3].flag = 0;}vTaskDelay(100);}
}

3 ) task2:

void task2(void *pvParameters)
{while (1){xEventGroupWaitBits(event_group, 1 << 0 | 1 << 1, pdTRUE, pdTRUE, portMAX_DELAY);printf("事件0和1同時有效,執行task2...\r\n");vTaskDelay(100);}
}

4 ) task3:

void task3(void *pvParameters)
{while (1){xEventGroupWaitBits(event_group, 1 << 2 | 1 << 3, pdTRUE, pdFALSE, portMAX_DELAY);printf("事件2和3任一有效,執行task3...\r\n");vTaskDelay(100);}
}

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/web/71576.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/71576.shtml
英文地址,請注明出處:http://en.pswp.cn/web/71576.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

【虛擬化】Docker Desktop 架構簡介

在閱讀前您需要了解 docker 架構&#xff1a;Docker architecture WSL 技術&#xff1a;什么是 WSL 2 1.Hyper-V backend 我們知道&#xff0c;Docker Desktop 最開始的架構的后端是采用的 Hyper-V。 Docker daemon (dockerd) 運行在一個 Linux distro (LinuxKit build) 中&…

Unity光照之Halo組件

簡介 Halo 組件 是一種用于在游戲中創建光暈效果的工具&#xff0c;主要用于模擬光源周圍的發光區域&#xff08;如太陽、燈泡等&#xff09;或物體表面的光線反射擴散效果。 核心功能 1.光暈生成 Halo 組件會在光源或物體的周圍生成一個圓形光暈&#xff0c;模擬光線在空氣…

Flink深入淺出之01:應用場景、基本架構、部署模式

Flink 1?? 一 、知識要點 &#x1f4d6; 1. Flink簡介 Apache Flink — Stateful Computations over Data StreamsApache Flink 是一個分布式大數據處理引擎&#xff0c;可對有界數據流和無界數據流進行有狀態的計算。Flink 能在所有常見集群環境中運行&#xff0c;并能以…

2025年【高壓電工】報名考試及高壓電工考試總結

隨著電力行業的快速發展&#xff0c;高壓電工成為確保電力系統安全穩定運行的重要一環。為了提高高壓電工的專業技能和安全意識&#xff0c;“安全生產模擬考試一點通”平臺特別整理了2025年高壓電工報名考試的相關信息及考試總結&#xff0c;并提供了一套完整的題庫&#xff0…

網絡HTTP

HTTP Network Request Library A Retrofit-based HTTP network request encapsulation library that provides simple and easy-to-use API interfaces with complete network request functionality. 基于Retrofit的HTTP網絡請求封裝庫&#xff0c;提供簡單易用的API接口和完…

os-copilot安裝和使用體驗測評

簡介&#xff1a; OS Copilot是阿里云基于大模型構建的Linux系統智能助手&#xff0c;支持自然語言問答、命令執行和系統運維調優。本文介紹其產品優勢、功能及使用方法&#xff0c;并分享個人開發者在云服務器資源管理中的實際應用體驗。通過-t/-f/管道功能&#xff0c;OS Cop…

Python Flask框架學習匯編

1、入門級&#xff1a; 《Python Flask Web 框架入門》 這篇博文條理清晰&#xff0c;由簡入繁&#xff0c;案例豐富&#xff0c;分十五節詳細講解了Flask框架&#xff0c;強烈推薦&#xff01; 《python的簡單web框架flask【附例子】》 講解的特別清楚&#xff0c;每一步都…

【HarmonyOS Next之旅】DevEco Studio使用指南(一)

目錄 1 -> 工具簡介 1.1 -> 概述 1.2 -> HarmonyOS應用/服務開發流程 1.2.1 -> 開發準備 1.2.2 -> 開發應用/服務 1.2.3 -> 運行、調試和測試應用/服務 1.2.4 -> 發布應用/服務 2 -> 工程介紹 2.1 -> APP包結構 2.2 -> 切換工程視圖 …

Manus開源平替-開源通用智能體

原文鏈接:https://i68.ltd/notes/posts/250306-opensource-agi-agent/ OWL-比Manus還強的全能開源Agent OWL: Optimized Workforce Learning for General Multi-Agent Assistance in Real-World Task Automation&#xff0c;現實世界中執行自動化任務的通用多代理輔助優化學習…

【3.2-3.8學習周報】

提示&#xff1a;文章寫完后&#xff0c;目錄可以自動生成&#xff0c;如何生成可參考右邊的幫助文檔 文章目錄 摘要Abstract一、方法介紹1.任務適應性持續預訓練&#xff08;TACP&#xff09;2.領域自適應連續預訓練&#xff08;DACP&#xff09;3.ETS-DACP和ETA-DACP 二、實驗…

【Linux】用戶和組

思考 使用useradd在Linux下面創建一個用戶&#xff0c;默認情況下&#xff0c;會自動創建一個同名組&#xff0c;并且加入其中&#xff0c;那么是先創建用戶呢&#xff1f;還是先創建組呢&#xff1f; 很簡單&#xff0c;我們實踐一下不就知道了&#xff0c;如下所示&#xff…

新編大學應用英語綜合教程2 U校園全套參考答案

全套答案獲取&#xff1a; 鏈接&#xff1a;https://pan.quark.cn/s/389618f53143

SAP 顧問的五年職業規劃

SAP 顧問的職業發展受到技術進步、企業需求變化和全球經濟環境的影響&#xff0c;因此制定長遠規劃充滿挑戰。面對 SAP 產品路線圖的不確定性&#xff0c;如向 S/4HANA 和 Business Technology Platform (BTP) 的轉變&#xff0c;顧問必須具備靈活性&#xff0c;以保持競爭力和…

圖像生成-ICCV2019-SinGAN: Learning a Generative Model from a Single Natural Image

圖像生成-ICCV2019-SinGAN: Learning a Generative Model from a Single Natural Image 文章目錄 圖像生成-ICCV2019-SinGAN: Learning a Generative Model from a Single Natural Image主要創新點模型架構圖生成器生成器源碼 判別器判別器源碼 損失函數需要源碼講解的私信我 S…

Networking Based ISAC Hardware Testbed and Performance Evaluation

文章目錄 Applications and Challenges of Networked SensingCooperation Mechanism in Networked SensingChallenges and Key Enabling Technologies 5G NR Frame Structure Based ISAC ApproachSignals Available for Radio SensingMulti-Dimensiona Resource Optimization S…

2025年主流原型工具測評:墨刀、Axure、Figma、Sketch

2025年主流原型工具測評&#xff1a;墨刀、Axure、Figma、Sketch 要說2025年國內產品經理使用的主流原型設計工具&#xff0c;當然是墨刀、Axure、Figma和Sketch了&#xff0c;但是很多剛入行的產品經理不了解自己適合哪些工具&#xff0c;本文將從核心優勢、局限短板、協作能…

我代表中國受邀在亞馬遜云科技全球云計算大會re:Invent中技術演講

大家好我是小李哥&#xff0c;本名叫李少奕&#xff0c;目前在一家金融行業公司擔任首席云計算工程師。去年5月很榮幸在全球千萬名開發者中被選為了全球亞馬遜云科技認證技術專家&#xff08;AWS Hero&#xff09;&#xff0c;是近10年來大陸地區僅有的第9名大陸專家。同時作為…

LeetCode 解題思路 12(Hot 100)

解題思路&#xff1a; 定義三個指針&#xff1a; prev&#xff08;前驅節點&#xff09;、current&#xff08;當前節點&#xff09;、nextNode&#xff08;臨時保存下一個節點&#xff09;遍歷鏈表&#xff1a; 每次將 current.next 指向 prev&#xff0c;移動指針直到 curre…

Ubuntu搭建最簡單WEB服務器

安裝apache2 sudo apt install apache2 檢查狀態 $ sudo systemctl status apache2 ● apache2.service - The Apache HTTP ServerLoaded: loaded (/lib/systemd/system/apache2.service; enabled; vendor prese>Active: active (running) since Thu 2025-03-06 09:51:10…

Linux 軟硬鏈接

目錄 軟硬鏈接 軟鏈接 硬鏈接 軟硬鏈接的區別 硬鏈接場景 軟連接場景 軟硬鏈接 軟鏈接 我們可以通過以下命令創建一個文件的軟連接 ln -s mytest softlink-mytest 通過 ls -i -l 命令我們可以看到&#xff0c;軟鏈接文件的inode號與源文件的inode號是不同的&#xff0c…