FreeRTOS事件组和任务通知详解:为什么任务通知比队列更轻量?

FreeRTOS事件组和任务通知详解:为什么任务通知比队列更轻量?

在 FreeRTOS 中,任务之间经常需要进行:

  • 任务同步
  • 状态等待
  • 事件通知
  • 数据传递

前面我们学习了:

  • 二值信号量
  • 计数信号量
  • 队列 Queue
  • 互斥量 Mutex

这些机制解决了不同的问题。

但是在实际项目中,还会遇到两个非常典型的问题:


问题1:

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

例如:

系统启动:

复制代码
网络初始化完成

传感器初始化完成

Flash初始化完成

三个条件都满足:

系统才能进入正常运行。

这种场景:

使用:

Event Group(事件组)


问题2:

一个任务只需要通知另一个任务:

"我有事情,你运行一下"。

例如:

UART收到数据:

复制代码
UART中断

↓

通知UART任务处理

这种场景:

使用:

Task Notification(任务通知)

而且任务通知相比队列:

更加轻量。


一、什么是事件组 Event Group?

Event Group:

中文:

事件组

它是 FreeRTOS 提供的一种:

多事件同步机制。

它通过:

text 复制代码
bit位

表示不同事件。

例如:

一个8位事件组:

复制代码
BIT7 BIT6 BIT5 BIT4 BIT3 BIT2 BIT1 BIT0

 0    0    0    0    0    1    0    1

每一个bit代表一个事件。

例如:

复制代码
BIT0 = 网络连接完成

BIT1 = 传感器初始化完成

BIT2 = Flash初始化完成

如果:

复制代码
BIT0=1

BIT1=1

BIT2=1

表示:

三个条件全部完成。


二、为什么不用多个二值信号量?

假设系统启动需要等待:

三个模块:

复制代码
网络

传感器

存储

使用二值信号量:

可能设计:

复制代码
Network_Sem

Sensor_Sem

Flash_Sem

系统任务:

需要:

c 复制代码
xSemaphoreTake(Network_Sem);

xSemaphoreTake(Sensor_Sem);

xSemaphoreTake(Flash_Sem);

问题:

  • 对象太多
  • 管理复杂
  • 判断困难

如果增加:

复制代码
蓝牙

GPS

摄像头

代码会越来越复杂。


事件组:

只需要:

复制代码
一个 Event Group

管理:

多个事件。

结构:

复制代码
        Event Group


BIT0  网络完成

BIT1  传感器完成

BIT2  Flash完成

BIT3  蓝牙完成

更加清晰。


三、事件组的核心操作

FreeRTOS事件组主要有三个操作。


1. 创建事件组

函数:

c 复制代码
xEventGroupCreate()

示例:

c 复制代码
EventGroupHandle_t SystemEvent;


SystemEvent =
xEventGroupCreate();

2. 设置事件

函数:

c 复制代码
xEventGroupSetBits()

例如:

网络初始化完成:

c 复制代码
xEventGroupSetBits(
    SystemEvent,
    BIT0
);

表示:

BIT0事件发生。


3. 等待事件

函数:

c 复制代码
xEventGroupWaitBits()

例如:

系统任务等待:

网络 + 传感器 + Flash

c 复制代码
xEventGroupWaitBits(

SystemEvent,

BIT0 | BIT1 | BIT2,

pdTRUE,

pdTRUE,

portMAX_DELAY

);

含义:

等待:

复制代码
BIT0

+

BIT1

+

BIT2

全部为1。


四、事件组典型应用场景

场景1:系统启动同步

这是最经典应用。

例如:

嵌入式设备启动:

复制代码
Boot

↓

初始化网络

↓

初始化传感器

↓

初始化存储

↓

进入业务

每个任务完成后:

设置自己的bit。

例如:

网络任务:

c 复制代码
xEventGroupSetBits(
SystemEvent,
NET_READY
);

传感器:

c 复制代码
xEventGroupSetBits(
SystemEvent,
SENSOR_READY
);

主任务:

等待:

c 复制代码
NET_READY |
SENSOR_READY

场景2:多条件报警

例如:

设备报警任务。

需要关注:

复制代码
温度异常

风扇异常

通信异常

定义:

c 复制代码
#define TEMP_ERROR  (1<<0)

#define FAN_ERROR   (1<<1)

#define COMM_ERROR  (1<<2)

任何一个发生:

设置对应bit。

报警任务:

等待:

c 复制代码
TEMP_ERROR |
FAN_ERROR |
COMM_ERROR

即可。


场景3:模块状态汇总

例如:

智能设备:

复制代码
WiFi连接

MQTT连接

时间同步

App任务:

需要知道:

当前系统状态。

事件组:

非常适合。


五、事件组不适合什么?

事件组不是万能的。


1. 需要传递数据

例如:

温度值:

复制代码
25.6℃

事件组只能告诉:

复制代码
温度数据来了

但是不能传:

复制代码
25.6

这种情况:

使用:

Queue。


2. 保护共享资源

例如:

两个任务访问:

复制代码
UART
I2C
SPI

需要:

Mutex。


3. 单个简单通知

例如:

按键一次触发。

使用:

二值信号量。

更简单。


六、什么是任务通知 Task Notification?

任务通知:

是 FreeRTOS 中一种特殊通信机制。

特点:

每个任务内部自带一个通知值。

