当前位置: 首页 > news >正文

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决定如何修改接收任务的通知值,常用取值:

动作效果
eSetBitsulValue中的位与当前通知值做按位或(OR)
eIncrement通知值加 1ulValue被忽略)
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);

注意:退出时不清零,意味着后续调用可能看到相同的值,实际应用中通常需要清除已读的位。


三、示例行为分析

  1. 按键按下(GPIO 23 低电平):

    • taskGive调用xTaskNotify(xTaskWait, 0, eIncrement),使taskWait的通知值+1
    • 如果taskWait正阻塞在xTaskNotifyWait()上,立即唤醒。
    • 打印 “成功”。
  2. 等待任务

    • 进入循环后立即调用xTaskNotifyWait(),由于初始通知值为 0,且没有 pending 通知,任务进入阻塞。
    • 收到通知后,将通知值(例如 1、2、3…)打印出来。
    • 注意:示例中退出时没有清除通知值,因此多次按同一键,值会不断累加(eIncrement),打印的值会依次为 1,2,3,…
  3. 消抖vTaskDelay(120)简单消除了按键抖动。


四、运行效果与注意事项

  • 串口输出:每次按键会打印当前通知值(十进制,如需二进制可自行转换)。
  • 如果尝试使用eSetValueWithoutOverwrite而通知尚未被读取,第二次通知会失败(返回pdFAIL)。
  • 任务通知比二进制信号量更快、占用 RAM 更少,但一个任务只能被一个其他任务通知(通知值只有一份)。如需多对一通信,仍需使用队列或事件组。

五、总结

任务通知是 FreeRTOS 中轻量级的任务间同步利器,尤其适合以下场景:

  • 单生产者单消费者的简单事件传递
  • 计数型或二进制型信号量
  • 位掩码事件组(利用eSetBits

通过本文的示例,你可以快速上手xTaskNotify()xTaskNotifyWait(),并根据不同的eNotifyAction实现灵活的数值操作。当然,也要留意通知的覆盖、pending 状态等细节,避免数据丢失。

http://www.cnnetsun.cn/news/1612988.html

相关文章:

  • 开源项目xiaomusic配置教程:解决小爱音响设备DID配置问题
  • QQ空间记忆备份终极指南:3步永久保存你的数字青春
  • 【Java AI推理性能优化终极指南】:20年专家亲授JVM调优+ONNX Runtime集成+量化加速的5大黄金法则
  • Linux initramfs深度解析: 从内核启动到根文件系统的桥梁(4)
  • Halcon仿射矩阵求解:从vector_to_aniso到线性方程组,原理与实战代码解析
  • ESP32 Wi-Fi配网实战:AP+Web双模轻量级方案
  • 嵌入式动态内存管理:挑战与最佳实践
  • 从底层思维3分钟彻底弄清卷积神经网络CNN
  • VO2金属态Drude模型参数(文献校准版,别瞎改)
  • BiliBiliCCSubtitle开源工具:告别繁琐字幕提取的高效解决方案
  • 手把手教你搭建RAG知识库:从零到一,让你的知识库从“仓库”变“助手”!
  • AI-AGENT概念解析 - LLM本地部署
  • 1117系列线性稳压器性能对比与选型指南
  • 实战解析:东方财富网股票数据爬取与多板块自动化抓取策略
  • Transformer 从0到1:长时依赖问题的本质——梯度消失与爆炸
  • 3倍性能提升:ROCmLibs-for-gfx1103-AMD780M-APU性能调优方案与异构计算加速指南
  • 【7天Java面试突击版】100集Java面试八股文,巧拿高薪offer神器!
  • 如何高效使用draw.io桌面版:完整实用指南
  • 独立站SEO优化过程中常见的问题有哪些
  • Cosmos-Reason1-7B与卷积神经网络的融合应用探索
  • ARMv8-A异常处理实战:从SVC系统调用看Linux内核如何响应你的请求
  • 当nodepad遇见AI:利用快马平台快速集成智能代码补全与文本润色功能
  • [语音转文字工具] AsrTools:让音频转写效率提升300%的开源解决方案
  • 用快马AI五分钟搭建前端面试题库:交互式原型开发实战
  • 深度解析:相机、LiDAR与IMU紧耦合SLAM技术的最新进展与挑战
  • PaddleOCR-VL-WEB部署避坑指南:常见问题与优化建议汇总
  • C++ Move 构造函数性能优化
  • 利用快马平台快速原型origin风格的数据可视化应用
  • Watchy开源电子墨水屏手表:低功耗嵌入式系统全栈解析
  • 如何通过5个策略打造完美的Obsidian个性化主页:终极定制方案