前面章节我们学习了如何使用信号量来实现同步,但是使用信号量来同步的 话任务只能与单个的任务进行同步。有时候某个任务可能会需要与多个任务进行 同步,此时信号量就无能为力。FreeRTOS 为此提供了一个可选的解决方法,那 就是事件标志组。本章要实现的功能是:主要创建了两个任务,一个是设置事件 任务,一个是等待事件任务,两个任务独立运行,设置事件任务通过检测按键的 按下情况设置不同的事件标志位,等待事件任务则获取这两个事件标志位,并且 判断两个事件是否都发生,如果是则输出相应信息,LED 进行翻转。等待事件任 务的等待时间是 portMAX_DELAY,一直在等待事件的发生,等待到事件之后清除 对应的事件标记位。本章分为如下几部分内容: 9.1 事件简介 9.2 常用事件 API 函数 9.3 硬件设计 9.4 软件设计 9.5 实验现象
9.1 事件简介
事件是一种实现任务间通信的机制,主要用于实现多任务间的同步,但事件 通信只能是事件类型的通信,无数据传输。与信号量不同的是,它可以实现一对 多,多对多的同步。即一个任务可以等待多个事件的发生:可以是任意一个事件 发生时唤醒任务进行事件处理;也可以是几个事件都发生后才唤醒任务进行事件 处理。同样,也可以是多个任务同步多个事件。 每一个事件组只需要很少的 RAM 空间来保存事件组的状态。事件组存储在 一个 EventBits_t 类型的变量中,该变量在事件组结构体中定义。如果宏 configUSE_16_BIT_TICKS 定义为 1,那么变量 uxEventBits 就是 16 位的,其 中有 8 个位用来存储事件组;而如果宏 configUSE_16_BIT_TICKS 定义为 0, 那么变量 uxEventBits 就是 32 位的,其中有 24 个位用来存储事件组。在 STM32 中,我们一般将 configUSE_16_BIT_TICKS 定义为 0,那么 uxEventBits 是 32 位的,有 24 个位用来实现事件标志组。每一位代表一个事件,任务通过“逻辑 与”或“逻辑或”与一个或多个事件建立关联,形成一个事件组。事件的“逻辑 或”也被称作是独立型同步,指的是任务感兴趣的所有事件任一件发生即可被唤 醒;事件“逻辑与”则被称为是关联型同步,指的是任务感兴趣的若干事件都发 生时才被唤醒,并且事件发生的时间可以不同步。 多任务环境下,任务、中断之间往往需要同步操作,一个事件发生会告知等 待中的任务,即形成一个任务与任务、中断与任务间的同步。事件可以提供一对 多、多对多的同步操作。一对多同步模型:一个任务等待多个事件的触发,这种 情况是比较常见的;多对多同步模型:多个任务等待多个事件的触发。 任务可以通过设置事件位来实现事件的触发和等待操作。FreeRTOS 的事件 仅用于同步,不提供数据传输功能。 FreeRTOS 提供的事件具有如下特点: ●事件只与任务相关联,事件相互独立,一个 32 位的事件集合 (EventBits_t 类型的变量,实际可用与表示事件的只有 24 位),用于标识该 任务发生的事件类型,其中每一位表示一种事件类型(0 表示该事件类型未发生、 1 表示该事件类型已经发生),一共 24 种事件类型。 ●事件仅用于同步,不提供数据传输功能。 ●事件无排队性,即多次向任务设置同一事件(如果任务还未来得及读走), 等效于只设置一次。 ●允许多个任务对同一事件进行读写操作。 ●支持事件等待超时机制。 在 FreeRTOS 事件中,每个事件获取的时候,用户可以选择感兴趣的事件, 并且选择读取事件信息标记,它有三个属性,分别是逻辑与,逻辑或以及是否清 除标记。当任务等待事件同步时,可以通过任务感兴趣的事件位和事件信息标记 来判断当前接收的事件是否满足要求,如果满足则说明任务等待到对应的事件, 系统将唤醒等待的任务;否则,任务会根据用户指定的阻塞超时时间继续等待下 去。
9.1.1 事件应用场景
FreeRTOS 的事件用于事件类型的通讯,无数据传输,也就是说,我们可以 用事件来做标志位,判断某些事件是否发生了,然后根据结果做处理,那很多人 又会问了,为什么我不直接用变量做标志呢,岂不是更好更有效率?非也非也, 若是在裸机编程中,用全局变量是最为有效的方法,这点我不否认,但是在操作 系统中,使用全局变量就要考虑以下问题了: ●如何对全局变量进行保护呢,如何处理多任务同时对它进行访问? ●如何让内核对事件进行有效管理呢?使用全局变量的话,就需要在任务中 轮询查看事件是否发送,这简直就是在浪费 CPU 资源啊,还有等待超时机制, 使用全局变量的话需要用户自己去实现。 所以,在操作系统中,还是使用操作系统给我们提供的通信机制就好了,简 单方便还实用。 在某些场合,可能需要多个时间发生了才能进行下一步操作,比如一些危险 机器的启动,需要检查各项指标,当指标不达标的时候,无法启动,但是检查各 个指标的时候,不能一下子检测完毕,所以,需要事件来做统一的等待,当所有 的事件都完成了,那么机器才允许启动,这只是事件的其中一个应用。 事件可使用于多种场合,它能够在一定程度上替代信号量,用于任务与任务 间,中断与任务间的同步。一个任务或中断服务例程发送一个事件给事件对象, 而后等待的任务被唤醒并对相应的事件进行处理。但是它与信号量不同的是,事 件的发送操作是不可累计的,而信号量的释放动作是可累计的。事件另外一个特 性是,接收任务可等待多种事件,即多个事件对应一个任务或多个任务。同时按 照任务等待的参数,可选择是“逻辑或”触发还是“逻辑与”触发。这个特性也 是信号量等所不具备的,信号量只能识别单一同步动作,而不能同时等待多个事 件的同步。 各个事件可分别发送或一起发送给事件对象,而任务可以等待多个事件,任 务仅对感兴趣的事件进行关注。当有它们感兴趣的事件发生时并且符合感兴趣的 条件,任务将被唤醒并进行后续的处理动作。
9.1.2 事件运作机制
接收事件时,可以根据感兴趣的事件类型接收事件的单个或者多个事件类 型。事件接收成功后,必须使用 xClearOnExit 选项来清除已接收到的事件类型, 否则不会清除已接收到的事件,这样就需要用户显式清除事件位。用户可以自定 义通过传入参数 xWaitForAllBits 选择读取模式,是等待所有感兴趣的事件还 是等待感兴趣的任意一个事件。 设置事件时,对指定事件写入指定的事件类型,设置事件集合的对应事件位 为 1,可以一次同时写多个事件类型,设置事件成功可能会触发任务调度。 清除事件时,根据入参数事件句柄和待清除的事件类型,对事件对应位进行 清 0 操作。 事件不与任务相关联,事件相互独立,一个 32 位的变量(事件集合,实际 用于表示事件的只有 24 位),用于标识该任务发生的事件类型,其中每一位表 示一种事件类型(0 表示该事件类型未发生、1 表示该事件类型已经发生),一 共 24 种事件类型具体如下。
事件唤醒机制,当任务因为等待某个或者多个事件发生而进入阻塞态,当事 件发生的时候会被唤醒,其过程具体如下。
任务 1 对事件 3 或事件 5 感兴趣(逻辑或),当发生其中的某一个事件 都会被唤醒,并且执行相应操作。而任务 2 对事件 3 与事件 5 感兴趣(逻辑 与),当且仅当事件 3 与事件 5 都发生的时候,任务 2 才会被唤醒,如果只 有一个其中一个事件发生,那么任务还是会继续等待事件发生。如果在接收事件 函数中设置了清除事件位 xClearOnExit,那么当任务唤醒后将把事件 3 和事件 5 的事件标志清零,否则事件标志将依然存在。
9.2 常用事件 API 函 数
这里我不载过多介绍,如果需要了解API函数的可以看我上传的API手册
白嫖的可以getee搜索 SunYinggang_Gitee / FreeRTOS_API_Reference_CN
下载FreeRTOS中文参考API函数手册
整体代码