任务通知
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 两类函数¶
功能 | 简化版 | 专业版 |
---|---|---|
发出通知 | xTaskNotifyGive 和vTaskNotifyGiveFromISR |
xTaskNotify 和xTaskNotifyFromISR |
取出通知 | 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
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);
}
}
}
}