本节需要掌握以下内容:
1、任务通知的简介(了解)
2、任务通知值和通知状态(熟悉)
3、任务通知相关API函数介绍(熟悉)
4、任务通知模拟信号量实验(掌握)
5、任务通知模拟消息邮箱实验(掌握)
6、任务通知模拟事件标志组实验(掌握)
7、课堂总结(掌握)
一、任务通知的简介(了解)
1.1 任务通知的相关概念
任务通知:用来通知任务的,任务控制块中的结构体成员变量ulNotifiedValue就是这个通知值。
(队列、信号量、事件标志组也可以用来通知任务。队列可以往其它任务发数据,信号量同样发送一个资源,释放信号量,另外一个任务获取信号量,事件标志组把某一位置一,另一个任务就来读这个位是不是1。这些都能用来通知任务,为什么我们还要用任务通知呢?
最主要的是:内存消耗比较小,因为队列、信号量、事件标志组使用之前都要提前创建好,才能去操作它,而任务通知就不用去创建,因为它的结构体成员就在任务控制块TCB里面。
每创建一个任务就会给这个任务的任务控制块分配个内存,任务控制块中的结构体成员变量ulNotifiedValue就是这个通知值。而任务创建好之后,这个结构体成员变量就被创建好了)
- 使用队列、信号量、事件标志组时都需创建一个结构体,通过中间的结构体进行间接通信!
使用队列/信号量/事件标志组时发送数据,就是把发送数据放在队列/信号量/事件标志组的结构体中,接收也是从队列/信号量/事件标志组的结构体中读出来。
- 使用任务通知时,任务结构体TCB中就包含了内部对象,可以直接接收别人发过来的“通知”
发送的时候实际就是任务一直接去操作任务二 任务控制块中的 结构体成员变量ulNotifiedValue,给这个成员写一个值,接收的时候就直接去读这个值。
任务通知值的更新方式
- 不覆盖接受任务的通知值(这个结构体数据成员有数值就不写进去,没有数值的时候才写进去)
- 覆盖接受任务的通知值(不管有没有数值,都能写进去)
- 更新接受任务通知值的一个或多个bit
- 增加接受任务的通知值
只要合理、灵活的利用任务通知的特点,可以在一些场合中替代队列、信号量、时间标志组!
1.2 任务通知的优势及劣势
任务通知的优势:
- 效率更高:使用任务通知向任务发送时间或数据比使用队列、时间标志组或信号快得多(freeRTOS官方也是做了一个测试,使用任务通知来模拟二值信号量这样的一个方式来解除任务的阻塞事件,相对于常规的二值信号量,快了45%)
- 使用内存更小:使用其它方法时都要创建对应的结构体,使用任务通知时无需额外创建结构体
任务通知的劣势:
- 无法发送数据给ISR:ISR没有任务结构体,所以无法给ISR发送数据。但是ISR可以使用任务通知的功能,发数据给任务。(发送可以不能接收)
- 无法广播给多个任务:任务通知只能是被指定的一个任务接受并处理
- 无法缓存多个数据:任务通知时通过更新任务通知值来发送数据的,任务结构体中只有一个任务通知值,只能一个数据。(队列的话,只要有m个队列项就保存m个数据)
- 发送受阻不支持阻塞:发送方无法进入阻塞状态等待(队列已满,可以阻塞)
二、任务通知值和通知状态
任务都有一个结构体:任务控制块TCB,它里边有两个结构体成员变量:
- 一个是uint32_t类型,用来表示通知值
- 一个是uint8_t类型,用来表示通知状态
2.1 任务通知值
任务通知值的更新方式有多种类型:
- 计数值(发送一次任务通知值更新,数值类型累加,类似信号量)
- 相应位置一(发送一次任务通知值更新,相应位置一,类似事件标志组)
- 任意数值(支持覆写和不覆写,类似队列)
2.2 任务通知状态
其中任务通知状态共有三种值:
- 任务未等待通知:任务通知默认的初始化状态
- 等待通知:接收方已经准备好了(调用了接收任务通知函数),等待发送放给个通知
- 等待接收:发送方已经发送出去了(调用了发送任务通知函数),等待接收方接收
三、任务通知相关API函数介绍(介绍)
任务通知API函数主要有两类:①发送通知,②接收通知。
注意:发送通知API函数可以用于任务和中断服务函数中;接收通知API函数只能用在任务中。(因为中断没有任务控制块这个结构体)
3.1 发送通知相关API函数:
函数 | 描述 |
xTaskNotify() | 发送通知,带有通知值 |
xTaskNotifyAndQuery() | 发送通知,带有通知值并且保留接收任务的原通知值 |
xTaskNotifyGive() | 发送通知,不带通知值 |
xTaskNotifyFromISR() | 在中断中发送任务通知 |
xTaskNotifyAndQueryFromISR() | |
vTaskNotifyGiveFromISR() |
在任务中发送通知的3个API函数原型如下所示,可以看到内部都是都是调用的xTaskGenericNotity()函数,唯一的区别就是带入的入口参数有去区别
(下面这个适用于信号量,上面两个适用于队列事件标志组等)
xTaskGenericNotity()函数参数 如下:
形参 | 描述 |
xTaskToNotify | 接收任务通知的任务句柄(指定哪一个任务用于接收) |
uxIndexToNotify | 任务的指定通知(任务通知相关数组成员)0 |
ulValue | 任务通知值 |
eAction | 通知方式(通知值更新方式)(通知值+1) |
pulPreviousNotificationValue | 用于保存更新前的任务通知值(为NULL则不保存) |
任务通知方式共有以下几种:一般用的的是下面的四种,无参数
3.2 接收通知相关API函数:
函数 | 描述 |
ulTaskNotifyTake() | 获取任务通知,可以设置在退出此函数的时候将任务通知值清零或者减一。 当任务通知用作二值信号量或者计数信号量的时候,使用此函数来获取信号量。 |
xTaskNotifyWait() | 获取任务通知,比 ulTaskNotifyTak()更为复杂,可获取通知值和清除通知值的指定位 |
总结:
- 当任务通知用作于信号量时,使用函数获取信号量:ulTaskNotifyTake()
- 当任务通知用作于事件标志组或队列时,使用此函数来获取: xTaskNotifyWait()
3.2.1 任务通知用作于信号量ulTaskNotifyTake()
此函数用于接收任务通知值,可以设置在退出此函数的时候将任务通知值清零或者减一
形参 | 描述 |
uxIndexToWaitOn | 任务的指定通知(任务通知相关数组成员)0 |
xClearCountOnExit | 指定在成功接收通知后,将通知值清零或减 1, pdTRUE:把通知值清零;pdFALSE:把通知值减一 (清0就是模拟二值信号量的获取 pdTRUE 减1 就是模拟计数型信号量 pdFALSE ) |
xTicksToWait | 阻塞等待任务通知值的最大时间 |
返回值 | 描述 |
0 | 接收失败 |
非 0 | 接收成功,返回任务通知的通知值 |
3.2.2 任务通知用作于事件标志组或队列 xTaskNotifyWait()
此函数用于获取通知值和清除通知值的指定位值,适用于模拟队列和事件标志组,使用该函数来获取任务通知。
形参 | 描述 |
uxIndexToWaitOn | 任务的指定通知(任务通知相关数组成员) |
ulBitesToClearOnEntry | 等待前清零指定任务通知值的比特位(旧值对应bit清0)(0x03就是bit1和bit0都清零) |
ulBitesToClearOnExit | 成功等待后清零指定的任务通知值比特位(新值对应bit清0) |
pulNotificationValue | 用来取出通知值(如果不需要取出,可设为NULL) |
xTicksToWait | 阻塞等待任务通知值的最大时间 |
返回值 | 描述 |
pdTRUE | 等待任务通知成功 |
pdFALSE | 等待任务通知失败 |
四、任务通知模拟信号量实验(掌握)
4.1、实验目的:
学习 FreeRTOS 的任务通知功能模拟二值信号量和计数型信号量
4.2、实验设计:
将设计三个任务:start_task、task1、task2
三个任务的功能如下:
- start_task:用来创建task1和task2任务
- task1:用于按键扫描,当检测到按键KEY0被按下时,将发送任务通知
- task2:用于接收任务通知,并打印相关提示信息
4.3 实验代码
4.3.1 模拟二值信号量demo.c
/******************************************************************************************************/
/*FreeRTOS配置*/
/* START_TASK 任务 配置
* 包括: 任务句柄 任务优先级 堆栈大小 创建任务
*/
#define START_TASK_PRIO 1
#define START_TASK_STACK_SIZE 128
TaskHandle_t start_task_handler;
void start_task( void * pvParameters );
/* TASK1 任务 配置
* 包括: 任务句柄 任务优先级 堆栈大小 创建任务
*/
#define TASK1_PRIO 2
#define TASK1_STACK_SIZE 128
TaskHandle_t task1_handler;
void task1( void * pvParameters );
/* TASK2 任务 配置
* 包括: 任务句柄 任务优先级 堆栈大小 创建任务
*/
#define TASK2_PRIO 3
#define TASK2_STACK_SIZE 128
TaskHandle_t task2_handler;
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_handler );
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_handler );
xTaskCreate((TaskFunction_t ) task2,
(char * ) "task2",
(configSTACK_DEPTH_TYPE ) TASK2_STACK_SIZE,
(void * ) NULL,
(UBaseType_t ) TASK2_PRIO,
(TaskHandle_t * ) &task2_handler );
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_handler);
}
vTaskDelay(10);
}
}
/* 任务二,接收任务通知值 */
void task2( void * pvParameters )
{
uint32_t rev = 0;
while(1)
{
rev = ulTaskNotifyTake(pdTRUE , portMAX_DELAY);
if(rev != 0)
{
printf("接收任务通知成功,模拟获取二值信号量!\r\n");
}
}
}
按下复位先打印: 任务通知模拟二值信号量释放!
接收任务通知成功,模拟获取二值信号量!
4.3.2 模拟 计数型信号量 demo.c
****************************************************************************************************
*/
#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_handler;
void start_task( void * pvParameters );
/* TASK1 任务 配置
* 包括: 任务句柄 任务优先级 堆栈大小 创建任务
*/
#define TASK1_PRIO 2
#define TASK1_STACK_SIZE 128
TaskHandle_t task1_handler;
void task1( void * pvParameters );
/* TASK2 任务 配置
* 包括: 任务句柄 任务优先级 堆栈大小 创建任务
*/
#define TASK2_PRIO 3
#define TASK2_STACK_SIZE 128
TaskHandle_t task2_handler;
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_handler );
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_handler );
xTaskCreate((TaskFunction_t ) task2,
(char * ) "task2",
(configSTACK_DEPTH_TYPE ) TASK2_STACK_SIZE,
(void * ) NULL,
(UBaseType_t ) TASK2_PRIO,
(TaskHandle_t * ) &task2_handler );
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_handler);
}
vTaskDelay(10);
}
}
/* 任务二,接收任务通知值 */
void task2( void * pvParameters )
{
uint32_t rev = 0;
while(1)
{
rev = ulTaskNotifyTake(pdFALSE , portMAX_DELAY);//与模拟二值信号量唯一的区别就是pdFALSE
if(rev != 0)
{
printf("rev:%d\r\n",rev);
}
vTaskDelay(1000);//加个延时,不然一获取立马就被释放了
}
}
按下复位先打印: 任务通知模拟计数型信号量释放!
rev:1!(直到减到1)
五、任务通知模拟信息邮箱实验(掌握)
5.1、实验目的:
学习 FreeRTOS 的任务通知功能模拟消息邮箱
5.2、实验设计:
将设计三个任务:start_task、task1、task2
三个任务的功能如下:
- start_task:用来创建task1和task2任务
- task1:用于按键扫描,将按下的按键键值通过任务通知发送给指定任务
- task2:用于接收任务通知,并根据接收到的数据做相应动作
5.3 实验代码
demo.c
/******************************************************************************************************/
/*FreeRTOS配置*/
/* START_TASK 任务 配置
* 包括: 任务句柄 任务优先级 堆栈大小 创建任务
*/
#define START_TASK_PRIO 1
#define START_TASK_STACK_SIZE 128
TaskHandle_t start_task_handler;
void start_task( void * pvParameters );
/* TASK1 任务 配置
* 包括: 任务句柄 任务优先级 堆栈大小 创建任务
*/
#define TASK1_PRIO 2
#define TASK1_STACK_SIZE 128
TaskHandle_t task1_handler;
void task1( void * pvParameters );
/* TASK2 任务 配置
* 包括: 任务句柄 任务优先级 堆栈大小 创建任务
*/
#define TASK2_PRIO 3
#define TASK2_STACK_SIZE 128
TaskHandle_t task2_handler;
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_handler );
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_handler );
xTaskCreate((TaskFunction_t ) task2,
(char * ) "task2",
(configSTACK_DEPTH_TYPE ) TASK2_STACK_SIZE,
(void * ) NULL,
(UBaseType_t ) TASK2_PRIO,
(TaskHandle_t * ) &task2_handler );
vTaskDelete(NULL);
taskEXIT_CRITICAL(); /* 退出临界区 */
}
/* 任务一,发送任务通知值 */
void task1( void * pvParameters )
{
uint8_t key = 0;
while(1)
{
key = key_scan(0);
if((key != 0) && (task2_handler != NULL))
{
printf("任务通知模拟消息邮箱发送,发送的键值为:%d\r\n",key);
xTaskNotify( task2_handler, key, eSetValueWithOverwrite );
}
vTaskDelay(10);
}
}
/* 任务二,接收任务通知值 */
void task2( void * pvParameters )
{
uint32_t noyify_val = 0;
while(1)
{
xTaskNotifyWait( 0, 0xFFFFFFFF, &noyify_val, portMAX_DELAY );
/* 0 不清除 0xFFFFFFFF等到后把所有位都清零(32位8个F) 用来取出通知值 阻塞等待任务通知值的最大时间*/
switch(noyify_val)
{
case KEY0_PRES:
{
printf("接收到的通知值为:%d\r\n",noyify_val);
LED0_TOGGLE();
break;
}
case KEY1_PRES:
{
printf("接收到的通知值为:%d\r\n",noyify_val);
LED1_TOGGLE();
break;
}
default : break;
}
}
}
六、任务通知模拟时间标志组实验(掌握)
6.1、实验目的:
学习 FreeRTOS 的任务通知功能模拟事件标志组
6.2、实验设计:
将设计三个任务:start_task、task1、task2
三个任务的功能如下:
- start_task:用来创建task1和task2任务
- task1:用于按键扫描,当检测到按键按下时,发送任务通知设置不同标志位
- task2:用于接收任务通知,并打印相关提示信息
6.3 实验代码
demo.c
****************************************************************************************************
*/
#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_handler;
void start_task( void * pvParameters );
/* TASK1 任务 配置
* 包括: 任务句柄 任务优先级 堆栈大小 创建任务
*/
#define TASK1_PRIO 2
#define TASK1_STACK_SIZE 128
TaskHandle_t task1_handler;
void task1( void * pvParameters );
/* TASK2 任务 配置
* 包括: 任务句柄 任务优先级 堆栈大小 创建任务
*/
#define TASK2_PRIO 3
#define TASK2_STACK_SIZE 128
TaskHandle_t task2_handler;
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_handler );
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_handler );
xTaskCreate((TaskFunction_t ) task2,
(char * ) "task2",
(configSTACK_DEPTH_TYPE ) TASK2_STACK_SIZE,
(void * ) NULL,
(UBaseType_t ) TASK2_PRIO,
(TaskHandle_t * ) &task2_handler );
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_handler, EVENTBIT_0, eSetBits );
}else if(key == KEY1_PRES)
{
printf("将bit1位置1\r\n");
xTaskNotify( task2_handler, EVENTBIT_1, eSetBits );
}
vTaskDelay(10);
}
}
/* 任务二,接收任务通知值 */
void task2( void * pvParameters )
{
uint32_t notify_val = 0,event_bit = 0;//另外定义一个变量保存bit值
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))//两个都是1了
{
printf("任务通知模拟事件标志组接收成功!!\r\n");
event_bit = 0;
}
}
}
七、课堂总结