FreeRTOS任务通知

目录

一、任务通知的概念

二、任务通知的更新方式

三、任务通知的优势和劣势

1、优势

2、劣势

四、任务通知相关API

1、发送通知

2、等待通知

五、任务通知实操

1、模拟二值信号量

1.1 CubeMX配置

1.2、代码实现

2、模拟计数型信号量

2.1 CubeMX配置

2.2 代码实现

3、模拟事件标志组

3.1 CubeMX配置

3.2 代码实现

4、模拟邮箱

4.1 CubeMX配置

4.2 代码实现


一、任务通知的概念

FreeRTOS 从版本 V8.2.0 开始提供任务通知这个功能,每个任务都有一个 32 位的通知值。按照 FreeRTOS 官方的说法,使用消息通知比通过二进制信号量方式解除阻塞任务快 45%, 并且更加 省内存(无需创建队列)。

在大多数情况下,任务通知可以替代二值信号量、计数信号量、事件标志组,可以替代长度为 1 的队列(可以保存一个 32 位整数或指针值),并且任务通知速度更快使用的RAM更少

二、任务通知的更新方式

FreeRTOS 提供以下几种方式发送通知给任务 :

  • 发送消息给任务,如果有通知未读, 不覆盖通知值
  • 发送消息给任务,直接覆盖通知值
  • 发送消息给任务,设置通知值的一个或者多个位
  • 发送消息给任务,递增通知值

通过对以上方式的合理使用,可以在一定场合下替代原本的队列、信号量、事件标志组等。

三、任务通知的优势和劣势

1、优势

  • 使用任务通知向任务发送事件或数据,比使用队列、事件标志组或信号量快得多
  • 使用其他方法时都要先创建对应的结构体,使用任务通知时无需额外创建结构体

2、劣势

  • 只有任务可以等待通知中断服务函数中不可以,因为中断没有 TCB (TCB:创建任务时会在内存中开辟一个空间,但在中断中无法开辟开辟空间)。
  • 通知只能一对一,因为通知必须指定任务
    BaseType_t xTaskNotify( TaskHandle_t xTaskToNotify,
                            uint32_t ulValue,
                            eNotifyAction eAction );

  • 等待通知的任务可以被阻塞, 但是发送消息的任务,任何情况下都不会被阻塞等待
  • 任务通知是通过更新任务通知值来发送数据的,任务结构体中只有一个任务通知值,只能保 持一个数据。

四、任务通知相关API

1、发送通知

             函数                        描述
xTaskNotify()发送通知,带有通知值
xTaskNotifyAndQuery()发送通知,带有通知值并且保留接收任务的原通知值
xTaskNotifyGive()发送通知,不带通知值
xTaskNotifyFromISR()在中断中发送任务通知
xTaskNotifyAndQueryFromISR()在中断中发送任务通知
vTaskNotifyGiveFromISR()在中断中发送任务通知
BaseType_t xTaskNotify( TaskHandle_t xTaskToNotify,
                        uint32_t ulValue,
                        eNotifyAction eAction );

参数:

xTaskToNotify:

  • 需要接收通知的任务句柄;

ulValue:

  • 用于更新接收任务通知值, 具体如何更新由形参 eAction 决定;

eAction:

  • 一个枚举,代表如何使用任务通知的值;
         枚举值                                描述
eNoAction发送通知,但不更新值(参数ulValue未使用)
eSetBits被通知任务的通知值按位或ulValue。(某些场景下可代替事件组,效率更高)
eIncrement被通知任务的通知值增加1(参数ulValue未使用),相当于 xTaskNotifyGive
eSetValueWithOverwrite被通知任务的通知值设置为 ulValue。(某些场景下可代替 xQueueOverwrite ,效率更高)
eSetValueWithoutOverwrite

如果被通知的任务当前没有通知,则被通知的任务的通知值设为ulValue。

如果被通知任务没有取走上一个通知,又接收到了一个通 知,则这次通知值丢弃,在这种情况下视为调用失败并返回 pdFALSE

(某些场景下可代替 xQueueSend ,效率更高)

返回值:

  • 如果被通知任务还没取走上一个通知,又接收了一个通知,则这次通知值未能更新并返回 pdFALSE, 而其他情况均返回pdPASS。
BaseType_t xTaskNotifyAndQuery( TaskHandle_t xTaskToNotify,
                                uint32_t ulValue,
                                eNotifyAction eAction,
                                uint32_t *pulPreviousNotifyValue );

参数:

xTaskToNotify:

  • 需要接收通知的任务句柄;

ulValue:

  • 用于更新接收任务通知值, 具体如何更新 由形参 eAction 决定;

eAction:

  • 一个枚举,代表如何使用任务通知的值;

