FreeRTOS_任务通知

犀利的毛毛虫 发布于 2025-03-12 396 次阅读


任务通知的简介(了解)

任务通知是 FreeRTOS 中一种用于任务间通信的机制,它允许一个任务向其他任务发送简单的通知或信号,以实现任务间的同步和协作。任务通知通常用于替代二值信号量或事件标志组,提供了更轻量级的任务间通信方式。

大多数任务间通信方法通过中间对象,如队列、信号量或事件组。发送任务写入通信对象,接收任务从通信对象读取。当使用直接任务通知时,顾名思义,发送任务直接向接收任务发送通知,而无需中间对象。

每个 RTOS 任务都有一个任务通知组,每条通知均独立运行,都有“挂起”或“非挂起”的通知状态,以及一个 32 位通知值。常量 configTASK_NOTIFICATION_ARRAY_ENTRIES 可设置任务通知组中的索引数量。在 FreeRTOS V10.4.0 版本前,任务只有单条任务通知(即只能一对一),没有任务通知组。

向任务发送“任务通知” 会将目标任务通知设为“挂起”状态。 正如任务可以阻塞中间对象 (如等待信号量可用的信号量),任务也可以阻塞任务通知, 以等待通知状态变为“挂起”。向任务发送“任务通知”也可以更新目标通知的值(可选),可使用下列任一方法:

  • 覆盖原值,无论接收任务是否已读取被覆盖的值。
  • 覆盖原值(仅当接收任务已读取被覆盖的值时)。
  • 在值中设置一个或多个位。
  • 对值进行增量(添加 1)。

RTOS 任务通知功能默认为启用状态,将configUSE_TASK_NOTIFICATIONS 设为0可以禁用。

任务通知相关API函数介绍(熟悉)

任务通知相关函数如下:

函数描述
xTaskNotify()发送通知,带有通知值
xTaskNotifyAndQuery()发送通知,带有通知值并且保留接收任务的原通知值
xTaskNotifyGive()发送通知,不带通知值
xTaskNotifyFromISR()在中断中发送任务通知
xTaskNotifyAndQueryFromISR()
vTaskNotifyGiveFromISR()
ulTaskNotifyTake()获取任务通知,可选退出函数时对通知值清零或减1
xTaskNotifyWait()获取任务通知,可获取通知值和清除通知值的指定位

注意:发送通知有相关ISR函数,接收通知没有ISR函数,不能在ISR中接收任务通知。

任务通知模拟信号量实验(掌握)实验目标

学习将任务通知用作轻量级二进制信号量:

  • start_task:用来创建其他2个任务。
  • task1:用于按键扫描,当检测到按键KEY1被按下时,将发送任务通知。
  • task2:用于接收任务通知,并打印相关提示信息。

freertos_demo.c代码清单

#include "freertos_demo.h"
/* freertos相关的头文件,必须的 */
#include "FreeRTOS.h"
#include "task.h"
/* 需要用到的其他头文件 */
#include "LED.h"
#include "Key.h"

/* 启动任务的配置 */
#define START_TASK_STACK 128
#define START_TASK_PRIORITY 1
TaskHandle_t start_task_handle;
void start_task(void *pvParameters);

/* 任务1的配置 */
#define TASK1_STACK 128
#define TASK1_PRIORITY 2
TaskHandle_t task1_handle;
void task1(void *pvParameters);

/* 任务2的配置 */
#define TASK2_STACK 128
#define TASK2_PRIORITY 3
TaskHandle_t task2_handle;
void task2(void *pvParameters);



/**
 * @description: 启动FreeRTOS
 * @return {*}
 */
void freertos_start(void)
{
    /* 1.创建一个启动任务 */
    xTaskCreate((TaskFunction_t)start_task,               // 任务函数的地址
                (char *)"start_task",                     // 任务名字符串
                (configSTACK_DEPTH_TYPE)START_TASK_STACK, // 任务栈大小,默认最小128,单位4字节
                (void *)NULL,                             // 传递给任务的参数
                (UBaseType_t)START_TASK_PRIORITY,         // 任务的优先级
                (TaskHandle_t *)&start_task_handle);      // 任务句柄的地址

    /* 2.启动调度器:会自动创建空闲任务 */
    vTaskStartScheduler();
}

