文章目录

  • 系列教程总目录
  • 概述
  • 8.1 事件组概念与操作
    • 8.1.1 事件组的概念
    • 8.1.2 事件组的操作
  • 8.2 事件组函数
    • 8.2.1 创建
    • 8.2.2 删除
    • 8.2.3 设置事件
    • 8.2.4 等待事件
    • 8.2.5 同步点
  • 8.3 示例20: 等待多个事件
  • 8.3 示例21: 任务同步

需要获取更好阅读体验的同学,请访问我专门设立的站点查看,地址:http://rtos.100ask.net/

系列教程总目录

本教程连载中,篇章会比较多,为方便同学们阅读,点击这里可以查看文章的 目录列表,目录列表页面地址:https://blog.csdn.net/thisway_diy/article/details/121399484

概述

学校组织秋游,组长在等待:

  • 张三:我到了
  • 李四:我到了
  • 王五:我到了
  • 组长说:好,大家都到齐了,出发!

秋游回来第二天就要提交一篇心得报告,组长在焦急等待:张三、李四、王五谁先写好就交谁的。

在这个日常生活场景中:

  • 出发:要等待这3个人都到齐,他们是"与"的关系
  • 交报告:只需等待这3人中的任何一个,他们是"或"的关系

在FreeRTOS中,可以使用事件组(event group)来解决这些问题。

本章涉及如下内容:

  • 事件组的概念与操作函数
  • 事件组的优缺点
  • 怎么设置、等待、清除事件组中的位
  • 使用事件组来同步多个任务

8.1 事件组概念与操作

8.1.1 事件组的概念

事件组可以简单地认为就是一个整数:

  • 的每一位表示一个事件
  • 每一位事件的含义由程序员决定,比如:Bit0表示用来串口是否就绪,Bit1表示按键是否被按下
  • 这些位,值为1表示事件发生了,值为0表示事件没发生
  • 一个或多个任务、ISR都可以去写这些位;一个或多个任务、ISR都可以去读这些位
  • 可以等待某一位、某些位中的任意一个,也可以等待多位

事件组用一个整数来表示,其中的高8位留给内核使用,只能用其他的位来表示事件。那么这个整数是多少位的?

  • 如果configUSE_16_BIT_TICKS是1,那么这个整数就是16位的,低8位用来表示事件
  • 如果configUSE_16_BIT_TICKS是0,那么这个整数就是32位的,低24位用来表示事件
  • configUSE_16_BIT_TICKS是用来表示Tick Count的,怎么会影响事件组?这只是基于效率来考虑
    • 如果configUSE_16_BIT_TICKS是1,就表示该处理器使用16位更高效,所以事件组也使用16位
    • 如果configUSE_16_BIT_TICKS是0,就表示该处理器使用32位更高效,所以事件组也使用32位

8.1.2 事件组的操作

事件组和队列、信号量等不太一样,主要集中在2个地方:

  • 唤醒谁?

    • 队列、信号量:事件发生时,只会唤醒一个任务
    • 事件组:事件发生时,会唤醒所有符号条件的任务,简单地说它有"广播"的作用
  • 是否清除事件?
    • 队列、信号量:是消耗型的资源,队列的数据被读走就没了;信号量被获取后就减少了
    • 事件组:被唤醒的任务有两个选择,可以让事件保留不动,也可以清除事件

以上图为列,事件组的常规操作如下:

  • 先创建事件组

  • 任务C、D等待事件:

    • 等待什么事件?可以等待某一位、某些位中的任意一个,也可以等待多位。简单地说就是"或"、"与"的关系。
    • 得到事件时,要不要清除?可选择清除、不清除。
  • 任务A、B产生事件:设置事件组里的某一位、某些位

8.2 事件组函数

8.2.1 创建

使用事件组之前,要先创建,得到一个句柄;使用事件组时,要使用句柄来表明使用哪个事件组。

有两种创建方法:动态分配内存、静态分配内存。函数原型如下:

