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 xTaskWaitNULL;// 等待通知的任务句柄staticTaskHandle_t xTaskGiveNULL;// 发送通知的任务句柄// 定义一个掩码低3位全为1即二进制 0b111#defineLOWTHREEBITS(1UL0UL)|(1UL1UL)|(1UL2UL)// 任务函数等待通知并读取通知值voidtaskWait(void*pvParam){uint32_tulNotificationValue;// 用于存放本任务收到的32位通知值BaseType_t xResult;// 保存API返回结果while(1){// vTaskDelay(1000); // (可选) 延时1秒此处被注释不使用// 等待通知参数说明// 0x00 : 进入等待前不清除通知值中的任何位// 0x00 : 成功收到通知后也不清除任何位保留原值// ulNotificationValue : 返回清除前的通知值// portMAX_DELAY : 无限等待直到收到通知xResultxTaskNotifyWait(0x00,// 运行前不清除任何位0x00,// 运行后不清除任何位ulNotificationValue,// 获取通知值portMAX_DELAY);// 一直等待if(xResultpdTRUE){// 如果成功收到通知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忽略第二个参数0xResultxTaskNotify(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(xResultpdPASS?成功\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中的位与当前通知值做按位或OReIncrement通知值加 1ulValue被忽略eSetValueWithOverwrite直接覆盖通知值无论之前是否有 pendingeSetValueWithoutOverwrite仅当接收任务没有 pending 通知时才覆盖值否则返回pdFAILeNoAction不改变通知值但将通知状态设为 pending仅用于唤醒示例中演示了多种用法例如// 通知值加1类似计数信号量xTaskNotify(xTaskWait,0,eIncrement);// 设置 bit3 为1与原有值 ORxTaskNotify(xTaskWait,(1UL4UL),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阻塞超时时间。示例中xResultxTaskNotifyWait(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 状态等细节避免数据丢失。