pulPreviousNotifyValue:

  • 对象任务的上一个任务通知值,如果为 NULL, 则不需要回传, 这个时候就等价于函数 xTaskNotify()。

返回值:

  • 如果被通知任务还没取走上一个通知,又接收了一个通知,则这次通知值未能更新并返回 pdFALSE, 而其他情况均返回pdPASS
BaseType_t xTaskNotifyGive( TaskHandle_t xTaskToNotify );

参数:

  • xTaskToNotify:接收通知的任务句柄, 并让其自身的任务通知值加 1

返回值:

  • 总是返回 pdPASS。

2、等待通知

等待通知API函数只能用在任务,不可应用于中断中!

  函数                                                描述
ulTaskNotifyTake()获取任务通知,可以设置在退出此函数的时候将任务通知值清零或者减 一。当任务通知用作二值信号量或者计数信号量的时候,使用此函数来 获取信号量。
xTaskNotifyWait()获取任务通知,比 ulTaskNotifyTake()更为复杂,可获取通知值和清除通知值的指定位
uint32_t ulTaskNotifyTake( BaseType_t xClearCountOnExit,
                            TickType_t xTicksToWait );

参数:

xClearCountOnExit:指定在成功接收通知后,将通知值清零或减 1

  • pdTRUE:把通知值清零(二值信号量);
  • pdFALSE:把通知值减一(计数型信号量);

xTicksToWait:阻塞等待任务通知值的最大时间

  • 超时时间,0 表示不超时;
  • portMAX_DELAY表示卡死等待;

返回值:

  • 0:接收失败
  • 非0:接收成功,返回任务通知的通知值
BaseType_t xTaskNotifyWait( uint32_t ulBitsToClearOnEntry,
                            uint32_t ulBitsToClearOnExit,
                            uint32_t *pulNotificationValue,
                            TickType_t xTicksToWait );

ulBitsToClearOnEntry:

  • 函数执行前清零任务通知值那些位 (一般设置成0)。

ulBitsToClearOnExit:

  • 表示在函数退出前,清零任务通知值那些位,在清 0 前,接收到的任务通知值会先被保存到形参 *pulNotificationValue 中。(一般在退出前全部清零,即配置0xfffffff

pulNotificationValue:

  • 用于保存接收到的任务通知值。 如果不需要使用,则设置为 NULL 即可  

xTicksToWait:等待消息通知的最大等待时间。

  • 超时时间,0 表示不超时,
  • portMAX_DELAY表示卡死等待

五、任务通知实操

这里已经将FreeRTOS移植到STM32F103C8T6,具体操作流程看前面的文章。

下面的实操都需要用到串口打印,所以在修改FreeRTOS.中的代码时,需要添加串口i重定向相关代码,为了不重复说明,我就在这里展示相关操作,如下:

 uart.c 重定向printf

#include "stdio.h"
int fputc(int ch,FILE *f)
{
	unsigned char temp[1] = {ch};
	HAL_UART_Transmit(&huart1,temp,1,0xffff);
	return ch;
}

需要打开魔术棒勾上红框内选项实现串口打印

1、模拟二值信号量

1.1 CubeMX配置

 查看原理图配置按键引脚

创建两个任务

1.2、代码实现

用到函数如下:

  • xTaskNotifyGive()
  • ulTaskNotifyTake()
void StartTaskSend(void const * argument)
{
  for(;;)
  {
		if(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) == GPIO_PIN_RESET)
		{
			osDelay(20);
			if (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) == GPIO_PIN_RESET)
			{
				xTaskNotifyGive(TaskReceiveHandle);//参数为接收任务通知的句柄
				printf("任务通知:模拟二值信号量发送成功\r\n");
			}
			while (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) == GPIO_PIN_RESET);
		}
    osDelay(10);
  }
}
 
void StartTaskReceive(void const * argument)
{
  uint32_t receive = 0;//任务通知的通知值
  for(;;)
  {
		if(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_1) == GPIO_PIN_RESET)
		{
			osDelay(20);
			if (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_1) == GPIO_PIN_RESET)
			{
				rev = ulTaskNotifyTake(pdTRUE,portMAX_DELAY);//第二个参数0 表示不超时,portMAX_DELAY表示卡死等待
				if(rev != 0)
					printf("任务通知:模拟二值信号量接收成功,接收的通知值为:%d\r\n",receive);
			}
			while (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_1) == GPIO_PIN_RESET);
		}
    osDelay(10);
  }
}

前面二值信号量的实验需求时按下KEY1释放二值信号量,按下KEY2获取二值信号量;而这里的实验成功模仿了二值信号量的获取释放,按下KEY1发送二至信号量,再次按下KEY2接收二值信号量。

2、模拟计数型信号量

2.1 CubeMX配置

这里的相关配置是基于上一个实验的,所以与上面的CubeMX配置流程相同。这里就不作演示。

