Skip to content

任务通知

8.1 任务通知的特性

8.1.1 任务通知的概念

任务通知 :是用来通知任务的,且在任务的TCB结构体中已经包含了内部对象,可以直接接收别人发过来的"通知"。

在源码中找到任务TCB结构体的定义,其中ulNotifiedValue通知值 、ucNotifyState通知状态 。

C
typedef  struct  tskTaskControlBlock 
{
    ListItem_t          xStateListItem; 
    ListItem_t          xEventListItem;     
    UBaseType_t         uxPriority;         
    StackType_t         *pxStack;           
    char                pcTaskName[ configMAX_TASK_NAME_LEN ];
    ...
    #if ( configUSE_TASK_NOTIFICATIONS  ==  1 )
        volatile  uint32_t    ulNotifiedValue [ configTASK_NOTIFICATION_ARRAY_ENTRIES ];
        volatile  uint8_t      ucNotifyState [ configTASK_NOTIFICATION_ARRAY_ENTRIES ];
    #endif
    ...
} tskTCB;

#define  configTASK_NOTIFICATION_ARRAY_ENTRIES  1   /* 定义任务通知数组的大小, 默认: 1

8.1.2 通知值与通知状态

通知值的更新方式:

  • 计数值(数值累加,类似信号量)
  • 相对应位置(类似事件标志组)
  • 任意数值(支持覆写和不覆写,类似队列)

通知状态的3种取值:

  • taskNOT_WAITING_NOTIFICATION:任务没有在等待通知
  • taskWAITING_NOTIFICATION:任务在等待通知
  • taskNOTIFICATION_RECEIVED:任务接收到了通知,也被称为pending(有数据了,待处理)

8.1.3 任务通知的优劣

任务通知的优点:

  • 效率更高:使用任务通知向任务发送事件或数据比使用队列、事件组或信号量更快。
  • 使用内存更小:使用其他方法时都需要先创建对应的结构体,使用任务通知时无需额外创建结构体。

任务通知的缺点:

  • 无法发送数据给ISR:ISR没有任务结构体,因此无法给ISR发送数据,但ISR可以使用任务通知的功能,发送数据给任务。
  • 无法广播给多个任务:任务通知只能被指定的一个任务接收并处理。
  • 无法缓存多个数据:任务通知是通过更新任务通知值来发送数据的,任务结构体中只有一个任务通知值,只能保持一个数据。
  • 发送受限不支持阻塞:发送方无法进入阻塞状态等待。

8.2 任务通知函数

8.2.1 两类函数

功能 简化版 专业版
发出通知 xTaskNotifyGivevTaskNotifyGiveFromISR xTaskNotifyxTaskNotifyFromISR
取出通知 ulTaskNotifyTake xTaskNotifyWait

8.2.1 发送通知

在任务中使用 xTaskNotifyGive 函数,在ISR中使用 vTaskNotifyGiveFromISR 函数,都是直接给其他任务发送通知:

  • 使得通知值加一。
  • 并使得通知状态变为"pending",也就是taskNOTIFICATION_RECEIVED,表示有数据了、待处理。

xTaskNotify 函数功能更强大:

  • 让接收任务的通知值加一:这时 xTaskNotify() 等同于 xTaskNotifyGive()
  • eAction置不同参数可以执行不同操作:
    • eSetBits:更新指定bit。
    • eIncrement:通知值加一。
    • eSetValueWithOverwrite:覆写的方式更新通知值。
    • eSetValueWithoutOverwrite:不覆写通知值。
    • eNoAction:无操作。

eAction在源码中的处理:

C
switch( eAction )
{
    case eSetBits   :
        pxTCB->ulNotifiedValue |= ulValue;
        break;

    case eIncrement :
        ( pxTCB->ulNotifiedValue )++;
        break;

    case eSetValueWithOverwrite :
        pxTCB->ulNotifiedValue = ulValue;
        break;

    case eSetValueWithoutOverwrite :
        if( ucOriginalNotifyState != taskNOTIFICATION_RECEIVED )
        {
            pxTCB->ulNotifiedValue = ulValue;
        }
        else
        {
            /* The value could not be written to the task. */
            xReturn = pdFAIL;
        }
        break;

    case eNoAction:
        /* The task is being notified without its notify value being
                    updated. */
        break;
}

