彻底弄清FreeRTOS中的事件组(Event Groups)

news2024/10/7 4:29:25

​之前已经学过两个任务之间可以利用信号量队列来通信,任务可以利用这两个机制等待某一个事件发生,但是假如需要等待多个事件发生呢?这就需要用到事件组了。

事件组可以让任务进入阻塞态等待一个或多个事件的组合发生。当事件发生时,事件组将正在等待同一事件或事件组合的所有任务解除阻塞。

事件组的特性使它们对于同步多个任务向多个任务广播事件允许任务在阻塞态下等待一组事件中的任何一个发生,以及允许任务在阻塞态下等待多个操作完成非常有用。通常可以用单个事件组替换多个二进制信号量,所以可以减少RAM的使用。

事件组功能是可选的。要包含事件组功能,构建FreeRTOS时要包含源文件event_groups.c。

目录

1.事件组的特性

2.如何使用事件组

2.1创建事件组

2.2设置事件位

2.3等待事件位

3.使用事件组同步任务


1.事件组的特性

事件标志(event flags)事件位(event bits)

事件标志是一个布尔值(1或0),用于指示事件是否发生。事件组其实就是一组事件标志。

一个事件标志只能是1或0,将一个事件标志的状态存储在一个位中,一个事件组中所有事件标志的状态存储在一个变量中。

事件组中每个事件标志的状态由EventBits_t类型变量中的单个位表示。因此,事件标志也被称为事件“位”。如果EventBits_t变量中的一个位被设置为1,那么该位表示的事件已经发生。如果EventBits_t变量中的一个位被设置为0,那么该位表示的事件还没有发生。

事件组中的事件位数取决于FreeRTOSConfig.h1中的configUSE_16_BIT_TICKS

如果configUSE_16_BIT_TICKS为1,则每个事件组包含8个可用的事件位。

如果configUSE_16_BIT_TICKS为0,则每个事件组包含24个可用的事件位。

使用事件组的实际例子:

FreeRTOS+TCP TCP/IP栈的实现提供了如何使用事件组来同时设计和最小化资源使用的实际示例。

TCP套接字必须响应许多不同的事件。包括接受事件(accept)、绑定事件(bind)、读取事件(read)和关闭事件(close)。套接字可以响应事件取决于套接字的状态。例如,如果套接字已经创建,但还没有绑定到地址,那么它可以接收绑定事件,但不会接收读取事件(如果没有地址,它就不能读取数据)。

FreeRTOS+TCP套接字的状态保存在一个名为FreeRTOS_Socket_t的结构中。该结构包含一个事件组,该事件组为套接字必须处理的每个事件定义了一个事件位。FreeRTOS+TCP API调用该块来等待事件或事件组,在事件组上阻塞。事件组还包含一个“abort”位,允许TCP连接被终止,无论套接字当时正在等待哪个事件。

2.如何使用事件组

2.1创建事件组

事件组是EventGroupHandle_t类型的变量。xEventGroupCreate() API函数用于创建事件组,并返回一个EventGroupHandle_t。

EventGroupHandle_t xEventGroupCreate( void );

2.2设置事件位

xEventGroupSetBits() API函数可以在事件组中设置一个或多个位,用于通知任务所设置的位表示的事件已经发生。

EventBits_t xEventGroupSetBits( EventGroupHandle_t xEventGroup,const EventBits_t uxBitsToSet );
参数作用
xEventGroup要设置位的事件组的句柄。
uxBitsToSet

指定事件组中要设置为1的事件位或事件位的位掩码。事件组的值通过将事件组的现有值与uxBitsToSet中传递的值按位或来更新。

例如,将uxBitsToSet设置为0x04(二进制0100)将导致事件组中的事件位3被设置,而事件组中的所有其他事件位保持不变。

返回值返回调用xEventGroupSetBits()时事件组的值。注意,返回值不一定包含uxBitsToSet指定的位,因为这些位可能已经被不同的任务再次清除。

