云计算百科
云计算领域专业知识百科平台

【FreeRTOS】任务间通讯5:事件组- Event Group

    咋样更轻松的理解FreeRTOS中的事件组?

    支付宝“集五福”活动是一项广受欢迎的春节互动游戏。参与者通过完成不同任务获取五类福卡:爱国福、富强福、和谐福、友善福和敬业福,每张福卡对应二进制编码中的特定位置:

  • 爱国福:位0
  • 富强福:位1
  • 和谐福:位2
  • 友善福:位3
  • 敬业福:位4

    参与者每次获得一张福卡,系统会自动将其对应的二进制位置1。福卡获取方式多样,包括扫福字、好友互赠、蚂蚁森林任务等。

    当集齐所有五张福卡(二进制表示为0b11111)时,即可在支付宝平台兑换奖金。兑换成功后,系统会清空当前福卡记录(重置为0b00000),参与者可重新开始新一轮的集福活动。

    上面描述的内容,就像EventGroup一样。好多任务独立运行,输出状态结果到事件组中,一个任务检测事件组状态,当状态满足条件组合后,开始执行,并清空相关事件组状态位。

    

一. 事件组概述

1.1 功能作用

    FreeRTOS的事件组(Event Group)是一种高效的线程间通信机制,它允许任务之间通过事件标志(event flags)进行同步和通信。事件组的主要功能包括:

  • 多事件同步:一个任务可以等待多个事件中的任意一个或全部发生

  • 广播通知:一个任务可以设置事件标志,同时唤醒多个等待这些事件的任务

  • 低资源消耗:相比使用多个信号量或消息队列,事件组更加节省内存

  • 原子操作:事件标志的设置和清除是原子操作,无需额外的同步机制

    事件组特别适合以下场景:

  • 一个任务需要等待多个条件同时满足

  • 多个任务需要等待同一个事件发生

  • 需要通知多个任务某个事件已发生

1.2 事件组的定义

    在FreeRTOS中,事件组由EventGroupHandle_t类型的句柄表示,本质上是一个包含多个事件标志(bit)的数据结构。每个事件标志可以看作是一个二进制信号,可以被设置或清除。

FreeRTOS的事件组实现具有以下特点:

  • 每个事件组包含24个可用的事件标志(bit)

  • 事件标志编号从0开始(最低有效位)到23(最高有效位)

  • 事件组的操作是线程安全的

  • 支持任务阻塞等待特定的事件组合

二. 事件组的使用

2.1 创建事件组

    在使用事件组前,必须先创建它:

EventGroupHandle_t xEventGroupCreate(void);

    此函数创建一个新的事件组并返回其句柄。如果返回NULL,表示创建失败(通常是因为内存不足)。

2.2 设置事件标志

    设置事件标志有两种方式:

    a.设置指定bit:

EventBits_t xEventGroupSetBits(EventGroupHandle_t xEventGroup,
const EventBits_t uxBitsToSet);

  • xEventGroup: 事件组句柄

  • uxBitsToSet: 要设置的bit掩码(如0x01设置bit0,0x05设置bit0和bit2)

  • 返回值:设置后的事件组值

    b.从ISR中设置bit:

BaseType_t xEventGroupSetBitsFromISR(EventGroupHandle_t xEventGroup,
const EventBits_t uxBitsToSet,
BaseType_t *pxHigherPriorityTaskWoken);

  • 参数与xEventGroupSetBits类似

  • pxHigherPriorityTaskWoken: 用于指示是否有高优先级任务被唤醒

  • 返回pdPASS表示成功,pdFAIL表示失败

2.3 等待事件标志

    任务可以等待一个或多个事件标志:

EventBits_t xEventGroupWaitBits(const EventGroupHandle_t xEventGroup,
const EventBits_t uxBitsToWaitFor,
const BaseType_t xClearOnExit,
const BaseType_t xWaitForAllBits,
TickType_t xTicksToWait);

  • xEventGroup: 事件组句柄

  • uxBitsToWaitFor: 要等待的bit掩码

  • xClearOnExit: 如果为pdTRUE,则在返回前清除等待的bit

  • xWaitForAllBits: 如果为pdTRUE,则等待所有指定bit置位;为pdFALSE则等待任一bit置位

  • xTicksToWait: 等待的最大时间(ticks),portMAX_DELAY表示无限等待

  • 返回值:满足条件的事件标志(可能包含未请求的bit)

2.4 清除事件标志

    清除事件标志:

EventBits_t xEventGroupClearBits(EventGroupHandle_t xEventGroup,
const EventBits_t uxBitsToClear);

  • xEventGroup: 事件组句柄

  • uxBitsToClear: 要清除的bit掩码

  • 返回值:清除前的事件组值

    从ISR中清除bit:

