第14章 任务通知TaskNotify
约 2386 字大约 8 分钟
2026-01-19
14.1 概述
使用队列、信号量或事件组进行任务间通信需要通过中间对象, 发送任务写入通信对象,接收任务从通信对象读取:

使用任务通知进行任务间通信,发送任务直接向接收任务发送通知,而无需中间对象:

相关宏:
configUSE_TASK_NOTIFICATIONS 是否启动任务通知功能,默认值是1
configTASK_NOTIFICATION_ARRAY_ENTRIES 定义每个任务的任务通知数组大小,即每个任务支持的通知条目数,默认值是1相关函数
xTaskNotify() 发送通知,带有通知值
xTaskNotifyAndQuery() 发送通知,带有通知值并且保留接收任务的原通知值
xTaskNotifyGive() 发送通知,不带通知值
xTaskNotifyFromISR() 在中断中发送任务通知
xTaskNotifyAndQueryFromISR()
vTaskNotifyGiveFromISR()
ulTaskNotifyTake() 获取任务通知,可选退出函数时对通知置清零或减1
xTaskNotifyWait() 获取任务通知,可获取通知值和清除通知值的指定位14.2 任务通知模拟信号量(Semaphore)案例
① 需求
task1:检测到按键 SW3 按下,向 task2 发送任务通知; 检测到按键 SW4 按下,向task3发送任务通知
task2:接收来自于 task1 的任务通知(模拟二进制信号量)。
task3:检测到按键 SW5 按下,接收来自于 task1 的通知,并打印通知值(模拟计数信号量)。② 所用函数
xTaskNotifyGive() 发送通知,通知值+1(从0开始)
ulTaskNotifyTake() 获取任务通知,第一个参数:设置pdTRUE模拟二进制信号量(通知值清0),设置为pdFLASE模拟计数信号量(通知值-1)③ 代码
App_Task.c
#include "App_Task.h"
// 任务1 ------------------------------------
// 任务1函数的原型
void task1_callback(void *pvParameters);
// 任务1名称
#define TASK1_NAME "task1"
// 任务1堆栈大小
#define TASK1_STACK_SIZE 128
// 任务1的优先级
#define TASK1_PRIORITY 1
// 任务1的句柄
TaskHandle_t task1_handle;
// 任务2 ------------------------------------
// 任务2函数的原型
void task2_callback(void *pvParameters);
// 任务2名称
#define TASK2_NAME "task2"
// 任务2堆栈大小
#define TASK2_STACK_SIZE 128
// 任务2的优先级
#define TASK2_PRIORITY 2
// 任务2的句柄
TaskHandle_t task2_handle;
// 任务3 ------------------------------------
// 任务3函数的原型
void task3_callback(void *pvParameters);
// 任务3名称
#define TASK3_NAME "task3"
// 任务3堆栈大小
#define TASK3_STACK_SIZE 128
// 任务3的优先级
#define TASK3_PRIORITY 3
// 任务3的句柄
TaskHandle_t task3_handle;
/**
* @brief 启动 FreeRTOS 任务管理
*
*/
void App_Task_Start(void)
{
// 进入临界区
taskENTER_CRITICAL();
// 创建任务1
xTaskCreate(task1_callback, TASK1_NAME, TASK1_STACK_SIZE, NULL, TASK1_PRIORITY, &task1_handle) == pdPASS ? printf("任务1创建成功! \n") : printf("任务1穿件失败! \n");
// 创建任务2
xTaskCreate(task2_callback, TASK2_NAME, TASK2_STACK_SIZE, NULL, TASK2_PRIORITY, &task2_handle) == pdPASS ? printf("任务2创建成功! \n") : printf("任务2穿件失败! \n");
// 创建任务3
xTaskCreate(task3_callback, TASK3_NAME, TASK3_STACK_SIZE, NULL, TASK3_PRIORITY, &task3_handle) == pdPASS ? printf("任务3创建成功! \n") : printf("任务3穿件失败! \n");
// 退出临界区
taskEXIT_CRITICAL();
// 启动任务调度器 ( vTaskStartScheduler() 后面的代码不会被执行)
printf("任务调度器启动... \n");
vTaskStartScheduler();
}
// 任务1函数的实现
void task1_callback(void *pvParameters)
{
printf("任务1启动... \n");
while (1)
{
// 检测按键
switch (Int_Key_IsDetect())
{
case 3:
// 按键3被按下,向任务2发送任务通知
printf("任务1: 按键3被按下, 向任务2发送任务通知 \n");
xTaskNotifyGive(task2_handle);
break;
case 4:
// 按键4被按下,向任务3发送任务通知
printf("任务1: 按键4被按下, 向任务3发送任务通知 \n");
xTaskNotifyGive(task3_handle);
break;
default:
break;
}
}
}
// 任务2函数的实现
void task2_callback(void *pvParameters)
{
printf("任务2启动... \n");
while (1)
{
// 等待任务1发送的任务通知 (模拟接收二进制信号量), 接收不到就一直阻塞
printf("任务2: 等待接收任务通知(模拟接收二进制信号量)... \n");
ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
printf("任务2: 任务1发送的任务通知已接收 \n");
}
}
// 任务3函数的实现
void task3_callback(void *pvParameters)
{
printf("任务3启动... \n");
// 定义变量,用于保存接收到的通知值
uint32_t notify_value = 0;
while (1)
{
// 检测按键
switch (Int_Key_IsDetect())
{
case 5:
// 按键SW5被按下,接收任务通知, 接收不到就一直阻塞
printf("任务3: 等待接收任务通知(模拟接收计数型信号量)... \n");
notify_value = ulTaskNotifyTake(pdFALSE, portMAX_DELAY);
printf("任务3: 任务通知已接收,通知值: %d \n", notify_value);
break;
default:
break;
}
// 阻塞延时
vTaskDelay(100);
}
}14.3 任务通知带有通知值(Mailbox,消息邮箱)案例
① 需求
task1:检测到按下按键 SW3,向 task2 发送任务通知,指定通知值。
task2:用于接收任务通知,获取通知值并打印。② 所用函数
xTaskNotify() 发送通知,指定通知值
xTaskNotifyWait() 获取任务通知,可获取通知值和清除通知值的指定位③ 代码
App_Task.c
#include "App_Task.h"
// 任务1 ------------------------------------
// 任务1函数的原型
void task1_callback(void *pvParameters);
// 任务1名称
#define TASK1_NAME "task1"
// 任务1堆栈大小
#define TASK1_STACK_SIZE 128
// 任务1的优先级
#define TASK1_PRIORITY 1
// 任务1的句柄
TaskHandle_t task1_handle;
// 任务2 ------------------------------------
// 任务2函数的原型
void task2_callback(void *pvParameters);
// 任务2名称
#define TASK2_NAME "task2"
// 任务2堆栈大小
#define TASK2_STACK_SIZE 128
// 任务2的优先级
#define TASK2_PRIORITY 2
// 任务2的句柄
TaskHandle_t task2_handle;
/**
* @brief 启动 FreeRTOS 任务管理
*
*/
void App_Task_Start(void)
{
// 进入临界区
taskENTER_CRITICAL();
// 创建任务1
xTaskCreate(task1_callback, TASK1_NAME, TASK1_STACK_SIZE, NULL, TASK1_PRIORITY, &task1_handle) == pdPASS ? printf("任务1创建成功! \n") : printf("任务1穿件失败! \n");
// 创建任务2
xTaskCreate(task2_callback, TASK2_NAME, TASK2_STACK_SIZE, NULL, TASK2_PRIORITY, &task2_handle) == pdPASS ? printf("任务2创建成功! \n") : printf("任务2穿件失败! \n");
taskEXIT_CRITICAL();
// 启动任务调度器 ( vTaskStartScheduler() 后面的代码不会被执行)
printf("任务调度器启动... \n");
vTaskStartScheduler();
}
// 任务1函数的实现
void task1_callback(void *pvParameters)
{
printf("任务1启动... \n");
while (1)
{
// 检测按键
switch (Int_Key_IsDetect())
{
case 3:
// 按键3被按下,向任务2发送任务通知,带有通知值
printf("任务1: 按键3被按下, 向任务2发送任务通知 \n");
xTaskNotify(task2_handle, 250, eSetValueWithOverwrite);
break;
default:
break;
}
}
}
// 任务2函数的实现
void task2_callback(void *pvParameters)
{
printf("任务2启动... \n");
// 定义变量,用于保存接收到的通知值
uint32_t notify_value = 0;
while (1)
{
// 等待任务1发送的任务通知 (带有通知值), 接收不到就一直阻塞
printf("任务2: 等待接收任务通知... \n");
xTaskNotifyWait(0xFFFFFFFF, 0xFFFFFFFF, ¬ify_value, portMAX_DELAY);
printf("任务2: 任务1发送的任务通知已接收,通知值: %d \n", notify_value);
}
}14.4 任务通知模拟事件标志组(EventGroup)案例
① 需求
task1:用于按键扫描,SW3按下,BIT0置1; SW4按下,BIT1置1,SW5按下,BIT2置1,
task2:用于接收任务通知(只有BIT0、BIT1、BIT2全部置1),并打印相关提示信息。② 所用函数
xTaskNotify() 发送通知,带有通知值
xTaskNotifyWait() 获取任务通知,可获取通知值和清除通知值的指定位③ 代码
App_Task.h
添加宏定义:
// 宏定义 事件组掩码 第0位掩码
#define EVENT_BIT_0 (1 << 0)
// 宏定义 事件组掩码 第1位掩码
#define EVENT_BIT_1 (1 << 1)
// 宏定义 事件组掩码 第2位掩码
#define EVENT_BIT_2 (1 << 2)App_Task.c
#include "App_Task.h"
// 任务1 ------------------------------------
// 任务1函数的原型
void task1_callback(void *pvParameters);
// 任务1名称
#define TASK1_NAME "task1"
// 任务1堆栈大小
#define TASK1_STACK_SIZE 128
// 任务1的优先级
#define TASK1_PRIORITY 1
// 任务1的句柄
TaskHandle_t task1_handle;
// 任务2 ------------------------------------
// 任务2函数的原型
void task2_callback(void *pvParameters);
// 任务2名称
#define TASK2_NAME "task2"
// 任务2堆栈大小
#define TASK2_STACK_SIZE 128
// 任务2的优先级
#define TASK2_PRIORITY 2
// 任务2的句柄
TaskHandle_t task2_handle;
/**
* @brief 启动 FreeRTOS 任务管理
*
*/
void App_Task_Start(void)
{
// 进入临界区
taskENTER_CRITICAL();
// 创建任务1
xTaskCreate(task1_callback, TASK1_NAME, TASK1_STACK_SIZE, NULL, TASK1_PRIORITY, &task1_handle) == pdPASS ? printf("任务1创建成功! \n") : printf("任务1穿件失败! \n");
// 创建任务2
xTaskCreate(task2_callback, TASK2_NAME, TASK2_STACK_SIZE, NULL, TASK2_PRIORITY, &task2_handle) == pdPASS ? printf("任务2创建成功! \n") : printf("任务2穿件失败! \n");
taskEXIT_CRITICAL();
// 启动任务调度器 ( vTaskStartScheduler() 后面的代码不会被执行)
printf("任务调度器启动... \n");
vTaskStartScheduler();
}
// 任务1函数的实现
void task1_callback(void *pvParameters)
{
printf("任务1启动... \n");
while (1)
{
// 检测按键
switch (Int_Key_IsDetect())
{
case 3:
// 按键3被按下,向任务2发送任务通知,将BIT0置1
printf("任务1: 按键3被按下, 向任务2发送任务通知,将BIT0置1 \n");
xTaskNotify(task2_handle, EVENT_BIT_0, eSetBits);
break;
case 4:
// 按键4被按下,向任务2发送任务通知,将BIT1置1
printf("任务1: 按键4被按下, 向任务2发送任务通知,将BIT1置1 \n");
xTaskNotify(task2_handle, EVENT_BIT_1, eSetBits);
break;
case 5:
// 按键5被按下,向任务2发送任务通知,将BIT2置1
printf("任务1: 按键5被按下, 向任务2发送任务通知,将BIT2置1 \n");
xTaskNotify(task2_handle, EVENT_BIT_2, eSetBits);
break;
default:
break;
}
}
}
// 任务2函数的实现
void task2_callback(void *pvParameters)
{
printf("任务2启动... \n");
// 定义变量,用于保存接收到的通知值
uint32_t notify_value = 0;
// 定义变量,作为模拟EventGroup使用
uint32_t event_group_value = 0;
while (1)
{
// 等待任务1发送的任务通知 (带有通知值), 接收不到就一直阻塞
printf("任务2: 等待接收任务通知... \n");
xTaskNotifyWait(0xFFFFFFFF, 0xFFFFFFFF, ¬ify_value, portMAX_DELAY);
if (notify_value == EVENT_BIT_0)
{
event_group_value |= EVENT_BIT_0;
}
if (notify_value == EVENT_BIT_1)
{
event_group_value |= EVENT_BIT_1;
}
if (notify_value == EVENT_BIT_2)
{
event_group_value |= EVENT_BIT_2;
}
// 只要BIT0、BIT1、BIT2都被置1,就打印当前EventGroup值
if (event_group_value == (EVENT_BIT_0 | EVENT_BIT_1 | EVENT_BIT_2))
{
// 打印消息
printf("任务2: BIT0、BIT1、BIT2都被置1,当前EventGroup值为: %d \n", event_group_value);
// 清零EventGroup
event_group_value = 0;
}
}
}