也就是说:

普通队列:

复制代码
Task

↓

Queue对象

↓

Task

需要额外创建:

Queue。

任务通知:

复制代码
Task A

↓

Task B内部通知值

直接通知目标任务。


七、任务通知为什么比队列轻量?

这是重点。


1. 少一个内核对象

Queue:

需要:

复制代码
Queue Control Block

+

Queue Storage

例如:

复制代码
任务

↓

队列对象

↓

数据

任务通知:

直接:

复制代码
任务

↓

Notification Value

少了一层。


2. 占用RAM更少

队列需要:

  • 队列控制结构
  • 缓冲空间
  • 数据存储

任务通知:

只需要:

任务自身的:

复制代码
32bit通知值

所以:

RAM更小。


3. 执行速度更快

队列:

流程:

复制代码
发送任务

↓

访问Queue

↓

复制数据

↓

唤醒任务

任务通知:

复制代码
发送

↓

修改通知值

↓

唤醒任务

路径更短。


八、任务通知使用方式

发送通知

任务:

c 复制代码
xTaskNotifyGive(TaskHandle);

接收通知

任务:

c 复制代码
ulTaskNotifyTake(
pdTRUE,
portMAX_DELAY
);

例如:

UART任务:

c 复制代码
void UART_Task(void *arg)
{

while(1)
{

ulTaskNotifyTake(
pdTRUE,
portMAX_DELAY
);


Process_UART();

}

}

中断:

c 复制代码
void UART_IRQHandler()
{

vTaskNotifyGiveFromISR(
UART_TaskHandle,
NULL
);

}

流程:

复制代码
UART中断

↓

任务通知

↓

UART任务运行

九、任务通知支持哪些功能?

FreeRTOS任务通知不仅可以通知。

还可以模拟:

1. 二值信号量

类似:

复制代码
0/1

2. 计数信号量

例如:

复制代码
事件次数

3. 事件标志

例如:

复制代码
BIT0

BIT1

BIT2

所以:

任务通知实际上非常强大。


十、任务通知和队列区别

比较 Queue Task Notification
通信对象 独立队列 任务自身
RAM占用 较高 很低
速度 较慢 更快
数据传输 支持任意数据 主要传递32位值
多个接收者 支持 通常一对一
使用复杂度 较高 简单

十一、什么时候使用任务通知?

适合:

1. 中断通知任务

例如:

复制代码
UART

DMA

ADC

GPIO

2. 一个任务唤醒另一个任务

例如:

复制代码
采集任务

↓

处理任务

3. 计数事件

例如:

收到多少次脉冲。


十二、什么时候不要使用任务通知?

1. 多任务共享数据

使用:

Queue。


2. 多个任务等待同一个消息

使用:

Queue/Event Group。


3. 复杂数据传输

例如:

结构体:

c 复制代码
typedef struct
{
int temp;
int voltage;
int state;

}Data;

使用:

Queue。


十三、事件组 VS 任务通知总结

功能 事件组 任务通知
核心作用 多个事件同步 任务间快速通知
管理对象 多个bit 单个任务
数据传输 不能 有限
RAM占用 较低 最低
速度 最快
典型场景 等待多个条件 中断唤醒任务

十四、工程选择建议

如果你的问题是:

"我需要等待多个条件"

例如:

复制代码
网络完成

传感器完成

存储完成

选择:

复制代码
Event Group

如果你的问题是:

"我要快速通知另一个任务"

例如:

复制代码
UART中断

DMA完成

定时器触发

选择:

复制代码
Task Notification

如果你的问题是:

"我要传递数据"

例如:

复制代码
温度值

传感器结构体

通信数据包

选择:

复制代码
Queue

十五、总结

FreeRTOS提供了很多任务同步机制:

  • Semaphore
  • Queue
  • Event Group
  • Task Notification
  • Mutex

它们解决的问题不同。

一句话总结:

事件组适合"一个任务等待多个事件",任务通知适合"一对一快速唤醒任务"。

再进一步理解:

队列传递数据,事件组管理状态,任务通知追求速度和轻量。

掌握这些机制后,FreeRTOS中的任务协作设计就基本形成完整体系。

相关推荐
wuyk5552 小时前
12.归并排序:分治思想的稳定排序算法
开发语言·数据结构·算法·排序算法
吴声子夜歌2 小时前
ApacheCommons——commons-text(模板替换与文本算法)
java·开发语言·算法·apache
quantdash_cc2 小时前
量化数据源怎么选:稳定性、实时性和数据完整性到底该如何权衡?
开发语言·python·数据分析·量化交易·股票数据·quantdash
萧瑟余晖3 小时前
Java深入解析篇五十四之对象模型详解
java·开发语言
张文是假的啊3 小时前
Java | record | Controller逻辑
java·开发语言
不甘先生3 小时前
Go 中 type、方法与指针接收者:从 str_name.Name() 看懂 Go 的类型系统
开发语言·后端·golang
PHP实战开发录3 小时前
PHP权限系统设计为什么越改越复杂
系统架构·php·开发
quantdash_cc4 小时前
Python 股票 K 线数据质量校验:字段、缺失值、重复行和价格异常
开发语言·python·数据分析·量化交易·股票数据·quantdash
Zenova EdgeOS4 小时前
工业网关重试机制:从固定间隔到指数退避的工程实战
开发语言·网络·php