【FreeRTOS】RTOS任务的同步与互斥:(五)任务通知

同步与互斥是学习FreeRTOS的重点内容,同步与互斥的相关的内容可参考:

FreeRTOS入门教程(同步与互斥)

RTOS任务的同步与互斥:(一)队列

同步与互斥的概念:

  • 同步: A等待B做完某件事

  • 互斥: 某一资源同一时间仅能有一个用户访问

  • RTOS同步与互斥的方式: 队列(queue)、信号量(semaphoe)、互斥量(mutex)、事件组(event group)、任务通知(task notification)等

任务通知相关概念

任务通知的概念

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

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

任务通知的更新方式

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

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

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

任务通知的优劣

任务通知的优势

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

任务通知的劣势

  1. 只有任务可以等待通知,中断服务函数中不可以,因为中断没有 TCB 。
  2. 通知只能一对一,因为通知必须指定任务。
  3. 等待通知的任务可以被阻塞, 但是发送消息的任务,任何情况下都不会被阻塞等待。
  4. 任务通知是通过更新任务通知值来发送数据的,任务结构体中只有一个任务通知值,只能保持一个数据。

任务通知相关API函数

1. 发送通知

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

函数1原型

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, 而其他情况均返回pdTRUE


函数2原型
该函数与函数1及其相似,区别在于多出一个参数用于收集上一个任务的通知值,而上述函数将上一任务的通知值直接丢弃。

BaseType_t xTaskNotifyAndQuery( TaskHandle_t xTaskToNotify,
                				uint32_t ulValue,
               					eNotifyAction eAction,
                				uint32_t *pulPreviousNotifyValue );
参数描述
xTaskToNotify需要接收通知的任务句柄
ulValue用于更新接收任务通知值, 具体如何更新由形参 eAction 决定;
eAction一个枚举,代表如何使用任务通知的值;
pulPreviousNotifyValue对象任务的上一个任务通知值,如果为 NULL, 则不需要回传, 这个时候就等价于函数 xTaskNotify()。

返回值:
  如果被通知任务还没取走上一个通知,又接收了一个通知,则这次通知值未能更新并返回 pdFALSE, 而其他情况均返回pdTRUE


函数3原型

BaseType_t xTaskNotifyGive( TaskHandle_t xTaskToNotify );

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

返回值:
  总是返回 pdTRUE

2. 等待通知

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

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

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

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

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

返回值:

  • 0:接收失败
  • 非0:接收成功,返回任务通知的通知

任务通知案例设计

cubeMX配置

  CubeMX工程的建立与移植建立在基础工程上,模板工程的创建及其底层驱动代码请参考:使用CubeMX快速移植FreeRTOS工程到蓝桥杯开发板

程序设计

模拟二值信号量
按键1按下发送二值信号量,按键2按下接收二值信号量

void startTaskSend(void const * argument)
{
	uint8_t key;
	while(1)
	{
		key = key_scan();
		if(key == KEY1_PRES)
		{
			xTaskNotifyGive(taskReceiveHandle);
			printf("任务通知 模拟二值信号量发送成功\r\n");
		}
		osDelay(10);
	}
}

void startTaskReceive(void const * argument)
{
	uint8_t key;
	uint32_t rev = 0;
	while(1)
	{
		key = key_scan();
		if(key == KEY2_PRES)
		{
			rev = ulTaskNotifyTake(pdTRUE,portMAX_DELAY);
			if(rev != 0)
				printf("任务通知 接收二值信号量发送成功 rev=%d\r\n",rev);
		}
		osDelay(10);
	}
}

串口调试结果: 接收到数据后清零

模拟计数型信号量
  模拟计数型信号量与二值信号量的区别在于等待通知的参数,在成功接收到通知后,将通知的值清零或减一,因此只需要将上述接收二值信号量的接收参数 pdTRUE(清零)改成 pdFALSE(减一)即可。

串口调试结果: 接收到数据后减一

模拟事件标志组
  任务1检测按键状态,当按键1按下时,bit0 置1,按键2按下时,bit1置1,任务2获取事件标志位数据,当检测到bit0和bit1都置1时,任务通知模拟事件标志组接收成功。

任务1: 检测按键状态并对相应的位置1

void startTaskSend(void const * argument)
{
	//模拟事件标志组
	uint8_t key;
	while(1)
	{
		key = key_scan();
		if(key == KEY1_PRES)
		{
			//模拟事件标志组
			printf("bit0 置1 导师通过请假\r\n");
			xTaskNotify(taskReceiveHandle, 0x01, eSetBits);
		}
		else if(key == KEY2_PRES)
		{
			//模拟事件标志组
			printf("bit1 置1 辅导员通过请假\r\n");
			xTaskNotify(taskReceiveHandle, 0x02, eSetBits);
		}
		osDelay(10);
	}
}

