FreeRTOS轻量级同步--任务通知

2023-11-17

1.简介

        在FreeRTOS的配置参数中的configUSE_TASK_NOTIFICATIONS宏打开,一般RTOS会默认打开,如图1所示。

图1 notify宏开关

        RTOS在创建任务时,会创建一个32位的通知值ulNotifiedValue,并初始化0,RTOS进行任务通知时,相当于直接向另一个任务的句柄发送notify通知,不用借助于其他内核对象,如信号量等,接收任务收到notify通知后执行对应的操作,从而解除任务阻塞。如图2所示。

图2 任务之间的通知

可以配置/更新接收任务的notification值的方式可以是如下的方式:

  • 直接写一个32位的notify值,类似于一个邮箱,可以发送同步内容也可发送指针
  • 让接收任务的notification自加1,可用于信号量(二值信号量、计数信号量)
  • 置位notification的某些位(可用于同步事件组)
  • 不改变接收任务的notification(当接收任务没有处理完时,不会更新发送的通知值

        凡是都有利弊,不然的话 FreeRTOS 还要内核的 IPC 通信机制干嘛,消息通知虽然处理更快,RAM 开销更小,但也有以下限制 :

  • 只能有一个任务接收通知消息,因为必须指定接收通知的任务。
  • 只有等待通知的任务可以被阻塞,发送通知的任务,在任何情况下都不会因为发送失败而进入阻塞态。
     

2.应用

2.1 轻量级二值信号量

        相当于一个二值信号量,eAction模式为eIncrement

发送任务的notify: xTaskNotifyGive( TaskHandle_t xTaskToNotify )

                                其中,xTaskToNotify为通知任务的句柄

接收任务的notify:rtval = ulTaskNotifyTake(pdTRUE, portMAX_DELAY);

                                其中,pdTRUE的话就是每获取take一次notify,value值复位到0,相当于二值                                    信号量,第二个量为等待时间..

                                 rtval是接收到的返回值,他是递减前或复位前的值。

demo分析

#include "APPTaskDef.h"
#include "led.h"
#include "delay.h"
#include "key.h"


#define 		START_TASK_PRIO			1
#define 		START_STACK_SIZE		128
TaskHandle_t 	startTask_handler;  //开始任务句柄
static void 	start_task(void *param);


#define			TASK1_PRIO		3
#define			TASK1_STACK_SIZE	128
TaskHandle_t	task1_handler; //控制task1任务句柄
static void 	task1(void *param);

#define			TASK2_PRIO		2
#define			TASK2_STACK_SIZE	128
TaskHandle_t	task2_handler; //控制task2任务句柄
static void 	task2(void *param);


void APP_task(void)
{
	xTaskCreate((TaskFunction_t)start_task,
				(const char *)"start_task",
				(uint16_t)START_STACK_SIZE,
				NULL,
				(UBaseType_t)START_TASK_PRIO,
				(TaskHandle_t *)&startTask_handler);
	vTaskStartScheduler();          //开启任务调度
}

static void start_task(void *param)
{
	
	taskENTER_CRITICAL();           //进入临界区
	

    //创建获取event的任务
    xTaskCreate((TaskFunction_t )task1,     	
                (const char*    )"task1",   	
                (uint16_t       )TASK1_STACK_SIZE, 
                (void*          )NULL,				
                (UBaseType_t    )TASK1_PRIO,	
                (TaskHandle_t*  )&task1_handler);   
				

	xTaskCreate((TaskFunction_t )task2,     	
			(const char*    )"task2",   	
			(uint16_t       )TASK2_STACK_SIZE, 
			(void*          )NULL,				
			(UBaseType_t    )TASK2_PRIO,	
			(TaskHandle_t*  )&task2_handler);

    vTaskDelete(startTask_handler); //删除开始任务
    taskEXIT_CRITICAL();            //退出临界区
}

/*
	timer control task
*/

static void task1(void *param)
{
	
	u32 NotifyValue = 0;
	int i = 0;
	while(1)
	{
		xTaskNotifyGive(task2_handler);
		
		printf("send notify!\r\n");
		LED0 = !LED0;

		vTaskDelay(1000);
		
	}
}


static void task2(void *param)
{
	
	u32 NotifyValue = 0;
	while(1)
	{
		NotifyValue = ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
//其中,为pdTRUE的话就是每获取take一次notify,value值复位到0----------------------------------------------------相当于二值信号量
		if(NotifyValue - 1 > 0)
		{
			LED1 = !LED1;
			printf("receive notify!\r\n");
			printf("cur notify value is %d\r\n", NotifyValue - 1);
			printf("--------------------------------------------\r\n");
		}else{
			vTaskDelay(1000);
		}
		
	}
}

2.2 轻量级计数信号量

        notify模拟计数信号量与二值信号量的区别是接受的信号值是以递减的方式,即pdFALSE, eAction模式为eIncrement

发送任务的notify: xTaskNotifyGive( TaskHandle_t xTaskToNotify )

                                其中,xTaskToNotify为通知任务的句柄

接收任务的notify:rtval = ulTaskNotifyTake(pdFALSE, portMAX_DELAY);

                                其中,pdFALSE的话就是每获取take一次notify,notifyvalue值减1,相当于                                     计数信号量,第二个量为等待时间..

                                 rtval是接收到的返回值,他是递减前或复位前的值。

demo分析

#include "APPTaskDef.h"
#include "led.h"
#include "delay.h"
#include "key.h"


#define 		START_TASK_PRIO			1
#define 		START_STACK_SIZE		128
TaskHandle_t 	startTask_handler;  //开始任务句柄
static void 	start_task(void *param);


#define			TASK1_PRIO		3
#define			TASK1_STACK_SIZE	128
TaskHandle_t	task1_handler; //控制task1任务句柄
static void 	task1(void *param);

#define			TASK2_PRIO		2
#define			TASK2_STACK_SIZE	128
TaskHandle_t	task2_handler; //控制task2任务句柄
static void 	task2(void *param);


void APP_task(void)
{
	xTaskCreate((TaskFunction_t)start_task,
				(const char *)"start_task",
				(uint16_t)START_STACK_SIZE,
				NULL,
				(UBaseType_t)START_TASK_PRIO,
				(TaskHandle_t *)&startTask_handler);
	vTaskStartScheduler();          //开启任务调度
}

static void start_task(void *param)
{
	
	taskENTER_CRITICAL();           //进入临界区
	

    //创建获取event的任务
    xTaskCreate((TaskFunction_t )task1,     	
                (const char*    )"task1",   	
                (uint16_t       )TASK1_STACK_SIZE, 
                (void*          )NULL,				
                (UBaseType_t    )TASK1_PRIO,	
                (TaskHandle_t*  )&task1_handler);   
				

	xTaskCreate((TaskFunction_t )task2,     	
			(const char*    )"task2",   	
			(uint16_t       )TASK2_STACK_SIZE, 
			(void*          )NULL,				
			(UBaseType_t    )TASK2_PRIO,	
			(TaskHandle_t*  )&task2_handler);

    vTaskDelete(startTask_handler); //删除开始任务
    taskEXIT_CRITICAL();            //退出临界区
}

/*
	timer control task
*/

static void task1(void *param)
{
	
	u32 NotifyValue = 0;
	int i = 0;
	while(1)
	{
		for(i = 0; i < 3; i++)
		{
			xTaskNotifyGive(task2_handler);
		}
		
		printf("send notify!\r\n");
		LED0 = !LED0;

		vTaskDelay(1000);
		
	}
}


static void task2(void *param)
{
	
	u32 NotifyValue = 0;
	while(1)
	{
		NotifyValue = ulTaskNotifyTake(pdFALSE, portMAX_DELAY);
		//ulTaskNotifyTake(pdFALSE, portMAX_DELAY);中为pdFALSE的话就是每获取take一次notify,value值减1-----------相当于计数信号量
		//为pdTRUE的话就是每获取take一次notify,value值复位到0----------------------------------------------------相当于二值信号量
		if(NotifyValue - 1 > 0)
		{
			LED1 = !LED1;
			printf("receive notify!\r\n");
			printf("cur notify value is %d\r\n", NotifyValue - 1);
			printf("--------------------------------------------\r\n");
		}else{
			vTaskDelay(1000);
		}
		
	}
}

2.3 轻量级邮箱

        任务通知也可用来向任务发送数据,但是相对于用队列发送消息,任务通知向任务发送消
息会受到很多限制!
        1)只能发送 32 位的数据值。
        2)消息被保存为任务的任务通知值,而且一次只能保存一个任务通知值,相当于队列长度
