一、事件的基本概念
事件是一种实现任务间通信的机制,主要用于实现多任务的同步,但事件通信只能是事件类型之间的通信,无数据的传输。与信号量不同的是,它可以实现一对多,多对多的同步。当configUSE_16_bit_Ticks定义为0,那么uxEventBits是32位的,有24个位用来实现事件标志组。一对多同步类型:一个任务等待多个事件的触发,这种情况是比较常见的。多对多同步模型:多个任务等待多个事件的触发。
二、事件特点
1、事件只与任务相关联,事件相互独立,一个32位的事件集合(EventBits_t 类型的变量,实际可用与表示事件的只有24位),用于标识该任务的事件类型,其中每一位位表示一种事件类型(0表示该类型事件未发生、1表示该事件已经发生),一共24种事件类型。
2、事件仅用于同步,不提供数据传输功能。
3、事件无排队性,即多次向任务设置同一事件(如果任务还未来得及读走),等效于只设置一次。
4、允许多个任务对同一事件进行读写操作。
5、支持事件等待超时机制。
三、事件的应用场景
FreeRTOS 的事件用于事件类型的通讯,无数据传输,也就是说,我们可以用事件来做标志位,判断某些事件是否发生了,然后根据结果做处理,作用类型于我们在裸机编写中的标志位,我们通常把全局变量作为标志位,来进行信息的传输。但使用全局变量作为标志位的话,就需要任务中轮询查看事件是否发送,这对CPU资源是一种极大的浪费,因此在操作系统当中使用事件这种通信机制就能解决以上问题。在一些特定的场合,需要多个事件发生才能进行下一步的操作,比如一些复杂的机器,需要各项指标的检测,当有一个指标不通过便无法启动该机器,但是指标的检测不可能在同一时间全部检测完,检测需要分先后顺序,当一个指标检测通过就发送一个事件,而触发机器启动的条件就是所有指标都通过也就是需要集齐所有的指标发送的事件,那么机器才允许启动。
事件也可在一定程度上替代信号量的工作,用于任务于任务,任务与中断之间的同步,一个任务或中断服务例程发送一个事件给事件对象,而后等待的任务被唤醒并对相应的事件进行处理。与信号量有所区别的是,信号量是一个信号量对应一个任务,而事件可多个事件对应一个任务或多个任务,同时按照任务等待的参数,可选择是“逻辑或”触发还 是“逻辑与”触发。这个特性也是信号量等所不具备的,信号量只能识别单一同步动作, 而不能同时等待多个事件的同步。
各个事件可单独也可一起发送事件对象,而一个任务可等待多个事件,满足任务所需获得的事件,才启动任务,任务仅对感兴趣的事件进行关注。
四、事件的运作机制
接收事件时,任务可以选择自己感兴趣的事件进行接收,事件接收成功之后必须使用xClearOnExit选项来清除已接受的事件选项,否则不会清除接收到的事件,这样就需要用户手动清除事位。
用户可 以自 定义 通过 传入参 数 xWaitForAllBits 选择读取模式,是等待所有感兴趣的事件还是等待感兴趣的任意一个事件。
设置事件时,对指定事件写入指定的事件类型,设置事件集合的对应事件位为 1,可 以一次同时写多个事件类型,设置事件成功可能会触发任务调度。
清除事件时,根据入参数事件句柄和待清除的事件类型,对事件对应位进行清 0 操作。 事件不与任务相关联,事件相互独立,一个 32位的变量(事件集合,实际用于表示事 件的只有 24 位),用于标识该任务发生的事件类型,其中每一位表示一种事件类型(0 表 示该事件类型未发生、1表示该事件类型已经发生),一共 24种事件类型具体见图