EventBits_t xEventGroupClearBitsFromISR(EventGroupHandle_t xEventGroup,
const EventBits_t uxBitsToClear);

2.5 获取事件组当前值

EventBits_t xEventGroupGetBits(EventGroupHandle_t xEventGroup);
EventBits_t xEventGroupGetBitsFromISR(EventGroupHandle_t xEventGroup);

2.6 销毁事件组

    当不再需要事件组时,应销毁它以释放资源:

void vEventGroupDelete(EventGroupHandle_t xEventGroup);

    注意:销毁前应确保没有任务在等待该事件组。

三. 事件组使用示例

    下面是一个完整的事件组使用示例,展示三个任务如何使用事件组进行同步:

#include "FreeRTOS.h"
#include "task.h"
#include "event_groups.h"

// 定义事件标志
#define TASK1_BIT (1 << 0) // bit0
#define TASK2_BIT (1 << 1) // bit1
#define ALL_SYNC_BITS (TASK1_BIT | TASK2_BIT)

EventGroupHandle_t xEventGroup;

void vTask1(void *pvParameters) {
const TickType_t xDelay200ms = pdMS_TO_TICKS(200);

for(;;) {
// 模拟任务1的工作
vTaskDelay(xDelay200ms);

printf("Task1 完成工作,设置bit0\\n");
xEventGroupSetBits(xEventGroup, TASK1_BIT);

// 等待两个任务都完成
printf("Task1 等待两个任务完成…\\n");
xEventGroupWaitBits(xEventGroup, ALL_SYNC_BITS, pdTRUE, pdTRUE, portMAX_DELAY);
printf("Task1: 两个任务都已完成!\\n");
}
}

void vTask2(void *pvParameters) {
const TickType_t xDelay300ms = pdMS_TO_TICKS(300);

for(;;) {
// 模拟任务2的工作
vTaskDelay(xDelay300ms);

printf("Task2 完成工作,设置bit1\\n");
xEventGroupSetBits(xEventGroup, TASK2_BIT);

// 等待两个任务都完成
printf("Task2 等待两个任务完成…\\n");
xEventGroupWaitBits(xEventGroup, ALL_SYNC_BITS, pdTRUE, pdTRUE, portMAX_DELAY);
printf("Task2: 两个任务都已完成!\\n");
}
}

void vTaskMonitor(void *pvParameters) {
EventBits_t uxBits;

for(;;) {
// 等待任一事件发生
uxBits = xEventGroupWaitBits(xEventGroup, ALL_SYNC_BITS, pdFALSE, pdFALSE, portMAX_DELAY);

if((uxBits & TASK1_BIT) != 0) {
printf("Monitor: 收到Task1完成信号\\n");
}

if((uxBits & TASK2_BIT) != 0) {
printf("Monitor: 收到Task2完成信号\\n");
}
}
}

int main(void) {
// 创建事件组
xEventGroup = xEventGroupCreate();

if(xEventGroup == NULL) {
printf("无法创建事件组!\\n");
return 1;
}
// 创建任务
xTaskCreate(vTask1, "Task1", configMINIMAL_STACK_SIZE, NULL, 2, NULL);
xTaskCreate(vTask2, "Task2", configMINIMAL_STACK_SIZE, NULL, 2, NULL);
xTaskCreate(vTaskMonitor, "Monitor", configMINIMAL_STACK_SIZE, NULL, 1, NULL);

// 启动调度器
vTaskStartScheduler();

return 0;
}

四. 事件组使用注意事项

    4.1 资源限制:

  • FreeRTOS事件组只有24个可用bit(0-23)
  • 使用前应合理规划bit分配,避免冲突

    4.2 性能考虑:

  • 事件组操作通常是原子操作,但等待操作可能导致任务阻塞
  • 在ISR中使用FromISR版本函数

    4.3 同步问题:

  • 事件标志是"或"的关系,多个任务可能同时响应同一事件
  • 对于关键操作,应结合其他同步机制(如互斥量)

    4.4 内存管理:

  • 不再使用的事件组应及时删除以避免内存泄漏
  • 确保删除事件组时没有任务在等待它

   4. 5 调试技巧:

  • 为每个事件bit定义有意义的宏名称
  • 在复杂系统中,记录事件组的设置和等待情况有助于调试

   4. 6 设计建议:

  • 避免过度依赖事件组实现复杂逻辑,保持简单
  • 考虑事件组的生命周期,确保创建和销毁的时机正确
  • 在高优先级任务中谨慎使用无限等待,可能导致低优先级任务饥饿

    小结:通过合理使用事件组,可以有效地实现任务间的同步和通信,构建更加清晰高效的FreeRTOS应用程序。

赞(0)
未经允许不得转载:网硕互联帮助中心 » 【FreeRTOS】任务间通讯5:事件组- Event Group
分享到: 更多 (0)

评论 抢沙发

评论前必须登录!