FreeRTOS 事件标志组详解

FreeRTOS 事件标志组详解

  • 一、Freertos事件标志组详解
    • 1、概念与作用
    • [2、 核心特性](#2、 核心特性)
    • [3、关键 API 函数详解](#3、关键 API 函数详解)
      • [3.1 、创建事件标志组](#3.1 、创建事件标志组)
      • [3.2、 设置事件标志位](#3.2、 设置事件标志位)
      • [3.3 、设置事件标志位(中断服务程序中使用)](#3.3 、设置事件标志位(中断服务程序中使用))
      • [3.4 、清除事件标志位](#3.4 、清除事件标志位)
      • [3.5 、等待事件标志位(核心)](#3.5 、等待事件标志位(核心))
    • 4、注意事项与最佳实践
    • [5、 总结](#5、 总结)
  • 二、代码示例

一、Freertos事件标志组详解

1、概念与作用

事件标志组是 FreeRTOS 提供的一种任务间通信和同步机制。其核心思想是使用一组二进制标志位(bit)来表示不同的事件状态(例如:事件发生/未发生)。任务可以:

  • 设置标志位(Set Bits): 当一个任务完成某项工作或检测到某个事件发生时,它可以设置(置 1)事件组中对应的标志位,通知其他等待该事件的任务。
  • 清除标志位(Clear Bits): 任务可以清除(置 0)事件组中的标志位,通常表示事件尚未发生或重新等待。
  • 等待标志位(Wait for Bits): 任务可以阻塞自身,等待事件组中一个或多个特定的标志位被设置(满足某种条件)。这是事件组实现同步的关键。

主要作用:

  • 任务同步: 允许多个任务等待一个或多个事件的发生。例如,任务 A 等待按键按下(事件 1)且数据接收完成(事件 2)后再继续执行。
  • 事件通知: 一个任务(或中断服务程序)可以通知其他任务,某个或多个事件已经发生。
  • 简化通信: 相比于使用多个信号量或队列来分别表示不同事件,事件标志组使用一个数据结构即可管理多个事件状态,更高效。

2、 核心特性

  • 位操作 : 事件标志组本质上是一个 EventBits_t 类型的变量(通常是 uint32_t),它的每一位(bit)都代表一个独立的事件标志。FreeRTOS 最多支持 24 个事件标志(受限于 configUSE_16_BIT_TICKS 配置)。
  • 原子操作: FreeRTOS 确保了设置、清除和等待标志位等操作是原子的。这意味着这些操作在执行过程中不会被其他任务或中断打断,保证了数据的一致性,避免了竞态条件。
  • 灵活的等待条件 : 任务在等待标志位时,可以指定需要哪些位被设置,并且可以选择逻辑关系:
    • 逻辑或 (OR) : 等待的位中任意一个 被设置即可解除阻塞。xEventGroupWaitBits(..., xBitsToWaitFor, pdTRUE, pdFALSE, ...)
    • 逻辑与 (AND) : 等待的位全部 被设置才能解除阻塞。xEventGroupWaitBits(..., xBitsToWaitFor, pdTRUE, pdTRUE, ...)
    • 自动清除 : 任务可以选择在成功等待到所需标志位后,是否自动清除这些位(方便下次等待)。通过 xClearOnExit 参数控制。
  • 阻塞时间 : 任务可以指定一个超时时间(xTicksToWait),如果在指定时间内没有等到所需的事件标志组合,任务将解除阻塞并返回当前事件组的状态(可能包含其他已设置的位)。

3、关键 API 函数详解

3.1 、创建事件标志组

c 复制代码
EventGroupHandle_t xEventGroupCreate(void);
  • 功能: 动态创建一个新的事件标志组。
  • 返回值
    • 成功: 返回事件标志组的句柄(EventGroupHandle_t)。
    • 失败: 返回 NULL(通常是因为内存不足)。
  • 初始状态: 所有事件标志位初始化为 0(未设置)。

3.2、 设置事件标志位

c 复制代码
EventBits_t xEventGroupSetBits(EventGroupHandle_t xEventGroup, const EventBits_t uxBitsToSet);
  • 功能 : 设置指定事件标志组中的一个或多个标志位(置 1)。此函数可以在任务或中断中使用,但中断中应使用带 FromISR 的版本
  • 参数
    • xEventGroup: 目标事件标志组的句柄。
    • uxBitsToSet: 一个位掩码(bitmask),指定要设置哪些位。例如,要设置位 0 和位 2,则 uxBitsToSet = (1 << 0) | (1 << 2)
  • 返回值: 设置操作完成后事件标志组的新值(所有位的状态)。注意,这个值可能包含其他任务在本次设置操作期间或之后设置的位。
  • 行为
    • 如果设置操作导致某个正在等待的任务所要求的条件得到满足,则该任务将被解除阻塞。
    • 该操作是原子的。

3.3 、设置事件标志位(中断服务程序中使用)

c 复制代码
BaseType_t xEventGroupSetBitsFromISR(EventGroupHandle_t xEventGroup, const EventBits_t uxBitsToSet, BaseType_t *pxHigherPriorityTaskWoken);
  • 功能: 在中断服务程序(ISR)中设置事件标志位。
  • 参数
    • xEventGroup: 目标事件标志组的句柄。
    • uxBitsToSet: 位掩码,指定要设置哪些位。
    • pxHigherPriorityTaskWoken: 指向一个 BaseType_t 变量的指针。函数会将这个变量设置为 pdTRUE,如果设置操作导致一个优先级高于 当前被中断任务的任务就绪(解除阻塞)。如果不需要此信息,可传入 NULL
  • 返回值
    • pdPASS: 设置请求已成功发送到守护任务(Daemon Task)。
    • pdFAIL: 设置请求未能发送到守护任务(通常是因为守护任务的事件队列已满)。
  • 行为
    • 中断中不能直接调用 xEventGroupSetBits(),因为其中可能包含阻塞操作(如唤醒任务)。
    • xEventGroupSetBitsFromISR() 会将设置请求发送给一个特殊的 FreeRTOS 守护任务(通常是定时器服务任务)。该守护任务稍后会实际执行设置操作。
    • 因此,设置操作在中断中不是即时生效 的,而是在守护任务中延迟执行
    • 如果 pxHigherPriorityTaskWoken 被设为 pdTRUE,中断退出前应进行一次上下文切换(portYIELD_FROM_ISR())。

3.4 、清除事件标志位

c 复制代码
EventBits_t xEventGroupClearBits(EventGroupHandle_t xEventGroup, const EventBits_t uxBitsToClear);
  • 功能: 清除指定事件标志组中的一个或多个标志位(置 0)。
  • 参数
    • xEventGroup: 目标事件标志组的句柄。
    • uxBitsToClear: 位掩码,指定要清除哪些位。例如,清除位 1 和位 3,uxBitsToClear = (1 << 1) | (1 << 3)
  • 返回值: 清除操作完成后事件标志组的新值。
  • 行为
    • 该操作是原子的。
    • 清除操作不会解除任何等待任务(因为清除通常表示事件未发生)。

3.5 、等待事件标志位(核心)

c 复制代码
EventBits_t xEventGroupWaitBits(const EventGroupHandle_t xEventGroup,
                                const EventBits_t uxBitsToWaitFor,
                                const BaseType_t xClearOnExit,
                                const BaseType_t xWaitForAllBits,
                                TickType_t xTicksToWait);
  • 功能: 阻塞调用任务,等待指定事件标志组中的一个或多个标志位满足特定条件。
  • 参数
    • xEventGroup: 要等待的事件标志组的句柄。
    • uxBitsToWaitFor: 位掩码,指定任务关心哪些位(要等待哪些事件)。
    • xClearOnExit
      • 如果设置为 pdTRUE: 当函数因为满足等待条件而返回时(不是因为超时),它会自动清除 uxBitsToWaitFor 中指定的那些位(无论是否设置了 xWaitForAllBits)。
      • 如果设置为 pdFALSE: 函数返回时不会改变事件组中的任何位。
    • xWaitForAllBits
      • 如果设置为 pdTRUE: 任务将等待 uxBitsToWaitFor 中指定的所有位都被设置(逻辑与 AND)。
      • 如果设置为 pdFALSE: 任务将等待 uxBitsToWaitFor 中指定的位中至少一个被设置(逻辑或 OR)。
    • xTicksToWait: 任务愿意阻塞等待的最大时间(以系统节拍周期为单位)。可以使用 portMAX_DELAY 表示无限期等待(需确保 configUSE_TIMEOUTS 为 1)。
  • 返回值
    • 如果满足等待条件: 返回事件组的值(此时满足条件)。如果 xClearOnExitpdTRUE,则返回的是清除指定位之前的值。
    • 如果超时: 返回事件组当前的值(可能包含一些已设置的位,但不满足等待条件)。
  • 行为
    • 该函数只能在任务中调用,不能在中断中使用。
    • 在调用此函数时,如果事件组的状态已经满足了等待条件(即 uxBitsToWaitFor 指定的位根据 xWaitForAllBits 的逻辑已经满足),则函数会立即返回,不会阻塞任务。
    • 如果不满足条件,任务将进入阻塞状态,直到满足条件、超时或其他任务/中断设置了标志位使其满足条件。
    • 操作是原子的。

4、注意事项与最佳实践

  • 位选择: 仔细规划每个事件标志位的含义。避免位冲突。
  • 清除时机 : 理解 xClearOnExit 的作用。如果多个任务等待同一个事件,自动清除可能不合适(第一个任务清除后,后续任务就等不到了)。这种情况下,可能需要由设置事件的任务或在更高层逻辑中手动清除。
  • 中断中的使用 : 务必在中断中使用 xEventGroupSetBitsFromISR 而不是 xEventGroupSetBits。注意其延迟执行的特性。
  • 优先级反转: 虽然事件标志组本身不会导致优先级反转,但在高优先级任务长期占用事件组(例如长时间等待)而低优先级任务需要设置事件时,也可能间接影响。需合理设计任务优先级。
  • 调试 : 使用 FreeRTOS 的跟踪功能(如 configUSE_TRACE_FACILITY)可以帮助调试事件标志组的状态变化。
  • 性能: 事件标志组操作通常非常高效,因为本质上是位操作和任务状态管理。

5、 总结

FreeRTOS 的事件标志组是一种强大且灵活的任务同步机制,特别适合于需要等待多个事件发生的场景。通过位操作、原子性保证以及灵活的等待条件(OR/AND、自动清除),它为复杂的任务协调提供了简洁高效的解决方案。理解其核心 API(Create, SetBits, SetBitsFromISR, ClearBits, WaitBits)的工作原理和使用场景是有效利用该机制的关键。

二、代码示例

c 复制代码
#include "FreeRTOS.h"
#include "task.h"
#include "event_groups.h"

// 事件标志组句柄
EventGroupHandle_t xEventGroup;

// 事件位定义
#define BIT_SENSOR_READY     (1 << 0)
#define BIT_KEY_PRESSED      (1 << 1)
#define BIT_UART_RECV_DONE   (1 << 2)

// ==================================================
// 任务1:等待【所有】事件:BIT_SENSOR_READY | BIT_KEY_PRESSED
// ==================================================
void vTask_Wait_All(void *pvParameters)
{
    EventBits_t uxBits;

    while (1)
    {
        // 等待 BIT0 + BIT1 同时满足
        // pdTRUE = 满足后自动清除这两个位
        // pdFALSE = 等待所有位(逻辑与)
        uxBits = xEventGroupWaitBits(
            xEventGroup,
            BIT_SENSOR_READY | BIT_KEY_PRESSED,
            pdTRUE,
            pdFALSE,
            portMAX_DELAY
        );

        if ((uxBits & (BIT_SENSOR_READY | BIT_KEY_PRESSED)) != 0)
        {
            printf("任务All:传感器就绪 + 按键按下,开始处理...\r\n");
        }
    }
}

// ==================================================
// 任务2:等待【任意】事件:BIT_SENSOR_READY 或 BIT_UART_RECV_DONE
// ==================================================
void vTask_Wait_Any(void *pvParameters)
{
    EventBits_t uxBits;

    while (1)
    {
        // 等待 BIT0 或 BIT2 任意一个
        // pdTRUE = 满足后自动清除触发的位
        // pdTRUE = 等待任意位(逻辑或)
        uxBits = xEventGroupWaitBits(
            xEventGroup,
            BIT_SENSOR_READY | BIT_UART_RECV_DONE,
            pdTRUE,
            pdTRUE,
            portMAX_DELAY
        );

        if ((uxBits & BIT_SENSOR_READY) != 0)
        {
            printf("任务Any:传感器数据就绪\r\n");
        }

        if ((uxBits & BIT_UART_RECV_DONE) != 0)
        {
            printf("任务Any:串口接收完成\r\n");
        }
    }
}

// ==================================================
// 任务3:模拟产生各种事件(设置标志位)
// ==================================================
void vEventSetterTask(void *pvParameters)
{
    while (1)
    {
        // 1. 模拟:传感器数据就绪
        vTaskDelay(pdMS_TO_TICKS(1000));
        xEventGroupSetBits(xEventGroup, BIT_SENSOR_READY);
        printf("设置:传感器就绪 BIT0\r\n");

        // 2. 模拟:串口接收完成
        vTaskDelay(pdMS_TO_TICKS(1500));
        xEventGroupSetBits(xEventGroup, BIT_UART_RECV_DONE);
        printf("设置:串口完成 BIT2\r\n");

        // 3. 模拟:按键按下
        vTaskDelay(pdMS_TO_TICKS(2000));
        xEventGroupSetBits(xEventGroup, BIT_KEY_PRESSED);
        printf("设置:按键按下 BIT1\r\n");

        // 循环往复
    }
}

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

    if (xEventGroup != NULL)
    {
        // 创建任务
        xTaskCreate(vTask_Wait_All,  "TaskAll",  1024, NULL, 2, NULL);
        xTaskCreate(vTask_Wait_Any,  "TaskAny",  1024, NULL, 2, NULL);
        xTaskCreate(vEventSetterTask,"SetEvent", 1024, NULL, 1, NULL);
    }

    while (1);
}
相关推荐
天涯铭2 年前
消息队列和事件标志组
单片机·嵌入式硬件·消息队列·事件标志组