FreeRTOS 任务通知实战——Direct Task Notification
/* 程序: Direct Task Notification 要点: 一个任务可以有多个notification 每个notification包含4个字节的value 和 1个字节的stats stats用来记录当前的notification有没有被处理 pending or not pending 我们不能对stats进行直接的读写操作,是系统自动的 我们只能对notification value 进行操作 本程序演示了对notification的读,写 和 等待。 公众号:孤独的二进制 */// 声明任务句柄,用于引用两个任务staticTaskHandle_t xTaskWait=NULL;// 等待通知的任务句柄staticTaskHandle_t xTaskGive=NULL;// 发送通知的任务句柄// 定义一个掩码:低3位全为1(即二进制 0b111)#defineLOWTHREEBITS(1UL<<0UL)|(1UL<<1UL)|(1UL<<2UL)// 任务函数:等待通知并读取通知值voidtaskWait(void*pvParam){uint32_tulNotificationValue;// 用于存放本任务收到的32位通知值BaseType_t xResult;// 保存API返回结果while(1){// vTaskDelay(1000); // (可选) 延时1秒,此处被注释不使用// 等待通知,参数说明:// 0x00 : 进入等待前不清除通知值中的任何位// 0x00 : 成功收到通知后也不清除任何位(保留原值)// &ulNotificationValue : 返回清除前的通知值// portMAX_DELAY : 无限等待直到收到通知xResult=xTaskNotifyWait(0x00,// 运行前不清除任何位0x00,// 运行后不清除任何位&ulNotificationValue,// 获取通知值portMAX_DELAY);// 一直等待if(xResult==pdTRUE){// 如果成功收到通知Serial.println(ulNotificationValue);// 打印通知值(十进制)}else{// 超时(本例不会发生,因为无限等待)Serial.println("Timeout");}}}// 任务函数:读取按键,向等待任务发送通知voidtaskGive(void*pvParam){pinMode(23,INPUT_PULLUP);// 设置GPIO23为上拉输入模式,读取按键(低电平表示按下)BaseType_t xResult;// 保存通知发送结果while(1){if(digitalRead(23)==LOW){// 检测按键按下(低电平有效)// 向 xTaskWait 任务发送通知,采用 eIncrement 动作:使对方通知值加1(忽略第二个参数0)xResult=xTaskNotify(xTaskWait,0,eIncrement);// 以下为其他可选的通知方式(已注释),可自行替换测试:// xResult = xTaskNotify( xTaskWait, 0, eNoAction ); // 不改变值,仅设置pending状态// xResult = xTaskNotify( xTaskWait, ( 1UL << 4UL ), eSetBits ); // 设置第4位为1(与原有值做或运算)// xResult = xTaskNotify( xTaskWait, 0, eIncrement ); // 值加1(同上方有效行)// xResult = xTaskNotify( xTaskWait, LOWTHREEBITS, eSetValueWithOverwrite ); // 直接覆盖为低3位全1// xResult = xTaskNotify( xTaskWait, 0b11111111, eSetValueWithOverwrite ); // 覆盖为0xFF// xResult = xTaskNotify( xTaskWait, 0xFF, eSetValueWithOverwrite ); // 同上// xResult = xTaskNotify( xTaskWait, LOWTHREEBITS, eSetValueWithoutOverwrite );// 若无pending则覆盖,否则失败// 打印发送结果:pdPASS 表示成功,否则失败Serial.println(xResult==pdPASS?"成功\n":"失败\n");vTaskDelay(120);// 延时120ms,简单消抖,避免一次按下触发多次通知}}}// Arduino 初始化函数voidsetup(){Serial.begin(115200);// 初始化串口通信,波特率115200// 创建发送通知的任务 taskGive,栈大小 4096 字节,优先级 1xTaskCreate(taskGive,"",1024*4,NULL,1,&xTaskGive);// 创建等待通知的任务 taskWait,栈大小 4096 字节,优先级 1xTaskCreate(taskWait,"",1024*4,NULL,1,&xTaskWait);}// Arduino 主循环(空闲任务会运行,此处不做任何事)voidloop(){}FreeRTOS 任务通知实战——Direct Task Notification
在嵌入式实时操作系统中,任务间的同步与通信是核心课题。FreeRTOS 提供了多种机制,如队列、信号量、事件组等,而任务通知(Task Notification)是一种更轻量、更高效的选择。每个任务都有一个 32 位的通知值(ulNotifiedValue)和一个通知状态(ucNotifyState),系统自动维护状态,我们只需读写这个 32 位的值。
本文将基于一段 Arduino + FreeRTOS 的示例代码,演示如何实现两个任务之间的通知:一个任务负责发送通知(taskGive),另一个任务负责等待并处理通知(taskWait)。
一、代码概览
示例代码创建了两个任务:
taskGive:读取 GPIO 23(带内部上拉)的按键状态,按下时调用xTaskNotify()向taskWait发送通知。taskWait:调用xTaskNotifyWait()阻塞等待通知,收到后打印通知值(二进制格式)。
关键点:
- 每个任务有自己的 32 位通知值,初始为 0。
- 通知状态由系统管理(pending / not pending),我们无法直接读写。
- 通过
xTaskNotify()修改接收任务的通知值,并触发其状态变为 pending。 - 通过
xTaskNotifyWait()等待并消费通知,同时可清除指定的位。
二、核心 API 解析
1.xTaskNotify()—— 发送通知
BaseType_txTaskNotify(TaskHandle_t xTaskToNotify,uint32_tulValue,eNotifyAction eAction);参数eAction决定如何修改接收任务的通知值,常用取值:
| 动作 | 效果 |
|---|---|
eSetBits | ulValue中的位与当前通知值做按位或(OR) |
eIncrement | 通知值加 1(ulValue被忽略) |
eSetValueWithOverwrite | 直接覆盖通知值(无论之前是否有 pending) |
eSetValueWithoutOverwrite | 仅当接收任务没有 pending 通知时,才覆盖值;否则返回pdFAIL |
eNoAction | 不改变通知值,但将通知状态设为 pending(仅用于唤醒) |
示例中演示了多种用法,例如:
// 通知值加1(类似计数信号量)xTaskNotify(xTaskWait,0,eIncrement);// 设置 bit3 为1(与原有值 OR)xTaskNotify(xTaskWait,(1UL<<4UL),eSetBits);// 覆盖为低3位全1xTaskNotify(xTaskWait,LOWTHREEBITS,eSetValueWithOverwrite);2.xTaskNotifyWait()—— 等待通知
BaseType_txTaskNotifyWait(uint32_tulBitsToClearOnEntry,uint32_tulBitsToClearOnExit,uint32_t*pulNotificationValue,TickType_t xTicksToWait);ulBitsToClearOnEntry:在进入等待前,先清除通知值中的这些位(用于清零历史数据)。ulBitsToClearOnExit:成功收到通知后,再清除通知值中的这些位(通常设为ULONG_MAX表示全部清零,或设为0保留)。pulNotificationValue:输出参数,返回清除前的通知值(可设为NULL忽略)。xTicksToWait:阻塞超时时间。
示例中:
xResult=xTaskNotifyWait(0x00,// 进入时不主动清除任何位0x00,// 退出时也不清除,保持原值&ulNotificationValue,portMAX_DELAY);注意:退出时不清零,意味着后续调用可能看到相同的值,实际应用中通常需要清除已读的位。
三、示例行为分析
按键按下(GPIO 23 低电平):
taskGive调用xTaskNotify(xTaskWait, 0, eIncrement),使taskWait的通知值+1。- 如果
taskWait正阻塞在xTaskNotifyWait()上,立即唤醒。 - 打印 “成功”。
等待任务:
- 进入循环后立即调用
xTaskNotifyWait(),由于初始通知值为 0,且没有 pending 通知,任务进入阻塞。 - 收到通知后,将通知值(例如 1、2、3…)打印出来。
- 注意:示例中退出时没有清除通知值,因此多次按同一键,值会不断累加(
eIncrement),打印的值会依次为 1,2,3,…
- 进入循环后立即调用
消抖:
vTaskDelay(120)简单消除了按键抖动。
四、运行效果与注意事项
- 串口输出:每次按键会打印当前通知值(十进制,如需二进制可自行转换)。
- 如果尝试使用
eSetValueWithoutOverwrite而通知尚未被读取,第二次通知会失败(返回pdFAIL)。 - 任务通知比二进制信号量更快、占用 RAM 更少,但一个任务只能被一个其他任务通知(通知值只有一份)。如需多对一通信,仍需使用队列或事件组。
五、总结
任务通知是 FreeRTOS 中轻量级的任务间同步利器,尤其适合以下场景:
- 单生产者单消费者的简单事件传递
- 计数型或二进制型信号量
- 位掩码事件组(利用
eSetBits)
通过本文的示例,你可以快速上手xTaskNotify()和xTaskNotifyWait(),并根据不同的eNotifyAction实现灵活的数值操作。当然,也要留意通知的覆盖、pending 状态等细节,避免数据丢失。