为 1。
因此说任务通知可以模拟一个轻量级的消息邮箱而不是轻量级的消息队列。任务通知值就
是消息邮箱的值。

邮箱发送:xTaskNotify( xTaskToNotify, ulValue, eAction )

                xTaskToNotify:通知任务句柄,

                ulValue:通知的值(邮箱的内容)

                eAction:4种模式:

typedef enum
{
	eNoAction = 0,				/* Notify the task without updating its notify value. */
	eSetBits,					/* Set bits in the task's notification value. */
	eIncrement,					/* Increment the task's notification value. */
	eSetValueWithOverwrite,		/* Set the task's notification value to a specific value even if the previous value has not yet been read by the task. */
	eSetValueWithoutOverwrite	/* Set the task's notification value if the previous value has been read by the task. */
} eNotifyAction;

邮箱接收:      BaseType_t xTaskNotifyWait( uint32_t ulBitsToClearOnEntry, 进入函数需要清空位

                                                uint32_t ulBitsToClearOnExit, 退出函数前需要清空的位

                                                 uint32_t *pulNotificationValue, 获得的notify值

                                                 TickType_t xTicksToWait )    等待的时间

demo分析

#include "APPTaskDef.h"
#include "led.h"
#include "delay.h"
#include "key.h"
#include "event_groups.h"

