编辑推荐: |
本文主要介绍了基于STM32的FreeRTOS事件组相关内容。 希望对你的学习有帮助。
本文来自于微信公众号猿力部落,由火龙果软件Linda编辑、推荐。 |
|
概述
文章对事件组的,应用场景,运作机制,以及事件的创建,删除,等待,置位,同步等操作
一、事件标志组简介
1、事件位(事件标志)
事件位用来表明某个事件是否发生,事件位通常用作事件标志,比如下面的几个例子:
● 当收到一条消息并且把这条消息处理掉以后就可以将某个位(标志)置 1,当队列中没有 消息需要处理的时候就可以将这个位(标志)置
0。
● 当把队列中的消息通过网络发送输出以后就可以将某个位(标志)置 1,当没有数据需要 从网络发送出去的话就将这个位(标志)置
0。
● 现在需要向网络中发送一个心跳信息,将某个位(标志)置 1。现在不需要向网络中发送 心跳信息,这个位(标志)置
0。
2、事件组
一个事件组就是一组的事件位,事件组中的事件位通过位编号来访问,同样,以上面列出 的三个例子为例:
● 事件标志组的 bit0 表示队列中的消息是否处理掉。
● 事件标志组的 bit1 表示是否有消息需要从网络中发送出去。
● 事件标志组的 bit2 表示现在是否需要向网络发送心跳信息。
3、事件标志组和事件位的数据类型
事件标志组的数据类型为 EventGroupHandle_t,当 configUSE_16_BIT_TICKS
为 1 的时候 事件标志组可以存储 8 个事件位,当 configUSE_16_BIT_TICKS
为 0 的时候事件标志组存储 24 个事件位。
事件标志组中的所有事件位都存储在一个无符号的 EventBits_t 类型的变量中,EventBits_t
在 event_groups.h 中有如下定义:
typedef TickType_t EventBits_t;
数据类型 TickType_t 在文件 portmacro.h 中有如下定义:
#if( configUSE_16_BIT_TICKS == 1 ) typedef uint16_t TickType_t; #define portMAX_DELAY ( TickType_t ) 0xffff typedef uint32_t TickType_t; #define portMAX_DELAY ( TickType_t ) 0xffffffffUL #define portTICK_TYPE_IS_ATOMIC 1
|
可以看出当 configUSE_16_BIT_TICKS 为 0 的时候 TickType_t 是个
32 位的数据类型,因 此 EventBits_t 也是个 32 位的数据类型。EventBits_t
类型的变量可以存储 24 个事件位,另外的 那高 8 位有其他用。事件位 0 存放在这个变量的 bit0
上,变量的 bit1 就是事件位 1,以此类推。 对于 STM32 来说一个事件标志组最多可以存储
24 个事件位,如图1所示
图1 事件标志组和事件位
二、事件的应用场景
FreeRTOS 的事件用于事件类型的通讯,无数据传输,也就是说,我们可以用事件来做 标志位,判断某些事件是否发生了,然后根据结果做处理,那很多人又会问了,为什么我
不直接用变量做标志呢,岂不是更好更有效率?非也非也,若是在裸机编程中,用全局变 量是最为有效的方法,这点我不否认,但是在操作系统中,使用全局变量就要考虑以下问
题了:
如何对全局变量进行保护呢,如何处理多任务同时对它进行访问?
如何让内核对事件进行有效管理呢?使用全局变量的话,就需要在任务中轮询查 看事件是否发送,这简直就是在浪费
CPU 资源啊,还有等待超时机制,使用全局 变量的话需要用户自己去实现。
所以,在操作系统中,还是使用操作系统给我们提供的通信机制就好了,简单方便还 实用。
在某些场合,可能需要多个时间发生了才能进行下一步操作,比如一些危险机器的启 动,需要检查各项指标,当指标不达标的时候,无法启动,但是检查各个指标的时候,不
能一下子检测完毕啊,所以,需要事件来做统一的等待,当所有的事件都完成了,那么机 器才允许启动,这只是事件的其中一个应用。
事件可使用于多种场合,它能够在一定程度上替代信号量,用于任务与任务间,中断 与任务间的同步。一个任务或中断服务例程发送一个事件给事件对象,而后等待的任务被
唤醒并对相应的事件进行处理。但是它与信号量不同的是,事件的发送操作是不可累计的, 而信号量的释放动作是可累计的。事件另外一个特性是,接收任务可等待多种事件,即多
个事件对应一个任务或多个任务。同时按照任务等待的参数,可选择是“逻辑或”触发还 是“逻辑与”触发。这个特性也是信号量等所不具备的,信号量只能识别单一同步动作,
而不能同时等待多个事件的同步。
各个事件可分别发送或一起发送给事件对象,而任务可以等待多个事件,任务仅对感 兴趣的事件进行关注。当有它们感兴趣的事件发生时并且符合感兴趣的条件,任务将被唤
醒并进行后续的处理动作。
三、事件运作机制
接收事件时,可以根据感兴趣的参事件类型接收事件的单个或者多个事件类型。事件 接收成功后,必须使用 xClearOnExit
选项来清除已接收到的事件类型,否则不会清除已接收 到的 事件 ,这样就需要用户显式清除事 位。
用户可 以自 定义 通过 传入参 数 xWaitForAllBits 选择读取模式,是等待所有感兴趣的事件还是等待感兴趣的任意一个事件。
设置事件时,对指定事件写入指定的事件类型,设置事件集合的对应事件位为 1,可 以一次同时写多个事件类型,设置事件成功可能会触发任务调度。
清除事件时,根据入参数事件句柄和待清除的事件类型,对事件对应位进行清 0 操作。 事件不与任务相关联,事件相互独立,一个
32位的变量(事件集合,实际用于表示事 件的只有 24 位),用于标识该任务发生的事件类型,其中每一位表示一种事件类型(0
表 示该事件类型未发生、1表示该事件类型已经发生),一共 24种事件类型具体见图2
图2事件集合 set(一个 32 位的变量)
事件唤醒机制,当任务因为等待某个或者多个事件发生而进入阻塞态,当事件发生的 时候会被唤醒,其过程具体见图3
图3 事件唤醒任务示意图
任务 1 对事件 3 或事件 5 感兴趣(逻辑或),当发生其中的某一个事件都会被唤醒, 并且执行相应操作。而任务
2 对事件 3 与事件 5 感兴趣(逻辑与),当且仅当事件 3 与事 件 5 都发生的时候,任务 2
才会被唤醒,如果只有一个其中一个事件发生,那么任务还是 会继续等待事件发生。如果接在收事件函数中设置了清除事件位
xClearOnExit,那么当任 务唤醒后将把事件 3 和事件 5 的事件标志清零,否则事件标志将依然存在。
四、事件控制块
事件标志组存储在一个 EventBits_t 类型的变量中,该变量在事件组结构体中定义,具 体见代码清单
20-1 加粗部分。如果宏 configUSE_16_BIT_TICKS 定义为 1,那么变量 uxEventBits
就 是 16 位 的 , 其 中 有 8 个 位 用来存储 事 件 组 , 如 果 宏 configUSE_16_BIT_TICKS
定义为 0,那么变量 uxEventBits 就是 32 位的,其中有 24 个位 用来存储事件组,每一位代表一个事件的发生与否,利用逻辑或、逻辑与等实现不同事件
的不同唤醒处理。在 STM32 中,uxEventBits 是 32 位的,所以我们有 24 个位用来实现事件
组。除了事件标志组变量之外,FreeRTOS 还使用了一个链表来记录等待事件的任务,所有 在等待此事件的任务均会被挂载在等待事件列表
xTasksWaitingForBits。
-
typedef
struct xEventGroupDefinition {
-
-
List_t
xTasksWaitingForBits;
-
-
#if(
configUSE_TRACE_FACILITY ==
1 )
-
UBaseType_t
uxEventGroupNumber;
-
-
-
#if(
( configSUPPORT_STATIC_ALLOCATION ==
1 )
\
-
&&
( configSUPPORT_DYNAMIC_ALLOCATION
==
1 )
)
-
uint8_t
ucStaticallyAllocated;
-
-
|
五、事件组函数
1.事件创建函数 xEventGroupCreate()
事件创建函数,顾名思义,就是创建一个事件,与其他内核对象一样,都是需要先创 建才能使用的资源,FreeRTOS
给我们提供了一个创建事件的函数 xEventGroupCreate(),当 创建一个事件时,系统会首先给我们分配事件控制块的内存空间,然后对该事件控制块进
行基本的初始化,创建成功返回事件句柄;创建失败返回 NULL。所以,在使用创建函数 之前,我们需要先定义有个事件的句柄。
EventGroupHandle_t xEventGroupCreate( void ); EventGroupHandle_t xEventGroupCreateStatic( StaticEventGroup_t * pxEventGroupBuffer );
|
2.事件删除函数 vEventGroupDelete()
vEventGroupDelete()用于删除由函数 xEventGroupCreate()创建的事件组,只有被创建
成功的事件才能被删除,但是需要注意的是该函数不允许在中断里面使用。当事件组被删 除之后,阻塞在该事件组上的任务都会被解锁,并向等待事件的任务返回事件组的值为
0, 其使用是非常简单的。
void vEventGroupDelete( EventGroupHandle_t xEventGroup )
|
3.事件组置位函数 xEventGroupSetBits()(任务)
xEventGroupSetBits()用于置位事件组中指定的位,当位被置位之后,阻塞在该位上的 任务将会被解锁。使用该函数接口时,通过参数指定的事件标志来设定事件的标志位,然
后遍历等待在事件对象上的事件等待列表,判断是否有任务的事件激活要求与当前事件对 象标志值匹配,如果有,则唤醒该任务。简单来说,就是设置我们自己定义的事件标志位
为 1,并且看看有没有任务在等待这个事件,有的话就唤醒它。
xEventGroupSetBits()函数说明
xEventGroupSetBits()函数使用实例
#define KEY1_EVENT (0x01 << 0)//设置事件掩码的位0 #define KEY2_EVENT (0x01 << 1)//设置事件掩码的位1 static EventGroupHandle_t Event_Handle =NULL; Event_Handle = xEventGroupCreate(); printf("Event_Handle 事件创建成功!\r\n"); static void KEY_Task(void* parameter) if( Key_Scan(KEY1_GPIO_PORT,KEY1_GPIO_PIN) == KEY_ON ) //如果KEY2被单击 xEventGroupSetBits(Event_Handle,KEY1_EVENT); if( Key_Scan(KEY2_GPIO_PORT,KEY2_GPIO_PIN) == KEY_ON ) //如果KEY2被单击 xEventGroupSetBits(Event_Handle,KEY2_EVENT); vTaskDelay(20); //每20ms扫描一次
|
4.等待事件函数 xEventGroupWaitBits()
既然标记了事件的发生,那么我怎么知道他到底有没有发生,这也是需要一个函数来 获 取 事 件 是 否
已 经 发 生 , FreeRTOS 提 供 了 一 个 等 待 指 定 事 件 的 函 数 — —
xEventGroupWaitBits(),通过这个函数,任务可以知道事件标志组中的哪些位,有什么事
件发生了,然后通过 “逻辑与”、“逻辑或”等操作对感兴趣的事件进行获取,并且这个 函数实现了等待超时机制,当且仅当任务等待的事件发生时,任务才能获取到事件信息。
在这段时间中,如果事件一直没发生,该任务将保持阻塞状态以等待事件发生。当其它任 务或中断服务程序往其等待的事件设置对应的标志位,该任务将自动由阻塞态转为就绪态。
当任务等待的时间超过了指定的阻塞时间,即使事件还未发生,任务也会自动从阻塞态转 移为就绪态。这样子很有效的体现了操作系统的实时性,如果事件正确获取(等待到)则
返回对应的事件标志位,由用户判断再做处理,因为在事件超时的时候也会返回一个不能 确定的事件值,所以需要判断任务所等待的事件是否真的发生。
xEventGroupWaitBits()函数说明
xEventGroupWaitBits()使用实例
static void LED_Task(void* parameter) EventBits_t r_event; /* 定义一个事件接收变量 */ /************************************************ ******************* r_event = xEventGroupWaitBits(Event_Handle, /* 事件对象句柄 */ KEY1_EVENT|KEY2_EVENT,/* 接收线程感兴趣的事件 */ portMAX_DELAY);/* 指定超时事件,一直等 */ if((r_event & (KEY1_EVENT|KEY2_EVENT)) == (KEY1_EVENT|KEY2_EVENT)) printf ( "KEY1与KEY2都按下\n");
|
5. xEventGroupClearBits()与 xEventGroupClearBitsFromISR()
xEventGroupClearBits()与 xEventGroupClearBitsFromISR()都是用于清除事件组指定的
位,如果在获取事件的时候没有将对应的标志位清除,那么就需要用这个函数来进行显式 清除,xEventGroupClearBits()函数不能在中断中使用,而是由具有中断保护功能
的 xEventGroupClearBitsFromISR() 来代替,中断清除事件标志位的操作在守护任务(也叫定
时 器 服 务 任 务 ) 里 面 完 成 。 守 护 进 程 的 优 先 级 由 FreeRTOSConfig.h
中 的 宏 configTIMER_TASK_PRIORITY 来定义 。 要 想 使 用 该 函
数 必 须 把 FreeRTOS/source/event_groups.c 这个 C 文件添加到工程中。
xEventGroupClearBits()与 xEventGroupClearBitsFromISR()函数说明
xEventGroupClearBits()函数使用实例
-
-
-
void aFunction(
EventGroupHandle_t xEventGroup )
-
-
-
/*
清楚事件组的 bit
0 and
bit
4 */
-
uxBits =
xEventGroupClearBits(
-
-
-
-
if
( ( uxBits &
( BIT_0
| BIT_4
) ) ==
( BIT_0
| BIT_4
) ) {
-
/*
在调用 xEventGroupClearBits()之前 bit0
和 bit4
都置位
-
-
} else
if
( ( uxBits &
BIT_0
) !=
0 )
{
-
/*
在调用 xEventGroupClearBits()之前 bit0
已经置位
-
-
} else
if
( ( uxBits &
BIT_4
) !=
0 )
{
-
/*
在调用 xEventGroupClearBits()之前 bit4
已经置位
-
-
-
/*
在调用 xEventGroupClearBits()之前 bit0
和 bit4
都没被置位 */
-
-
|
六、事件实验
事件标志组实验是在 FreeRTOS 中创建了两个任务,一个是设置事件任务,一个是等 待事件任务,两个任务独立运行,设置事件任务通过检测按键的按下情况设置不同的事件
标志位,等待事件任务则获取这两个事件标志位,并且判断两个事件是否都发生,如果是 则输出相应信息,LED
进行翻转。等待事件任务的等待时间是 portMAX_DELAY,一直在等待事件的发生,等待到事件之后清除对应的事件标记位,具体见代码清单
20-13 加粗部 分。
#include "event_groups.h" /**************************** 任务句柄 ********************************/ static TaskHandle_t AppTaskCreate_Handle = NULL;/* 创建任务句柄 */ static TaskHandle_t LED_Task_Handle = NULL;/* LED_Task任务句柄 */ static TaskHandle_t KEY_Task_Handle = NULL;/* KEY_Task任务句柄 */ /********************************** 内核对象句柄 *********************************/ static EventGroupHandle_t Event_Handle =NULL; /******************************* 全局变量声明 ************************************/ /******************************* 宏定义 ************************************/ #define KEY1_EVENT (0x01 << 0)//设置事件掩码的位0 #define KEY2_EVENT (0x01 << 1)//设置事件掩码的位1 ************************************************************************* ************************************************************************* static void AppTaskCreate(void);/* 用于创建任务 */ static void LED_Task(void* pvParameters);/* LED_Task 任务实现 */ static void KEY_Task(void* pvParameters);/* KEY_Task 任务实现 */ static void BSP_Init(void);/* 用于初始化板载相关资源 */ /***************************************************************** BaseType_t xReturn = pdPASS;/* 定义一个创建信息返回值,默认为pdPASS */ printf("这是一个FreeRTOS事件标志组实验!\n");
/*
创建AppTaskCreate任务 */ & */
(uint16_t )512, /* 任务栈大小 */ (void* )NULL,/* 任务入口函数参数 */ (UBaseType_t )1, /* 任务的优先级 */ (TaskHandle_t* )&AppTaskCreate_Handle);/* 任务控制块指针 */ vTaskStartScheduler(); /* 启动任务,开启调度 */ while(1); /* 正常不会执行到这里 */ /*********************************************************************** static void AppTaskCreate(void) BaseType_t xReturn = pdPASS;/* 定义一个创建信息返回值,默认为pdPASS */ taskENTER_CRITICAL(); //进入临界区 Event_Handle = xEventGroupCreate(); printf("Event_Handle 事件创建成功!\r\n"); xReturn = xTaskCreate((TaskFunction_t )LED_Task, /* 任务入口函数 */ (const char* )"LED_Task",/* 任务名字 */ (uint16_t )512, /* 任务栈大小 */ (void* )NULL, /* 任务入口函数参数 */ (UBaseType_t )2, /* 任务的优先级 */ (TaskHandle_t* )&LED_Task_Handle);/* 任务控制块指针 */ printf("创建LED_Task任务成功!\r\n"); xReturn = xTaskCreate((TaskFunction_t )KEY_Task, /* 任务入口函数 */ (const char* )"KEY_Task",/* 任务名字 */ (uint16_t )512, /* 任务栈大小 */ (void* )NULL,/* 任务入口函数参数 */ (UBaseType_t )3, /* 任务的优先级 */ (TaskHandle_t* )&KEY_Task_Handle);/* 任务控制块指针 */ printf("创建KEY_Task任务成功!\n"); vTaskDelete(AppTaskCreate_Handle); //删除AppTaskCreate任务 taskEXIT_CRITICAL(); //退出临界区 /********************************************************************** static void LED_Task(void* parameter) EventBits_t r_event; /* 定义一个事件接收变量 */ /******************************************************************* r_event = xEventGroupWaitBits(Event_Handle, /* 事件对象句柄 */ KEY1_EVENT|KEY2_EVENT,/* 接收线程感兴趣的事件 */ portMAX_DELAY);/* 指定超时事件,一直等 */
if((r_event
&
(KEY1_EVENT|KEY2_EVENT ))
==
(KEY1_EVENT|KEY2_EVENT))
printf ( "KEY1与KEY2都按下\n"); /********************************************************************** static void KEY_Task(void* parameter) if( Key_Scan(KEY1_GPIO_PORT,KEY1_GPIO_PIN) == KEY_ON ) //如果KEY2被单击 xEventGroupSetBits(Event_Handle,KEY1_EVENT); if( Key_Scan(KEY2_GPIO_PORT,KEY2_GPIO_PIN) == KEY_ON ) //如果KEY2被单击 xEventGroupSetBits(Event_Handle,KEY2_EVENT); vTaskDelay(20); //每20ms扫描一次 /*********************************************************************** static void BSP_Init(void) * STM32中断优先级分组为4,即4bit都用来表示抢占优先级,范围为:0~15 * 优先级分组只需要分组一次即可,以后如果有其他的任务需要用到中断, * 都统一用这个优先级分组,千万不要再分组,切忌。 NVIC_PriorityGroupConfig( NVIC_PriorityGroup_4 ); /********************************END OF FILE****************************/
|
七、实验现象
程序编译好,用 USB 线连接电脑和开发板的 USB 接口(对应丝印为 USB 转串口), 用 仿真器把配套程序下载到
STM32 开发板,在电脑上打开串口调试助手,然后复位开发板就可 以在调试助手中看到串口的打印信息,按下开发版的
KEY1 按键发送事件 1,按下 KEY2 按键发送事件 2;我们按下 KEY1 与 KEY2 试试,在串口调试助手中可以看到运行结果,
并且当事件 1 与事件 2都发生的时候,开发板的 LED 会进行翻转。
|