FreeRTOS任务通知使用以及例程

一.FreeRTOS任务通知简介

任务通知是FreeRTOS提供的一种轻量级通信机制,允许任务间直接发送事件或数据,无需创建队列、信号量等中间对象。每个任务拥有独立的通知值(32位)和状态标志,效率高且节省内存。

任务通知通常用于替代二值信号量或事件标志组,提供了更轻量级的任务间通信方式。大多数任务间通信方法通过中间对象,如队列、信号量或事件组。发送任务写入通信对象,接收任务从通信对象读取。使用任务通知时,发送任务直接向接收任务发送通知,而无需中间对象。(相当直接发到别人的邮箱里,缺点:只能一对一或者多对一)。

FreeRTOS任务通知使用以及例程_第1张图片

参考尚硅谷FreeRTOS

任务通知值设置在在哪里?

typedef struct tskTaskControlBlock       
{
                       .
                       .
                       .
    #if ( configUSE_TASK_NOTIFICATIONS == 1 )
        volatile uint32_t  ulNotifiedValue[ configTASK_NOTIFICATION_ARRAY_ENTRIES ];
        volatile uint8_t   ucNotifyState[ configTASK_NOTIFICATION_ARRAY_ENTRIES ];
    #endif
                       .
                       .
                       .
    
} tskTCB;

二.任务通知核心API

函数的实现可以看我的另一篇文章:FreeRTOS源码分析:任务通知-TaskNotify-CSDN博客

  1. 发送通知

    • xTaskNotifyGive():     发送通知并增加接收任务的通知值(类似二进制信号量)。
    • xTaskNotify():            发送通知并自定义操作(如设置/增加值、更新部分位)。
    • xTaskNotifyFromISR():中断服务程序中使用。
  2. 接收通知

    • ulTaskNotifyTake():等待通知并清零或递减通知值(类似信号量)。
    • xTaskNotifyWait():  等待通知并可选择保留部分值(支持复杂条件)。

函数

描述

xTaskNotify()

发送通知,带有通知值(通知值一块发过去)

xTaskNotifyAndQuery()

发送通知,带有通知值并且保留接收任务的原通知值

(给task4发b,函数返回task4返回原来值a)

xTaskNotifyGive()

发送通知,不带通知值(对值进行增量(添加 1),类似信号量)

xTaskNotifyFromISR()

在中断中发送任务通知

xTaskNotifyAndQueryFromISR()

vTaskNotifyGiveFromISR()

ulTaskNotifyTake()

获取任务通知,可选退出函数时对通知值清零或减1(类似信号量的做法)

xTaskNotifyWait()

获取任务通知,可获取通知值和清除通知值的指定位

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

三.任务通知使用例程

例1:模拟二进制信号量

函数:

xTaskNotifyGive()

发送通知,不带通知值(对值进行增量(添加 1),类似信号量)

ulTaskNotifyTake()

获取任务通知,可选退出函数时对通知值清零或减1(类似信号量的做法)

实验:

  • start_task:用来创建其他2个任务。
  • task1:用于按键扫描,当检测到按键被按下时,将发送任务通知。
  • task2:用于接收任务通知,并打印相关提示信息。
void task1(void *p)
{
    my_printf("task1 running............\r\n");
    HAL_Delay(3);

    while (1)
    {
        uint8_t key = 0;
        Key_scan();
        key = Key_GetValue();
        if (key)
        {
            My_OLED_ShowNum(2, 1, key, 2);
            HAL_Delay(1);
            xTaskNotifyGive(task_2);
            my_printf("任务通知成功.....\r\n");
            HAL_Delay(3);
        }
       

        vTaskDelay(1);
    }
}

void task2(void *p)
{
    my_printf("task2 running............\r\n");
    HAL_Delay(3);
    uint32_t rev = 0;
    while (1)
    {
   
        rev = ulTaskNotifyTake(pdTRUE , portMAX_DELAY);
        if(rev != 0)
        {
            my_printf("接收任务通知成功,模拟获取二值信号量.....\r\n");
            HAL_Delay(3);
        }

        vTaskDelay(500);
    }
}

实验现象:

task1执行xTaskNotifyGive(task_2),之后执行task2,再返回task1执行!!!

例2:模拟消息队列

函数:

xTaskNotify()

发送通知,带有通知值(通知值一块发过去)

xTaskNotifyWait()

获取任务通知,可获取通知值和清除通知值的指定位

实验:

  • task1:用于按键扫描,当检测到按键按下时,发送任务通知设置不同标志位。
  • task2:用于接收任务通知,并打印相关提示信息。
void task1(void *p)
{
    my_printf("task1 running............\r\n");
    HAL_Delay(3);

    while (1)
    {
        uint8_t key = 0;
        Key_scan();
        key = Key_GetValue();
        if (key!=0 && task_2!=NULL)
        {
            My_OLED_ShowNum(2, 1, key, 2);
            HAL_Delay(1);
            xTaskNotify(task_2, key, eSetValueWithOverwrite);   //覆盖写入,不管前一个通知是否被读走
            my_printf("任务通知模拟消息队列发送成功.....\r\n");
            HAL_Delay(3);
        }
       

        vTaskDelay(1);
    }
}

void task2(void *p)
{
    my_printf("task2 running............\r\n");
    HAL_Delay(3);
    uint32_t rev = 0;
    while (1)
    {
        xTaskNotifyWait(0, 0xFFFFFFFF, &rev, portMAX_DELAY);
        if(rev != 0)
        {
            my_printf("接收任务通知成功,value=%d.....\r\n",rev);
            HAL_Delay(3);
        }

        vTaskDelay(500);
    }
}

实验现象:

例3:模拟事件标志组

函数:

xTaskNotify()

发送通知,带有通知值(通知值一块发过去)

xTaskNotifyWait()

获取任务通知,可获取通知值和清除通知值的指定位

还是这两个函数

实验:

  • task1:用于按键扫描,当检测到按键按下时,发送任务通知设置不同标志位。
  • task2:用于接收任务通知,并打印相关提示信息。
#define EVENTBIT_1 1


void task1(void *p)                                //优先级为2
{
    my_printf("task1 running............\r\n");
    HAL_Delay(3);
    while (1)
    {
        uint8_t key = 0;
        Key_scan();
        key = Key_GetValue();
        if (key)
        {
            My_OLED_ShowNum(2, 1, key, 2);
            HAL_Delay(1);
            my_printf("将%d位置1..........\r\n", key);
            HAL_Delay(2);
            xTaskNotify(task_2, EVENTBIT_1 << (key - 1), eSetBits);
        }
        vTaskDelay(1);
    }
}

void task2(void *p)                                //优先级为3
{
    my_printf("task2 running............\r\n");
    HAL_Delay(3);
    uint32_t notify_val = 0, event_bit = 0;
    while (1)
    {
        xTaskNotifyWait(0, 0xFFFFFFFF, ¬ify_val, portMAX_DELAY);
        event_bit |= notify_val;
        if (event_bit == 0x05)
        {
            my_printf("task2等待到的标志位为第1位和第三位..\r\n");
            HAL_Delay(3);
        }
        vTaskDelay(500);
    }
}

实验现象:

FreeRTOS任务通知使用以及例程_第2张图片

四.注意事项

  • 多次通知:未及时处理的通知可能被覆盖(取决于API参数)。
  • 替代场景:复杂通信(如多任务共享数据)仍需使用队列或信号量。

任务通知适用于简单事件通知和轻量数据传递,能显著减少资源占用。

你可能感兴趣的:(FreeRTOS,stm32)