FreeRTOS事件组:嵌入式世界里的“红绿灯”与“集结号”,玩转ESP32任务调度哲学

文章总结(帮你们节约时间)

  • 事件组是FreeRTOS中一种高效的多任务同步工具,它本质是一个“事件位图”,允许单个任务灵活地等待一个或多个事件的发生(实现“与”或“或”逻辑),相比使用多个信号量更节省宝贵的RAM。
  • 事件组的核心在于xEventGroupWaitBits()函数,它提供了强大的“等待”策略:你可以选择等待到事件后是否自动清除标记(xClearOnExit),是必须等待所有指定事件都发生还是任意一个即可(xWaitForAllBits),以及是否设置超时(xTicksToWait),从而避免任务被永久阻塞。
  • 除了“等待”,事件组还有一个高级玩法叫“同步”,通过xEventGroupSync()函数实现。它能让多个任务在一个“集合点”汇合,当所有任务都到达后,再同时被唤醒执行后续代码,是实现多任务精确同步启动的利器。
  • 文章通过两个生动有趣的Arduino实战代码(“多传感器数据同步”和“火箭发射模拟”)详细演示了事件组的用法,并指出了使用时需要注意避免优先级反转和滥用无限期等待等潜在陷阱,帮助你写出更健壮的并发程序。

在嵌入式实时系统中,任务间的同步与通信至关重要。ESP32作为一款功能强大的双核微控制器,搭载了FreeRTOS操作系统,为开发者提供了丰富的任务同步机制。其中,事件组(Event Groups)是一种高效且灵活的同步工具,能够实现多任务之间的协调与通信。本文将深入探讨ESP32中FreeRTOS事件组的原理、应用场景及实战示例。

事件组基本概念

什么是事件组?

事件组是FreeRTOS提供的一种任务同步机制,它允许一个或多个任务等待一个或多个事件的发生。与信号量和队列不同,事件组可以同时等待和设置多个事件标志,使得任务协调变得更加灵活。

事件组实质上是一个位图,其中每一位代表一个事件标志。当某位被置为1时,表示对应的事件已发生;为0时,表示事件未发生。这种位操作机制使得事件组在资源受限的嵌入式系统中特别高效。

事件组的优势

  1. 资源节约:在内存受限的嵌入式系统中,使用事件组可以替代多个二值信号量,节省RAM资源
  2. 多事件同步:一个任务可以同时等待多个事件的发生,实现"与"或"或"逻辑
  3. 多任务触发:一个事件可以同时触发多个等待该事件的任务
  4. 灵活的位操作:通过位操作可以方便地设置、清除和检查事件状态

事件组的大小

在ESP32的FreeRTOS实现中,事件组的大小由配置决定:

  • 如果configUSE_16_BIT_TICKS = 1,事件组包含8个标志位
  • 如果configUSE_16_BIT_TICKS = 0,事件组包含24个标志位

在ESP32默认配置中,configUSE_16_BIT_TICKS = 0,因此可以使用24个事件标志位。

事件组API详解

创建与删除

// 创建事件组
EventGroupHandle_t xEventGroupCreate(void);

// 删除事件组
void vEventGroupDelete(EventGroupHandle_t xEventGroup);

xEventGroupCreate()函数创建一个新的事件组,并返回该事件组的句柄。如果由于内存不足而无法创建事件组,则返回NULL。

vEventGroupDelete()函数用于删除不再需要的事件组,释放相关资源。

设置事件位

// 设置事件位
EventBits_t xEventGroupSetBits(EventGroupHandle_t xEventGroup, const EventBits_t uxBitsToSet);

// 从ISR中设置事件位
BaseType_t xEventGroupSetBitsFromISR(EventGroupHandle_t xEventGroup, const EventBits_t uxBitsToSet, BaseType_t *pxHigherPriorityTaskWoken);

xEventGroupSetBits()函数用于设置指定事件组中的一个或多个事件位。uxBitsToSet参数指定要设置的位,可以使用按位或运算符(|)组合多个位。函数返回设置位后事件组的值。

xEventGroupSetBitsFromISR()函数是中断安全版本,可以在中断服务例程中调用。

清除事件位

// 清除事件位
EventBits_t xEventGroupClearBits(EventGroupHandle_t xEventGroup, const EventBits_t uxBitsToClear);

// 从ISR中清除事件位
BaseType_t xEventGroupClearBitsFromISR(EventGroupHandle_t xEventGroup, const EventBits_t uxBitsToClear);

xEventGroupClearBits()函数用于清除指定事件组中的一个或多个事件位。uxBitsToClear参数指定要清除的位,可以使用按位或运算符(|)组合多个位。函数返回清除位前事件组的值。

xEventGroupClearBitsFromISR()函数是中断安全版本,可以在中断服务例程中调用。

等待事件位

// 等待事件位
EventBits_t xEventGroupWaitBits(
    EventGroupHandle_t xEventGroup,
    const EventBits_t uxBitsToWaitFor,
    const BaseType_t xClearOnExit,
    const BaseType_t xWaitForAllBits,
    TickType_t xTicksToWait
);

