任务通知简介

任务通知在FreeRTOS中是一个可选的功能,要使用任务通知的话就需要将宏 configUSR_TASK_NOTIFICATIONS定义为1。

FreeRTOS的每个任务都有一个32位的通知值,任务控制款中的成员变量ulNotifiedValue就是这个通知值。任务通知是一个事件,假如某个任务通知的接收任务因为等待任务通知而阻塞的话,向这个接收任务发送任务通知以后就会解除这个任务的阻塞状态。也可以更新接收任务的任务通知值,任务通知可以通过如下方法更新接收任务的通知值:

  1. 不覆盖接收任务的通知值(如果上次发送给接收任务的通知还没有被处理)。
  2. 覆盖接收任务的通知值。
  3. 更新接收任务通知值的一个或多个bit。
  4. 增加接收任务的通知值。

合理、灵活地使用上面这些更改任务通知值的方法可以在一些场合中替代队列、二值信号量、计数型信号量和时间标志组。使用任务通知来实现二值信号量功能的时候,解除任务阻塞的时间比直接使用二值信号量要快45%(FreeRTOS官方测试结果,使用v8.1.2版本中的二值信号量,GCC编译器,-O2优化条件下测试的,没有使能断言函数configASSERT()),并且使用的RAM更少!

任务通知的发送使用函数xTaskNotify()或者xTaskNotifyGive()(还有次函数的中断版本)来完成,这个通知值会一直保存着,直到接收任务调用函数xTaskNotifyWait()或者ulTaskNotifyTake()来获取这个通知值。假如接收任务因为等待任务通知而阻塞的话那么在接收到任务通知以后就会解除阻塞态。

任务通知虽然可以提高速度,并且减少RAM的使用,但是任务通知也是由使用限制的:

  1. FreeRTOS的任务通知只能有一个接收任务,其实大多数的应用都是这种情况。
  2. 接收任务可以因为接收任务通知而进入阻塞态,但是发送任务不会因为任务通知发送失败而阻塞。

xTaskNotify()

此函数用于发送任务通知,此函数发送任务通知的时候带有通知值,此函数是个,真正执行的函数xTaskGenericNotify(),函数原型如下:

1
2
3
BaseType_t xTaskNotify( TaskHandle_t xTaskToNotify, 
uint32_t ulValue,
eNotifyAction eAction )

xTaskGenericNotify()

1
2
3
4
5
BaseType_t xTaskGenericNotify( TaskHandle_t xTaskToNotify,
UBaseType_t uxIndexToNotify,
uint32_t ulValue,
eNotifyAction eAction,
uint32_t * pulPreviousNotificationValue )
  • [xTaskToNotify] 被通知的任务句柄。
  • [uxIndexToNotify]
  • [ulValue] 更新的通知值
  • [eAction] 枚举类型,指明更新通知值的方法,枚举变量成员以及作用见表1-2所示。
  • [pulPreviousNotificationValue] 回传未被更新的任务通知值。如果不需要回传未被更新的任务通知值,这里设置为NULL。

表1-2

1
2
3
4
5
6
7
8
9
10
11
/* vTaskNotify() 函数可以执行的动作类型 */
typedef enum
{
eNoAction = 0, /* 发送通知但不更新通知值,这意味着参数ulValue 未使用 */
eSetBits, /* 被通知任务的通知值按位或上ulValue。 */
eIncrement, /* 被通知任务的通知值增加1,这种情况下,参数 ulValue 未使用,API函数xTaskNotifyGive()跟这个等价。 */
eSetValueWithOverwrite, /* 被通知任务的通知值设置为 ulValue。使用这种方法,可以在某种场景下代替 xQueueOverwrite(),但执行速度更快。 */
eSetValueWithoutOverwrite /* 1. 如果被通知任务当前没有通知,则被通知任务的通知值设置为 ulValue;
2. 如果被通知任务还没取走上一个通知,又接收到了一个通知,则这次通知值丢弃,在这种情况下,函数调用失败并返回 pdFALSE。
3. 使用这种方法可以在某些场景下替换长度为1的xQueueSend(), 但速度更快。 */
} eNotifyAction;

xTaskNotifyWait

函数原型:

1
2
3
4
BaseType_t xTaskNotifyWait( uint32_t ulBitsToClearOnEntry,
uint32_t ulBitsToClearOnExit,
uint32_t *pulNotificationValue,
TickType_t xTicksToWait );
  • ulBitsToClearOnEntry
    • 若调用xTaskNotifyWait()前,task没有notification pending,则ulBitsToClearOnEntry中所有置1的位,会清0notification value所对应的位。
    • ulBitsToClearOnEntry 为0x01时,task的notification value的第0位置清0; ulBitsToClearOnExit 为0xffffffff(ULONG_MAX)时, tasknotification value 所有位将清零
  • ulBitsToClearOnExit
    • 若xTaskNotifyWait()因为有notification pending而退出时,pulNotificationValue将先保存notification value内容之后,ulBitsToClearOnExit中置的位,将清空task notification value中对应的位
  • pulNotificationValue
    • pulNotificationValue 保存notification value被ulBitsToClearOnExit清零任何一位之前的值。
    • pulNotificationValue 是可选的,不需要时置为NULL。
  • xTicksToWait
    • pdMS_TO_TICKS(), portMAX_DELAY, porPeriod_tick_ms