/**
 * @description: 启动任务:用来创建其他Task
 * @param {void} *pvParameters
 * @return {*}
 */
void start_task(void *pvParameters)
{
    /* 进入临界区:保护临界区里的代码不会被打断 */
    taskENTER_CRITICAL();

    /* 创建3个任务 */
    xTaskCreate((TaskFunction_t)task1,
                (char *)"task1",
                (configSTACK_DEPTH_TYPE)TASK1_STACK,
                (void *)NULL,
                (UBaseType_t)TASK1_PRIORITY,
                (TaskHandle_t *)&task1_handle);
    xTaskCreate((TaskFunction_t)task2,
                (char *)"task2",
                (configSTACK_DEPTH_TYPE)TASK2_STACK,
                (void *)NULL,
                (UBaseType_t)TASK2_PRIORITY,
                (TaskHandle_t *)&task2_handle);

    /* 启动任务只需要执行一次即可,用完就删除自己 */
    vTaskDelete(NULL);

    /* 退出临界区 */
    taskEXIT_CRITICAL();
}

/**
 * @description: 任务一:用于按键扫描,当检测到按键KEY1被按下时,将发送任务通知。
 * @param {void} *pvParameters
 * @return {*}
 */
void task1(void *pvParameters)
{
    uint8_t key = 0;
    BaseType_t res = 0;
    while (1)
    {
        key = Key_Detect();
        if(key==KEY1_PRESS)
        {
            /* 发送任务通知 */
            res = xTaskNotifyGive(task2_handle);
            if(res == pdPASS)
            {
                printf("task1向task2发送任务通知成功...\r\n");
            }
        }
        vTaskDelay(500);
    }
}

/**
 * @description: 任务二:用于接收任务通知,并打印相关提示信息。
 * @param {void} *pvParameters
 * @return {*}
 */
void task2(void *pvParameters)
{
    uint32_t notify_value = 0;
    while (1)
    {
        printf("task2运行...\r\n");
        
        notify_value = ulTaskNotifyTake(
            // pdTRUE,            // 接受完通知后,是否对通知置清零: pdTRUE 清零, pdFALSE 不清零,通知值-1
            pdFALSE,            // 接受完通知后,是否对通知置清零: pdTRUE 清零, pdFALSE 不清零,通知值-1
            portMAX_DELAY      // 等待任务通知的最大阻塞时间
        );

        printf("Task2接收到通知值=%d\r\n",notify_value);


        // vTaskDelay(500);
        vTaskDelay(5000);
    }
}


任务通知模拟消息邮箱实验(掌握)实验目标

学习将任务通知用作轻量级邮箱:

  • start_task:用来创建其他2个任务。
  • task1:用于按键扫描,将按下的按键键值通过任务通知发送给指定任务。
  • task2:用于接收任务通知,并根据接收到的数据做相应动作。

freertos_demo.c代码清单

#include "freertos_demo.h"
/* freertos相关的头文件,必须的 */
#include "FreeRTOS.h"
#include "task.h"
/* 需要用到的其他头文件 */
#include "LED.h"
#include "Key.h"

/* 启动任务的配置 */
#define START_TASK_STACK 128
#define START_TASK_PRIORITY 1
TaskHandle_t start_task_handle;
void start_task(void *pvParameters);

/* 任务1的配置 */
#define TASK1_STACK 128
#define TASK1_PRIORITY 2
TaskHandle_t task1_handle;
void task1(void *pvParameters);

/* 任务2的配置 */
#define TASK2_STACK 128
#define TASK2_PRIORITY 3
TaskHandle_t task2_handle;
void task2(void *pvParameters);

/**
 * @description: 启动FreeRTOS
 * @return {*}
 */
void freertos_start(void)
{
    /* 1.创建一个启动任务 */
    xTaskCreate((TaskFunction_t)start_task,               // 任务函数的地址
                (char *)"start_task",                     // 任务名字符串
                (configSTACK_DEPTH_TYPE)START_TASK_STACK, // 任务栈大小,默认最小128,单位4字节
                (void *)NULL,                             // 传递给任务的参数
                (UBaseType_t)START_TASK_PRIORITY,         // 任务的优先级
                (TaskHandle_t *)&start_task_handle);      // 任务句柄的地址

    /* 2.启动调度器:会自动创建空闲任务 */
    vTaskStartScheduler();
}