/* 创建一个事件组,返回它的句柄。* 此函数内部会分配事件组结构体 * 返回值: 返回句柄,非NULL表示成功*/
EventGroupHandle_t xEventGroupCreate( void );/* 创建一个事件组,返回它的句柄。* 此函数无需动态分配内存,所以需要先有一个StaticEventGroup_t结构体,并传入它的指针* 返回值: 返回句柄,非NULL表示成功*/
EventGroupHandle_t xEventGroupCreateStatic( StaticEventGroup_t * pxEventGroupBuffer );

8.2.2 删除

对于动态创建的事件组,不再需要它们时,可以删除它们以回收内存。

vEventGroupDelete可以用来删除事件组,函数原型如下:

/** xEventGroup: 事件组句柄,你要删除哪个事件组*/
void vEventGroupDelete( EventGroupHandle_t xEventGroup )

8.2.3 设置事件

可以设置事件组的某个位、某些位,使用的函数有2个:

  • 在任务中使用xEventGroupSetBits()
  • 在ISR中使用xEventGroupSetBitsFromISR()

有一个或多个任务在等待事件,如果这些事件符合这些任务的期望,那么任务还会被唤醒。

函数原型如下:

/* 设置事件组中的位* xEventGroup: 哪个事件组* uxBitsToSet: 设置哪些位? *              如果uxBitsToSet的bitX, bitY为1, 那么事件组中的bitX, bitY被设置为1*               可以用来设置多个位,比如 0x15 就表示设置bit4, bit2, bit0* 返回值: 返回原来的事件值(没什么意义, 因为很可能已经被其他任务修改了)*/
EventBits_t xEventGroupSetBits( EventGroupHandle_t xEventGroup,const EventBits_t uxBitsToSet );/* 设置事件组中的位* xEventGroup: 哪个事件组* uxBitsToSet: 设置哪些位? *              如果uxBitsToSet的bitX, bitY为1, 那么事件组中的bitX, bitY被设置为1*               可以用来设置多个位,比如 0x15 就表示设置bit4, bit2, bit0* pxHigherPriorityTaskWoken: 有没有导致更高优先级的任务进入就绪态? pdTRUE-有, pdFALSE-没有* 返回值: pdPASS-成功, pdFALSE-失败*/
BaseType_t xEventGroupSetBitsFromISR( EventGroupHandle_t xEventGroup,const EventBits_t uxBitsToSet,BaseType_t * pxHigherPriorityTaskWoken );

值得注意的是,ISR中的函数,比如队列函数xQueueSendToBackFromISR、信号量函数xSemaphoreGiveFromISR,它们会唤醒某个任务,最多只会唤醒1个任务。

但是设置事件组时,有可能导致多个任务被唤醒,这会带来很大的不确定性。所以xEventGroupSetBitsFromISR函数不是直接去设置事件组,而是给一个FreeRTOS后台任务(daemon task)发送队列数据,由这个任务来设置事件组。

如果后台任务的优先级比当前被中断的任务优先级高,xEventGroupSetBitsFromISR会设置*pxHigherPriorityTaskWoken为pdTRUE。

如果daemon task成功地把队列数据发送给了后台任务,那么xEventGroupSetBitsFromISR的返回值就是pdPASS。

8.2.4 等待事件

使用xEventGroupWaitBits来等待事件,可以等待某一位、某些位中的任意一个,也可以等待多位;等到期望的事件后,还可以清除某些位。

函数原型如下:

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

先引入一个概念:unblock condition。一个任务在等待事件发生时,它处于阻塞状态;当期望的时间发生时,这个状态就叫"unblock condition",非阻塞条件,或称为"非阻塞条件成立";当"非阻塞条件成立"后,该任务就可以变为就绪态。

函数参数说明列表如下:

参数 说明
xEventGroup 等待哪个事件组?
uxBitsToWaitFor 等待哪些位?哪些位要被测试?
xWaitForAllBits 怎么测试?是"AND"还是"OR"?
pdTRUE: 等待的位,全部为1;
pdFALSE: 等待的位,某一个为1即可
xClearOnExit 函数提出前是否要清除事件?
pdTRUE: 清除uxBitsToWaitFor指定的位
pdFALSE: 不清除
xTicksToWait 如果期待的事件未发生,阻塞多久。
可以设置为0:判断后即刻返回;
可设置为portMAX_DELAY:一定等到成功才返回;
可以设置为期望的Tick Count,一般用pdMS_TO_TICKS()把ms转换为Tick Count
返回值 返回的是事件值,
如果期待的事件发生了,返回的是"非阻塞条件成立"时的事件值;
如果是超时退出,返回的是超时时刻的事件值。

举例如下:

事件组的值 uxBitsToWaitFor xWaitForAllBits 说明
0100 0101 pdTRUE 任务期望bit0,bit2都为1,
当前值只有bit2满足,任务进入阻塞态;
当事件组中bit0,bit2都为1时退出阻塞态
0100 0110 pdFALSE 任务期望bit0,bit2某一个为1,
当前值满足,所以任务成功退出
0100 0110 pdTRUE 任务期望bit1,bit2都为1,
当前值不满足,任务进入阻塞态;
当事件组中bit1,bit2都为1时退出阻塞态

你可以使用xEventGroupWaitBits()等待期望的事件,它发生之后再使用xEventGroupClearBits()来清除。但是这两个函数之间,有可能被其他任务或中断抢占,它们可能会修改事件组。

可以使用设置xClearOnExit为pdTRUE,使得对事件组的测试、清零都在xEventGroupWaitBits()函数内部完成,这是一个原子操作。

8.2.5 同步点

有一个事情需要多个任务协同,比如:

  • 任务A:炒菜
  • 任务B:买酒
  • 任务C:摆台
  • A、B、C做好自己的事后,还要等别人做完;大家一起做完,才可开饭

使用xEventGroupSync()函数可以同步多个任务:

  • 可以设置某位、某些位,表示自己做了什么事
  • 可以等待某位、某些位,表示要等等其他任务
  • 期望的时间发生后,xEventGroupSync()才会成功返回。
  • xEventGroupSync成功返回后,会清除事件

xEventGroupSync函数原型如下:

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

参数列表如下:

参数 说明
xEventGroup 哪个事件组?
uxBitsToSet 要设置哪些事件?我完成了哪些事件?
比如0x05(二进制为0101)会导致事件组的bit0,bit2被设置为1
uxBitsToWaitFor 等待那个位、哪些位?
比如0x15(二级制10101),表示要等待bit0,bit2,bit4都为1
xTicksToWait 如果期待的事件未发生,阻塞多久。
可以设置为0:判断后即刻返回;
可设置为portMAX_DELAY:一定等到成功才返回;
可以设置为期望的Tick Count,一般用pdMS_TO_TICKS()把ms转换为Tick Count
返回值 返回的是事件值,
如果期待的事件发生了,返回的是"非阻塞条件成立"时的事件值;
如果是超时退出,返回的是超时时刻的事件值。

8.3 示例20: 等待多个事件

本节源码是FreeRTOS_20_event_group_wait_multi_events

要使用事件组,代码中要有如下操作:

/* 1. 工程中添加event_groups.c *//* 2. 源码中包含头文件 */
#include "event_groups.h"

假设大厨要等手下做完这些事才可以炒菜:洗菜、生火。

本程序创建3个任务:

  • 任务1:洗菜
  • 任务2:生火
  • 任务3:炒菜。

main函数代码如下,它创建了3个任务:

int main( void )
{prvSetupHardware();/* 创建递归锁 */xEventGroup = xEventGroupCreate( );if( xEventGroup != NULL ){/* 创建3个任务: 洗菜/生火/炒菜*/xTaskCreate( vWashingTask, "Task1", 1000, NULL, 1, NULL );xTaskCreate( vFiringTask,  "Task2", 1000, NULL, 2, NULL );xTaskCreate( vCookingTask, "Task3", 1000, NULL, 3, NULL );/* 启动调度器 */vTaskStartScheduler();}else{/* 无法创建事件组 */}/* 如果程序运行到了这里就表示出错了, 一般是内存不足 */return 0;
}

