摘要(From AI):
这篇博客详细介绍了 FreeRTOS 中的事件组和任务通知机制,讲解了事件组如何通过位操作实现任务间的同步与通信,以及任务如何通过通知机制进行阻塞解除和数据传递。博客提供了多个代码示例,展示了如何使用事件组和任务通知在多任务环境中实现任务同步,特别适用于任务间的依赖关系和信号传递
前言:本文档是本人在依照B站UP:Michael_ee的视频教程进行学习时所做的学习笔记,可能存在疏漏和错误,如有发现,敬请指正。
文章目录
- Event Group
- Event Group Wait
- xEventGroupCreate()
- xEventGroupSetBits()
- xEventGroupWaitBits()
- Example Code:Event Group Synchronization with Multiple Tasks
- Event Group Sync
- xEventGroupSync()
- Example Code:Event Group Synchronization
- Notification
- Notification Sync
- xTaskNotifyGive()
- ulTaskNotifyTake()
- Example Code:Simple Task Notification in FreeRTOS
- Notification Value
- xTaskNotify()
- xTaskNotifyWait()
- Example Code:Task Notification with Conditional Actions Based on Values
参考资料
Michael_ee 视频教程
freeRTOS官网
espressif 在线文档
Event Group
事件组是一种同步机制,用于任务之间的通信。它们允许任务设置、清除和等待多个事件的组合
每个事件组有多个位,任务可以操作这些位来表示不同的状态或事件。
关键功能
位操作
事件组可以被看作是一个二进制位的集合,任务可以对这些位进行设置、清除和等待同步机制
任务可以等待事件组中的某些位变为设定状态(例如,位为1),这样可以使任务在等待某些事件发生时暂停执行,直到事件发生多任务通信
事件组可以在多个任务之间传递信息
使用场景
- 在多个任务之间传递控制信号或数据标志
- 实现任务之间的依赖关系,如任务A完成某项工作后,任务B才可以执行
Event Group Wait
xEventGroupCreate()
创建一个新的事件组,并返回可以引用创建的事件组的句柄
事件组包含的标志位(或位)的数量依赖于 configUSE_16_BIT_TICKS
配置项
-
如果
configUSE_16_BIT_TICKS = 1
,则事件组有 8 位(标志位) -
如果
configUSE_16_BIT_TICKS = 0
,则事件组有 24 位(标志位) -
配置文件路径(v5.3.1)
idf\v5.3.1\esp-idf\components\freertos\config\include\freertos
#include "FreeRTOS.h"
#include "event_groups.h"EventGroupHandle_t xEventGroupCreate( void );
返回值
EventGroupHandle_t
创建了事件组,返回的值是创建的事件组的句柄
NULL
无法创建事件组,因为可用的 FreeRTOS 堆内存不足
xEventGroupSetBits()
在RTOS事件组中设置位
- 这个函数不能从中断中调用
#include "FreeRTOS.h"
#include "event_groups.h"EventBits_t xEventGroupSetBits( EventGroupHandle_t xEventGroup,const EventBits_t uxBitsToSet );
参数
xEventGroup
需要设置 bit 的事件组
uxBitsToSet
一个按位的值,表示要在事件组中设置的一个或多个位
-
通过设置不同的二进制值来指定要等待的位
- 如果想等待 bit 0 和 bit 2 设置,
uxBitsToWaitFor
应该是0x05
(即00000101
) - 如果想等待 bit 0、bit 1 和 bit 2 设置,
uxBitsToWaitFor
应该是0x07
(即00000111
)
- 如果想等待 bit 0 和 bit 2 设置,
-
可以根据需求组合多个位来构造不同的掩码值
返回值
EventBits_t
事件组中各位(bits)在调用 xEventGroupSetBits()
函数返回时的状态
可能被改变状态的情况
-
自动清除(
xClearBitsOnExit
参数- 当调用
xEventGroupSetBits()
设置位后,可能有任务正在等待这些位(通过xEventGroupWaitBits()
),如果等待任务设置了xClearBitsOnExit
参数为pdTRUE
,则这些位在任务被唤醒时会自动被清除,在xEventGroupSetBits()
返回时,返回值中的位可能已经被清除
- 当调用
-
高优先级 Task 清除位
- 如果设置事件位后,有更高优先级的任务因这些位的设置从阻塞状态切换为就绪状态(Ready),并立即执行,它可能会修改事件组的值
在 xEventGroupSetBits()
返回时,返回值可能反映的是任务执行后事件组的状态,而不是立即设置位后的状态
xEventGroupWaitBits()
读取RTOS事件组中的位,可选择进入阻塞状态(带超时)以等待一个位或一组位被设置
- 这个函数不能从中断中调用
#include "FreeRTOS.h"
#include "event_groups.h"EventBits_t xEventGroupWaitBits( const EventGroupHandle_t xEventGroup, const EventBits_t uxBitsToWaitFor, const BaseType_t xClearOnExit, const BaseType_t xWaitForAllBits, TickType_t xTicksToWait );
参数
xEventGroup
需要测试(查看)bit 的事件组
uxBitsToWaitFor
一个位运算值,用于指定在事件组中要等待的位
- 不能被设置为
0
xClearOnExit
设置是否清除事件
-
pdTRUE
- 如果设置为
pdTRUE
,那么在函数返回时,事件组中由 uxBitsToWaitFor
指定的那些位会被清除(即设置为 0),前提是函数返回的原因不是超时 - 这通常用于在检测到某些事件发生后,自动清除事件状态,避免其他任务误判这些事件仍然有效。
- 如果设置为
-
pdFALSE
- 如果设置为
pdFALSE
,事件组中的位不会被清除,即便函数成功返回。这种方式适用于需要让其他任务也能检测到这些位的场景
- 如果设置为
xWaitForAllBits
决定任务等待位时的逻辑条件是 逻辑 AND(等待所有指定的位都被设置)还是 逻辑 OR(只需等待任意一个指定的位被设置)
-
pdTRUE
(逻辑 AND)- 函数会等待事件组中的所有指定位都被设置为 1
- 如果所有位在等待时间内都被设置,函数返回
- 如果等待时间到期(
xTicksToWait
超时),函数返回超时结果
-
pdFALSE
(逻辑 OR)- 函数会等待事件组中的任意一个指定位被设置为 1
- 如果任意一位在等待时间内被设置,函数立即返回
- 如果等待时间到期且没有任何位被设置,函数返回超时结果
xTicksToWait
等待一个/全部 bit 的最大时间
返回值
EventBits_t
事件组的当前值,这个值表示函数返回时,事件组中哪些位(bits)是被设置(1)的
Example Code:Event Group Synchronization with Multiple Tasks
#include <stdio.h>
#include <inttypes.h>
#include "sdkconfig.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_chip_info.h"
#include "esp_flash.h"
#include "esp_system.h"#include "freeRTOS/event_groups.h"EventGroupHandle_t eventGroup;#define BIT_0 (1 << 0)
#define BIT_4 (1 << 4)void Task1(void *pvParam)
{printf("Task1 is running\n");while (true){printf("Task1 is begin to wait\n");// xEventGroupWaitBits(eventGroup, BIT_0 | BIT_4, pdTRUE, pdFALSE, portMAX_DELAY);// // 检测第一位和第四位是否被设置,如果设置则唤醒Task1// // 检测完成后,第一位和第四位将被清除// printf("BIT_0 or BIT_4 is set, Task1 is woken up\n");xEventGroupWaitBits(eventGroup, BIT_0 | BIT_4, pdTRUE, pdTRUE, portMAX_DELAY);printf("BIT_0 or BIT_4 is set, Task1 is woken up\n");vTaskDelay(pdMS_TO_TICKS(1000));}
}void Task2(void *pvParam)
{printf("Task2 is running\n");vTaskDelay(pdMS_TO_TICKS(1000));while (true){printf("Task2 is begin to set bit0\n");xEventGroupSetBits(eventGroup, BIT_0);vTaskDelay(pdMS_TO_TICKS(5000));printf("Task2 is begin to set bit4\n");xEventGroupSetBits(eventGroup, BIT_4);vTaskDelay(pdMS_TO_TICKS(5000));}
}void app_main(void)
{eventGroup = xEventGroupCreate(); // 创建事件组if (eventGroup == NULL){printf("Event group creation failed\n");}else{vTaskSuspendAll();xTaskCreatePinnedToCore(Task1, "Task1", 2048, NULL, 1, NULL, 0);xTaskCreatePinnedToCore(Task2, "Task2", 2048, NULL, 1, NULL, 0);xTaskResumeAll();}
}
Event Group Sync
wait
和sync
的不同:
wait
等待事件组的 Task(设为 waitTask) 在进入 wait 状态后,等待设置事件组的 Task(设为 setTask)对事件组进行设置,waitTask 在检测到事件组满足要求后继续运行,setTask 在调用xEventGroupSetBits()
后不阻塞,继续运行
sync
setTask 在设置事件组的目标位后进入阻塞状态,等待其它 setTask 对事件组进行设置,当满足各 setTask 对事件组的要求后,所有进入阻塞状态的 setTask 同时进入运行状态
即 setTask 在设置事件组之后也在 wait 事件组
xEventGroupSync()
在事件组中设置位,然后等待在同一事件组中设置位的组合
此功能通常用于同步多个任务(通常称为任务集合),其中每个任务在继续之前必须等待其他任务到达同步点
如果uxBitsToWaitFor
参数指定的位被设置或在该时间内被设置,则该函数将在其时间到期之前返回,这种情况下,由uxBitsToWaitFor
指定的所有位将在函数返回之前自动清除
这个函数不能从中断中调用
#include "FreeRTOS.h"
#include "event_groups.h"EventBits_t xEventGroupSync( EventGroupHandle_t xEventGroup, const EventBits_t uxBitsToSet, const EventBits_t uxBitsToWaitFor, TickType_t xTicksToWait );
参数
xEventGroup
需要设置 bit 的时间组
uxBitsToSet
一个位运算值,用于指定在事件组中要设置的位
uxBitsToWaitFor
一个位运算值,用于指定在事件组中要等待的位
xTicksToWait
等待 bits 的最大时间
返回值
EventBits_t
表示事件组的状态,具体包括以下两种情况:
-
等待的位被设置
- 如果
xEventGroupSync()
返回是因为所有等待的位被设置,则返回值是事件组中这些位在被清除前的状态
- 如果
-
超时到期
- 如果
xEventGroupSync()
返回是因为超时时间到期,则可能并非所有等待的位都被设置,返回值表示超时时事件组的状态
- 如果
Example Code:Event Group Synchronization
#include <stdio.h>
#include <inttypes.h>
#include "sdkconfig.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_chip_info.h"
#include "esp_flash.h"
#include "esp_system.h"#include "freeRTOS/event_groups.h"EventGroupHandle_t eventGroup;#define BIT_0 (1 << 0)
#define BIT_1 (1 << 1)
#define BIT_2 (1 << 2)
#define ALL_SYNC_BITS (BIT_0 | BIT_1 | BIT_2)void Task0(void *pvParam)
{printf("Task0 is running\n");while (true){vTaskDelay(pdMS_TO_TICKS(1000));printf("Task0 set BIT_0\n");xEventGroupSync(eventGroup, BIT_0, ALL_SYNC_BITS, portMAX_DELAY); // 设置 BIT_0,进入同步等待printf("Task0 sync\n");vTaskDelay(pdMS_TO_TICKS(5000));}
}void Task1(void *pvParam)
{printf("Task1 is running\n");while (true){vTaskDelay(pdMS_TO_TICKS(3000));printf("Task1 set BIT_1\n");xEventGroupSync(eventGroup, BIT_1, ALL_SYNC_BITS, portMAX_DELAY);printf("Task1 sync\n");vTaskDelay(pdMS_TO_TICKS(5000));}
}void Task2(void *pvParam)
{printf("Task2 is running\n");while (true){vTaskDelay(pdMS_TO_TICKS(6000));printf("Task2 set BIT_2\n");xEventGroupSync(eventGroup, BIT_2, ALL_SYNC_BITS, portMAX_DELAY);printf("Task2 sync\n");vTaskDelay(pdMS_TO_TICKS(5000));}
}void app_main(void)
{eventGroup = xEventGroupCreate(); // 创建事件组if (eventGroup == NULL){printf("Event group creation failed\n");}else{vTaskSuspendAll();xTaskCreatePinnedToCore(Task0, "Task0", 2048, NULL, 1, NULL, 0);xTaskCreatePinnedToCore(Task1, "Task1", 2048, NULL, 1, NULL, 0);xTaskCreatePinnedToCore(Task2, "Task2", 2048, NULL, 1, NULL, 0);xTaskResumeAll();}
}
Notification
每个任务都有一个 32 位的通知值,该值在任务创建时初始
任务通知是直接发送给任务的事件,它可以解除接收任务的阻塞,并可选择更新接收任务的通知值
通知值有两种用法:按位、增量
Notification Sync
xTaskNotifyGive()
使目标任务的通知值递增
- RTOS 任务通知功能在默认情况下是启用的,并且可以从构建中排除(每个任务节省8字节)通过在
FreeRTOSConfig.h
设置configUSE_TASK_NOTIFICATIONS
为0
#include "FreeRTOS.h"
#include "task.h"BaseType_t xTaskNotifyGive( TaskHandle_t xTaskToNotify );
参数
xTaskToNotify
被通知的任务的句柄,其通知值递增(增量用法)
返回值
总是返回pdPASS
ulTaskNotifyTake()
任务可以使用 ulTaskNotifyTake()
来选择性地阻塞,等待通知值变为非零,在任务的通知值不为零时返回
在退出时可以选择将通知值清零(此时通知值类似于二值信号量)或将通知值递减(此时通知值更像计数信号量)
#include "FreeRTOS.h"
#include "task.h"uint32_t ulTaskNotifyTake( BaseType_t xClearCountOnExit, TickType_t xTicksToWait );
参数
xClearCountOnExit
-
pdFALSE
- 每次成功调用后通知值减 1,类似计数信号量的效果
-
pdTRUE
- 每次成功调用后通知值重置为 0,类似二值信号量的效果
xTicksToWait
等待通知的最大时间
返回值
任务的通知值在被递减或清除之前的值
Example Code:Simple Task Notification in FreeRTOS
#include <stdio.h>
#include <inttypes.h>
#include "sdkconfig.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_chip_info.h"
#include "esp_flash.h"
#include "esp_system.h"#include "freeRTOS/event_groups.h"TaskHandle_t task0Handle = NULL;
TaskHandle_t task1Handle = NULL;void Task0(void *pvParam)
{printf("Task0 is running\n");while (true){printf("Task0 is waitting for notification\n");ulTaskNotifyTake(pdTRUE, portMAX_DELAY);printf("Task0 got notification\n"); // Task0 等待 Task1 的通知vTaskDelay(pdMS_TO_TICKS(1000));}
}void Task1(void *pvParam)
{printf("Task1 is running\n");vTaskDelay(pdMS_TO_TICKS(5000));while (true){printf("Task1 is sending notification\n");xTaskNotifyGive(task0Handle); // Task1 通知 Task0vTaskDelay(pdMS_TO_TICKS(5000));}
}void app_main(void)
{vTaskSuspendAll();xTaskCreatePinnedToCore(Task0, "Task0", 2048, NULL, 1, &task0Handle, 0);xTaskCreatePinnedToCore(Task1, "Task1", 2048, NULL, 1, &task1Handle, 0);xTaskResumeAll();
}
Notification Value
xTaskNotify()
用于直接向任务发送事件并解除阻塞,并可选地以以下方式之一更新接收任务的通知值
- 将一个 32 位的数字写入通知值
- 增加一个(增量)通知值
- 设置一个或多个通知值
- 保持通知值不变
#include "FreeRTOS.h"
#include "task.h"BaseType_t xTaskNotify( TaskHandle_t xTaskToNotify, uint32_t ulValue, eNotifyAction eAction );
参数
xTaskToNotify
被通知的 Task 句柄
ulValue
用于更新被通知任务的通知值,如何解释 ulValue 取决于 eAction 参数的值
eAction
eNoAction
任务被通知,但通知值不变eSetBits
任务的通知值与 ulValue 进行按位或(or) 操作eIncrement
任务的通知值加 1eSetValueWithOverwrite
任务的通知值被无条件设置为 ulValue,即使之前已经有通知eSetValueWithoutOverwrite
如果任务已经有通知待处理,则通知值不会被改变,xTaskNotify()
将返回pdFAIL
;如果任务没有待处理的通知,则其通知值会被设置为ulValue
返回值
在除eSetValueWithoutOverwrite
所有其他情况下,返回 pdPASS
xTaskNotifyWait()
如果接收任务已经被阻塞并等待通知,当一个通知到达时,接收任务将从阻塞状态移除并清除通知
#include "FreeRTOS.h"
#include "task.h"BaseType_t xTaskNotifyWait( uint32_t ulBitsToClearOnEntry, uint32_t ulBitsToClearOnExit, uint32_t *pulNotificationValue, TickType_t xTicksToWait );
参数
ulBitsToClearOnEntry
- 在调用
xTaskNotifyWait()
时,通知值中的某些位会在函数进入时被清除 - 如果
ulBitsToClearOnEntry
设置为 0x01,则任务通知值中的第 0 位会在函数进入时被清除 - 如果设置为
0xffffffff
(ULONG_MAX
),则通知值的所有位都会被清除,相当于将通知值重置为 0 - 注意:仅当调用时没有挂起的通知时,清除操作才会执行
ulBitsToClearOnExit
- 在接收到通知后,在函数退出前通知值中的某些位会被清除
- 如果设置为
0xffffffff
(ULONG_MAX
),则通知值的所有位都会被清除
pulNotificationValue
- 用于将任务的通知值传递给调用者
- 保存的是 在清除
ulBitsToClearOnExit
的位之前的通知值 - 如果不需要获取通知值,可以将其设置为
NULL
xTicksToWait
最大等待时间
返回值
pdTRUE
接收到了通知,或在调用时通知已挂起pdFALSE
在等待超时时间内没有接收到通知
Example Code:Task Notification with Conditional Actions Based on Values
#include <stdio.h>
#include <inttypes.h>
#include "sdkconfig.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_chip_info.h"
#include "esp_flash.h"
#include "esp_system.h"#include "freeRTOS/event_groups.h"TaskHandle_t task0Handle = NULL;
TaskHandle_t task1Handle = NULL;void Task0(void *pvParam)
{printf("Task0 is running\n");uint32_t notifiedValue = 0;while (true){xTaskNotifyWait(0x00, 0xffffffff, ¬ifiedValue, portMAX_DELAY);if (notifiedValue == 0x00000001) // 当接收到的通知值为 0x01 时,执行相应操作{printf("Task0 get notification: bit_0\n");}else if (notifiedValue == 0x00000002){printf("Task0 get notification: bit_1\n");}else if (notifiedValue == 0x00000004){printf("Task0 get notification: bit_2\n");}else{printf("Task0 get notification: unknown\n");}vTaskDelay(pdMS_TO_TICKS(3000));}
}void Task1(void *pvParam)
{printf("Task1 is running\n");vTaskDelay(5000 / portTICK_PERIOD_MS);while (true){printf("Task1 is sending notification\n");xTaskNotify(task0Handle, 0x01, eSetValueWithOverwrite); // 发送 bit_0,覆盖之前的值vTaskDelay(5000 / portTICK_PERIOD_MS);xTaskNotify(task0Handle, 0x02, eSetValueWithOverwrite);vTaskDelay(5000 / portTICK_PERIOD_MS);xTaskNotify(task0Handle, 0x03, eSetValueWithOverwrite);vTaskDelay(5000 / portTICK_PERIOD_MS);xTaskNotify(task0Handle, 0x04, eSetValueWithOverwrite);vTaskDelay(5000 / portTICK_PERIOD_MS);}
}void app_main(void)
{vTaskSuspendAll();xTaskCreatePinnedToCore(Task0, "Task0", 2048, NULL, 1, &task0Handle, 0);xTaskCreatePinnedToCore(Task1, "Task1", 2048, NULL, 1, &task1Handle, 0);xTaskResumeAll();
}