/**
 * @description: 启动任务:用来创建其他Task
 * @param {void} *pvParameters
 * @return {*}
 */
void start_task(void *pvParameters)
{
    /* 进入临界区:保护临界区里的代码不会被打断 */
    taskENTER_CRITICAL();

    /* 创建3个任务 */
    xTaskCreate((TaskFunction_t)task1,
                (char *)"task1",
                (configSTACK_DEPTH_TYPE)TASK1_STACK,
                (void *)NULL,
                (UBaseType_t)TASK1_PRIORITY,
                (TaskHandle_t *)&task1_handle);
    xTaskCreate((TaskFunction_t)task2,
                (char *)"task2",
                (configSTACK_DEPTH_TYPE)TASK2_STACK,
                (void *)NULL,
                (UBaseType_t)TASK2_PRIORITY,
                (TaskHandle_t *)&task2_handle);

    /* 启动任务只需要执行一次即可,用完就删除自己 */
    vTaskDelete(NULL);

    /* 退出临界区 */
    taskEXIT_CRITICAL();
}

/**
 * @description: 任务一:用于按键扫描,当检测到按键KEY1被按下时,将发送任务通知。
 * @param {void} *pvParameters
 * @return {*}
 */
void task1(void *pvParameters)
{
    uint8_t key = 0;
    BaseType_t res = 0;
    while (1)
    {
        key = Key_Detect();
        if (key == KEY1_PRESS || key == KEY2_PRESS)
        {
            /* 发送任务通知 */
            res = xTaskNotify(
                task2_handle,          // 接收方的任务句柄
                key,                   // 要发送的通知值
                eSetValueWithOverwrite // 写入的行为:强行覆盖
            );

            if (res == pdPASS)
            {
                printf("task1向task2发送任务通知[%d]成功...\r\n", key);
            }
        }
        vTaskDelay(500);
    }
}

/**
 * @description: 任务二:用于接收任务通知,并打印相关提示信息。
 * @param {void} *pvParameters
 * @return {*}
 */
void task2(void *pvParameters)
{
    uint32_t notify_value = 0;
    BaseType_t res = 0;
    while (1)
    {
        printf("task2运行...\r\n");

        res = xTaskNotifyWait(
            0x00000000,    // 接收通知前是否清理通知值,全0,表示32bit的都是0,都不清理
            0xffffffff,    // 接收到通知值后,是否清理通知值, 全1,表示32bit都是1,都要清零
            &notify_value, // 用来保存读取到的通知值
            portMAX_DELAY);
            
        if (res == pdTRUE)
        {

            printf("Task2接收到通知值=%d\r\n", notify_value);
        }
    }
}

任务通知模拟事件标志组实验(掌握)实验目标

学习将任务通知用作轻量级事件标志组:

  • start_task:用来创建其他2个任务。
  • task1:用于按键扫描,当检测到按键按下时,发送任务通知设置不同标志位。
  • task2:用于接收任务通知,并打印相关提示信息。

freertos_demo.c代码清单

#include "freertos_demo.h"
/* freertos相关的头文件,必须的 */
#include "FreeRTOS.h"
#include "task.h"
/* 需要用到的其他头文件 */
#include "LED.h"
#include "Key.h"

/* 启动任务的配置 */
#define START_TASK_STACK 128
#define START_TASK_PRIORITY 1
TaskHandle_t start_task_handle;
void start_task(void *pvParameters);

/* 任务1的配置 */
#define TASK1_STACK 128
#define TASK1_PRIORITY 2
TaskHandle_t task1_handle;
void task1(void *pvParameters);

/* 任务2的配置 */
#define TASK2_STACK 128
#define TASK2_PRIORITY 3
TaskHandle_t task2_handle;
void task2(void *pvParameters);

/**
 * @description: 启动FreeRTOS
 * @return {*}
 */