事件唤醒机制,任务因等待某个事件或多个事件而进入阻塞态,当事件产生时才会被唤醒。任务1对事件3或事件5感兴趣,于是事件3产生时,任务一便会被唤醒,然后执行相应的操作。任务1对事件3和事件5都有兴趣,于是事件5产生时同样也会唤醒任务1,也就是逻辑或的关系。(这就相当于一个胃口小的小孩,吃饭或吃面都能让他吃饱)然而对于任务二来说,它对事件三和事件五也同样有兴趣,但是使它运行的条件是两个事件都有产生,当事件三产生时,任务1满足唤醒条件,任务2此时缺少另外一个事件5因此无法唤醒,所有当事件三产生后又产生事件5,这时两个任务都被唤醒,都相继运行对应的任务。(任务2相当与胃口大的小朋友,吃完饭还要吃面才能吃饱,“逻辑与”)

五、事件控制块

如果宏 configUSE_16_BIT_TICKS 定义为 1,那么变量uxEventBits为16位,其中8个位用来储存事件组,若configUSE_16_BIT_TICKS 定义为 0,那么变量uxEventBits为32位,其中24个位用来存储事件组,每一个位代表的一个事件的发生与否,利用逻辑或者逻辑与来做不同的唤醒处理。在STM32中,uxEventBits是32位的,所以我们有24个位来储存事件组。xTasksWaitingForBits为记录等待事件任务的链表,所有等待事件的任务都会被挂载在该链表上。
六、事件常用API函数
1.事件创建函数 xEventGroupCreate()
当我们使用xEventGroupCreate()创建事件时,因为我们采用的是动态分配内存的方式,所有系统会给我们分配事件控制块的内存空间,然后对该事件控制块进行初始化,若创建成功则返回任务句柄,失败则返回NULL。创建我们需要先定义这个事件的句柄。
/* 创建一个事件组,返回它的句柄。
* 此函数内部会分配事件组结构体
* 返回值: 返回句柄,非NULL表示成功
*/
EventGroupHandle_t xEventGroupCreate( void );
/* 创建一个事件组,返回它的句柄。
* 此函数无需动态分配内存,所以需要先有一个StaticEventGroup_t结构体,并传入它的指针
* 返回值: 返回句柄,非NULL表示成功
*/
EventGroupHandle_t xEventGroupCreateStatic( StaticEventGroup_t * pxEventGroupBuffer );
2.事件删除函数 vEventGroupDelete()
只有创建成功的事件才能被删除,该函数不能用于中断当中,当该事件组被删除之后,所有等待该事件的任务都会被解锁,并向等待事件的任务返回事件组的值为0。
/*
* xEventGroup: 事件组句柄,你要删除哪个事件组
*/
void vEventGroupDelete( EventGroupHandle_t xEventGroup )
3.事件组置位函数 xEventGroupSetBits()
xEventGroupSetBits()用于置位事件组中指定的位,当位被置位之后,原本等待该位上的任务就会被唤醒。简单来说,就是设置我们自己定义的事件标志位 为 1,并且看看有没有任务在等待这个事件,有的话就唤醒它。

xEventGroupSetBits()运用实例:
#define KEY1_EVENT (0x01 << 0)//设置事件掩码的位0
#define KEY2_EVENT (0x01 << 1)//设置事件掩码的位1
static EventGroupHandle_t Event_Handle =NULL;
Event_Handle = xEventGroupCreate();
if(NULL != Event_Handle)
printf("Event_Handle 事件创建成功!\r\n");
static void KEY_Task(void* parameter)
{
/* 任务都是一个无限循环,不能返回 */
while (1)
{
if( Key_Scan(KEY1_GPIO_PORT,KEY1_GPIO_PIN) == KEY_ON ) //如果KEY2被单击
{
printf ( "KEY1被按下\n" );
/* 触发一个事件1 */
xEventGroupSetBits(Event_Handle,KEY1_EVENT);
}
if( Key_Scan(KEY2_GPIO_PORT,KEY2_GPIO_PIN) == KEY_ON ) //如果KEY2被单击
{
printf ( "KEY2被按下\n" );
/* 触发一个事件2 */
xEventGroupSetBits(Event_Handle,KEY2_EVENT);
}
vTaskDelay(20); //每20ms扫描一次
}
}
4.等待事件函数 xEventGroupWaitBits()
xEventGroupWaitBits(),通过这个函数任务可以查看事件组的标志位,有什么事件发生,然后通过逻辑或和逻辑与来获取自己感兴趣的事件,并且因为等待超时机制,若设置阻塞时间为最大值,当且仅当事件发生,任务才能获取到事件,若事件一直没有发生,则任务就一直处于阻塞状态来等待事件的发生,若其它任务或中断往其等待的事件设置标志位,则该任务获取到事件由阻塞态转为就绪态。若当阻塞设置为一段时间,在这段时间内,任务没有等待该事件,任务也从阻塞态转为就绪态。这样子很有效的体现了操作系统的实时性,如果事件正确获取(等待到)则 返回对应的事件标志位,由用户判断再做处理,因为在事件超时的时候也会返回一个不能确定的事件值,所以需要判断任务所等待的事件是否真的发生。