#define 		START_TASK_PRIO			1
#define 		START_STACK_SIZE		128
TaskHandle_t 	startTask_handler;  //开始任务句柄
static void 	start_task(void *param);


#define 		NOTIFY_MAIL_SEND_TASK_PRIO				3
#define 		NOTIFY_MAIL_SEND_TASK_STACK_SIZE		128
TaskHandle_t 	notify_mail_send_task_handler;		//任务1句柄
static void 	notify_mail_send_task(void *pvPara);


#define 		NOTIFY_MAIL_RCV_TASK_PRIO				2
#define 		NOTIFY_MAIL_RCV_TASK_STACK_SIZE		128
TaskHandle_t 	notify_mail_rcv_task_handler;		//任务1句柄
static void 	notify_mail_rcv_task(void *pvPara);


//EventGroupHandle_t		key_event;	//key event

#define EVENTBIT_0	(1<<0)				//事件位
#define EVENTBIT_1	(1<<1)
#define EVENTBIT_2	(1<<2)
#define EVENTBIT_ALL	(EVENTBIT_0|EVENTBIT_1|EVENTBIT_2)

void APP_task(void)
{
	xTaskCreate((TaskFunction_t)start_task,
				(const char *)"start_task",
				(uint16_t)START_STACK_SIZE,
				NULL,
				(UBaseType_t)START_TASK_PRIO,
				(TaskHandle_t *)&startTask_handler);
	vTaskStartScheduler();          //开启任务调度
}

static void start_task(void *param)
{
	
	EventGroupHandle_t key_event = xEventGroupCreate(); //创建一个事件event
	taskENTER_CRITICAL();           //进入临界区

    //创建获取event的任务
    xTaskCreate((TaskFunction_t )notify_mail_send_task,     	
                (const char*    )"notifyMailSend",   	
                (uint16_t       )NOTIFY_MAIL_SEND_TASK_STACK_SIZE, 
                (void*          )key_event,				
                (UBaseType_t    )NOTIFY_MAIL_SEND_TASK_PRIO,	
                (TaskHandle_t*  )&notify_mail_send_task_handler);   
    //创建设置event的任务
    xTaskCreate((TaskFunction_t )notify_mail_rcv_task,     
                (const char*    )"set_event",   
                (uint16_t       )NOTIFY_MAIL_RCV_TASK_STACK_SIZE, 
                (void*          )key_event,
                (UBaseType_t    )NOTIFY_MAIL_RCV_TASK_PRIO,
                (TaskHandle_t*  )&notify_mail_rcv_task_handler);      

    vTaskDelete(startTask_handler); //删除开始任务
    taskEXIT_CRITICAL();            //退出临界区
}