xEventGroupSetBitsFromISR()是xEventGroupSetBits()的中断安全版本。

给出信号量是一种确定性操作,因为预先知道给出信号量最多会导致一个任务离开阻塞态。但是当在事件组中设置位时,预先不知道有多少任务将离开阻塞状态,因此在事件组中设置位不是一个确定性操作。

FreeRTOS设计和实现标准不允许在中断服务例程中或中断被禁用时执行非确定性操作。因此,xEventGroupSetBitsFromISR()不直接在中断服务例程中设置事件位,而是将操作推迟到RTOS守护任务。

不了解守护任务的可以看这篇:FreeRTOS全解析-7.中断安全API和推迟中断处理

BaseType_t xEventGroupSetBitsFromISR( EventGroupHandle_t xEventGroup,const EventBits_t uxBitsToSet,BaseType_t *pxHigherPriorityTaskWoken );
参数作用
xEventGroup要设置位的事件组的句柄。
uxBitsToSet指定事件组中要设置为1的事件位或事件位的位掩码。
pxHigherPriorityTaskWoken

xEventGroupSetBitsFromISR()不直接在中断服务例程中设置事件位,而是通过在timer命令队列上发送命令将操作推迟到RTOS守护进程任务。如果守护任务处于阻塞态,等待timer命令队列上的数据可用,则写入timer命令队列将使守护任务离开阻塞态。如果守护任务的优先级高于当前正在执行的任务(被中断的任务)的优先级,那么,在内部,xEventGroupSetBitsFromISR()将把* pxhigherprioritytaskkoken设置为pdTRUE。

如果xEventGroupSetBitsFromISR()值为pdTRUE,则应该在中断退出之前执行上下文切换。这将确保中断直接返回到守护进程任务,因为守护进程任务将是最高优先级的就绪态任务。

还是不会用的可以看这篇FreeRTOS全解析-7.中断安全API和推迟中断处理

返回值

1.pdPASS,数据成功发送到timer命令队列。

2. pdFALSE,如果'set bits'命令不能写入定时器命令队列,因为队列已满,则返回pdFALSE。

2.3等待事件位

xEventGroupWaitBits() API函数允许任务读取事件组的值,如果事件组中的一个或多个事件位尚未设置,则可以选择在阻塞态下等待事件组中的一个或多个事件位设置完成。

EventBits_t xEventGroupWaitBits( const EventGroupHandle_t xEventGroup,const EventBits_t uxBitsToWaitFor,const BaseType_t xClearOnExit,const BaseType_t xWaitForAllBits,TickType_t xTicksToWait );

uxBitsToWaitFor和xWaitForAllBits参数值的组合决定了调用xEventGroupWaitBits() 的任务,是否要进入阻塞态等待。

uxBitsToWaitFor指定要测试事件组中的事件位;

xWaitForAllBits指定按位或测试或按位与测试。pdTRUE表示uxBitsToWaitFor中指定的位都被设置,才会解除阻塞,pdFALSE表示有一个设置了就行。

例子:

事件组的值uxBitsToWaitForxWaitForAllBits结果
00000101pdFALSE进入阻塞态,因为位0和2都没有设置
01000101pdTRUE进入阻塞态,位0没有设置,位2设置了,但pdTRUE表示两个都得被设置
01000110pdFALSE不会阻塞,位1没有设置,位2设置了,pdFALSE表示有一个设置了就行
01000110pdTRUE进入阻塞态,位1没有设置,位2设置了,pdTRUE表示都得设置

xClearOnExit被设置为pdTRUE,那么在调用任务退出xEventGroupWaitBits() API函数之前,uxBitsToWaitFor指定的事件位将被清除回0。如果xClearOnExit被设置为pdFALSE,那么事件组中事件位的状态不会被xEventGroupWaitBits() API函数修改。

事件位还可以使用xEventGroupClearBits() API函数手动清除,手动清除要注意访问冲突问题,如果xClearOnExit设置为pdTRUE,则事件位的测试和清除将显示在将任务调用为原子操作(不可被其他任务或中断中断)。