EventGroupWaitBits()运用实例:
static void LED_Task(void* parameter)
{
EventBits_t r_event; /* 定义一个事件接收变量 */
/* 任务都是一个无限循环,不能返回 */
while (1)
{
/*******************************************************************
* 等待接收事件标志
*
* 如果xClearOnExit设置为pdTRUE,那么在xEventGroupWaitBits()返回之前,
* 如果满足等待条件(如果函数返回的原因不是超时),那么在事件组中设置
* 的uxBitsToWaitFor中的任何位都将被清除。
* 如果xClearOnExit设置为pdFALSE,
* 则在调用xEventGroupWaitBits()时,不会更改事件组中设置的位。
*
* xWaitForAllBits如果xWaitForAllBits设置为pdTRUE,则当uxBitsToWaitFor中
* 的所有位都设置或指定的块时间到期时,xEventGroupWaitBits()才返回。
* 如果xWaitForAllBits设置为pdFALSE,则当设置uxBitsToWaitFor中设置的任何
* 一个位置1 或指定的块时间到期时,xEventGroupWaitBits()都会返回。
* 阻塞时间由xTicksToWait参数指定。
*********************************************************/
r_event = xEventGroupWaitBits(Event_Handle, /* 事件对象句柄 */
KEY1_EVENT|KEY2_EVENT,/* 接收线程感兴趣的事件 */
pdTRUE, /* 退出时清除事件位 */
pdTRUE, /* 满足感兴趣的所有事件 */
portMAX_DELAY);/* 指定超时事件,一直等 */
if((r_event & (KEY1_EVENT|KEY2_EVENT)) == (KEY1_EVENT|KEY2_EVENT))
{
/* 如果接收完成并且正确 */
printf ( "KEY1与KEY2都按下\n");
LED1_TOGGLE; //LED1 反转
}
else
printf ( "事件错误!\n");
}
}
5、xEventGroupClearBits()与 xEventGroupClearBitsFromISR()
xEventGroupClearBits()用与清除事件组指定的位,xEventGroupClearBitsFromISR()用于中断当中清除事件组指定的位。