这3个任务的代码和执行流程如下:

  • A:"炒菜任务"优先级最高,先执行。它要等待的2个事件未发生:洗菜、生火,进入阻塞状态
  • B:"生火任务"接着执行,它要等待的1个事件未发生:洗菜,进入阻塞状态
  • C:"洗菜任务"接着执行,它洗好菜,发出事件:洗菜,然后调用F等待"炒菜"事件
  • D:"生火任务"等待的事件满足了,从B处继续执行,开始生火、发出"生火"事件
  • E:"炒菜任务"等待的事件满足了,从A出继续执行,开始炒菜、发出"炒菜"事件
  • F:"洗菜任务"等待的事件满足了,退出F、继续执行C

要注意的是,代码B处等待到"洗菜任务"后并不清除该事件,如果清除的话会导致"炒菜任务"无法执行。

运行结果如下图所示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GH1MFoqF-1637808522268)(pic/chap8/03_multi_events_result.png)]

8.3 示例21: 任务同步

本节代码是FreeRTOS_21_event_group_task_sync

假设ABC三人要吃饭,各司其职:

  • A:炒菜
  • B:买酒
  • C:摆台

三人都做完后,才可以开饭。

main函数代码如下,它创建了3个任务:

int main( void )
{prvSetupHardware();/* 创建递归锁 */xEventGroup = xEventGroupCreate( );if( xEventGroup != NULL ){/* 创建3个任务: 洗菜/生火/炒菜*/xTaskCreate( vCookingTask, "task1", 1000, "A", 1, NULL );xTaskCreate( vBuyingTask,  "task2", 1000, "B", 2, NULL );xTaskCreate( vTableTask,   "task3", 1000, "C", 3, NULL );/* 启动调度器 */vTaskStartScheduler();}else{/* 无法创建事件组 */}/* 如果程序运行到了这里就表示出错了, 一般是内存不足 */return 0;
}

被创建的3个任务,代码都很类似,以任务1为例:

static void vCookingTask( void *pvParameters )
{const TickType_t xTicksToWait = pdMS_TO_TICKS( 100UL );       int i = 0;/* 无限循环 */for( ;; ){/* 做自己的事 */printf("%s is cooking %d time....\r\n", (char *)pvParameters, i);/* 表示我做好了, 还要等别人都做好 */xEventGroupSync(xEventGroup, COOKING, ALL, portMAX_DELAY);/* 别人也做好了, 开饭 */printf("%s is eating %d time....\r\n", (char *)pvParameters, i++);vTaskDelay(xTicksToWait);}
}

要点在于xEventGroupSync函数,它有3个功能:

  • 设置事件:表示自己完成了某个、某些事件
  • 等待事件:跟别的任务同步
  • 成功返回后,清除"等待的事件"

运行结果如下图所示:

韦东山freeRTOS系列教程之【第八章】事件组(event group)相关推荐

  1. 韦东山freeRTOS系列教程之【第十章】软件定时器(software timer)

    文章目录 系列教程总目录 概述 10.1 软件定时器的特性 10.2 软件定时器的上下文 10.2.1 守护任务 10.2.2 守护任务的调度 10.2.3 回调函数 10.3 软件定时器的函数 10 ...

  2. 韦东山freeRTOS系列教程之【第二章】内存管理

    文章目录 教程目录 2.1 为什么要自己实现内存管理 2.2 FreeRTOS的5种内存管理方法 2.2.1 Heap_1 2.2.2 Heap_2 2.2.3 Heap_3 2.2.4 Heap_4 ...

  3. 【学习笔记】韦东山freertos直播学习笔记

    韦东山FreeRTOS学习 如果刚好有大佬看到发现笔记中有什么写错了的,欢迎大佬指点,我十分乐意受到大佬的指点哈哈哈,顺便我还想问一下csdn的编辑器有没有保存的快捷键啊,CTRL+S好像没有用啊. ...

  4. C#微信公众号开发系列教程五(接收事件推送与消息排重)

    C#微信公众号开发系列教程五(接收事件推送与消息排重) 原文:C#微信公众号开发系列教程五(接收事件推送与消息排重) 微信公众号开发系列教程一(调试环境部署) 微信公众号开发系列教程一(调试环境部署续 ...

  5. 韦东山鸿蒙开发教程02 - 资料下载方法

    在线课堂:https://www.100ask.net/index(课程观看) 论  坛:http://bbs.100ask.net/(学术答疑) 开 发 板:https://100ask.taoba ...

  6. [FreeRTOS系列教程]学习FreeRTOS前的准备工作-----初学者必看

    转自:http://bbs.armfly.com/read.php?tid=1552 转载说明:本文仅为转载,下面有几位同学询问对应的教程,教程请参考下面的链接,是以帖子的形式分章节说明. http: ...

  7. freeRtos学习笔记 (5)事件组

    freeRtos学习笔记 freeRtos事件组 为什么要用事件组? 多任务环境下, 任务.中断之间往往需要同步操作,一个事件发生会告知等待中的任 务,即形成一个任务与任务.中断与任务间的同步.事件可 ...

  8. 韦东山FreeRtos的内部机制,截图+学习笔记【2000字】【原创】

    文章目录

  9. 【Java基础系列教程】第八章 Java面向对象详解(三)_抽象类、接口、内部类、深拷贝与浅拷贝

    一.JavaBean规范 1.1 什么是JavaBean JavaBeans是Java中一种特殊的类,可以将多个对象封装到一个对象(bean)中.特点是可序列化,提供无参构造器,提供getter方法和 ...

  10. 韦东山 IMX6ULL和正点原子_万事开头难 - 介绍IMX6ULL启动方式

    不同开发板,启动方式不一样,今天我们来介绍imx6ull开发板的启动方式,这非常重要.若不了解清楚启动方式,后面的所有开发工作便无从谈起. 本文摘自100ask_imx6ull 开发板 配套学习手册- ...

最新文章

  1. 数学之美——隐含马尔科夫模型
  2. Mysql的int类型探究
  3. extjs 计算日期之和_财报分析之利润表的重构(2)——以医药制造行业为例
  4. js 格式化日期 (/Date(1400046388387)/)
  5. Java谓词的延迟执行
  6. 前端学习(1610):hook使用
  7. 尚硅谷李老师Mysql基础笔记
  8. java保存文件到linux指定目录_怎么使用java编程实现linux下全部文件目录的遍历
  9. from gi.repository import Gtk, GObject
  10. ZooKeeper系列(三)
  11. MAC OS上将项目提交到github
  12. C语言的sqrt函数的调用
  13. mysql5.5软件官方下载_MySQL5.5 64位下载
  14. 《自适应滤波器原理》西蒙.赫金 百度云
  15. 疲劳检测(Fatigue Detection Algorithm)
  16. 写学位论文Word操作技巧:奇偶页眉不同,页码连续
  17. VC实现:bmp转jpg、jpg转bmp、截屏保存jpg
  18. B/S文件上传下载解决方案
  19. 关于Windows PE
  20. 2018秋季阿里实习生面试总结

热门文章

  1. Java工程师成神之路【转】
  2. jsPDF介绍与兼容IE的补丁
  3. c语言学生综合测评系统_学生综合评价系统
  4. 勤哲excel服务器2017试用
  5. 【JSP内置对象】之9大内置对象(JavaWeb必背必掌握)
  6. i9-10900K比9900K性能提升了多少?i9-10900K和i9-9900K区别对比评测
  7. 《啊哈算法》学习笔记(三)——最短路径
  8. JDK官网下载速度缓慢解决方法
  9. 单模光纤和多模光纤的区别,及光纤收发机(光电收发器)的介绍
  10. 智能手机玩转Smart3D三维建模介绍