3.使用事件组同步任务

一个任务等待多个事件很简单,比如A任务的操作需要B、C、D任务中的某个操作,那A任务调用xEventGroupWaitBits()去等待,当B、C、D中操作完成了,B、C、D分别调用一下xEventGroupSetBits(),都调用后A任务就会继续运行。

但是有时需要两个或多个任务相互同步。例如:

任务A接收一个事件,然后将该事件所需的一些处理委托给其他三个任务:任务B、任务C和任务D(也就是说,没有A就没有BCD)。如果任务A在任务B、C和D都完成处理之前不能接收另一个事件(也就是说没有BCD就没有A),那么这四个任务将需要彼此同步。每个任务的同步点在该任务完成处理后,并且在其他每个任务完成处理之前不能继续进行。任务A只有在所有四个任务都达到同步点后才能接收另一个事件。

事件组用来创建同步点:

必须参与同步的每个任务在事件组中被分配一个唯一的事件位。

每个任务在到达同步点时设置自己的事件位。

在设置了自己的事件位之后,每个任务都阻塞在事件组上,等待其他同步任务的事件位也设置好。

但是,xEventGroupSetBits()和xEventGroupWaitBits() API函数不能在此场景中使用。如果使用了它们,那么位的设置(表示任务已达到其同步点)和位的测试(确定其他同步任务是否已达到其同步点)将作为两个独立的操作执行。要明白为什么这是一个问题,考虑一个场景,任务A,任务B和任务C尝试使用事件组进行同步:

1.任务A和任务B已经到达同步点,所以它们的事件位在事件组中设置,它们处于阻塞态,等待任务C的事件位也设置完成。

2. 任务C到达同步点,使用xEventGroupSetBits()设置其在事件组中的位。只要任务C的位设置好,任务A和任务B就会离开阻塞状态,并清除所有三个事件位。

3.然后任务C调用xEventGroupWaitBits()来等待所有三个事件位都设置好,但是到那时,所有三个事件位都已经被清除,任务A和任务B已经离开了各自的同步点,因此同步失败。

要成功地使用事件组创建同步点,事件位的设置和随后的事件位测试必须作为单个不可中断操作执行,就要用到xEventGroupSync() API函数。

EventBits_t xEventGroupSync( EventGroupHandle_t xEventGroup,const EventBits_t uxBitsToSet,const EventBits_t uxBitsToWaitFor,TickType_t xTicksToWait );

参数意思和xEventGroupWaitBits() 是一样的。

如果xEventGroupSync() 是因为满足了解除阻塞条件而返回,返回前会自动清除相应的位。

返回值是事件组的值,满足条件返回时,虽然事件组相应的位会被清0,但是返回值是没清除0的值。因为等待超时返回的话,就是事件组的值。

例如:等待的位是1和2,当事件组为0110,即位1,和2都被设置为1了,也就是说符合解除阻塞条件了,函数就会返回这时,返回值就是0110.

等待的位是1和2,当事件组为0100,或者0000,或者其他不是0110的情况,并且已经超时了,函数就会返回这时,返回值就是为0100,或者0000,或者其他不是0110的情况.

一个完整例子:

static void vSyncingTask( void *pvParameters ){  const TickType_t xMaxDelay = pdMS_TO_TICKS( 4000UL );  const TickType_t xMinDelay = pdMS_TO_TICKS( 200UL );  TickType_t xDelayTime;  EventBits_t uxThisTasksSyncBit;  const EventBits_t uxAllSyncBits = ( mainFIRST_TASK_BIT |mainSECOND_TASK_BIT |mainTHIRD_TASK_BIT );  uxThisTasksSyncBit = ( EventBits_t ) pvParameters;  for( ;; )  {    xDelayTime = ( rand() % xMaxDelay ) + xMinDelay;    vTaskDelay( xDelayTime );    vPrintTwoStrings( pcTaskGetTaskName( NULL ), "reached sync point" );    xEventGroupSync( xEventGroup,uxThisTasksSyncBit,uxAllSyncBits,portMAX_DELAY );    vPrintTwoStrings( pcTaskGetTaskName( NULL ), "exited sync point" );  }}EventGroupHandle_t xEventGroup;int main( void ){  xEventGroup = xEventGroupCreate();  xTaskCreate( vSyncingTask, "Task 1", 1000, mainFIRST_TASK_BIT, 1, NULL );  xTaskCreate( vSyncingTask, "Task 2", 1000, mainSECOND_TASK_BIT, 1, NULL );  xTaskCreate( vSyncingTask, "Task 3", 1000, mainTHIRD_TASK_BIT, 1, NULL );  vTaskStartScheduler();  for( ;; );  return 0;}