xEventGroupWaitBits()函数是事件组最核心的API,用于等待一个或多个事件位被设置。参数说明:

  • xEventGroup:要等待的事件组句柄
  • uxBitsToWaitFor:要等待的事件位,可以使用按位或运算符(|)组合多个位
  • xClearOnExit:如果设为pdTRUE,则在函数返回前清除等待的事件位
  • xWaitForAllBits:如果设为pdTRUE,则只有当所有指定的事件位都被设置时才返回;如果设为pdFALSE,则当任一指定事件位被设置时返回
  • xTicksToWait:最长等待时间,以系统节拍为单位。如果设为portMAX_DELAY,则无限期等待

函数返回退出时事件组的值。

获取事件组当前值

// 获取事件组当前值
EventBits_t xEventGroupGetBits(EventGroupHandle_t xEventGroup);

// 从ISR中获取事件组当前值
EventBits_t xEventGroupGetBitsFromISR(EventGroupHandle_t xEventGroup);

xEventGroupGetBits()函数用于获取事件组的当前值,不会阻塞任务。

xEventGroupGetBitsFromISR()函数是中断安全版本,可以在中断服务例程中调用。

事件组的底层实现

数据结构

FreeRTOS中事件组的核心数据结构如下:

typedef struct EventGroupDef_t
{
   
   
    EventBits_t uxEventBits;
    List_t xTasksWaitingForBits; // 等待事件的任务列表
    #if( configUSE_TRACE_FACILITY == 1 )
        UBaseType_t uxEventGroupNumber;
    #endif
    #if( ( configSUPPORT_STATIC_ALLOCATION == 1 ) && ( configSUPPORT_DYNAMIC_ALLOCATION == 1 ) )
        uint8_t ucStaticallyAllocated;
    #endif
} EventGroup_t;

其中:

  • uxEventBits:存储事件组的当前位值
  • xTasksWaitingForBits:等待事件的任务列表

工作原理

  1. 事件组创建

    • 分配内存并初始化事件组结构体
    • 初始化等待任务列表
    • 初始化事件位为0
  2. 设置事件位

    • 通过位操作设置指定的事件位
    • 检查等待任务列表,唤醒满足条件的任务
  3. 等待事件位

    • 检查当前事件位是否满足条件
    • 如果满足,根据xClearOnExit参数决定是否清除事件位,然后返回
    • 如果不满足且xTicksToWait > 0,将任务加入等待列表并阻塞
    • 当事件位被设置时,任务被唤醒,再次检查条件
  4. 清除事件位

    • 通过位操作清除指定的事件位

事件组应用场景

多事件同步

事件组最常见的应用场景是等待多个事件同时发生。例如,一个任务需要等待传感器数据采集完成、网络连接建立以及用户输入确认这三个事件都发生后才能继续执行。

任务通知

事件组可以用作任务间的通知机制,一个任务设置事件位,另一个任务等待该事件位,实现任务间的通信。

资源管理

使用事件组可以管理多个资源的状态,例如标记哪些外设当前可用,哪些正在被使用。

中断处理

在中断服务例程中设置事件位,然后在任务中等待这些事件,实现中断与任务的协作。

实战示例:多传感器数据采集同步

以下示例演示了如何使用事件组同步多个传感器的数据采集:

#include <Arduino.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/event_groups.h"

// 定义事件位
#define TEMP_SENSOR_BIT (1 << 0)  // 温度传感器事件位
#define HUMID_SENSOR_BIT (1 << 1) // 湿度传感器事件位
#define PRESS_SENSOR_BIT (1 << 2) // 气压传感器事件位

// 所有传感器事件位的组合
#define ALL_SENSOR_BITS (TEMP_SENSOR_BIT | HUMID_SENSOR_BIT | PRESS_SENSOR_BIT)

// 事件组句柄
EventGroupHandle_t sensorEventGroup;

// 传感器数据
float temperature = 0.0;
float humidity = 0.0;
float pressure = 0.0;

// 温度传感器任务
void tempSensorTask(void *pvParameters) {
   
   
  while (1) {
   
   
    // 模拟读取温度传感器
    temperature = 25.0 + random(0, 100) / 10.0;
    Serial.printf("温度读取完成: %.1f°C\n", temperature);
    
    // 设置温度传感器事件位
    xEventGroupSetBits(sensorEventGroup, TEMP_SENSOR_BIT);
    
    // 每2秒读取一次
    vTaskDelay(pdMS_TO_TICKS(2000));
  }
}

// 湿度传感器任务
void humidSensorTask(void *pvParameters) {
   
   
  while (1) {
   
   
    // 模拟读取湿度传感器
    humidity = 50.0 + random(0, 200) / 10.0;
    Serial.printf("湿度读取完成: %.1f%%\n", humidity);
    
    // 设置湿度传感器事件位
    xEventGroupSetBits(sensorEventGroup, HUMID_SENSOR_BIT);
    
    // 每3秒读取一次
    vTaskDelay(pdMS_TO_TICKS(3000));
  }
}

// 气压传感器任务<
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值