C
/* 发送通知 */
BaseType_t xTaskNotifyGive( TaskHandle_t xTaskToNotify );

/* A version of xTaskNotifyGive() that can be called from an interrupt service routine (ISR). */
void vTaskNotifyGiveFromISR( TaskHandle_t xTaskHandle, BaseType_t *pxHigherPriorityTaskWoken );

/* 发送通知 */
BaseType_t xTaskNotify( TaskHandle_t xTaskToNotify, uint32_t ulValue, eNotifyAction eAction );

/* A version of xTaskNotify() that can be used from an interrupt service routine (ISR). */
BaseType_t xTaskNotifyFromISR( 
        TaskHandle_t xTaskToNotify,
        uint32_t ulValue, 
        eNotifyAction eAction, 
        BaseType_t *pxHigherPriorityTaskWoken );

8.2.2 专业版

使用 ulTaskNotifyTake 函数可以实现轻量级的、高效的二进制信号量、计数型信号量:

  • xClearCountOnExit设置通知值清零,可模拟二进制信号量。
  • xClearCountOnExit设置通知值减一,配合xTaskNotifyGive 可模拟计数型信号量。
  • 可以返回通知计数值。

使用 xTaskNotifyWait() 函数取出通知值可以实现更多操作:

  • 可以获取接收的通知值。
  • 还可以在函数进入、退出时,清除通知值的指定位。
C
/* 取出通知值 */
uint32_t ulTaskNotifyTake( BaseType_t xClearCountOnExit, TickType_t xTicksToWait );

/* 取出通知值 */
BaseType_t xTaskNotifyWait( 
        uint32_t ulBitsToClearOnEntry, 
        uint32_t ulBitsToClearOnExit, 
        uint32_t *pulNotificationValue, 
        TickType_t xTicksToWait );

8.3 任务通知示例

8.3.1 模拟二进制信号量

car1运行到终点后,给car2发送二进制信号量,给car3发送数值。car2等待二进制信号量,car3等待特定的通知值。

添加任务句柄

C
static TaskHandle_t g_Task2Handle;
static TaskHandle_t g_Task3Handle;

xTaskCreate(OLED_PrintTask1, "Task1", 128, &g_car[0], osPriorityNormal, NULL);
xTaskCreate(OLED_PrintTask2, "Task2", 128, &g_car[1], osPriorityNormal + 2, &g_Task2Handle);
xTaskCreate(OLED_PrintTask3, "Task3", 128, &g_car[2], osPriorityNormal + 2, &g_Task3Handle);

模拟二进制信号量,使用任务通知传递特定值

C
void OLED_PrintTask1(void *params) {
    Car *car = params;
    ShowCar(car);
    while (1) {
        HideCar(car);
        car->x += 1;
        if (car->x > 128) { car->x = 120; }
        ShowCar(car);
        vTaskDelay(50);
        if (car->x == 120) {
            /* 给Task2发送任务通知 */
            xTaskNotifyGive(g_Task2Handle);
            /* 给Task3发送任务通知值100(eSetValueWithOverwrite操作) */
            xTaskNotify(g_Task3Handle, 100, eSetValueWithOverwrite);
            /* 设置事件 */
            vTaskDelete(NULL);
        }
    }
}

void OLED_PrintTask2(void *params) {
    Car *car = params;
    ShowCar(car);
    /* 取出任务通知,将通知值清零,模拟二进制信号量 */
    ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
    while (1) {
        HideCar(car);
        car->x += 1;
        if (car->x > 128) { car->x = 120; }
        ShowCar(car);
        vTaskDelay(100);
        if (car->x == 120) {
            vTaskDelete(NULL);
        }
    }
}

