参照正点原子以及以下gitee笔记整理本博客,并将实验结果附在文末。
https://gitee.com/xrbin/FreeRTOS_learning/tree/master
一、任务通知的简介(通知任务)
1、任务通知介绍
答:任务通知:用来通知任务的,任务控制块中的结构体成员变量ulNotifiedValue就是这个通知值。(该结构体成员变量,有通知值和通知状态)
使用队列、信号量、事件标志组时都需要另外创建一个结构体,通过中间的结构体进行间接通信。
使用任务通知时,任务结构体TCB中就包含了内部对象,可以直接接收别人发过来的“通知”。
2、任务通知值的更新方式
答:
- 不覆盖接收任务的通知值。(类似队列)
- 覆盖接收任务的通知值。(类似队列)
- 更新接收任务通知值的一个或多个位。(类似事件标志组)
- 增加接收任务的通知值。(类似信号量)
只要合理,灵活的利用任务通知的特点,可以在一些场合中替代队列、信号量、事件标志组。
3、任务通知值的优势及劣势
答:(单对单可用任务通知)
任务通知的优势:
- 效率更高:使用任务通知向任务发送事件或数据比使用队列、事件标志组或信号量快得多。
- 使用内存更小:使用其他方法时都要先创建对应的结构体,使用任务通知时无需额外创建结构体。
任务通知的劣势:
- 无法发送数据给ISR:ISR没有任务结构体,所以无法给ISR发送数据。但是ISR可以使用任务通知的功能,发数据给任务。
- 无法广播给多个任务:任务通知只能是被指定的一个任务接收并处理 。
- 无法缓存多个数据:任务通知是通过更新任务通知值来发送数据的,任务结构体中只有一个任务通知值,只能保持一个数据。
- 发送受阻不支持阻塞:发送方无法进入阻塞状态等待。
二、任务通知值和通知状态
1、任务通知结构体
答:
任务都有一个结构体—任务控制块(TCB),它里面有两个结构体成员变量:
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;
#define configTASK_NOTIFICATION_ARRAY_ENTRIES 1 /* 定义任务通知数组的大小, 默认: 1 */
- 一个是uint32_t类型,用来表示任务通知值。
- 一个是uint16_t类型,用来表示任务通知状态。
2、任务通知值
答:
任务通知值的更新方式有多种类型:
- 计数值(数值累加,类似信号量)。
- 相应位置1(类似事件标志组)。
- 任意数值(支持覆写或不覆写,类似队列)。
3、任务通知状态
答:任务通知状态共有3种。
#define taskNOT_WAITING_NOTIFICATION ( ( uint8_t ) 0 ) /* 任务未等待通知 */
#define taskWAITING_NOTIFICATION ( ( uint8_t ) 1 ) /* 任务在等待通知 */
#define taskNOTIFICATION_RECEIVED ( ( uint8_t ) 2 ) /* 任务在等待接收 */
- 任务未等待通知:任务通知默认的初始化状态。
- 等待通知:接收方已经准备好了(调用了接收任务通知函数),等待发送方给个通知。(此时发送方尚未发送通知)
- 等待接收:发送方已经发送过去(调用了发送任务通知函数),等待接收方接收。(此时接收方尚未接收通知)
三、任务通知相关API函数
1、任务通知相关API函数介绍
答:任务通知API函数主要有两类:1-发送通知,2-接收通知。
注意:发送通知API函数可以用于任务和中断函数中,但接受通知API函数只能用在任务中(因为中断函数没有任务控制块)。
发送通知相关API函数:
接收通知相关API函数:
2、发送任务通知函数
答:
所有发送任务通知函数:
共同点:
1、都是宏定义
2、都是调用xTaskGenericNotify,但是入口参数不一样。
(用于信号量,事件标志组或队列)
#define xTaskNotifyAndQuery( xTaskToNotify, ulValue, eAction, pulPreviousNotifyValue )
xTaskGenericNotify( ( xTaskToNotify ),
( tskDEFAULT_INDEX_TO_NOTIFY ),
( ulValue ),
( eAction ),
( pulPreviousNotifyValue ) )
(用于信号量,事件标志组或队列)
#define xTaskNotify (xTaskToNotify , ulValue , eAction)
xTaskGenericNotify( ( xTaskToNotify ) ,
( tskDEFAULT_INDEX_TO_NOTIFY ) ,
( ulValue ) ,
( eAction ) ,
NULL )
(用于信号量)
#define xTaskNotifyGive( xTaskToNotify )
xTaskGenericNotify( ( xTaskToNotify ) ,
( tskDEFAULT_INDEX_TO_NOTIFY ) ,
( 0 ) ,
eIncrement ,
NULL )
关键函数:
BaseType_t xTaskGenericNotify( TaskHandle_t xTaskToNotify,
UBaseType_t uxIndexToNotify,
uint32_t ulValue,
eNotifyAction eAction,
uint32_t * pulPreviousNotificationValue )
发送任务通知的关键函数的参数:
任务通知方式枚举:
typedef enum
{
eNoAction = 0, /* 无操作 */
eSetBits /* 更新指定bit */
eIncrement /* 通知值加一 */
eSetValueWithOverwrite /* 覆写的方式更新通知值 */
eSetValueWithoutOverwrite /* 不覆写通知值 */
} eNotifyAction;
3、接收任务通知函数
答:
ulTaskNotifyTake()
如果模拟二值信号量,则用清零
如果模拟计数型信号量,则用减一
xTaskNotifyWait()
模拟队列或者事件标志组
注意:
- 当任务通知用于信号量时,使用函数 ulTaskNotifyTake() 获取获取信号量。
- 当任务通知用于事件标志组或队列时,使用函数 xTaskNotifyWait() 来获取。
ulTaskNotifyTake()函数:
#define ulTaskNotifyTake( xClearCountOnExit , xTicksToWait )
ulTaskGenericNotifyTake( ( tskDEFAULT_INDEX_TO_NOTIFY ),
( xClearCountOnExit ),
( xTicksToWait ) )
此函数用于接收任务通知值,可以设置在退出此函数的时候将任务通知值清零或者减一。
函数参数:
函数返回值:
xTaskNotifyWait()函数:
此函数用于获取通知值和清除通知值的指定位值,是用与模拟队列和事件标志组,使用该函数来获取任务通知。
#define xTaskNotifyWait( ulBitsToClearOnEntry,
ulBitsToClearOnExit,
pulNotificationValue,
xTicksToWait)
xTaskGenericNotifyWait( tskDEFAULT_INDEX_TO_NOTIFY,
( ulBitsToClearOnEntry ),
( ulBitsToClearOnExit ),
( pulNotificationValue ),
( xTicksToWait ) )
BaseType_t xTaskGenericNotifyWait( UBaseType_t uxIndexToWaitOn,
uint32_t ulBitsToClearOnEntry,
uint32_t ulBitsToClearOnExit,
uint32_t * pulNotificationValue,
TickType_t xTicksToWait );
函数参数:
函数返回值:
四、任务通知实验
1、任务通知模拟信号量实验
实验简介
实验现象
二值信号量
计数型信号量
实验代码
二值信号量
/**
****************************************************************************************************
* @file freertos.c
* @author 正点原子团队(ALIENTEK)
* @version V1.4
* @date 2022-01-04
* @brief FreeRTOS 移植实验
* @license Copyright (c) 2020-2032, 广州市星翼电子科技有限公司
****************************************************************************************************
* @attention
*
* 实验平台:正点原子 探索者F407开发板
* 在线视频:www.yuanzige.com
* 技术论坛:www.openedv.com
* 公司网址:www.alientek.com
* 购买地址:openedv.taobao.com
*
****************************************************************************************************
*/
#include "freertos_demo.h"
#include "./SYSTEM/usart/usart.h"
#include "./BSP/LED/led.h"
#include "./BSP/LCD/lcd.h"
#include "./BSP/KEY/key.h"
#include "./SYSTEM/delay/delay.h"
#include "./MALLOC/malloc.h"
/*FreeRTOS*********************************************************************************************/
#include "FreeRTOS.h"
#include "task.h"
#include "event_groups.h"
/******************************************************************************************************/
/*FreeRTOS配置*/
/* START_TASK 任务 配置
* 包括: 任务句柄 任务优先级 堆栈大小 创建任务
*/
#define START_TASK_PRIO 1
#define START_TASK_STACK_SIZE 128
TaskHandle_t start_task_handle;
void start_task( void * pvParameters );
/* TASK1 任务 配置
* 包括: 任务句柄 任务优先级 堆栈大小 创建任务
*/
#define TASK1_PRIO 2
#define TASK1_STACK_SIZE 128
TaskHandle_t task1_handle;
void task1(void * pvParameters);
/* TASK2 任务 配置
* 包括: 任务句柄 任务优先级 堆栈大小 创建任务
*/
#define TASK2_PRIO 3
#define TASK2_STACK_SIZE 128
TaskHandle_t task2_handle;
void task2(void * pvParameters);
/******************************************************************************************************/
/**
* @brief FreeRTOS例程入口函数
* @param 无
* @retval 无
*/
void freertos_demo(void)
{
xTaskCreate( (TaskFunction_t ) start_task,
(char * ) "start_task",
(configSTACK_DEPTH_TYPE) START_TASK_STACK_SIZE,
(void * ) NULL,
(UBaseType_t ) START_TASK_PRIO,
(TaskHandle_t * ) &start_task_handle);
//开启任务调度器
vTaskStartScheduler();
}
void start_task( void * pvParameters )
{
taskENTER_CRITICAL(); /*进入临界区,任务切换不会进行*/
xTaskCreate( (TaskFunction_t ) task1,
(char * ) "task1",
(configSTACK_DEPTH_TYPE) TASK1_STACK_SIZE,
(void * ) NULL,
(UBaseType_t ) TASK1_PRIO,
(TaskHandle_t * ) &task1_handle);
xTaskCreate( (TaskFunction_t ) task2,
(char * ) "task2",
(configSTACK_DEPTH_TYPE) TASK2_STACK_SIZE,
(void * ) NULL,
(UBaseType_t ) TASK2_PRIO,
(TaskHandle_t * ) &task2_handle);
vTaskDelete( NULL );
taskEXIT_CRITICAL(); /*退出临界区,才会开始任务切换*/
/*
简单而言,临界区保护,就是保护那些不想被打断的从程序段
*/
}
/* 任务一,发送任务通知值 */
void task1(void * pvParameters)
{
uint8_t key = 0;
while(1)
{
key = key_scan(0);
if(key == KEY0_PRES)
{
printf("任务通知模拟二值信号量释放\r\n");
xTaskNotifyGive(task2_handle);
}
vTaskDelay(10);
}
}
/* 任务二,接收任务通知值 */
void task2(void * pvParameters)
{
uint32_t rev = 0;
while(1)
{
rev = ulTaskNotifyTake(pdTRUE,portMAX_DELAY); /*二值信号量*/
if(rev != 0)
{
printf("接收任务通知成功,模拟获取二值信号量\r\n");
}
}
}
计数型信号量
void task2(void * pvParameters)
{
uint32_t rev = 0;
while(1)
{
rev = ulTaskNotifyTake(pdFALSE,portMAX_DELAY);
if(rev != 0)
{
printf("rev:%d\r\n",rev);
}
vTaskDelay(1000);
}
}
2、任务通知模拟消息邮箱
实验简介
实验现象
另外,LED会随着按键按下会翻转。
实验代码
3、任务通知模拟事件标志组实验
实验简介
task1:key0 按下, bit0 置 1;key1按下,bit1置1.
实验现象
实验代码
/**
****************************************************************************************************
* @file freertos.c
* @author 正点原子团队(ALIENTEK)
* @version V1.4
* @date 2022-01-04
* @brief FreeRTOS 移植实验
* @license Copyright (c) 2020-2032, 广州市星翼电子科技有限公司
****************************************************************************************************
* @attention
*
* 实验平台:正点原子 探索者F407开发板
* 在线视频:www.yuanzige.com
* 技术论坛:www.openedv.com
* 公司网址:www.alientek.com
* 购买地址:openedv.taobao.com
*
****************************************************************************************************
*/
#include "freertos_demo.h"
#include "./SYSTEM/usart/usart.h"
#include "./BSP/LED/led.h"
#include "./BSP/LCD/lcd.h"
#include "./BSP/KEY/key.h"
#include "./SYSTEM/delay/delay.h"
#include "./MALLOC/malloc.h"
/*FreeRTOS*********************************************************************************************/
#include "FreeRTOS.h"
#include "task.h"
#include "event_groups.h"
/******************************************************************************************************/
/*FreeRTOS配置*/
/* START_TASK 任务 配置
* 包括: 任务句柄 任务优先级 堆栈大小 创建任务
*/
#define START_TASK_PRIO 1
#define START_TASK_STACK_SIZE 128
TaskHandle_t start_task_handle;
void start_task( void * pvParameters );
/* TASK1 任务 配置
* 包括: 任务句柄 任务优先级 堆栈大小 创建任务
*/
#define TASK1_PRIO 2
#define TASK1_STACK_SIZE 128
TaskHandle_t task1_handle;
void task1(void * pvParameters);
/* TASK2 任务 配置
* 包括: 任务句柄 任务优先级 堆栈大小 创建任务
*/
#define TASK2_PRIO 3
#define TASK2_STACK_SIZE 128
TaskHandle_t task2_handle;
void task2(void * pvParameters);
/******************************************************************************************************/
#define EVENTBIT_0 1<<0
#define EVENTBIT_1 1<<1
/**
* @brief FreeRTOS例程入口函数
* @param 无
* @retval 无
*/
void freertos_demo(void)
{
xTaskCreate( (TaskFunction_t ) start_task,
(char * ) "start_task",
(configSTACK_DEPTH_TYPE) START_TASK_STACK_SIZE,
(void * ) NULL,
(UBaseType_t ) START_TASK_PRIO,
(TaskHandle_t * ) &start_task_handle);
//开启任务调度器
vTaskStartScheduler();
}
void start_task( void * pvParameters )
{
taskENTER_CRITICAL(); /*进入临界区,任务切换不会进行*/
xTaskCreate( (TaskFunction_t ) task1,
(char * ) "task1",
(configSTACK_DEPTH_TYPE) TASK1_STACK_SIZE,
(void * ) NULL,
(UBaseType_t ) TASK1_PRIO,
(TaskHandle_t * ) &task1_handle);
xTaskCreate( (TaskFunction_t ) task2,
(char * ) "task2",
(configSTACK_DEPTH_TYPE) TASK2_STACK_SIZE,
(void * ) NULL,
(UBaseType_t ) TASK2_PRIO,
(TaskHandle_t * ) &task2_handle);
vTaskDelete( NULL );
taskEXIT_CRITICAL(); /*退出临界区,才会开始任务切换*/
/*
简单而言,临界区保护,就是保护那些不想被打断的从程序段
*/
}
/* 任务一, 发送任务通知值*/
void task1(void * pvParameters)
{
uint8_t key = 0;
while(1)
{
key = key_scan(0);
if(key == KEY0_PRES)
{
printf("更新bit0置1\r\n");
xTaskNotify(task2_handle, EVENTBIT_0, eSetBits);
}else if(key == KEY1_PRES)
{
printf("更新bit1置1\r\n");
xTaskNotify(task2_handle, EVENTBIT_1, eSetBits);
}
vTaskDelay(10);
}
}
/* 任务二, 接收任务通知值*/
void task2(void * pvParameters)
{
uint32_t notify_val = 0,event_bit = 0;
while(1)
{
xTaskNotifyWait( 0, 0xFFFFFFFF, ¬ify_val,portMAX_DELAY );
if(notify_val & EVENTBIT_0)
{
event_bit |= EVENTBIT_0;
}
if(notify_val & EVENTBIT_1)
{
event_bit |= EVENTBIT_1;
}
if(event_bit == (EVENTBIT_0 | EVENTBIT_1))
{
printf("任务通知模拟事件标志组接收成功!!\r\n");
event_bit = 0;
}
}
}
为什么需要累积到 event_bit?
因为任务通知每次只能接收一个通知值,但是我们需要两个独立的事件都发生才算成功。
场景示例:
用户先按KEY0 → task2收到通知值1,记录事件0发生
用户再按KEY1 → task2收到通知值2,记录事件1发生
此时 event_bit = 3 (二进制11),满足条件,输出成功信息
关键设计思想:
任务通知值:临时存储(notify_val),每次接收后被清零
event_bit:持久记录,累积已发生的事件,直到满足条件才重置
这样就用任务通知实现了类似事件标志组的"等待多个事件同时满足"的功能!
总结