void freertos_start(void)
{
    /* 1.创建一个启动任务 */
    xTaskCreate((TaskFunction_t)start_task,               // 任务函数的地址
                (char *)"start_task",                     // 任务名字符串
                (configSTACK_DEPTH_TYPE)START_TASK_STACK, // 任务栈大小,默认最小128,单位4字节
                (void *)NULL,                             // 传递给任务的参数
                (UBaseType_t)START_TASK_PRIORITY,         // 任务的优先级
                (TaskHandle_t *)&start_task_handle);      // 任务句柄的地址

    /* 2.启动调度器:会自动创建空闲任务 */
    vTaskStartScheduler();
}

/**
 * @description: 启动任务:用来创建其他Task
 * @param {void} *pvParameters
 * @return {*}
 */
void start_task(void *pvParameters)
{
    /* 进入临界区:保护临界区里的代码不会被打断 */
    taskENTER_CRITICAL();

    /* 创建3个任务 */
    xTaskCreate((TaskFunction_t)task1,
                (char *)"task1",
                (configSTACK_DEPTH_TYPE)TASK1_STACK,
                (void *)NULL,
                (UBaseType_t)TASK1_PRIORITY,
                (TaskHandle_t *)&task1_handle);
    xTaskCreate((TaskFunction_t)task2,
                (char *)"task2",
                (configSTACK_DEPTH_TYPE)TASK2_STACK,
                (void *)NULL,
                (UBaseType_t)TASK2_PRIORITY,
                (TaskHandle_t *)&task2_handle);

    /* 启动任务只需要执行一次即可,用完就删除自己 */
    vTaskDelete(NULL);

    /* 退出临界区 */
    taskEXIT_CRITICAL();
}

/**
 * @description: 任务一:用于按键扫描,当检测到按键KEY1被按下时,将发送任务通知。
 * @param {void} *pvParameters
 * @return {*}
 */
#define EVENTBIT_0 (1 << 0)
#define EVENTBIT_1 (1 << 1)
void task1(void *pvParameters)
{
    uint8_t key = 0;
    BaseType_t res = 0;
    while (1)
    {
        key = Key_Detect();
        if (key == KEY1_PRESS)
        {
            /* 发送任务通知 */
            res = xTaskNotify(
                task2_handle, // 接收方的任务句柄
                EVENTBIT_0,   // 要发送的通知值: 需要置位的bit置1
                eSetBits      // 写入的行为:设置bit位
            );

            if (res == pdPASS)
            {
                printf("KEY1按下,设置bit0为1..\r\n");
            }
        }
        else if (key == KEY2_PRESS)
        {
            /* 发送任务通知 */
            res = xTaskNotify(
                task2_handle, // 接收方的任务句柄
                EVENTBIT_1,   // 要发送的通知值: 需要置位的bit置1
                eSetBits      // 写入的行为:设置bit位
            );

            if (res == pdPASS)
            {
                printf("KEY2按下,设置bit1为1..\r\n");
            }
        }
        vTaskDelay(500);
    }
}

/**
 * @description: 任务二:用于接收任务通知,并打印相关提示信息。
 * @param {void} *pvParameters
 * @return {*}
 */
void task2(void *pvParameters)
{
    uint32_t notify_value = 0;
    uint32_t expect_value = 0;
    BaseType_t res = 0;
    while (1)
    {
        printf("task2运行...\r\n");

        res = xTaskNotifyWait(
            0x00000000,    // 接收通知前是否清理通知值,全0,表示32bit的都是0,都不清理
            0xffffffff,    // 接收到通知值后,是否清理通知值, 全1,表示32bit都是1,都要清零
            &notify_value, // 用来保存读取到的通知值
            portMAX_DELAY);
        if (notify_value & EVENTBIT_0)
        {
            /* bit0 = 1 */
            printf("接收到的bit0=1\r\n");
            expect_value |= EVENTBIT_0;
        }
        if (notify_value & EVENTBIT_1)
        {
            /* bit1 = 1 */
            printf("接收到的bit1=1\r\n");
            expect_value |= EVENTBIT_1;
        }

        /* 判断,是否希望的bit位都是1,如果是,则进行***处理 */
        if(expect_value == 3)
        {
            printf("期望条件满足,值=%d\r\n",expect_value);
        }

        vTaskDelay(500);
    }
}