任务在到达同步点时打印"reached sync point",然后等待同步,同步后离开同步点,打印"exited sync point"

系列文章合集:

FreeRTOS文章合集

嵌入式Linux基础文章合集

ARM学习系列合集

C语言学习系列合集

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/444985.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

根据 cadence 设计图学习硬件知识 day02 了解飞腾 X100芯片

1. 飞腾芯片 X100的介绍 飞腾套片X100是一款微处理器的配套芯片,主要功能包括图形图像处理和接口扩展两类。在图形图像处理方面,集成了图形处理加速GPU、视频解码VPU、显示控制接口DisplayPort以及显存控制器;在接口扩展方面,支持…

【矩形面积】

题目来源:https://leetcode.cn/problems/rectangle-area/ 目录 矩形面积 矩形面积 题目介绍 给你 二维 平面上两个 由直线构成且边与坐标轴平行/垂直 的矩形,请你计算并返回两个矩形覆盖的总面积。 每个矩形由其 左下 顶点和 右上 顶点坐标表示&#xf…

数字化转型危与机,20年老厂的升级之路

“投资大、周期长、见效慢”,是每一家企业在考虑数字化战略时,都会纠结的问题。 打江山容易,守江山难 企业在快速扩张的过程中,往往可以不需要过多的考虑细节的问题,跑马圈地的打法会更加有效。 但是市场占有量开始饱…

JS Array数组常用方法

会改变原数组的方法 1、array.push(需要在末尾添加的数据)【给数组末尾添加一个元素】 2、array.unshift(需要在首位添加的数据)【给数组首位添加一个元素】 3、array.pop()【从数组末尾删除元素,不需要传参】 4、array.shift()【从数组首位开始删除元素,不需要传参】 5、arra…

数据处理方法(LFSR)

一、线性反馈移位寄存器(LFSR)编码概述 线性反馈移位寄存器(LFSR):通常由移位寄存器和异或门逻辑组成。其主要应用在: 伪随机数,伪噪声序列,计数器,BIST,数据…

树脂吸附含钴废水的实际案例

三元聚合物-锂电池是指正极材料使用锂、镍、钴、锰三元正极材料的锂电池,锂离子电池的正极材料有很多种,主要有钴酸锂、锰酸锂、镍酸锂、三元材料、磷酸铁锂等。用三元材料作为正极材料的动力锂电池,近年来凭借其容量高、循环稳定性&#xff…

Java笔记一(黑马)