#define BIT_0 ( 1 << 0 )
#define BIT_4 ( 1 << 4 )
void aFunction( EventGroupHandle_t xEventGroup )
{
EventBits_t uxBits;
/* 清楚事件组的 bit 0 and bit 4 */
uxBits = xEventGroupClearBits(
xEventGroup,
BIT_0 | BIT_4 );
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 已经置位
但是现在是被清除了*/
} else {
/* 在调用 xEventGroupClearBits()之前 bit0 和 bit4 都没被置位 */
}
}
七、事件实验
该实验创建了两个任务,一个设置事件任务,另一个等待事件任务,两个任务独立运行,设置事件通过检测按键按下的情况来设置不同的事件标志位,等待事件任务就是获取这两个事件标志位,并且判断两个事件是否都产生,如果都产生则对LED的状态进行翻转。
#include "system.h"
#include "SysTick.h"
#include "led.h"
#include "usart.h"
#include "FreeRTOS.h"
#include "task.h"
#include "key.h"
#include "event_groups.h"
//任务优先级
#define START_TASK_PRIO 1
//任务堆栈大小
#define START_STK_SIZE 128
//任务句柄
TaskHandle_t StartTask_Handler;
//任务函数
void start_task(void *pvParameters);
//任务优先级
#define LED1_TASK_PRIO 2
//任务堆栈大小
#define LED1_STK_SIZE 50
//任务句柄
TaskHandle_t LED1Task_Handler;
//任务函数
void led1_task(void *pvParameters);
//任务优先级
#define LED2_TASK_PRIO 3
//任务堆栈大小
#define LED2_STK_SIZE 50
//任务句柄
TaskHandle_t LED2Task_Handler;
//任务函数
void led2_task(void *pvParameters);
//任务优先级
#define KEY_TASK_PRIO 4
//任务堆栈大小
#define KEY_STK_SIZE 512
//任务句柄
TaskHandle_t KEYTask_Handler;
//任务函数
void key_task(void *pvParameters);
EventGroupHandle_t Event_Handle =NULL;
#define KEY1_EVENT (0x01 << 0)//设置事件掩码的位0
#define KEY2_EVENT (0x01 << 1)//设置事件掩码的位1
/*******************************************************************************
* 函 数 名 : main
* 函数功能 : 主函数
* 输 入 : 无
* 输 出 : 无
*******************************************************************************/
int main()
{
SysTick_Init(72);
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);//设置系统中断优先级分组4
LED_Init();
KEY_Init();
USART1_Init(115200);
printf("FreeRTOS事件标志组实验\r\n");
//创建开始任务
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(); //进入临界区
/* 创建 Event_Handle */
Event_Handle = xEventGroupCreate();
//创建LED1任务
xTaskCreate((TaskFunction_t )led1_task,
(const char* )"led1_task",
(uint16_t )LED1_STK_SIZE,
(void* )NULL,
(UBaseType_t )LED1_TASK_PRIO,
(TaskHandle_t* )&LED1Task_Handler);
//创建LED2任务
xTaskCreate((TaskFunction_t )led2_task,
(const char* )"led2_task",
(uint16_t )LED2_STK_SIZE,
(void* )NULL,
(UBaseType_t )LED2_TASK_PRIO,
(TaskHandle_t* )&LED2Task_Handler);
//创建KEY任务
xTaskCreate((TaskFunction_t )key_task,
(const char* )"key_task",
(uint16_t )KEY_STK_SIZE,
(void* )NULL,
(UBaseType_t )KEY_TASK_PRIO,
(TaskHandle_t* )&KEYTask_Handler);
vTaskDelete(StartTask_Handler); //删除开始任务
taskEXIT_CRITICAL(); //退出临界区
}
//LED1任务函数
void led1_task(void *pvParameters)
{
while(1)
{
LED1=0;
vTaskDelay(200);
LED1=1;
vTaskDelay(800);
}
}
//LED2任务函数
void led2_task(void *pvParameters)
{
EventBits_t r_event; /* 定义一个事件接收变量 */
while(1)
{
r_event = xEventGroupWaitBits(Event_Handle, /* 事件对象句柄 */
KEY1_EVENT|KEY2_EVENT,/* 接收线程感兴趣的事件 */
pdTRUE, /* 退出时清除事件位 */
pdTRUE, /* 满足感兴趣的所有事件 */
portMAX_DELAY);/* 指定超时事件,一直等 */
if((r_event & (KEY1_EVENT|KEY2_EVENT)) == (KEY1_EVENT|KEY2_EVENT))
{
printf ( "KEY1与KEY2都按下\n");
LED2=!LED2;
}
else
printf ("事件错误!\n");
}
}
//KEY任务函数
void key_task(void *pvParameters)
{
u8 key=0;
while(1)
{
key=KEY_Scan(0);
if(key==KEY1_PRESS)
{
printf ( "KEY1被按下\n" );
/* 触发一个事件1 */
xEventGroupSetBits(Event_Handle,KEY1_EVENT);
}
else if(key==KEY2_PRESS)
{
printf ( "KEY2被按下\n" );
/* 触发一个事件2 */
xEventGroupSetBits(Event_Handle,KEY2_EVENT);
}
vTaskDelay(20);
}
}
实验现象 :

1223

被折叠的 条评论
为什么被折叠?