/*
创建task0,不调用vTaskDelay();即不会引起任务调度,
为大致限定任务的执行时间,用delay_xms(10);这个函数不会引起任务调度,只是CPU在空等
*/
static void notify_mail_send_task(void *param)
{
	u8 key_value = 0;
BaseType_t error = 0;
	
	while(1){
		key_value = KEY_Scan(0);
		if(notify_mail_rcv_task_handler && key_value)
		{
			error = xTaskNotify(notify_mail_rcv_task_handler,
						key_value,
						eSetValueWithOverwrite);
		}

		if(error == pdFAIL)
		{
			printf("send notify failure!\r\n");
		}else{
			printf("send notify succeed\r\n");
		}
		
		vTaskDelay(100);
			
	}
}

/*
创建task0,不调用vTaskDelay();即不会引起任务调度,
为大致限定任务的执行时间,用delay_xms(10);这个函数不会引起任务调度,只是CPU在空等
*/
static void notify_mail_rcv_task(void *param)
{

	u32 notify_val;
	while(1)
	{
		xTaskNotifyWait(0,				//进入函数前不清空
						0xffffffff,		//退出函数后清空所有位
						&notify_val,	//得到notify
						portMAX_DELAY);	//永远等待
		switch(notify_val)
		{
			case KEY0_PRESS:
				printf("KEY0 press\r\n");
				break;
			case KEY1_PRESS:
				printf("KEY1 press\r\n");
				break;
			case KEY_UP_PRESS:
				printf("KEY_UP press\r\n");
				break;
			default:
				break;				
		}
		
		vTaskDelay(100);
	}
}


2.4 模拟事件组

        事件组与邮箱很类似,只是模式不同,只是eAction为eSetBits,其他处理类似邮箱,只要通过事件与,事件或得到接收事件值即可,这里不作详细赘述。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

FreeRTOS轻量级同步--任务通知 的相关文章