例子1 eSetBits

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
/**
* @file main.c
* @author 信念D力量
* @brief
* @version 0.1
* @date 2022-03-08
*
* @copyright Copyright (c) 2022
*
*/
#include "main.h"
#include "FreeRTOS.h"
#include "semphr.h"
#include "task.h"
#include "debug_printf.h"

#include "gd32f1x0.h"
#include "gd32f1x0r_eval.h"
#include <stdio.h>
#include <limits.h>
#include "key.h"

/* LED1 Task */
#define LED1_TASK_PRIO 0 // 任务优先级
#define LED1_STK_SIZE 50 // 任务堆栈大小
TaskHandle_t LED1Task_Handler; // 定义任务句柄,一个任务一个任务句柄
void led1_task(void *pvParameters); // 任务函数

/* LED2 Task */
#define LED2_TASK_PRIO 0
#define LED2_STK_SIZE 50
TaskHandle_t LED2Task_Handler;
void led2_task(void *pvParameters);

/* 全局变量定义 */


// 主函数
int main(void) {
// ------ 函数初始化代码区 ------
key_init();
debug_printf_init(EVAL_COM0); // 使用 EVAL 库初始化 USART1
gd_eval_led_init(LED1); // 使用 EVAL 库初始化 LED1
gd_eval_led_init(LED2); // 使用 EVAL 库初始化 LED2
gd_eval_led_init(LED3); // 使用 EVAL 库初始化 LED3

// 创建任务 led1_task
xTaskCreate((TaskFunction_t)led1_task, \
(const char*)"led1_task", \
(uint16_t)LED1_STK_SIZE, \
(void*)NULL, \
(UBaseType_t)LED1_TASK_PRIO, \
(TaskHandle_t*)&LED1Task_Handler);

// 创建任务 led2_task
xTaskCreate((TaskFunction_t)led2_task, \
(const char*)"led2_task", \
(uint16_t)LED2_STK_SIZE, \
(void*)NULL, \
(UBaseType_t)LED2_TASK_PRIO, \
(TaskHandle_t*)&LED2Task_Handler);

app_main();
// 启动任务调度器
vTaskStartScheduler();


while (1) {

}
}


/*******************************************************************************
function_name: led1_task
description: FreeRTOS 任务函数
calls:
called by:
input: void*
output:
return: void
others:
*******************************************************************************/
void led1_task(void *pvParameters)
{
uint32_t count = 0;
while(1) {
gd_eval_led_toggle(LED1);
count++;
vTaskDelay(1000);
if(count >= 10) {
xTaskNotify(LED2Task_Handler, (1UL << 8UL), eSetBits);
count = 0;
}

}
}

/*******************************************************************************
function_name: led1_task
description: FreeRTOS 任务函数
calls:
called by:
input: void*
output:
return: void
others:
*******************************************************************************/
void led2_task(void *pvParameters)
{
uint32_t ulNotifiedValue;
while(1) {
// ULONG_MAX 定义在 limits.h 头文件中
xTaskNotifyWait(0x00, ULONG_MAX, &ulNotifiedValue, portMAX_DELAY);

if((ulNotifiedValue & 0x100) != 0) {
printf("count: 10\n");
}
// gd_eval_led_toggle(LED2);
// vTaskDelay(500);
}
}

串口打印结果:

例子2 eNoAction

使用 eNoAction 方式发送通知,将不使用通知值,其功能类似于 xTaskNotifyGive()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
/*******************************************************************************
function_name: led1_task
description: FreeRTOS 任务函数
calls:
called by:
input: void*
output:
return: void
others:
*******************************************************************************/
void led1_task(void *pvParameters)
{
uint32_t count = 0;
uint32_t notify_value = 1;
while(1) {
gd_eval_led_toggle(LED1);
count++;
vTaskDelay(1000);
if(count >= 10) {
xTaskNotify(LED2Task_Handler, notify_value, eNoAction);
count = 0;
}

}
}

/*******************************************************************************
function_name: led1_task
description: FreeRTOS 任务函数
calls:
called by:
input: void*
output:
return: void
others:
*******************************************************************************/
void led2_task(void *pvParameters)
{
uint32_t ulNotifiedValue;
while(1) {
xTaskNotifyWait(0x00, ULONG_MAX, &ulNotifiedValue, portMAX_DELAY);
printf("ulNotifiedValue: %d\n", ulNotifiedValue);
gd_eval_led_toggle(LED2);
}
}

串口打印结果:

例子3 eIncrement

使用 eIncrement 模式xTaskNotify() 函数,每调用一次任务的通知值就会加一。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
/*******************************************************************************
function_name: led1_task
description: FreeRTOS 任务函数
calls:
called by:
input: void*
output:
return: void
others:
*******************************************************************************/
void led1_task(void *pvParameters)
{
uint32_t count = 0;
static uint32_t notify_value = 1;
while(1) {
gd_eval_led_toggle(LED1);
count++;
vTaskDelay(1000);
if(count >= 2) {
xTaskNotify(LED2Task_Handler, 0, eIncrement);
xTaskNotify(LED2Task_Handler, 0, eIncrement);
count = 0;
}

}
}

