FreeRTOS入门(06):任务通知

文章目录

  • 目的
  • 基础说明
  • 使用演示
  • 作为二进制信号量
  • 作为计数信号量
  • 作为事件组
  • 作为队列或邮箱
  • 相关函数
  • 总结
  • 目的

    任务通知(TaskNotify)是RTOS中相对常用的用于任务间交互的功能,这篇文章将对相关内容做个介绍。

    本文代码测试环境见前面的文章:《FreeRTOS入门(01):基础说明与使用演示》

    基础说明

    前面介绍的队列、信号量、互斥量、队列集、事件组等功能都需要有个独立于任务的对象,任务通过主动去访问对象来使用相关的功能。事实上目前FreeRTOS的任务句柄本身就带有一个对象,用于任务间交互使用,这就是任务通知。

    任务通知因为上面的原因有两大优势:一是轻量(因为不需要额外的对象);二是可以直接通知某个任务(也是因为没有中间商)。

    任务中的任务通知结构包含状态和一个32位的数据。FreeRTOS v10.4.0 起支持单任务多条通知( 任务通知数组 ),所以现在很多函数都是因为兼容性冗余存在的。使用任务通知时需要设置下面参数:

    configUSE_TASK_NOTIFICATIONS // 为1才能使用任务通知(默认为1)
    configTASK_NOTIFICATION_ARRAY_ENTRIES // 为任务通知的每个任务数组中的索引数量(默认为1)
    

    任务通知根据使用的不同可以当作二进制信号量、计数信号量、事件组、队列、邮箱等功能来使用。

    使用演示

    作为二进制信号量

    任务通知作为二进制信号量使用就和真正的信号量一样,使用 givetake 函数来操作:

    #include "debug.h"
    #include "FreeRTOS.h"     // 引入头文件
    #include "task.h"         // 引入头文件
    
    TaskHandle_t Task1_Handler; // 任务句柄
    TaskHandle_t Task2_Handler; // 任务句柄
    
    void task1(void *pvParameters) {
        while(1) {
            uint32_t data = ulTaskNotifyTake(pdTRUE, portMAX_DELAY); // 等待任务通知(注意第一个参数)
            printf("%u task1: %u\r\n", xTaskGetTickCount(), data); // 打印任务通知的值
            vTaskDelete(NULL);
        }
    }
    
    void task2(void *pvParameters) {
        while(1) {
            vTaskDelay(500);
            xTaskNotifyGive(Task1_Handler); // 向task1给出任务通知
            vTaskDelete(NULL);
        }
    }
    
    int main(void) {
        NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
        SystemCoreClockUpdate();
        Delay_Init();
        USART_Printf_Init(115200);
    
        xTaskCreate(task1, "task1", 256, NULL, 5, &Task1_Handler);
        xTaskCreate(task2, "task2", 256, NULL, 5, &Task2_Handler);
    
        vTaskStartScheduler(); // 任务调度,任务将在这里根据情况开始运行,程序将在这里无序循环
    
        while(1) {} // 程序不会运行到这里
    }
    
    

    作为计数信号量

    任务通知作为计数信号量使用和作为二进制信号量一样,使用 givetake 函数,唯一的区别就是 take 函数的参数不同:

    #include "debug.h"
    #include "FreeRTOS.h"     // 引入头文件
    #include "task.h"         // 引入头文件
    
    TaskHandle_t Task1_Handler; // 任务句柄
    TaskHandle_t Task2_Handler; // 任务句柄
    
    void task1(void *pvParameters) {
        vTaskDelay(500);
        while(1) {
            uint32_t data = ulTaskNotifyTake(pdFALSE, portMAX_DELAY); // 等待任务通知(注意第一个参数)
            printf("%u task1: %u\r\n", xTaskGetTickCount(), data); // 打印任务通知的值
        }
    }
    
    void task2(void *pvParameters) {
        while(1) {
            xTaskNotifyGive(Task1_Handler); // 向task1给出任务通知
            xTaskNotifyGive(Task1_Handler); // 向task1给出任务通知
            xTaskNotifyGive(Task1_Handler); // 向task1给出任务通知
            vTaskDelete(NULL);
        }
    }
    
    int main(void) {
        NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
        SystemCoreClockUpdate();
        Delay_Init();
        USART_Printf_Init(115200);
    
        xTaskCreate(task1, "task1", 256, NULL, 5, &Task1_Handler);
        xTaskCreate(task2, "task2", 256, NULL, 5, &Task2_Handler);
    
        vTaskStartScheduler(); // 任务调度,任务将在这里根据情况开始运行,程序将在这里无序循环
    
        while(1) {} // 程序不会运行到这里
    }
    

    作为事件组

    任务通知作为事件组使用时,任务通知的 notify 函数相当于事件组的 setBits 函数,任务通知的 wait 函数相当于事件组的 waitBits 函数。

    #include "debug.h"
    #include "FreeRTOS.h"     // 引入头文件
    #include "task.h"         // 引入头文件
    
    TaskHandle_t Task1_Handler; // 任务句柄
    
    void task1(void *pvParameters) {
        while(1) {
            uint32_t value = 0;
            BaseType_t ret = xTaskNotifyWait(0xfffffffa, 0, &value, portMAX_DELAY); // 在等待前清除bit2和bit0之外的值,事件触发后不清除任何位
            if (ret == pdPASS ) {
                printf("%u task1: %u\r\n", xTaskGetTickCount(), value); // 打印任务通知的值
            }
        }
    }
    
    void task2(void *pvParameters) {
        while(1) {
            vTaskDelay(500);
            xTaskNotify(Task1_Handler, 0b0100, eSetBits ); // bit2设置为1
            vTaskDelete(NULL);
        }
    }
    
    void task3(void *pvParameters) {
        while(1) {
            vTaskDelay(1000);
            xTaskNotify(Task1_Handler, 0b0001, eSetBits ); // bit0设置为1
            vTaskDelete(NULL);
        }
    }
    
    int main(void) {
        NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
        SystemCoreClockUpdate();
        Delay_Init();
        USART_Printf_Init(115200);
    
        xTaskCreate(task1, "task1", 256, NULL, 5, &Task1_Handler);
        xTaskCreate(task2, "task2", 256, NULL, 5, NULL);
        xTaskCreate(task3, "task3", 256, NULL, 5, NULL);
    
        vTaskStartScheduler(); // 任务调度,任务将在这里根据情况开始运行,程序将在这里无序循环
    
        while(1) {} // 程序不会运行到这里
    }
    

    作为队列或邮箱

    任务通知不管是作为队列还是作为邮箱使用都相当于一个长度只有 1 的队列,使用的是 notifysetBits 函数,对这两个参数使用不同的方式会相当于不同的功能。

    #include "debug.h"
    #include "FreeRTOS.h"     // 引入头文件
    #include "task.h"         // 引入头文件
    
    TaskHandle_t Task1_Handler; // 任务句柄
    
    void task1(void *pvParameters) {
        while(1) {
            uint32_t value = 0;
            BaseType_t ret = xTaskNotifyWait(0xffffffff, 0, &value, portMAX_DELAY); // 事件触发后清除数据相当于队列
    //        BaseType_t ret = xTaskNotifyWait(0xffffffff, 0xffffffff, &value, portMAX_DELAY); // 事件触发后不清除数据相当于邮箱
            if (ret == pdPASS ) {
                printf("%u task1: %u\r\n", xTaskGetTickCount(), value); // 打印任务通知的值
            }
        }
    }
    
    void task2(void *pvParameters) {
        while(1) {
            vTaskDelay(500);
            xTaskNotify(Task1_Handler, 233, eSetValueWithoutOverwrite); // 写入数据
                                                                        // eSetValueWithoutOverwrite相当于队列,eSetValueWithOverwrite相当于邮箱
        }
    }
    
    int main(void) {
        NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
        SystemCoreClockUpdate();
        Delay_Init();
        USART_Printf_Init(115200);
    
        xTaskCreate(task1, "task1", 256, NULL, 5, &Task1_Handler);
        xTaskCreate(task2, "task2", 256, NULL, 5, NULL);
    
        vTaskStartScheduler(); // 任务调度,任务将在这里根据情况开始运行,程序将在这里无序循环
    
        while(1) {} // 程序不会运行到这里
    }
    

    相关函数

    // 给出通知(索引0)
    BaseType_t xTaskNotifyGive( TaskHandle_t xTaskToNotify )
    void vTaskNotifyGiveFromISR( TaskHandle_t xTaskToNotify, BaseType_t *pxHigherPriorityTaskWoken );
    
    // 给出通知,uxIndexToNotify为指定索引值
    BaseType_t xTaskNotifyGiveIndexed( TaskHandle_t xTaskToNotify, UBaseType_t uxIndexToNotify )
    void vTaskNotifyGiveIndexedFromISR( TaskHandle_t xTaskHandle, UBaseType_t uxIndexToNotify,  BaseType_t *pxHigherPriorityTaskWoken );
    
    // 等待通知(索引0)
    // xClearCountOnExit为pdFALSE则任务通知的值在该函数退出时递减,为pdTRUE则在退出时清零
    uint32_t ulTaskNotifyTake( BaseType_t xClearCountOnExit, TickType_t xTicksToWait );
    // 等待通知,uxIndexToWaitOn为指定索引值
    uint32_t ulTaskNotifyTakeIndexed( UBaseType_t uxIndexToWaitOn, BaseType_t xClearCountOnExit, TickType_t xTicksToWait );
    
    
    // 给出通知(索引0)
    // ulValue为通知的值
    // eAction可选值如下:
    // eNoAction - 目标任务接收事件,但不用更新值,此时不关心ulValue
    // eSetBits - 目标通知的值使用按位或与ulValue进行运算(这通常被当作事件组使用)
    // eIncrement - 目标通知的值自增,此时不关心ulValue(这通常被当作信号量使用)
    // eSetValueWithOverwrite - 使用ulValue覆盖目标通知的值(这通常被当作邮箱使用)
    // eSetValueWithoutOverwrite - (这通常被当作长度为1的队列使用)
    BaseType_t xTaskNotify( TaskHandle_t xTaskToNotify, uint32_t ulValue, eNotifyAction eAction );
    BaseType_t xTaskNotifyFromISR( TaskHandle_t xTaskToNotify, uint32_t ulValue, eNotifyAction eAction, BaseType_t *pxHigherPriorityTaskWoken );
    
    // 给出通知,uxIndexToNotify为指定索引值
    BaseType_t xTaskNotifyIndexed( TaskHandle_t xTaskToNotify, UBaseType_t uxIndexToNotify, uint32_t ulValue, eNotifyAction eAction );
    BaseType_t xTaskNotifyIndexedFromISR( TaskHandle_t xTaskToNotify, UBaseType_t uxIndexToNotify, uint32_t ulValue, eNotifyAction eAction, BaseType_t *pxHigherPriorityTaskWoken );
    
    // 等待通知(索引0)
    // ulBitsToClearOnEntry表示等待前清零的任务通知值的位
    // ulBitsToClearOnExit表示通知发生后清零的任务通知值的位
    // pulNotificationValue用来接收任务通知发生时的值
    // 返回值pdPASS表示成功,pdFAIL表示超时
    BaseType_t xTaskNotifyWait( uint32_t ulBitsToClearOnEntry, uint32_t ulBitsToClearOnExit, uint32_t *pulNotificationValue, TickType_t xTicksToWait );
    // 等待通知,uxIndexToWaitOn为指定索引值
    BaseType_t xTaskNotifyWaitIndexed( UBaseType_t uxIndexToWaitOn, uint32_t ulBitsToClearOnEntry, uint32_t ulBitsToClearOnExit, uint32_t *pulNotificationValue, TickType_t xTicksToWait );
    
    // 给出通知
    // 功能类似xTaskNotify
    // pulPreviousNotifyValue可以用来接收修改前的目标任务通知的值
    BaseType_t xTaskNotifyAndQuery( TaskHandle_t xTaskToNotify, uint32_t ulValue, eNotifyAction eAction, uint32_t *pulPreviousNotifyValue );
    BaseType_t xTaskNotifyAndQueryFromISR( TaskHandle_t xTaskToNotify, uint32_t ulValue, eNotifyAction eAction, uint32_t *pulPreviousNotifyValue, BaseType_t *pxHigherPriorityTaskWoken );
    BaseType_t xTaskNotifyAndQueryIndexed( TaskHandle_t xTaskToNotify, UBaseType_t uxIndexToNotify, uint32_t ulValue, eNotifyAction eAction, uint32_t *pulPreviousNotifyValue );
    BaseType_t xTaskNotifyAndQueryIndexedFromISR( TaskHandle_t xTaskToNotify, UBaseType_t uxIndexToNotify uint32_t ulValue, eNotifyAction eAction, uint32_t *pulPreviousNotifyValue, BaseType_t *pxHigherPriorityTaskWoken );
    
    // 将通知的状态设置为默认
    BaseType_t xTaskNotifyStateClear( TaskHandle_t xTask );
    BaseType_t xTaskNotifyStateClearIndexed( TaskHandle_t xTask, UBaseType_t uxIndexToClear );
    // 将通知的值设置为默认
    uint32_t ulTaskNotifyValueClear( TaskHandle_t xTask, uint32_t ulBitsToClear );
    uint32_t ulTaskNotifyValueClearIndexed( TaskHandle_t xTask, UBaseType_t uxIndexToClear, uint32_t ulBitsToClear );
    

    总结

    任务通知功能丰富、又轻量,如果可以满足业务功能需求的话,使用任务通知是个不错的选择。

    物联沃分享整理
    物联沃-IOTWORD物联网 » FreeRTOS入门(06):任务通知

    发表评论