FreeRTOS事件组-笔记
FreeRTOS事件组-笔记
- **一、事件组的核心概念**
- **1. 定义与特性**
- **二、事件组的存储结构**
- **1. 位数与配置**
- **三、事件组的核心API函数**
- **1. 创建与删除**
- **2. 事件位操作**
- **3. 读取事件组当前值**
- **4. 等待事件条件成立**
- **四、事件组的典型应用场景**
- **1. 多事件组合触发**
- **2. 事件广播**
- **3. 任务同步**
- **五、ISR中使用事件组的注意事项**
- **六、事件组与队列/信号量的对比**
- **七、代码示例**
- **1. 按键事件触发多个任务**
- **2. 等待多个事件同时发生**
一、事件组的核心概念
1. 定义与特性
-
事件组是FreeRTOS中一种进程间通信技术,用于处理多个事件触发一个或多个任务运行的情况。与队列和信号量不同,事件组允许任务等待一个或多个事件的组合,并能解除所有等待同一事件的任务的阻塞状态,适用于事件的广播和多个任务的同步运行。其特性使得事件组适用于等待一组事件中某个事件发生后做出响应、一组事件都发生后的响应、事件广播和任务间的同步等场景。
-
事件组(Event Group):本质是一个位掩码(bitmask),用于表示多个事件的状态(每个位代表一个事件)。支持一对多广播,可同时唤醒多个任务。
-
与传统同步机制的对比:
- 队列/信号量:仅支持一对一唤醒(一个任务被唤醒),且每次操作消耗资源(如队列数据被读走)。
- 事件组:支持一对多唤醒(多个任务可同时响应同一事件),且事件位可保留或清除,无需消耗资源。
例如1:
先后按下左键和右键,或者只按下其中一个键,那事件组会解除所有等待同一事件的任务的阻塞状态。
例如2:
在任务1中使用LED1来闪烁报警,任务2中使用蜂鸣器报警。当报警事件发生时,两个任务同时解除阻塞状态,两个任务都开始运行。
二、事件组的存储结构
1. 位数与配置
- 32位事件组:
- 低24位(bit0-bit23):用户可用事件位(表示具体事件,如按键、串口就绪)。
- 高8位(bit24-bit31):保留给内核使用(如任务阻塞状态)。
- 16位事件组(若
configUSE_16_BIT_TICKS == 1
):- 低8位(bit0-bit7):用户可用事件位。
- 高8位(bit8-bit15):保留给内核使用。
三、事件组的核心API函数
1. 创建与删除
- 创建:
EventGroupHandle_t xEventGroupCreate(void); // 动态分配内存 EventGroupHandle_t xEventGroupCreateStatic(StaticEventGroup_t *pxEventGroupBuffer); // 静态分配
- 删除:
void vEventGroupDelete(EventGroupHandle_t xEventGroup);
2. 事件位操作
- 设置事件位:
EventBits_t xEventGroupSetBits(EventGroupHandle_t xEventGroup, EventBits_t uxBitsToSet); // 任务中使用 BaseType_t xEventGroupSetBitsFromISR(EventGroupHandle_t xEventGroup, EventBits_t uxBitsToSet, BaseType_t *pxHigherPriorityTaskWoken); // ISR中使用
- 清除事件位:
EventBits_t xEventGroupClearBits(EventGroupHandle_t xEventGroup, EventBits_t uxBitsToClear); // 任务中使用 BaseType_t xEventGroupClearBitsFromISR(EventGroupHandle_t xEventGroup, EventBits_t uxBitsToClear); // ISR中使用
3. 读取事件组当前值
#define xEventGroupGetBits(xEventGroup) xEventGroupClearBits(xEventGroup, 0) // 不清除事件位,仅读取当前值
4. 等待事件条件成立
- 通用等待函数:
EventBits_t xEventGroupWaitBits(EventGroupHandle_t xEventGroup,EventBits_t uxBitsToWaitFor, // 需要等待的事件位掩码BaseType_t xClearOnExit, // pdTRUE:退出时清除指定事件位;pdFALSE:不清除BaseType_t xWaitForAllBits, // pdTRUE:需所有事件位都置位;pdFALSE:任一事件位置位即可TickType_t xTicksToWait // 超时时间(portMAX_DELAY表示无限等待) );
- 同步等待函数(用于同步点):
EventBits_t xEventGroupSync(EventGroupHandle_t xEventGroup,EventBits_t uxBitsToSet, // 需要置位的事件位EventBits_t uxBitsToWaitFor, // 需要等待的事件位TickType_t xTicksToWait // 超时时间 );
四、事件组的典型应用场景
1. 多事件组合触发
- 或关系:等待任意一个事件发生(如按键按下或串口数据到达)。
- 与关系:等待所有事件发生(如左键和右键同时按下)。
// 等待 BIT_0 或 BIT_1 中任意一个事件 EventBits_t xBits = xEventGroupWaitBits(xEventGroup, BIT_0 | BIT_1, pdFALSE, pdFALSE, portMAX_DELAY); if (xBits & BIT_0) { /* BIT_0 触发 */ } if (xBits & BIT_1) { /* BIT_1 触发 */ }// 等待 BIT_0 和 BIT_1 同时发生 xBits = xEventGroupWaitBits(xEventGroup, BIT_0 | BIT_1, pdTRUE, pdTRUE, portMAX_DELAY);
2. 事件广播
- 多任务响应同一事件:当某个事件位被置位时,所有等待该事件的任务都会被唤醒。
// 任务A:等待 BIT_0 xEventGroupWaitBits(xEventGroup, BIT_0, pdFALSE, pdFALSE, portMAX_DELAY);// 任务B:等待 BIT_0 xEventGroupWaitBits(xEventGroup, BIT_0, pdFALSE, pdFALSE, portMAX_DELAY);// 中断中置位 BIT_0 xEventGroupSetBitsFromISR(xEventGroup, BIT_0, &xHigherPriorityTaskWoken);
3. 任务同步
- 同步点:多个任务在某个同步点等待其他任务完成后再继续执行。
// 任务1:设置 BIT_0 并等待 BIT_1 xEventGroupSync(xEventGroup, BIT_0, BIT_1, portMAX_DELAY);// 任务2:设置 BIT_1 并等待 BIT_0 xEventGroupSync(xEventGroup, BIT_1, BIT_0, portMAX_DELAY);
五、ISR中使用事件组的注意事项
- 非确定性操作:FreeRTOS禁止在ISR中直接执行非确定性操作(如唤醒多个任务)。因此,
xEventGroupSetBitsFromISR
实际通过定时器守护任务异步执行事件位设置。 - 上下文切换:若唤醒的高优先级任务需要立即运行,需在ISR中调用
portYIELD_FROM_ISR(xHigherPriorityTaskWoken)
。
六、事件组与队列/信号量的对比
特性 | 事件组 | 队列/信号量 |
---|---|---|
唤醒方式 | 多任务唤醒(一对多广播) | 单任务唤醒(一对一) |
事件组合 | 支持或/与关系(多个事件组合触发) | 仅支持单一事件触发 |
资源消耗 | 低(仅需一个位掩码) | 高(需存储数据或计数) |
适用场景 | 多任务同步、事件广播、多条件等待 | 数据传递、资源管理、简单同步 |
七、代码示例
1. 按键事件触发多个任务
// 创建事件组
EventGroupHandle_t xButtonEventGroup = xEventGroupCreate();// 任务A:LED闪烁报警
void vLEDTask(void *pvParameters) {while (1) {xEventGroupWaitBits(xButtonEventGroup, BIT_0, pdTRUE, pdFALSE, portMAX_DELAY);// LED闪烁}
}// 任务B:蜂鸣器报警
void vBuzzerTask(void *pvParameters) {while (1) {xEventGroupWaitBits(xButtonEventGroup, BIT_0, pdTRUE, pdFALSE, portMAX_DELAY);// 蜂鸣器响}
}// 中断服务程序:按键按下触发事件
void GPIO_IRQHandler(void) {BaseType_t xHigherPriorityTaskWoken = pdFALSE;xEventGroupSetBitsFromISR(xButtonEventGroup, BIT_0, &xHigherPriorityTaskWoken);portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
}
2. 等待多个事件同时发生
// 任务A:等待 BIT_0 和 BIT_1 同时发生
void vTaskA(void *pvParameters) {while (1) {EventBits_t xBits = xEventGroupWaitBits(xEventGroup, BIT_0 | BIT_1, pdTRUE, pdTRUE, portMAX_DELAY);if ((xBits & BIT_0) && (xBits & BIT_1)) {// 执行操作}}
}