2.2 代码实现

用到函数如下

  • xTaskNotifyGive()
  • ulTaskNotifyTake()
void StartTaskSend(void const * argument)
{
  for(;;)
  {
		if(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) == GPIO_PIN_RESET)
		{
			osDelay(20);
			if (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) == GPIO_PIN_RESET)
			{
				xTaskNotifyGive(TaskReceiveHandle);//参数为接收任务通知的句柄
				printf("任务通知:模拟计数型信号量发送成功\r\n");
			}
			while (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) == GPIO_PIN_RESET);
		}
    osDelay(10);
  }
}
 
void StartTaskReceive(void const * argument)
{
  uint32_t receive = 0;//任务通知的通知值
  for(;;)
  {
		if(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_1) == GPIO_PIN_RESET)
		{
			osDelay(20);
			if (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_1) == GPIO_PIN_RESET)
			{
				rev = ulTaskNotifyTake(pdFALSE,portMAX_DELAY);//第二个参数0 表示不超时,portMAX_DELAY表示卡死等待
				if(rev != 0)
					printf("任务通知:模拟计数型信号量接受成功,接收的任务通知值为%d\r\n",receive);
			}
			while (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_1) == GPIO_PIN_RESET);
		}
    osDelay(10);
  }
}

按键KEY1按下发送多个计数型型号量,按键KEY2按下一次接收计数型型号了,同时任务通知值递减。

3、模拟事件标志组

3.1 CubeMX配置

这里的相关配置是基于上一个实验的,所以与上面的CubeMX配置流程相同。这里就不作演示。

3.2 代码实现

用到函数如下:

  • xTaskNotify()
  • xTaskNotifyWait()
void StartTaskSend(void const * argument)
{
  for(;;)
  {
		if(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) == GPIO_PIN_RESET)
		{
			osDelay(20);
			if (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) == GPIO_PIN_RESET)
			{                                               
				printf("将bit0位置1\r\n");
				xTaskNotify(TaskReceiveHandle,0x01,eSetBits);//按下为0x01
			}
			while (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) == GPIO_PIN_RESET);
		}
		if(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_1) == GPIO_PIN_RESET)
		{
			osDelay(20);
			if (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_1) == GPIO_PIN_RESET)
			{                                               
				printf("将bit1位置1\r\n");
				xTaskNotify(TaskReceiveHandle,0x02,eSetBits);//按下为0x02
			}
			while (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_1) == GPIO_PIN_RESET);
		}	
    osDelay(10);
  }
}
 
void StartTaskReceive(void const * argument)
{
  uint32_t receive	= 0;//任务通知的通知值
  uint32_t event = 0;//存储接收到的任务通知值
  for(;;)
  {
		xTaskNotifyWait(0,0xffffffff,&receive,portMAX_DELAY);
		if(receive & 0x01)//判断KEY1是否按下
			event |= 0x01;
		if(receive & 0x02)//判断KEY2是否按下
			event |= 0x02;
		if(event == (0x01 | 0x02))
		{
			printf("任务通知模拟事件标志组接收成功!\r\n");
			event = 0;
		}
    osDelay(10);
  }
}

按键KEY1或者按键KEY2先后按下任务通知值才能接收成功。

4、模拟邮箱

邮箱实际上是长度为11的队列!

4.1 CubeMX配置

这里的相关配置是基于上一个实验的,所以与上面的CubeMX配置流程相同。这里就不作演示。

4.2 代码实现

用到函数如下:

  • xTaskNotify()
  • xTaskNotifyWait()
void StartTaskSend(void const * argument)
{
  for(;;)
  {
		if(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) == GPIO_PIN_RESET)
		{
			osDelay(20);
			if (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) == GPIO_PIN_RESET)
			{                                               
				printf("按键1按下\r\n");
				xTaskNotify(TaskReceiveHandle,1,eSetValueWithOverwrite);
			}
			while (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) == GPIO_PIN_RESET);
		}
		if(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_1) == GPIO_PIN_RESET)
		{
			osDelay(20);
			if (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_1) == GPIO_PIN_RESET)
			{                                               
				printf("按键2按下\r\n");
				xTaskNotify(TaskReceiveHandle,2,eSetValueWithOverwrite);
			}
			while (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_1) == GPIO_PIN_RESET);
		}		
    osDelay(1);
  }
}
 
void StartTaskReceive(void const * argument)
{
  uint32_t notify_rev = 0;
  for(;;)
  {
		xTaskNotifyWait(0,0xffffffff,&receive,portMAX_DELAY);
		printf("接收到的通知值为:%d\r\n",receive);
    osDelay(1);
  }
}

按键1、2按下发送不同任务通知,同时接收端会接收到任务通知值。

  • 21
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值