目录 一.标准的javaBean二.反射1.获取class对象的三种方式2.反射获取构造方法3.反射获取成员变量4.反射获取成员方法 三.网络编程TCP Serve/ClientNIO框架Netty 一.标准的javaBean package com.itheima.domain;public class Student {//1.成员变量私有化//2.空参,带…

数据结构-树及相关算法

二叉树 递归算法的关键要明确函数的定义,相信这个定义,而不要跳进递归细节。 写二叉树的算法题,都是基于递归框架的,我们先要搞清楚 root 节点它自己要做什么,然后根据题目要求选择使用前序,中序&#xff0…

colmap使用教程

COLMAP 是具有图形和命令行界面的通用运动结构 (SfM) 和多视图立体 (MVS) 管道。 它为有序和无序图像集的重建提供了广泛的功能。 常见的基于nerf算法均需要colmap来计算位姿 安装colmap: conda install colmap 需要使用python9 step1:准备多视角图…

第十三节 I/O流与文件操作

文件读写 输入输出流 文本文件 一个字节一个字节写 8位十进制最大255 int read() throws IOException 字节 java.io.OutputStream 将十进制数按字节 用文件的类 表达文件 1.txt 相对路径 :工程是同级的 绝对路径C盘下 D 万物皆对象 都能封装成对象…

xss-flash钓鱼配合msf捆绑上线

这里写目录标题 一,后门文件生成与制作二,flash安装钓鱼网站搭建三,监听四,钓鱼 一,后门文件生成与制作 打开kali,查看ip add 查看 ip 地址为 192.168.1.8 输入 msfconsole 1.生成后门 msfvenom -p window…

LeetCode 1026. Maximum Difference Between Node and Ancestor【DFS,BFS,树】中等

本文属于「征服LeetCode」系列文章之一,这一系列正式开始于2021/08/12。由于LeetCode上部分题目有锁,本系列将至少持续到刷完所有无锁题之日为止;由于LeetCode还在不断地创建新题,本系列的终止日期可能是永远。在这一系列刷题文章…

Direct3D 12——模板——平面镜效果

1.将实物照常渲染到后台缓冲区内(不包括镜子)。注意,此步骤不修改模 板缓冲区。 2.清理模板缓冲区,将其整体置零。 将实物都绘制到后台缓冲区中,并将模板缓冲区清理为0 (用浅灰色来表示)。 绘…

socked编程

socket是什么?套接字是什么? 什么是 socket? socket 的原意是“插座”,在计算机通信领域,socket 被翻译为“套接字”,它是计算机之间进行通信的一种约定或一种方式。通过 socket 这种约定,一台…

Motion Planning学习笔记一:配置空间、图、图搜索、图遍历

学习高飞博士的路径规划课程所总结的学习笔记。 目录 1、配置空间(Configuration Space, C-space) 2、图(Graphs) 3、图搜索(Graph Search Basis) 3.1、总体框架 3.2、两种基本的图遍历算法 3.3、启…

漫谈大数据 - HiveSQL总结(二)查询操作

导语:HiveSQL各关键字详解,hive函数大全,类似于个人记录工具书,后续遇到其他的也会继续加进来。 有关hive库表操作请见上篇:漫谈大数据 - HiveSQL总结(一)库表操作_昊昊该干饭了的博客-CSDN博客…

一条SQL如何被MySQL架构中的各个组件操作执行的?

文章目录 1. 单表查询SQL在MySQL架构中的各个组件的执行过程2. SELECT的各个关键字在哪里执行?3. 表关联查询SQL在MySQL架构中的各个组件的执行过程4. LEFT JOIN将过滤条件放在子查询中再关联和放在WHERE子句上有什么区别?5. 聚集索引和全表扫描有什么区…

推动开发者平台本土化,高通加速中国XR内容生态发展

随着VR和AR技术快速发展,产品不断成熟,体验也变得越来越优秀。据悉,Meta Quest系列VR头显出货量超2000万台,基本证明了VR开始在消费类电子产品中占据一席之地。与此同时,近两年AR眼镜也在逐渐升温,成为了创…

day17_异常

今日内容 零、 复习昨日 一、作业 二、异常 三、自定义异常 零、 复习昨日 见晨考,重点是String类的方法 StringBuffer和StringBuiler面试问 日期解析和格式化 int i Integer.parseInt(“111”); 一、作业 略,见答案二、异常 2.1 介绍 异常,就是程序出现的不正常的情况. 2.2…

RPA流程自动化技术在金融机构的落地方案详解

金融机构在面向数字化运营的转型过程中,需将智能流程自动化技术整合到数字化转型战略中,规划建设统一的企业流程自动化处理平台,作为数字化运营的辅助支撑类系统,明确流程治理方法和运营模式,确保足够的规模弹性&#…