实用指南:FreeRTOS 事件组详解

1. 事件组基本概念

1.1 与队列、信号量的区别

事件组与队列、信号量有两个主要区别:

唤醒机制不同:

  • 队列、信号量:事件发生时,只会唤醒一个任务

  • 事件组:事件发生时,会唤醒所有符合条件的任务,具有"广播"作用

事件处理方式不同:

  • 队列、信号量:消耗型资源(队列数据被读走就消失;信号量被获取后减少)

  • 事件组:被唤醒的任务可以选择是否清除事件

2. 事件组操作流程

2.1 基本使用流程

  1. 创建事件组

  2. 任务C、D等待事件

  3. 任务A、B产生事件

2.2 事件等待选项

  • 等待类型:可以等待某一位、某些位中的任意一个(或关系),或等待多位同时成立(与关系)

  • 清除选项:可选择在获取事件后清除或保留事件

3. 事件组API详解

3.1 创建事件组

动态创建:

c

EventGroupHandle_t xEventGroupCreate(void);
  • 内部自动分配事件组结构体内存

  • 返回句柄,非NULL表示成功

静态创建:

c

EventGroupHandle_t xEventGroupCreateStatic(StaticEventGroup_t *pxEventGroupBuffer);
  • 需要预先分配StaticEventGroup_t结构体

  • 避免动态内存分配,适合内存受限场景

3.2 删除事件组

c

void vEventGroupDelete(EventGroupHandle_t xEventGroup);
  • 删除动态创建的事件组,回收内存

  • 参数:要删除的事件组句柄

3.3 设置事件

在任务中设置:

c

EventBits_t xEventGroupSetBits(EventGroupHandle_t xEventGroup, 
                              const EventBits_t uxBitsToSet);
  • 设置事件组中的特定位

  • 可以同时设置多个位(如0x15表示设置bit4、bit2、bit0)

在ISR中设置:

c

BaseType_t xEventGroupSetBitsFromISR(EventGroupHandle_t xEventGroup,
                                   const EventBits_t uxBitsToSet,
                                   BaseType_t *pxHigherPriorityTaskWoken);
  • 通过向FreeRTOS后台任务发送队列数据来间接设置事件组

  • 避免在ISR中直接执行可能唤醒多个任务的操作

3.4 等待事件

c

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

参数详解:

参数说明
xEventGroup要等待的事件组句柄
uxBitsToWaitFor要等待的位(位掩码)
xWaitForAllBits测试方式:pdTRUE-所有位都为1(与关系),pdFALSE-任意位为1(或关系)
xClearOnExit退出时是否清除事件:pdTRUE-清除,pdFALSE-保留
xTicksToWait阻塞超时时间:0-立即返回,portMAX_DELAY-无限等待,具体tick数

返回值:

  • 事件发生时:返回"非阻塞条件成立"时的事件值

  • 超时退出:返回超时时刻的事件值

4. 事件等待示例分析

4.1 "与"关系等待(所有位必须为1)

c

// 示例1:等待bit0和bit2同时为1
EventBits_t uxBits = xEventGroupWaitBits(xEventGroup, 
                                        0x05,  // bit0和bit2
                                        pdTRUE, // 退出时清除
                                        pdTRUE, // 等待所有位
                                        portMAX_DELAY);

// 事件组值变化过程:
// 初始值: 0100 (只有bit2为1) → 任务阻塞
// 设置bit0: 0101 → 满足条件,任务唤醒

4.2 "或"关系等待(任意位为1即可)

c

// 示例2:等待bit1或bit2任意一个为1
EventBits_t uxBits = xEventGroupWaitBits(xEventGroup,
                                        0x06,  // bit1和bit2
                                        pdTRUE, // 退出时清除
                                        pdFALSE, // 等待任意位
                                        100);   // 等待100个tick

// 事件组值变化过程:
// 初始值: 0100 (bit2为1) → 立即满足条件,任务不阻塞

5. 实际代码示例

5.1 基本事件组使用

c

// 定义事件位
#define TASK_READY_BIT    (1 << 0)
#define DATA_READY_BIT    (1 << 1)
#define ERROR_BIT         (1 << 2)

EventGroupHandle_t xEventGroup;

// 任务A:设置事件
void vTaskA(void *pvParameters)
{
    while(1)
    {
        // 执行某些操作...
        xEventGroupSetBits(xEventGroup, TASK_READY_BIT);
        vTaskDelay(pdMS_TO_TICKS(1000));
    }
}

// 任务B:等待事件
void vTaskB(void *pvParameters)
{
    const EventBits_t xBitsToWaitFor = TASK_READY_BIT | DATA_READY_BIT;
    
    while(1)
    {
        // 等待任意一个事件发生(或关系)
        EventBits_t xEvent = xEventGroupWaitBits(
            xEventGroup,           // 事件组句柄
            xBitsToWaitFor,        // 等待的位
            pdTRUE,                // 退出时清除事件
            pdFALSE,               // 或关系:任意位为1即可
            portMAX_DELAY);        // 无限等待
        
        if((xEvent & TASK_READY_BIT) != 0)
        {
            // 处理TASK_READY事件
        }
        
        if((xEvent & DATA_READY_BIT) != 0)
        {
            // 处理DATA_READY事件
        }
    }
}

5.2 复杂事件条件处理

c

// 等待多个条件同时满足(与关系)
void vComplexTask(void *pvParameters)
{
    // 等待系统就绪且数据有效
    const EventBits_t xAllRequiredBits = TASK_READY_BIT | DATA_READY_BIT;
    
    while(1)
    {
        EventBits_t xEvent = xEventGroupWaitBits(
            xEventGroup,
            xAllRequiredBits,
            pdTRUE,        // 清除事件
            pdTRUE,        // 与关系:所有位必须为1
            pdMS_TO_TICKS(5000));  // 最多等待5秒
        
        if((xEvent & xAllRequiredBits) == xAllRequiredBits)
        {
            // 所有条件满足,执行操作
        }
        else
        {
            // 超时,处理超时逻辑
        }
    }
}

6. 重要注意事项

6.1 原子操作优势

使用xClearOnExit参数为pdTRUE时,事件组的测试和清零在xEventGroupWaitBits()函数内部原子完成,避免了任务切换导致的事件状态不一致问题。

6.2 ISR中的特殊处理

在ISR中使用xEventGroupSetBitsFromISR()时需要注意:

  • 实际设置操作由后台任务执行,存在延迟

  • 可能唤醒多个任务,带来不确定性

  • 需要检查pxHigherPriorityTaskWoken参数,必要时进行任务切换

6.3 事件位管理

  • 合理规划事件位分配,避免冲突

  • 使用宏定义提高代码可读性

  • 注意事件位的清除时机,避免误清除

posted on 2025-09-28 21:05  slgkaifa  阅读(90)  评论(0)    收藏  举报

导航