void OLED_PrintTask3(void *params) {
    uint32_t value;
    Car *car = params;
    ShowCar(car);
    do {
        /* 等待特定的通知值 */
        xTaskNotifyWait(0, 0, &value, portMAX_DELAY);
    } while (value != 100);
    while (1) {
        HideCar(car);
        car->x += 1;
        if (car->x > 128) { car->x = 120; }
        ShowCar(car);
        vTaskDelay(100);
//        HAL_Delay(100);
        if (car->x == 120) {
            vTaskDelete(NULL);
        }
    }
}

8.3.2 模拟技术型信号量

修改任务 1 和 2 部分参数

C
void OLED_PrintTask1(void *params) {
    Car *car = params;
    ShowCar(car);
    while (1) {
        HideCar(car);
        car->x += 1;
        if (car->x > 128) { car->x = 120; }
        ShowCar(car);
        vTaskDelay(50);
        if (car->x == 120) {
            /* 给Task2发送两次任务通知,使得通知值为2,模拟计数型信号量 */
            xTaskNotifyGive(g_Task2Handle);
            xTaskNotifyGive(g_Task2Handle);
            /* 给Task3发送任务通知值100(eSetValueWithOverwrite操作) */
            xTaskNotify(g_Task3Handle, 100, eSetValueWithOverwrite);
            /* 设置事件 */
            vTaskDelete(NULL);
        }
    }
}

void OLED_PrintTask2(void *params) {
    Car *car = params;
    uint8_t cnt;
    ShowCar(car);
    /* 取出任务通知,将通知值清零,模拟二进制信号量 */
    cnt = ulTaskNotifyTake(pdFALSE, portMAX_DELAY);
    while (1) {
        HideCar(car);
        car->x += 1;
        if (car->x > 128) { car->x = 120; }
        ShowCar(car);
        vTaskDelay(100);
        if (car->x == 120) {
            /* Task1取出一次任务通知,cnt = 2-1 = 1 */
            OLED_ShowNum(0, 0, cnt, 1, 16);
            vTaskDelete(NULL);
        }
    }
}

8.3.2 模拟消息邮箱

前两个例子中,Task3即为此案例。

8.3.3 模拟事件组

car1运行到终点后,发送任务通知将Task3的 Bit0 置 1 ,car2运行到终点后,发送任务通知将Task3的 Bit1 置 1,Task接收任务通知判断Bit0 和 Bit1。

定义两个数据位

C
#define EventBit0 (1<<0)    // 0000 0001
#define EventBit1 (1<<1)    // 0000 0010

修改任务函数

C
void OLED_PrintTask1(void *params) {
    Car *car = params;
    ShowCar(car);
    while (1) {
        HideCar(car);
        car->x += 1;
        if (car->x > 128) { car->x = 120; }
        ShowCar(car);
        vTaskDelay(50);
        if (car->x == 120) {
            /* 置Task3的Bit0为1 */
            xTaskNotify(g_Task3Handle, EventBit0, eSetBits);
            /* 设置事件 */
            vTaskDelete(NULL);
        }
    }
}

void OLED_PrintTask2(void *params) {
    Car *car = params;
    ShowCar(car);
    while (1) {
        HideCar(car);
        car->x += 1;
        if (car->x > 128) { car->x = 120; }
        ShowCar(car);
        vTaskDelay(100);
        if (car->x == 120) {
            /* 置Task3的Bit1为1 */
            xTaskNotify(g_Task3Handle, EventBit1, eSetBits);
            vTaskDelete(NULL);
        }
    }
}

void OLED_PrintTask3(void *params) {
    uint32_t value;
    uint32_t event_bit = 0;
    Car *car = params;
    ShowCar(car);
    /* 等待特定的通知值 */
    while (1) {
        xTaskNotifyWait(0, 0xffffffffUL, &value, portMAX_DELAY);
        if (value & EventBit0) {
            event_bit |= EventBit0;
        }
        if (value & EventBit1) {
            event_bit |= EventBit1;
        }
        if(event_bit == (EventBit0|EventBit1)) {
            event_bit = 0;
            while (car->x != 120) {
                HideCar(car);
                car->x += 1;
                if (car->x > 128) { car->x = 120; }
                ShowCar(car);
                vTaskDelay(100);
            }
            if (car->x == 120) {
                vTaskDelete(NULL);
            }
        }
    }
}