目录
2.2 函数 xEventGroupCreateStatic()
3.2 函数 xEventGroupClearBitsFromISR()
3.4 函数 xEventGroupSetBitsFromISR()
4.2 函数 xEventGroupGetBitsFromISR()
信号量可以来完成同步,但是使用信号量来同步的话任务只能与单个的事件或任务进行同步。但是有时候某个任务可能会需要与多个事件或任务进行同步,此时信号量就无能为力了。FreeRTOS 为此提供了一个解决方法,就是事件标志组。
1. 事件标志组简介
事件位(事件标志):
事件位用来表明某个事件是否发生,事件位通常用作事件标志,比如下面这几个例子:
- 当收到一条消息并且把这条消息处理掉以后就可以将某个位(标志)置 1,当队列中没有消息需要处理的时候就可以将这个位(标志)置 0。
- 当把队列中的消息通过网络发送输出以后就可以将某个位(标志)置 1,当没有数据需要从网络发送出去的话就将这个位(标志)置 0.
- 现在需要向网络中发送一个心跳信息,将某个位(标志)置 1,现在不需要向网络中发送心跳信息,这个位(标志)置 0.
事件组:
一个事件组就是一组的事件位,事件组中的事件位通过位编号来访问,同样,以上面列出的三个例子为例:
- 事件标志组的 bit0 表示队列中的消息是否处理掉。
- 事件标志组的 bit1 表示是否有消息需要从网络中发送出去。
- 事件标志组的 bit2 表示现在是否需要向网络发送心跳信息。
事件标志组和事件位的数据类型:
事件标志组的数据类型为 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
#else
typedef uint32_t TickType_t;
#define portMAX_DELAY ( TickType_t ) 0xffffffffUL
#define portTICK_TYPE_IS_ATOMIC 1
#endif
通过该程序可以看出:
当 configUSE_16_BIT_TICKS 等于 0 的时候,0xffffffffUL 表示TickType_t 是一个 32 位的数据
类型,因此 EventBits_t 也是一个 32 位的数据类型。
EventBits_t 类型的变量可以存储 24 个事件位,另外的那高 8 位有其他用处。
事件 0 存放在这个变量的 bit0 上,变量的 bit1 就是事件位 1,以此类推。
对于 STM32 来说一个事件标志组最多可以存储 24 个事件位。
2. 创建事件标志组
FreeRTOS 提供了两个用于创建事件标志组的函数。
函数:
xEventGroupCreate() 使用动态方法创建事件标志组。
xEventGroupCreateStatic() 使用静态方法创建事件标志组。
2.1 函数 xEventGroupCreate()
此函数用来创建一个事件标志组,所需要的内存通过动态内存管理方法分配。由于内部处理的原因,事件标志组可用的 bit 数取决于 configUSE_16_BIT_TICKS,当 configUSE_16_BIT_TICKS 为 1 的时候事件标志组有 8 个可用的位(bit 0 ~ bit 7),当 configUSE_16_BIT_TICKS 为 0 的时候事件标志组有 24 个可用的位(bit 0 ~ bit 23)。EventBits_t 类型的变量用来存储事件标志位组中的各个事件位,函数原型如下:
EventGroupHandle_t xEventGroupCreate(void)
参数:
无
返回值:
NULL: 事件标志组创建失败。
其他值: 创建成功的事件标志组句柄。
2.2 函数 xEventGroupCreateStatic()
此函数用于创建一个事件标志组,所需要的内存需要由用户自行分配,此函数原型如下:
EventGroupHandle_t xEventGroupCreateStatic(StaticEventGroup_t *pxEventGroupBuffer)
参数:
pxEventGroupBuffer: 参数指向一个 StaticEnentGroup_t 类型的变量,用来保存事件标志组结构体。
返回值:
NULL: 事件标志组创建失败。
其他值: 创建成功的事件标志组句柄。
3. 设置事件位
FreeRTOS 提供了 4 个函数用来设置事件标志组中事件位(标志),事件位(标志)的设置包括清零和置 1 两种操作。
函数:
xEventGroupClearBits() 将指定的事件位清零,用在任务中。
xEventGroupClearBitsFromISR() 将指定的事件位清零,用在中断服务函数中。
xEventGroupSetBits() 将指定的事件位置 1 ,用在任务中。
xEventGroupSetBitsFromISR() 将指定的事件位置 1,用在中断服务函数中。
3.1 函数 xEventGroupClearBits()
将事件标志组中的指定事件位清零,此函数只能用在任务中,不能用在中断服务函数中!中断服务函数中有其他的 API 函数。函数原型如下:
EventBits_t xEventGroupClearBits(EventGroupHandle_t xEventGroup,
const EventBits_t uxBitsToClear);
参数:
xEventGroup: 要操作的事件标志组的句柄。
uxBitsToClear: 要清零的事件位,比如要清除 bit3 的话就设置为 0x08。可以同时清除多个 bit,如设置为 0x09 的话就是同时清除 bit3 和 bit0。
返回值:
任何值: 将指定事件位清零之前的事件组值。
3.2 函数 xEventGroupClearBitsFromISR()
此函数为函数 xEventGroupClearBits() 的中断级版本,也是将指定的事件位(标志)清零。此函数用在中断服务函数中,此函数原型如下:
BaseType_t xEventGroupClearBitsFromISR(EventGroupHandle_t xEventGroup,
const EventBits_t uxBitsToSet);
参数:
xEventGroup: 要操作的事件标志组的句柄。
uxBitsToClear: 要清零的事件位,比如要清除 bit3 的话就设置为 0x08。可以同时清除多个 bit,如设置为 0x09 的话就是同时清除 bit3 和 bit0。
返回值:
pdPASS: 事件位清零成功。
pdFALSE: 事件位清零失败。
3.3 函数 xEventGroupSetBits()
设置指定的事件位为 1,此函数只能用在任务中,不能用于中断服务函数,此函数原型如下:
EventBits_t xEventGroupSetBits(EventGroupHandle_t xEventGroup,
const EventBits_t uxBitsToSet);
参数:
xEventGroup: 要操作的事件标志组的句柄。
uxBitsToClear: 指定要置 1 的事件位,比如要将 bit3 置 1 的话就设置为 0x08。可以同时将多个 bit 置 1,如设置为 0x09 的话就是同时将 bit3 和 bit0 置 1。
返回值:
任何值: 在将指定事件位置 1 后的事件组值。
3.4 函数 xEventGroupSetBitsFromISR()
此函数也用于将指定的事件位置 1 ,此函数是 xEventGroupSetBits() 的中断版本,用在中断服务函数中,函数原型如下:
EventBits_t xEventGroupSetBitsFromISR(EventGroupHandle_t xEventGroup,
const EventBits_t uxBitsToSet,
BaseType_t* pxHigherPriorityTaskWoken);
参数:
xEventGroup: 要操作的事件标志组的句柄。
uxBitsToClear: 指定要置 1 的事件位,比如要将 bit3 置 1 的话就设置为 0x08。可以同时将多个 bit 置 1,如设置为 0x09 的话就是同时将 bit3 和 bit0 置 1。
pxHigherPriorityTaskWoken:标记退出此函数以后是否进行任务切换,这个变量的值函数会自动的设置,用户不用进行设置。用户只需要提供一个变量来保存这个值就可以了。当此值
为 pdTRUE 的时候在退出中断服务函数之前一定要进行一次任务切换。
返回值:
pdPASS: 事件位置 1 成功。
pdFALSE: 事件位置 1 失败。
4. 获取事件标志组值
可以通过 FreeRTOS 提供的 API 函数查询事件标志组值。
函数:
xEventGroupGetBits() 获取当前事件标志组的值(各个事件位的值),用在任务中。
xEventGroupGetBitsFromISR() 获取当前事件标志组的值,用在中断服务函数中。
4.1 函数 xEventGroupGetBits()
此函数用于获取当前事件标志组的值,也就是各个事件位的值。此函数用在任务中,不能用在中断服务函数中。此函数是个宏,真正执行的是函数 xEventGroupClearBits(),函数原型如下:
EventBits_t xEventGroupGetBits(EventGroupHandle_t xEventGroup)
参数:
xEventGroup: 要获取的事件标志组的句柄。
返回值:
任何值: 当前事件标志组的值。
4.2 函数 xEventGroupGetBitsFromISR()
获取当前事件标志组的值,此函数是 xEventGroupGetBits() 的中断版本,函数原型如下:
EventBits_t xEventGroupGetBitsFromISR(EventGroupHandle_t xEventGroup)
参数:
xEventGroup: 要获取的事件标志组的句柄。
返回值:
任何值: 当前事件标志组的值。
5. 等待指定的事件位
某个任务可能需要与多个事件进行同步,那么这个任务就需要等待并判断多个事件位(标志),使用函数 xEventGroupWaitBits() 可以完成这个功能。调用函数以后如果任务要等待的事件位还没有准备好(置 1 或清零)的话任务就会进入阻塞态,直到阻塞时间到达或者所等待的事件位准备好。函数原型如下:
EventBits_t xEventGroupWaitBits(EventGroupHandle_t xEventGroup,
const EventBits_t uxBitsToWaitFor,
const BaseType_t xClearOnExit,
const BaseType_t xWaitForAllBits,
const TickType_t xTicksToWait);
参数:
xEventGroup: 指定要等待的事件标志组。
uxBitsToWaitFor: 指定要等待的事件位,比如要等待 bit0 和 bit2 的时候此参数就是 0x05,如果要等待 bit0 和 bit1 和 bit2 的时候此参数就是 0x07,以此类推。
xClearOnExit: 此参数要是为 pdTRUE 的话,那么在退出此函数之前由参数 uxBitsToWaitFor 所设置的这些事件位就会清零。如果设置位 pdFALSE 的话这些事件
位就不会改变。
xWaitForAllBits: 此参数如果设置为 pdTRUE 的话,当 uxBitsToWaitFor 所设置的这些事件位都置 1 ,或者指定的阻塞时间到的时候函数 xEventGroupWaitBits() 才会返回。
当此函数为 pdFALSE 的话,只要 uxBitsToWaitFor 所设置的这些事件位其中的任意一个置 1,或者指定的阻塞时间到的话函数 xEventGroupWaitBits() 就
会返回。
xTicksToWait: 设置阻塞时间,单位为节拍数。
返回值:
任何值。 返回当所等待的事件位置 1 以后的事件标志组的值,或者阻塞时间到。根据这个值我们就知道哪些事件位置 1 了。如果函数因为阻塞时间到而返回的话
那么这个返回值就不代表任何的含义。
6. 事件标志组实验
本实验设计四个任务:start_task、eventsetbit_task、eventgroup_task 和 eventquery_task 这四个任务的任务功能如下:
start_task:用来创建其他三个任务和事件标志组。
eventsetbit_task:读取按键值,根据不同的按键值将事件标志组中相应的事件位置 1 ,用来模拟事件的发生。
eventgroup_task:同时等待事件标志组中的多个事件位,当这些事件位都置 1 的话就执行相应的处理,例程中的处理是刷新 LCD 指定区域的背景色。
eventquery_task:查询事件组的值,也就是各个事件位的值。获取到事件组值以后就将其显示到 LCD 上,并且也通过串口打印出来。
实验还创建了一个事件标志组:EventGroupHandler,实验中用到了这个事件标志组的三个事件位,分别位 bit0,bit1 和 bit2。
实验中用到了三个按键:KEY0、KEY1 和 KEY2,其中按键 KEY1 和 KEY2 为普通的输入模式。按键 KEY0 为中断输入模式,KEY0 用来演示如何在中断服务程序调用事件标志组的 API 函数。
6.1 main.c
#include "stm32f4xx.h"
#include "FreeRTOS.h" //这里注意必须先引用FreeRTOS的头文件,然后再引用task.h
#include "task.h" //存在一个先后的关系
#include "LED.h"
#include "LCD.h"
#include "Key.h"
#include "usart.h"
#include "delay.h"
#include "string.h"
#include "beep.h"
#include "malloc.h"
#include "timer.h"
#include "exti.h"
#include "event_groups.h"
//任务优先级
#define START_TASK_PRIO 1 //用于创建其他任务
//任务堆栈大小
#define START_STK_SIZE 256
//任务句柄
TaskHandle_t StartTask_Handler;
//任务函数
void start_task(void *pvParameters);
//任务优先级
#define EVENTSETBIT_TASK_PRIO 2 //读取按键值,根据不同的按键值将事件标志组中相应的事件位置 1 ,用来模拟事件的发生。
//任务堆栈大小
#define EVENTSETBIT_STK_SIZE 256
//任务句柄
TaskHandle_t EventSetBit_Handler;
//任务函数
void eventsetbit_task(void *pvParameters);
//任务优先级
#define EVENTGROUP_TASK_PRIO 3 //同时等待事件标志组中的多个事件位,当这些事件位都置 1 的话就执行相应的处理,例程中的处理是刷新 LCD 指定区域的背景色。
//任务堆栈大小
#define EVENTGROUP_STK_SIZE 256
//任务句柄
TaskHandle_t EventGroupTask_Handler;
//任务函数
void eventgroup_task(void *pvParameters);
//任务优先级
#define EVENTQUERY_TASK_PRIO 4 //查询事件组的值,也就是各个事件位的值。获取到事件组值以后就将其显示到 LCD 上,并且也通过串口打印出来。
//任务堆栈大小
#define EVENTQUERY_STK_SIZE 256
//任务句柄
TaskHandle_t EventQueryTask_Handler;
//任务函数
void eventquery_task(void *pvParameters);
EventGroupHandle_t EventGroupHandler; //事件标志组句柄
//事件标志组是个类似于数组的形式,数组是以下角标的形式进行定义的
//EVENTBIT_0 操作数组下角标为 0 的位,1左移0位,也就是把数组第一位设置为1
//EVENTBIT_1 操作数组下角标为 1 的位,1左移1位,也就是把数组第二位设置为1
//EVENTBIT_2 操作数组下角标为 2 的位,1左移2位,也就是把数组第三位设置为1
//EVENTBIT_ALL 操作数组下角标为 0 1 2 的位,或表示把数组第一位、第二位和第三位设置为1
#define EVENTBIT_0 (1<<0) //事件位
#define EVENTBIT_1 (1<<1)
#define EVENTBIT_2 (1<<2)
#define EVENTBIT_ALL (EVENTBIT_0|EVENTBIT_1|EVENTBIT_2)
//LCD刷屏时使用的颜色
int lcd_discolor[14]={ WHITE, BLACK, BLUE, BRED,
GRED, GBLUE, RED, MAGENTA,
GREEN, CYAN, YELLOW,BROWN,
BRRED, GRAY };
int main(void)
{
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);
delay_init(168);
uart_init(115200);
LED_Init();
KEY_Init();
EXTIX_Init(); //初始化外部中断
LCD_Init();
my_mem_init(SRAMIN); //初始化内部内存池
POINT_COLOR=RED;
LCD_ShowString(30,10,200,16,16,"ATK STM32F407");
LCD_ShowString(30,30,200,16,16,"FreeRTOS Example");
LCD_ShowString(30,50,200,16,16,"Event Group");
LCD_ShowString(30,70,200,16,16,"ATOM@ALIENTEK");
LCD_ShowString(30,90,200,16,16,"2023/11/04");
POINT_COLOR = BLACK;
LCD_DrawRectangle(5,130,234,314); //画一个矩形
POINT_COLOR = BLUE;
LCD_ShowString(30,110,200,16,16,"Event Group Value:0");
//创建开始任务
xTaskCreate((TaskFunction_t)start_task, //任务函数
(const char* )"start_task", //任务名称
(uint16_t )START_STK_SIZE, //任务堆栈大小
(void* )NULL, //传递给任务函数的参数
(UBaseType_t )START_TASK_PRIO, //任务优先级
(TaskHandle_t* )&StartTask_Handler);//任务句柄
vTaskStartScheduler(); //开启任务调度
}
//开始任务任务函数
void start_task(void *pvParameters)
{
taskENTER_CRITICAL();
//创建事件标志组
EventGroupHandler=xEventGroupCreate(); //创建事件标志组
//创建设置事件位的任务
xTaskCreate((TaskFunction_t)eventsetbit_task, //任务函数
(const char* )"eventsetbit_task", //任务名称
(uint16_t )EVENTSETBIT_STK_SIZE, //任务堆栈大小
(void* )NULL, //传递给任务函数的参数
(UBaseType_t )EVENTSETBIT_TASK_PRIO, //任务优先级
(TaskHandle_t* )&EventSetBit_Handler);//任务句柄
//创建事件标志组处理任务
xTaskCreate((TaskFunction_t)eventgroup_task, //任务函数
(const char* )"eventgroup_task", //任务名称
(uint16_t )EVENTGROUP_STK_SIZE, //任务堆栈大小
(void* )NULL, //传递给任务函数的参数
(UBaseType_t )EVENTGROUP_TASK_PRIO, //任务优先级
(TaskHandle_t* )&EventGroupTask_Handler);//任务句柄
//创建事件标志组查询任务
xTaskCreate((TaskFunction_t)eventquery_task, //任务函数
(const char* )"eventquery_task", //任务名称
(uint16_t )EVENTQUERY_STK_SIZE, //任务堆栈大小
(void* )NULL, //传递给任务函数的参数
(UBaseType_t )EVENTQUERY_TASK_PRIO, //任务优先级
(TaskHandle_t* )&EventQueryTask_Handler);//任务句柄
vTaskDelete(StartTask_Handler); //删除开始任务
taskEXIT_CRITICAL();
}
//设置事件位的任务
//读取按键值,根据不同的按键值将事件标志组中相应的事件位置 1 ,用来模拟事件的发生。
void eventsetbit_task(void *pvParameters)
{
u8 key;
while(1)
{
if(EventGroupHandler!=NULL) //事件标志组创建成功
{
key = KEY_Scan(0);
switch(key)
{
case KEY1_PRES:
xEventGroupSetBits(EventGroupHandler,EVENTBIT_1); //KEY1按下,设置事件标志组的第二位为1
break;
case KEY2_PRES:
xEventGroupSetBits(EventGroupHandler,EVENTBIT_2); //KEY2按下,设置事件标志组的第三位为1
break;
}
}
vTaskDelay(10); //延时10ms,也就是10个时钟节拍
}
}
//事件标志组处理任务
//同时等待事件标志组中的多个事件位,当这些事件位都置 1 的话就执行相应的处理,例程中的处理是刷新 LCD 指定区域的背景色。
void eventgroup_task(void *pvParameters)
{
u8 num;
EventBits_t EventValue;
while(1)
{
if(EventGroupHandler!=NULL)
{
//等待事件组中相应的事件位
EventValue = xEventGroupWaitBits((EventGroupHandle_t )EventGroupHandler, //指定要等待的事件组句柄
(EventBits_t )EVENTBIT_ALL, //指定要等待的事件位,这里设置等待第一位、第二位和第三位
(BaseType_t )pdTRUE, //此参数要是为 pdTRUE 的话,那么在退出此函数之前由参数 uxBitsToWaitFor 所设置的这些事件位就会清零。
(BaseType_t )pdTRUE, //此参数如果设置为 pdTRUE 的话,当 uxBitsToWaitFor 所设置的这些事件位都置 1 ,或者指定的阻塞时间到的时候函数 xEventGroupWaitBits() 才会返回。
(TickType_t )portMAX_DELAY); //设置阻塞时间,单位为节拍数
//返回值:返回当所等待的事件位置1以后的事件标志组的值,或者阻塞时间到。
// 根据这个值我们就知道哪些事件位置 1 了,如果函数因为阻塞时间到而返回的话那么这个返回值不代表任何的含义
printf("时间标志组的值:%d\r\n",EventValue);
LCD_ShowxNum(174,110,EventValue,1,16,0);
num++;
LED1=!LED1;
LCD_Fill(6,131,233,313,lcd_discolor[num%14]);
}
else
{
vTaskDelay(10); //延时10ms,也就是10个时钟节拍
}
}
}
//事件查询任务
//查询事件组的值,也就是各个事件位的值。获取到事件组值以后就将其显示到 LCD 上,并且也通过串口打印出来。
void eventquery_task(void *pvParameters)
{
u8 num=0;
EventBits_t NewValue,LastValue;
while(1)
{
if(EventGroupHandler!=NULL)
{
NewValue = xEventGroupGetBits(EventGroupHandler); //获取事件组的值
if(NewValue!=LastValue)
{
LastValue = NewValue;
printf("事件标志组的值:%d\r\n",NewValue);
LCD_ShowxNum(174,110,NewValue,1,16,0);
}
}
num++;
if(num==10) //每500msLED0闪烁一次
{
num=0;
LED0=!LED0;
}
vTaskDelay(50); //延时50ms,也就是50个时钟节拍
}
}
6.2 exti.c
#include "exti.h"
#include "delay.h"
#include "key.h"
#include "FreeRTOS.h"
#include "event_groups.h"
#define EVENTBIT_0 (1<<0) //此宏定义的意思是:1左移0位,也就是后续有定义EVENTBIT_0的话,直接就是宏定义这个值为1
//外部中断初始化程序
//初始化PE4为中断输入
void EXTIX_Init(void)
{
NVIC_InitTypeDef NVIC_InitStructure;
EXTI_InitTypeDef EXTI_InitStructure;
KEY_Init();
RCC_APB2PeriphClockCmd(RCC_APB2Periph_SYSCFG,ENABLE); //使能SYSCFG时钟
SYSCFG_EXTILineConfig(EXTI_PortSourceGPIOE,EXTI_PinSource4); //PE4连接到中断线4
//配置外部中断线4
EXTI_InitStructure.EXTI_Line = EXTI_Line4; //外部中断线4
EXTI_InitStructure.EXTI_LineCmd = ENABLE;
EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt; //中断事件
EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling; //下降沿触发
EXTI_Init(&EXTI_InitStructure);
NVIC_InitStructure.NVIC_IRQChannel = EXTI4_IRQn; //外部中断4
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //使能外部中断通道
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x06; //抢占优先级6
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x00; //子优先级0
NVIC_Init(&NVIC_InitStructure);
}
//事件标志组句柄
extern EventGroupHandle_t EventGroupHandler;
//外部中断4服务程序
void EXTI4_IRQHandler(void)
{
BaseType_t Result,xHigherPriorityTaskWoken;
delay_xms(20); //消抖
if(KEY0==0)
{
Result = xEventGroupSetBitsFromISR(EventGroupHandler,EVENTBIT_0,&xHigherPriorityTaskWoken); //在中断中设置事件标志组的值
//要操作的事件标志组的句柄
//指定要置1的事件位
//标记退出此函数以后是否进行任务切换
if(Result!=pdFAIL)
{
portYIELD_FROM_ISR(xHigherPriorityTaskWoken); //进行一次任务切换
}
}
EXTI_ClearITPendingBit(EXTI_Line4); //清除中断线4上的中断标志位
}
6.3 实验现象
通过上述程序,我们来认真的分析一下串口收到的实验现象:
首先,我们需要明确,串口标志组是为了一个任务和多个任务实现同步。多个任务体现在串口标志组是一个数组的形式,数组的每一位代表一个任务,同步体现在每个任务都实现,也就是串口标志组的所有位都为1时在执行后续功能,部分任务同步不会执行后续进程。按键0按下,会在外部中断线4将事件标志组的位0设置为1;按键1按下,将事件标志组的位1设置为1;按键2按下,将事件标志组的位2设置为1;
首先初始化事件标志组的值为0,按下按键0,事件标志组值变为1,这个时候再按下按键2,事件标志组的值会变为3,原因为 0000 0011,后两位为1,表示按键0和按键1控制的这两个任务实现了同步;同理,按键2按下,事件标志组值变为7,0000 0111,表示按键0、按键1和按键2控制的这三个事件实现了同步;这就控制了多个任务实现同步。
//等待事件组中相应的事件位
EventValue = xEventGroupWaitBits((EventGroupHandle_t )EventGroupHandler, //指定要等待的事件组句柄
(EventBits_t )EVENTBIT_ALL, //指定要等待的事件位,这里设置等待第一位、第二位和第三位
(BaseType_t )pdTRUE, //此参数要是为 pdTRUE 的话,那么在退出此函数之前由参数 uxBitsToWaitFor 所设置的这些事件位就会清零。
(BaseType_t )pdTRUE, //此参数如果设置为 pdTRUE 的话,当 uxBitsToWaitFor 所设置的这些事件位都置 1 ,或者指定的阻塞时间到的时候函数 xEventGroupWaitBits() 才会返回。
(TickType_t )portMAX_DELAY); //设置阻塞时间,单位为节拍数
//返回值:返回当所等待的事件位置1以后的事件标志组的值,或者阻塞时间到。
// 根据这个值我们就知道哪些事件位置 1 了,如果函数因为阻塞时间到而返回的话那么这个返回值不代表任何的含义
之所以当三个事件都实现时就会将事件标志组变为0,是因为上述函数的第三个参数,此参数设置为 pdTRUE 时,在退出此函数之前由参数 uxBitsToWaitFor 所设置的这个事件位就会清零。也就是说一旦实现了第二个参数设置的事件位,那么在退出整个函数之前,就会将之前设置的所有事件位都清零。