/*******************************************************************************
function_name: led1_task
description: FreeRTOS 任务函数
calls:
called by:
input: void*
output:
return: void
others:
*******************************************************************************/
void led2_task(void *pvParameters)
{
uint32_t ulNotifiedValue;
while(1) {
xTaskNotifyWait(0x00, ULONG_MAX, &ulNotifiedValue, portMAX_DELAY);
printf("ulNotifiedValue: %d\n", ulNotifiedValue);
gd_eval_led_toggle(LED2);
}
}

xTaskNotifyGive()

发送任务通知,相对于函数xTaskNotify(),此函数发送任务通知的时候不带有通知值。此函数只是将任务通知值简单加一。此函数是个宏,真正执行的是函数xTaskGenericNotify(),此函数原型如下:

1
BaseType_t xTaskNotifyGive( TaskHandle_t xTaskToNotify )

参数:

xTaskToNotify:任务句柄,指定任务通知是发送给哪个任务的。

返回值:

pdPASS:此函数只会返回pdPASS

vTaskNotifyGiveFromISR()

此函数为xTaskNotifyGive()的中断版本,用在中断服务函数中,函数原型如下:

1
2
void vTaskNotifyGiveFromISR( TaskHandle_t xTaskHandle, 
BaseType_t *pxHigherPriorityTaskWoken )

参数:

xTaskToNotify:任务句柄,指定任务通知是发送给哪个任务的。

pxHigherPriorityTaskWoken:退出此函数以后是否进行任务切换,这个变量的值会自动设置的,用户不用进行设置,用户只需要提供一个变量来保存这个值就行了。当此值为pdTRUE的时候在退出中断服务函数之前一定要进行一次任务切换。

返回值:

  无。

例子1 xTaskNotifyGive() & ulTaskNotifyTake()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
/**
* @file main.c
* @author 信念D力量
* @brief
* @version 0.1
* @date 2022-03-08
*
* @copyright Copyright (c) 2022
*
*/
#include "main.h"
#include "FreeRTOS.h"
#include "semphr.h"
#include "task.h"
#include "debug_printf.h"

#include "gd32f1x0.h"
#include "gd32f1x0r_eval.h"
#include <stdio.h>
#include "key.h"

/* LED1 Task */
#define LED1_TASK_PRIO 0 // 任务优先级
#define LED1_STK_SIZE 50 // 任务堆栈大小
TaskHandle_t LED1Task_Handler; // 定义任务句柄,一个任务一个任务句柄
void led1_task(void *pvParameters); // 任务函数

/* LED2 Task */
#define LED2_TASK_PRIO 0
#define LED2_STK_SIZE 50
TaskHandle_t LED2Task_Handler;
void led2_task(void *pvParameters);

/* 定义 */
static char pcWriteBuffer[256] = {0};

// 主函数
int main(void) {
// ------ 函数初始化代码区 ------
key_init();
debug_printf_init(EVAL_COM0); // 使用 EVAL 库初始化 USART1
gd_eval_led_init(LED1); // 使用 EVAL 库初始化 LED1
gd_eval_led_init(LED2); // 使用 EVAL 库初始化 LED2
gd_eval_led_init(LED3); // 使用 EVAL 库初始化 LED3

// 创建任务 led1_task
xTaskCreate((TaskFunction_t)led1_task, \
(const char*)"led1_task", \
(uint16_t)LED1_STK_SIZE, \
(void*)pv_string, \
(UBaseType_t)LED1_TASK_PRIO, \
(TaskHandle_t*)&LED1Task_Handler);

// 创建任务 led2_task
xTaskCreate((TaskFunction_t)led2_task, \
(const char*)"led2_task", \
(uint16_t)LED2_STK_SIZE, \
(void*)NULL, \
(UBaseType_t)LED2_TASK_PRIO, \
(TaskHandle_t*)&LED2Task_Handler);

app_main();
// 启动任务调度器
vTaskStartScheduler();


while (1) {

}
}


/*******************************************************************************
function_name: led1_task
description: FreeRTOS 任务函数
calls:
called by:
input: void*
output:
return: void
others:
*******************************************************************************/
void led1_task(void *pvParameters)
{
uint32_t count = 1;
while(1) {
gd_eval_led_toggle(LED1);
vTaskDelay(1000);
printf("led1_task(%d)...\n", count);
count++;
if (count > 10) {
count = 1;
/* 发出通知 */
xTaskNotifyGive(LED2Task_Handler);
}

}
}

/*******************************************************************************
function_name: led1_task
description: FreeRTOS 任务函数
calls:
called by:
input: void*
output:
return: void
others:
*******************************************************************************/
void led2_task(void *pvParameters)
{
while(1) {
printf("led2_task...\n");
ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
gd_eval_led_toggle(LED2);
vTaskDelay(500);
}
}

void app_main(void)
{

}