随机推荐

  • gdbserver配置、远程调试以及ssh配置

    引言 GDB调试主要有两种方法 1 直接在目标板上通过gdb调试程序 2 在目标板上通过gdbserver运行程序 在宿主机上通过gdb调试程序 本篇文章主要来说明一下gdbserver远程调试的方法 主要以VScode举例说明 步骤 一
  • idea下载Scala插件(详细)

    目录 1 idea下载Scala 2 点击 Restart IDE 重启IDEA即可 3 创建scala目录 4 Mark scala目录为 source root 5 在windows的电脑安装scala jdk并且配置 环境变量 6 在
  • labelImg支持中文标注的文件

    链接 https pan baidu com s 1XCuLTlKRN7gVxJdQkcKnUw 密码 iaws
  • 读者-写者问题 (操作系统-进程)

    读者 写者问题 读进程优先算法 写者优先算法 问题描述 有读者和写者两组并发进程 共享一个文件 当两个或两个以上的读进程同时访问共享数据时不会产生副作用 但若某个写进程和其他进程 读进程或写进程 同时访问共享数据时则可能导致数据不一致的错误
  • 用vue3+elementplus做的一个滚动菜单栏的组件

    目录 起因 概览 设计及解决思路 1 滚动条竖起来 2 绑定菜单 3 吸附 优化 组件全部代码 起因 在elementplus中看到了滚动条绑定了slider 但是这个感觉很不实用 在底部 而且横向滚动 最常见的应该是那种固定在左上角的带着
  • 交叉编译适配mips架构的GDB

    交叉编译GDB 交叉编译GDB 1 下载GDB源码 2 解压并创建安装目录 3 编译安装 4 可能遇到的错误解决方法 1 下载termcap 2 将上面的编译安装gdb的脚本改一下 3 对于最后的权限不够无法删除PC机上termcap h文
  • 使用UE4(UnrealEngine)创建工程

    UE4系列文章目录 文章目录 UE4系列文章目录 前言 一 步骤 1 打开UE4软件 2 新建工程 3 选择游戏类型模板 4 项目设置 运行游戏 前言 使用UE4 UnrealEngine 创建工程 我这里的ue4版本是4 27 2 一 步
  • stm32循迹小车详细制作过程(附加完全版代码)

    stm32循迹小车详细制作过程 一 材料准备 1 主控板 Stm32f103c8t6 推荐 便宜够用 2 下载器 USB转TTL串口模块 3 电源 12v锂电池组 配套充电器 推荐下图这种 方便 好接线 12v 12v 12v 4 电机驱动
  • No module named ‘dateutil‘解决

    运行程序报错 无法直接pip install dateutil 需要pip install python dateutil
  • [非线性控制理论]6_滑模控制 (sliding mode control)

    非线性控制理论 1 Lyapunov直接方法 非线性控制理论 2 不变性原理 非线性控制理论 3 基础反馈稳定控制器设计 非线性控制理论 4 反馈线性化 反步法 非线性控制理论 5 自适应控制器 Adaptive controller 非线
  • CF 935E - Fafa and Ancient Mathematics

    CF 935E Fafa and Ancient Mathematics 题目描述 定义合法数学表达式 E E E 为一个数或两个合法数学表达式中间加上一个加或减运算符 并且在外面加上一对括号 给定一个合法数学表达式 将其中加减运算符用
  • QML Image内部缓存导致的问题

    QML Image BUG BUG描述 两个界面login qml 和 modify qml 页面 内部代码大致如下 Camera id camera imageProcessing whiteBalanceMode CameraImage
  • python实现vlookup_干货一:怎么在python里面实现vlookup

    vlookup应该是excel里用的比较多的功能 我刚接触excel的时候 反正觉得这个功能非常神奇 省了很多事 但是用久了以后就发现vlookup的限制太多了 第一大痛点 只能往右边 gt 找 如果要往左边找 最笨的办法就是把要找的col
  • rpm软件包解读

    一 linux应用程序与系统命令关系 二 典型应用程序的目录结构 三 常见的软件包封装类型 RPM包管理工具 RPM软件包管理器RED HAT PACKAGE MANAGER 由Red Hat公司提出 被众多linux发行版所采用 建立统一
  • 【机器学习】Q-Learning详细介绍

    Q learning Q learning 是一种机器学习方法 它使模型能够通过采取正确的操作来迭代学习和改进 Q learning属于强化学习的算法 通过强化学习 可以训练机器学习模型来模仿动物或儿童的学习方式 好的行为会得到奖励或加强
  • 如何从几何角度上理解方程组只有一个解_线性方程组的解集及其几何意义

    由于这三者之间的等价关系 我们解决现实问题时可以自由选取其中任意一个作为模型 我个人认为 线性方程组是最 质朴 的形式 向量方程则是与几何建立了关系 这将方便我们进行更直观的推理 矩阵方程则是向量方程的一种 封装 是向量方程的一种抽象 它将
  • Hotspot 垃圾回收之ConcurrentMarkSweepThread 源码解析

    目录 一 ConcurrentGCThread 二 SurrogateLockerThread 1 make 2 loop manipulatePLL 三 ConcurrentMarkSweepThread 1 定义 2 start和构造方
  • 微服务系列(六) 服务熔断与服务降级

    一 背景 分布式系统环境下 服务间类似依赖非常常见 一个业务调用通常依赖多个基础服务 如下图 对于同步调用 当库存服务不可用时 商品服务请求线程被阻塞 当有大批量请求调用库存服务时 最终可能导致整个商品服务资源耗尽 无法继续对外提供服务 并
  • element-ui表格列el-table-column如何根据数据不同显示不同的值,获取prop值

    方法一 格式化数据 在使用element ui的表格时 有时候后台给你的字段和你要显示在表格列里的内容不一致 例如后台给的字段是state 它的值为true或false 要求显示在表格里是 正确 或 错误 这时可以给el table col
  • FreeRTOS轻量级同步--任务通知

    1 简介 在FreeRTOS的配置参数中的configUSE TASK NOTIFICATIONS宏打开 一般RTOS会默认打开 如图1所示 图1 notify宏开关 RTOS在创建任务时 会创建一个32位的通知值ulNotifiedVal