FreeRTOS事件标志组的介绍与使用
一、概述
\quad
事件是一种实现任务间通信的机制,主要用于实现多任务间的同步,但事件通信只能是事件类型的通信,无数据传输。与信号量不同的是,它可以实现一对多,多对多的同步
。 即一个任务可以等待多个事件的发生:可以是任意一个事件发生时唤醒任务进行事件处理; 也可以是几个事件都发生后才唤醒任务进行事件处理。同样,也可以是多个任务同步多个事件。
\quad
每一个事件组只需要很少的 RAM 空间来保存事件组的状态。事件组存储在一个 EventBits_t 类型的变量中,该变量在事件组结构体中定义。
\quad
如果宏configUSE_16_BIT_TICKS定义为1,那么变量uxEventBits就是16位的,其中有8个位用来存储事件组;而如果宏 configUSE_16_BIT_TICKS 定义为0,那么变量uxEventBits就是32位的, 其中有24个位用来存储事件组。
\quad
在STM32中,我们一般将configUSE_16_BIT_TICKS定义为0,那么uxEventBits是32位的,有24个位用来实现事件标志组。每一位代表一个事件,任务通过“逻辑与”或“逻辑或”与一个或多个事件建立关联,形成一个事件组。
\quad
例如,如果一个事件组的值是0x92(二进制1001 0010),那么只有事件位1、4和7被设置,因此只有由1、4和7表示的事件发生。下图显示了EventBits_t类型的变量,它设置了事件位1、4和7,并清除了所有其他事件位,使事件组的值为0x92。
\quad
事件的“逻辑或”也被称作是独立型同步
,指的是任务感兴趣的所有事件任一件发生即可被唤醒;事件“逻辑与”则被称为是关联型同步
,指的是任务感兴趣的若干事件都发生时才被唤醒,并且事件发生的时间可以不同步。
\quad
多任务环境下,任务、中断之间往往需要同步操作,一个事件发生会告知等待中的任务,即形成一个任务与任务、中断与任务间的同步。事件可以提供一对多、多对多的同步操作。一对多同步模型:一个任务等待多个事件的触发,这种情况是比较常见的;多对多同步模型:多个任务等待多个事件的触发。
\quad
任务可以通过设置事件位来实现事件的触发和等待操作。FreeRTOS 的事件仅用于同步,不提供数据传输功能。
FreeRTOS 提供的事件具有如下特点:
- 事件只与任务相关联,事件相互独立,一个 32 位的事件集(EventBits_t 类型的 变量,实际可用与表示事件的只有 24位),用于标识该任务发生的事件类型,其中每一位表示一种事件类型(0 表示该事件类型未发生、1 表示该事件类型已经发生),一共 24种事件类型。
- 事件仅用于同步,不提供数据传输功能。
- 事件无排队性,即多次向任务设置同一事件(如果任务还未来得及读走),等效于只设置一次。
- 允许多个任务对同一事件进行读写操作。
- 支持事件等待超时机制。
\quad 在 FreeRTOS 事件中,每个事件获取的时候,用户可以选择感兴趣的事件,并且选择读取事件信息标记,它有三个属性,分别是逻辑与,逻辑或以及是否清除标记。当任务等待事件同步时,可以通过任务感兴趣的事件位和事件信息标记来判断当前接收的事件是否满足要求,如果满足则说明任务等待到对应的事件,系统将唤醒等待的任务;否则,任务会根据用户指定的阻塞超时时间继续等待下去。
\quad
事件可使用于多种场合,它能够在一定程度上替代信号量,用于任务与任务间,中断与任务间的同步。一个任务或中断服务例程发送一个事件给事件对象,而后等待的任务被 唤醒并对相应的事件进行处理。但是它与信号量不同的是,事件的发送操作是不可累计的,而信号量的释放动作是可累计的。事件另外一个特性是,接收任务可等待多种事件,即多个事件对应一个任务或多个任务。同时按照任务等待的参数,可选择是“逻辑或”触发还 是“逻辑与”触发。这个特性也是信号量等所不具备的,信号量只能识别单一同步动作, 而不能同时等待多个事件的同步。
各个事件可分别发送或一起发送给事件对象,而任务可以等待多个事件,
\quad
任务仅对感兴趣的事件进行关注。当有它们感兴趣的事件发生时并且符合感兴趣的条件,任务将被唤 醒并进行后续的处理动作。
二、事件的运作机制
\quad
接收事件时,可以根据感兴趣的参事件类型接收事件的单个或者多个事件类型。事件接收成功后,必须使用xClearOnExit选项来清除已接收到的事件类型,否则不会清除已接收到的事件,这样就需要用户显式清除事件位。用户可以自定义通过传入参数xWaitForAllBits选择读取模式,是等待所有感兴趣的事件还是等待感兴趣的任意一个事件。
\quad
设置事件时,对指定事件写入指定的事件类型,设置事件集合的对应事件位为1,可以一次同时写多个事件类型,设置事件成功可能会触发任务调度。
\quad
清除事件时,根据入参数事件句柄和待清除的事件类型,对事件对应位进行清0操作。
\quad
事件不与任务相关联,事件相互独立,一个 32 位的变量(事件集合,实际用于表示事件的只有 24 位),用于标识该任务发生的事件类型,其中每一位表示一种事件类型(0 表示该事件类型未发生、1 表示该事件类型已经发生),一共24种事件类型具体如下图。
\quad
事件唤醒机制,当任务因为等待某个或者多个事件发生而进入阻塞态,当事件发生的时候会被唤醒,其过程具体如下图。
\quad 任务1对事件3或事件5感兴趣(逻辑或),当发生其中的某一个事件都会被唤醒,并且执行相应操作。而任务2对事件3与事件5感兴趣(逻辑与),当且仅当事件3与事件5都发生的时候,任务2才会被唤醒,如果只有一个其中一个事件发生,那么任务还是会继续等待事件发生。如果接在收事件函数中设置了清除事件位xClearOnExit,那么当任务唤醒后将把事件3和事件5的事件标志清零,否则事件标志将依然存在。
三、函数接口
1、事件标志组创建
#include "event_groups.h"
EventGroupHandle_t xEventGroupCreate(void)
参数说明:
- 无
返回值:
- 成功-返回事件标志组的句柄。
- 失败-返回NULL。
2、事件标志组删除
void vEventGroupDelete( EventGroupHandle_t xEventGroup )
参数说明:
- xEventGroup-事件标志组的句柄
返回值:
- 无
3、事件标志组指定的位置位
EventBits_t xEventGroupSetBits( EventGroupHandle_t xEventGroup, const EventBits_t uxBitsToSet )
参数说明:
- xEventGroup-事件标志组的句柄
- uxBitsToSet-指定事件中的事件标志位。如设置uxBitsToSet为0x08(0000 1000)则只置位位3,如果设置uxBitsToSet为0x09(0000 1001)则位3和位0都需要被置位。
返回值:
- 成功-返回调用xEventGroupSetBits()时事件组中的值。
4、在中断中执行事件标志组置位
BaseType_t xEventGroupSetBitsFromISR( EventGroupHandle_t xEventGroup, const EventBits_t uxBitsToSet, BaseType_t *pxHigherPriorityTaskWoken )
参数说明:
- xEventGroup-事件标志组的句柄
- uxBitsToSet-指定事件中的事件标志位。如设置 uxBitsToSet为0x08则只置位位3,如果设置 uxBitsToSet 为
0x09 则位3和位0都需要被置位。 - pxHigherPriorityTaskWoken-pxHigherPriorityTaskWoken 在使用之前必须初始化成 pdFALSE。如果API函数(即xEventGroupSetBitsFromISR)导致一个任务解锁,并且解锁的任务优先级高于当前运行的任务,则API函数(即xEventGroupSetBitsFromISR)将*pxHigherPriorityTaskWoken设置成pdTRUE,在中断退出前,触发一次任务切换,可提高实时性;若为pdFALSE,则在下一个时钟节拍才进行任务切换。pxHigherPriorityTaskWoken 作为一个可选参数,可以设置为NULL。
返回值:
- 成功-返回pdPASS。
- 失败-返回pdFAIL。
5、等待事件标志组
EventBits_t xEventGroupWaitBits( EventGroupHandle_t xEventGroup,
const EventBits_t uxBitsToWaitFor,
const BaseType_t xClearOnExit,
const BaseType_t xWaitForAllBits,
TickType_t xTicksToWait )
参数说明:
- xEventGroup-事件标志组的句柄
- uxBitsToWaitFor-一个按位或的值,指定需要等待事件组中的哪些位置 1。如果需要等待 bit 0 and/or bit 2 那么 uxBitsToWaitFor 配置为 0x05(0101’b)。如 果需要等待 bits 0 and/or bit 1and/or bit 2 那么 uxBitsToWaitFor 配置 为 0x07(0111’b)。
- xClearOnExit-pdTRUE:当 xEventGroupWaitBits()等待到满足任务唤醒的事件 时,系统将清除由形参 uxBitsToWaitFor 指定的事件标志位。 pdFALSE:不会清除由形参 uxBitsToWaitFor 指定的事件标志位。
- xWaitForAllBits-pdTRUE: 当形参uxBitsToWaitFor指定的位都置位的时候,xEventGroupWaitBits()才满足任务唤醒的条件,这也是“逻辑与”等待事件,并且在没有超时的情况下返回对应的事件标志位的值。 pdFALSE:当形参uxBitsToWaitFor-指定的位有其中任意一个置位的时候,这也是常说的“逻辑或”等待事件,在没有超时的情况下函数返回对应的事件标志位的值。
- xTicksToWait-最大超时时间,单位为系统节拍周期,常量 portTICK_PERIOD_MS 用于辅助把时间转换成 MS。
返回值:
返回事件中的哪些事件标志位被置位,返回值很可能并不是用户指定的事件位,需要 对返回值进行判断再处理。
6、事件标志组指定的位清零
EventBits_t xEventGroupClearBits( EventGroupHandle_t xEventGroup, const EventBits_t uxBitsToClear )
参数说明:
- xEventGroup-事件标志组的句柄
- uxBitsToClear-指定事件组中的哪个位需要清除。如设置 uxBitsToSet 为 0x08 则只清除位 3,如果设置uxBitsToSet 为 0x09 则位3和位0都需要被清除。
返回值:
- 事件标志组还没有清除指定位之前的值。
7、在中断中执行事件标志组指定的位清零
BaseType_t xEventGroupClearBitsFromISR( EventGroupHandle_t xEventGroup,
const EventBits_t uxBitsToClear )
参数说明:
- xEventGroup-事件标志组的句柄
- uxBitsToClear-指定事件组中的哪个位需要清除。如设置 uxBitsToSet 为 0x08 则只清除位 3,如果设置uxBitsToSet 为 0x09 则位3和位0都需要被清除。
返回值:
- 成功-返回pdPASS。
- 失败-返回pdFAIL。
注意事项:
\quad
若使用xEventGroupSetBitsFromISR、xEventGroupClearBitsFromISR等函数,官方要求3个宏定义生效,详细代码如下:
#if ( ( configUSE_TRACE_FACILITY == 1 ) && ( INCLUDE_xTimerPendFunctionCall == 1 ) && ( configUSE_TIMERS == 1 ) )
BaseType_t xEventGroupSetBitsFromISR( EventGroupHandle_t xEventGroup,
const EventBits_t uxBitsToSet,
BaseType_t *pxHigherPriorityTaskWoken )
{
........................
}
BaseType_t xEventGroupClearBitsFromISR( EventGroupHandle_t xEventGroup, const EventBits_t uxBitsToClear )
{
........................
}
FreeRTOSConfig.h
#define configUSE_TRACE_FACILITY 1
#define configUSE_TIMERS 1
FreeRTOS.h
#ifndef INCLUDE_xTimerPendFunctionCall
#define INCLUDE_xTimerPendFunctionCall 1
#endif
四、示例代码
int main(void)
{
/* 设置系统中断优先级分组4 */
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);
/* 系统定时器中断频率为configTICK_RATE_HZ */
SysTick_Config(SystemCoreClock/configTICK_RATE_HZ);
/* 初始化串口1 */
uart_init(9600);
/* 创建app_task1任务 */
xTaskCreate((TaskFunction_t )app_task1, /* 任务入口函数 */
(const char* )"app_task1", /* 任务名字 */
(uint16_t )512, /* 任务栈大小 */
(void* )NULL, /* 任务入口函数参数 */
(UBaseType_t )5, /* 任务的优先级 */
(TaskHandle_t* )&app_task1_handle); /* 任务控制块指针 */
/* 创建app_task2任务 */
xTaskCreate((TaskFunction_t )app_task2, /* 任务入口函数 */
(const char* )"app_task2", /* 任务名字 */
(uint16_t )512, /* 任务栈大小 */
(void* )NULL, /* 任务入口函数参数 */
(UBaseType_t )5, /* 任务的优先级 */
(TaskHandle_t* )&app_task2_handle); /* 任务控制块指针 */
/* 创建app_task3任务 */
xTaskCreate((TaskFunction_t )app_task3, /* 任务入口函数 */
(const char* )"app_task3", /* 任务名字 */
(uint16_t )512, /* 任务栈大小 */
(void* )NULL, /* 任务入口函数参数 */
(UBaseType_t )5, /* 任务的优先级 */
(TaskHandle_t* )&app_task3_handle); /* 任务控制块指针 */
//创建事件标志组
g_event_grp=xEventGroupCreate();
/* 开启任务调度 */
vTaskStartScheduler();
printf("none run here...\r\n");
while(1);
}
static void app_task1(void* pvParameters)
{
for(;;)//while(1)
{
printf("app_task1 is running ...\r\n");
//将事件标志组的bit0置1
xEventGroupSetBits(g_event_grp,0x01);
vTaskDelay(2000);
}
}
static void app_task2(void* pvParameters)
{
EventBits_t event_bits;
for(;;)//while(1)
{
//逻辑或等待,一直阻塞等待bit0、bit1置1,等待成功后自动清零对应的标志位
event_bits=xEventGroupWaitBits(g_event_grp,0x01|0x02,pdTRUE,pdFALSE,portMAX_DELAY);
//逻辑与等待,一直阻塞等待bit0、bit1都置1,等待成功后自动清零对应的标志位
//event_bits=xEventGroupWaitBits(g_event_grp,0x01|0x02,pdTRUE,pdTRUE,portMAX_DELAY);
if(event_bits & 0x01)
{
printf("app_task2 bit0 set\r\n");
}
if(event_bits & 0x02)
{
printf("app_task2 bit1 set\r\n");
}
}
}
static void app_task3(void* pvParameters)
{
for(;;)//while(1)
{
printf("app_task3 is running ...\r\n");
//将事件标志组的bit1置1
xEventGroupSetBits(g_event_grp,0x02);
vTaskDelay(5000);
}
}