任务2: 检测最低两位的数值

void startTaskReceive(void const * argument)
{
	uint32_t notify_val = 0;
	uint32_t event_bit = 0;
	while(1)
	{
		xTaskNotifyWait(0, 0xFFFFFFFF, &notify_val, portMAX_DELAY);
		if(notify_val & 0x01)
			event_bit |= 0x01;
		if(notify_val & 0x02)
			event_bit |= 0x02;
		if(event_bit == (0x01 | 0x02))
		{
			printf("\r\n任务通知模拟事件标志组接收成功\r\n");
			printf("\r\n导师与辅导员同时通过请假\r\n");
			event_bit = 0;
		}
		osDelay(10);
	}
}

串口调试结果:

模拟邮箱
  邮箱是长度为1的队列,发送按键1按下的次数,按键2接收队列数据。

任务1

void startTaskSend(void const * argument)
{
	uint8_t key;
	uint8_t cnt = 0;
	while(1)
	{
		key = key_scan();
		if(key == KEY1_PRES)
		{
			cnt++;
			printf("按键1按下 send cnt = %d\r\n",cnt);
			xTaskNotify(taskReceiveHandle, cnt, eSetValueWithOverwrite);
		}
		osDelay(10);
	}
}

任务2

void startTaskReceive(void const * argument)
{
	//模拟邮箱 长度为1的队列
	uint32_t notify_val = 0;
	uint8_t key;
	while(1)
	{
		key = key_scan();
		if(key == KEY2_PRES)
		{
			xTaskNotifyWait(0, 0xFFFFFFFF, &notify_val, portMAX_DELAY);
			printf("接收到的通知值: = %d",notify_val);
		}
		osDelay(10);
	}
}

串口调试结果:

结果分析: 按下3次key1,队列中数据被复写成了3,因此接收时候只接收到了3,再次按下key2无现象,因为此时队列已置空,无数据。

若将程序设置为非覆盖, 第一次写入1,无论key1再次按下,队列中数据仍然保存为1,接收的时候也只能接收到1,若设置为非覆盖,只需要将发送函数 xTaskNotify 的宏 eAction 改为 eSetValueWithoutOverwrite 即可

串口调试结果:

  • 8
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
FreeRTOS是一个开源的实时操作系统(RTOS),它支持多任务处理。在FreeRTOS中,任务是操作系统的基本执行单位。每个任务具有自己的独立的执行环境,包括堆栈、程序计数器和相关的寄存器。 FreeRTOS使用优先级调度算法来确定任务的执行顺序。每个任务被分配一个优先级,优先级越高,任务就越容易被调度。当多个任务具有相同的优先级时,FreeRTOS使用时间片轮转调度策略来平均分配CPU时间。 在使用FreeRTOS进行多任务处理时,你需要定义和创建多个任务,并为每个任务指定一个入口函数。这些任务可以并行运行,并且可以通过各种机制(如信号量、队列和事件标志)进行通信和同步。 下面是一个使用FreeRTOS的简单示例代码: ``` #include "FreeRTOS.h" #include "task.h" // 任务1的入口函数 void task1(void *pvParameters) { while (1) { // 执行任务1的操作 // ... } } // 任务2的入口函数 void task2(void *pvParameters) { while (1) { // 执行任务2的操作 // ... } } int main(void) { // 创建任务1 xTaskCreate(task1, "Task 1", configMINIMAL_STACK_SIZE, NULL, tskIDLE_PRIORITY + 1, NULL); // 创建任务2 xTaskCreate(task2, "Task 2", configMINIMAL_STACK_SIZE, NULL, tskIDLE_PRIORITY + 2, NULL); // 启动任务调度器 vTaskStartScheduler(); return 0; } ``` 在上面的示例中,我们创建了两个任务(task1和task2),并通过xTaskCreate函数为每个任务指定了一个入口函数。然后,我们使用vTaskStartScheduler函数启动任务调度器,它将开始按照优先级调度算法来调度任务的执行。 需要注意的是,FreeRTOS还提供了其他功能,如互斥锁、定时器和软件定时器等,以帮助你更好地管理任务和资源。通过合理地使用这些功能,你可以实现复杂的多任务处理系统。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

不会编程的小江江

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值