FreeRTOS菜鸟入门(十四)·事件
目录
1. 基本概念
2. 应用场景
3. 运作机制
4. 控制块
5. 事件函数接口
5.1 事件创建函数 xEventGroupCreate()
5.2 事件删除函数 vEventGroupDelete()
5.3 事件组置位函数 xEventGroupSetBits()(非中断)
5.4 事件组置位函数 xEventGroupSetBitsFromISR()(中断)
5.5 等待事件函数 xEventGroupWaitBits()
5.6 xEventGroupClearBits()与 xEventGroupClearBitsFromISR()
1. 基本概念
事件是一种实现任务间通信的机制,主要用于实现多任务间的同步,但事件通信只能是事件类型的通信,无数据传输。与信号量不同的是,它可以实现一对多,多对多的同步。即一个任务可以等待多个事件的发生:可以是任意一个事件发生时唤醒任务进行事件处理;也可以是几个事件都发生后才唤醒任务进行事件处理。同样,也可以是多个任务同步多个事件。
每一个事件组只需要很少的 RAM 空间来保存事件组的状态。事件组存储在一个 EventBits_t 类 型 的变量中 , 该变量在事件组结构体中定义 。
- 如果宏 configUSE_16_BIT_TICKS 定义为 1,那么变量 uxEventBits 就是 16 位的,其中有 8 个位用来存储事件组;
- 如果宏 configUSE_16_BIT_TICKS 定义为 0,那么变量 uxEventBits 就是 32 位的 , 其中有 24 个位用来存储事件组 。
在 STM32 中 , 我们一般将 configUSE_16_BIT_TICKS 定义为 0,那么 uxEventBits 是 32 位的,有 24 个位用来实现事件标志组。每一位代表一个事件,任务通过“逻辑与”或“逻辑或”与一个或多个事件建立关联,形成一个事件组。
- 事件的“逻辑或”也被称作是独立型同步,指的是任务感兴趣的所有事件任一件发生即可被唤醒;
- 事件“逻辑与”则被称为是关联型同步,指的是任务感兴趣的若干事件都发生时才被唤醒,并且事件发生的时间可以不同步。
多任务环境下,任务、中断之间往往需要同步操作,一个事件发生会告知等待中的任务,即形成一个任务与任务、中断与任务间的同步。事件可以提供一对多、多对多的同步操作。一对多同步模型:一个任务等待多个事件的触发,这种情况是比较常见的;多对多同步模型:多个任务等待多个事件的触发。
任务可以通过设置事件位来实现事件的触发和等待操作。FreeRTOS 的事件仅用于同步,不提供数据传输功能。
FreeRTOS 提供的事件具有如下特点:
- 事件只与任务相关联,事件相互独立,一个 32 位的事件集合(EventBits_t 类型的变量,实际可用与表示事件的只有 24位),用于标识该任务发生的事件类型,其中每一位表示一种事件类型(0 表示该事件类型未发生、1 表示该事件类型已经发生),一共 24 种事件类型。
- 事件仅用于同步,不提供数据传输功能。
- 事件无排队性,即多次向任务设置同一事件(如果任务还未来得及读走),等效于只设置一次。
- 允许多个任务对同一事件进行读写操作。
- 支持事件等待超时机制。
2. 应用场景
比如一些危险机器的启动,需要检查各项指标,当指标不达标的时候,无法启动,但是检查各个指标的时候,不能一下子检测完毕啊,所以,需要事件来做统一的等待,当所有的事件都完成了,那么机器才允许启动,这只是事件的其中一个应用。
事件可使用于多种场合,它能够在一定程度上替代信号量,用于任务与任务间,中断与任务间的同步。一个任务或中断服务例程发送一个事件给事件对象,而后等待的任务被唤醒并对相应的事件进行处理。但是它与信号量不同的是,事件的发送操作是不可累计的,而信号量的释放动作是可累计的。事件另外一个特性是,接收任务可等待多种事件,即多个事件对应一个任务或多个任务。同时按照任务等待的参数,可选择是“逻辑或”触发还是“逻辑与”触发。这个特性也是信号量等所不具备的,信号量只能识别单一同步动作,而不能同时等待多个事件的同步。
3. 运作机制
事件接收成功后,必须使用 xClearOnExit 选项来清除已接收到的事件类型,否则不会清除已接收到的事件,这样就需要用户显式清除事件位。 用户可以自定义通过传入参数 xWaitForAllBits 选择读取模式,是等待所有感兴趣的事件还是等待感兴趣的任意一个事件。
设置事件时,对指定事件写入指定的事件类型,设置事件集合的对应事件位为 1,可以一次同时写多个事件类型,设置事件成功可能会触发任务调度。
清除事件时,根据入参数事件句柄和待清除的事件类型,对事件对应位进行清 0 操作。
事件不与任务相关联,事件相互独立,一个 32位的变量(事件集合,实际用于表示事件的只有 24 位),用于标识该任务发生的事件类型,其中每一位表示一种事件类型(0 表示该事件类型未发生、1表示该事件类型已经发生),一共 24种事件类型具体见图:
事件唤醒机制,当任务因为等待某个或者多个事件发生而进入阻塞态,当事件发生的时候会被唤醒:
任务 1 对事件 3 或事件 5 感兴趣(逻辑或),当发生其中的某一个事件都会被唤醒,并且执行相应操作。
而任务 2 对事件 3 与事件 5 感兴趣(逻辑与),当且仅当事件 3 与事件 5 都发生的时候,任务 2 才会被唤醒,如果只有一个其中一个事件发生,那么任务还是会继续等待事件发生。如果接在收事件函数中设置了清除事件位 xClearOnExit,那么当任务唤醒后将把事件 3 和事件 5 的事件标志清零,否则事件标志将依然存在。
4. 控制块
事件标志组存储在一个 EventBits_t 类型的变量中,该变量在事件组结构体中定义:
typedef struct xEventGroupDefinition {EventBits_t uxEventBits; /* 当前事件标志位(32-bit变量) */List_t xTasksWaitingForBits; /* 等待事件位的任务链表 */#if (configUSE_TRACE_FACILITY == 1)UBaseType_t uxEventGroupNumber; /* 调试用的事件组编号 */
#endif#if ( (configSUPPORT_STATIC_ALLOCATION == 1) && \(configSUPPORT_DYNAMIC_ALLOCATION == 1) )uint8_t ucStaticallyAllocated; /* 标识是否静态分配:pdTRUE/pdFALSE */
#endif
} EventGroup_t;
- 如果宏 configUSE_16_BIT_TICKS 定义为 1,那么变量 uxEventBits 就是 16 位的,其中有 8 个位用来存储事件组;
- 如果宏 configUSE_16_BIT_TICKS 定义为 0,那么变量 uxEventBits 就是 32 位的 , 其中有 24 个位用来存储事件组 。
5. 事件函数接口
5.1 事件创建函数 xEventGroupCreate()
xEventGroupCreate()用于创建一个事件组,并返回对应的句柄。要想使用该函数必须在头文件 FreeRTOSConfig.h 定义宏 configSUPPORT_DYNAMIC_ALLOCATION 为 1(在FreeRTOS.h 中默认定义为 1)且需要把 FreeRTOS/source/event_groups.c 这个 C 文件添加到工程中。
- 如果使用函数 xEventGroupCreate()来创建一个事件,那么需要的 RAM 是动态分配的。
- 如果使用函数xEventGroupCreateStatic()来创建一个事件,那么需要的 RAM 是静态分配的。
xEventGroupCreate()源码:
#if (configSUPPORT_DYNAMIC_ALLOCATION == 1)EventGroupHandle_t xEventGroupCreate(void)
{EventGroup_t *pxEventBits;/* 动态分配事件控制块内存 */pxEventBits = (EventGroup_t *)pvPortMalloc(sizeof(EventGroup_t)); // (1)if (pxEventBits != NULL) { // (2)/* 初始化事件标志位 */pxEventBits->uxEventBits = 0;/* 初始化等待任务链表 */vListInitialise(&(pxEventBits->xTasksWaitingForBits));#if (configSUPPORT_STATIC_ALLOCATION == 1){/* 标记为动态分配(与静态分配区分) */pxEventBits->ucStaticallyAllocated = pdFALSE; // (3)}#endiftraceEVENT_GROUP_CREATE(pxEventBits); // (4)} else {traceEVENT_GROUP_CREATE_FAILED(); // (5)}return (EventGroupHandle_t)pxEventBits; // (6)
}#endif /* configSUPPORT_DYNAMIC_ALLOCATION */
关键点 | 说明 |
---|---|
(1) 内存分配 | 使用FreeRTOS内存管理函数pvPortMalloc 动态分配EventGroup_t 结构体内存 |
(2) 空指针检查 | 验证内存是否分配成功 |
(3) 静态分配标记 | 当同时启用静态分配时,明确标记该实例为动态分配 |
(4) 追踪钩子 | 调试用追踪宏,记录创建成功事件 |
(5) 失败追踪 | 记录内存分配失败事件 |
(6) 类型转换 | 将内部结构体指针转换为对外句柄类型 |
举个例子,这里我们以之前创建的空白模版为例,在其中进行更改:
基于STM32F1系列移植FreeRTOS模版资源-CSDN文库
首先进行头文件包含:
#include "event_groups.h"
并且在头文件 FreeRTOSConfig.h 定义宏 configSUPPORT_DYNAMIC_ALLOCATION 为 1,也就是动态内存分配,我们已经设成1了因此不用动:
在使用创建函数前我们需要有一个事件的句柄,以便后续访问:
static EventGroupHandle_t Event_Handle =NULL;
然后在任务创建函数AppTaskCreate()调用事件创建函数:
//任务创建函数
static void AppTaskCreate(void)
{BaseType_t xReturn = pdPASS;/* 定义一个创建信息返回值,默认为pdPASS */taskENTER_CRITICAL(); //进入临界区/* 创建 Event_Handle */Event_Handle = xEventGroupCreate(); if(NULL != Event_Handle)printf("Event_Handle 事件创建成功!\r\n"); elseprintf("Event_Handle 事件创建失败!\r\n"); vTaskDelete(AppTaskCreate_Handle); //删除AppTaskCreate任务taskEXIT_CRITICAL(); //退出临界区
}
完整main函数代码:
#include "stm32f10x.h" // Device header
#include "Delay.h"#include "LED.h"
#include "Usart.h"
#include "Key.h" /* FreeRTOS头文件 */
#include "FreeRTOS.h"
#include "task.h"
#include "event_groups.h"/* * 任务句柄是一个指针,用于指向一个任务,当任务创建好之后,它就具有了一个任务句柄* 以后我们要想操作这个任务都需要通过这个任务句柄,如果是自身的任务操作自己,那么* 这个句柄可以为NULL。*/
static TaskHandle_t AppTaskCreate_Handle = NULL; /* 创建任务句柄 *//********************************** 内核对象句柄 *********************************/
/** 信号量,消息队列,事件标志组,软件定时器这些都属于内核的对象,要想使用这些内核* 对象,必须先创建,创建成功之后会返回一个相应的句柄。实际上就是一个指针,后续我* 们就可以通过这个句柄操作这些内核对象。** 内核对象说白了就是一种全局的数据结构,通过这些数据结构我们可以实现任务间的通信,* 任务间的事件同步等各种功能。至于这些功能的实现我们是通过调用这些内核对象的函数* 来完成的* */
static EventGroupHandle_t Event_Handle =NULL;/******************************* 宏定义 ************************************/
/** 当我们在写应用程序的时候,可能需要用到一些宏定义。*///一些函数声明
static void AppTaskCreate(void);/* 用于创建任务 */
static void All_Function_Init(void);/* 用于初始化板载相关资源 */int main(void)
{BaseType_t xReturn = pdPASS;/* 定义一个创建信息返回值,默认为pdPASS */All_Function_Init();//硬件初始化while (1){/* 创建AppTaskCreate任务 */xReturn = xTaskCreate((TaskFunction_t )AppTaskCreate, /* 任务入口函数 */(const char* )"AppTaskCreate",/* 任务名字 */(uint16_t )512, /* 任务栈大小 */(void* )NULL,/* 任务入口函数参数 */(UBaseType_t )1, /* 任务的优先级 */(TaskHandle_t* )&AppTaskCreate_Handle);/* 任务控制块指针 */ /* 启动任务调度 */ if(pdPASS == xReturn)vTaskStartScheduler(); /* 启动任务,开启调度 */elsereturn -1; }
}//任务创建函数
static void AppTaskCreate(void)
{BaseType_t xReturn = pdPASS;/* 定义一个创建信息返回值,默认为pdPASS */taskENTER_CRITICAL(); //进入临界区/* 创建 Event_Handle */Event_Handle = xEventGroupCreate(); if(NULL != Event_Handle)printf("Event_Handle 事件创建成功!\r\n"); elseprintf("Event_Handle 事件创建失败!\r\n"); vTaskDelete(AppTaskCreate_Handle); //删除AppTaskCreate任务taskEXIT_CRITICAL(); //退出临界区
}//初始化声明
static void All_Function_Init(void)
{/** STM32中断优先级分组为4,即4bit都用来表示抢占优先级,范围为:0~15* 优先级分组只需要分组一次即可,以后如果有其他的任务需要用到中断,* 都统一用这个优先级分组,千万不要再分组,切忌。*/NVIC_PriorityGroupConfig( NVIC_PriorityGroup_4 );/* LED 初始化 */LED_GPIO_Config();/* 串口初始化 */USART_Config();//按键初始化Key_GPIO_Config();}
运行结果:
完整工程:
FreeRTOS事件-创建函数.zip资源-CSDN文库
5.2 事件删除函数 vEventGroupDelete()
在很多场合,某些事件只用一次的,就好比在事件应用场景说的危险机器的启动,假如各项指标都达到了,并且机器启动成功了,那这个事件之后可能就没用了,那就可以进行销毁了。想要删除事件怎么办?FreeRTOS 给我们提供了一个删除事件的函数——vEventGroupDelete(),使用它就能将事件进行删除了。当系统不再使用事件对象时,可以通过删除事件对象控制块来释放系统资源,vEventGroupDelete()源码:
/*-----------------------------------------------------------*/
void vEventGroupDelete(EventGroupHandle_t xEventGroup)
{EventGroup_t *pxEventBits = (EventGroup_t *)xEventGroup;const List_t *pxTasksWaitingForBits = &(pxEventBits->xTasksWaitingForBits);vTaskSuspendAll(); /* (1) 挂起所有任务,因为接下来的操作不知道需要多长的时间,并且在删除的时候,不希望其他任务来操作这个事件标志组,所以暂时把调度器挂起,让当前任务占有 CPU。 */{traceEVENT_GROUP_DELETE(xEventGroup);/* (2) 处理所有等待该事件的任务 */while (listCURRENT_LIST_LENGTH(pxTasksWaitingForBits) > (UBaseType_t)0) {configASSERT(pxTasksWaitingForBits->xListEnd.pxNext != (ListItem_t *)&(pxTasksWaitingForBits->xListEnd));/* (3) 将任务移出事件等待列表 */(void)xTaskRemoveFromUnorderedEventList(pxTasksWaitingForBits->xListEnd.pxNext,eventUNBLOCKED_DUE_TO_BIT_SET);}#if ((configSUPPORT_DYNAMIC_ALLOCATION == 1) && \(configSUPPORT_STATIC_ALLOCATION == 0)){/* (4) 释放动态分配的内存 */vPortFree(pxEventBits);}#endif/* 静态分配处理部分已省略 */}(void)xTaskResumeAll(); /* (5) 恢复任务调度 */
}
/*-----------------------------------------------------------*/
vEventGroupDelete()用于删除由函数 xEventGroupCreate()创建的事件组,只有被创建成功的事件才能被删除,但是需要注意的是该函数不允许在中断里面使用。当事件组被删除之后,阻塞在该事件组上的任务都会被解锁,并向等待事件的任务返回事件组的值为 0。
这里在之前代码的基础上对AppTaskCreate()进行修改,其实只要添加一句话就够了:
vEventGroupDelete(Event_Handle);
不过为了更容易看到实验现象,增加一些验证:
Event_Handle = NULL; // 手动置空句柄// 验证是否删除if (Event_Handle == NULL) {printf("事件组已删除!\r\n");} else {printf("事件组删除失败!\r\n");}
如果事件组已被删除,再次操作(如 xEventGroupSetBits()下面会讲,这里先使用验证一下)会导致内存错误或断言失败:
// 尝试操作已删除的事件组(验证是否崩溃)xEventGroupSetBits(Event_Handle, 0x01); // 如果已删除,可能会触发断言或 HardFault
完整更改代码:
//任务创建函数
static void AppTaskCreate(void)
{BaseType_t xReturn = pdPASS;/* 定义一个创建信息返回值,默认为pdPASS */taskENTER_CRITICAL(); //进入临界区/* 创建 Event_Handle */Event_Handle = xEventGroupCreate(); if(NULL != Event_Handle){printf("Event_Handle 事件创建成功!\r\n"); vEventGroupDelete(Event_Handle);Event_Handle = NULL; // 手动置空句柄// 验证是否删除if (Event_Handle == NULL) {printf("事件组已删除!\r\n");} else {printf("事件组删除失败!\r\n");}// 尝试操作已删除的事件组(验证是否崩溃)xEventGroupSetBits(Event_Handle, 0x01); // 如果已删除,可能会触发断言或 HardFault}elseprintf("Event_Handle 事件创建失败!\r\n"); vTaskDelete(AppTaskCreate_Handle); //删除AppTaskCreate任务taskEXIT_CRITICAL(); //退出临界区
}
运行结果,会发现事件删除后,再去调用会出现错误:
完整工程:
FreeRTOS事件-删除函数.zip资源-CSDN文库
5.3 事件组置位函数 xEventGroupSetBits()(非中断)
xEventGroupSetBits()用于置位事件组中指定的位,当位被置位之后,阻塞在该位上的任务将会被解锁。使用该函数接口时,通过参数指定的事件标志来设定事件的标志位,然后遍历等待在事件对象上的事件等待列表,判断是否有任务的事件激活要求与当前事件对象标志值匹配,如果有,则唤醒该任务。
简单来说,就是设置我们自己定义的事件标志位为 1,并且看看有没有任务在等待这个事件,有的话就唤醒它。
注意的是该函数不允许在中断中使用。
函数原型 | EventBits_t xEventGroupSetBits( EventGroupHandle_t xEventGroup, const EventBits_t uxBitsToSet); | |
功能 | 置位事件组中指定的位。 | |
参数 | xEventGroup | 事件句柄。 |
uxBits ToSet | 指定事件中的事件标志位。如设置uxBitsToSet为0x08则只置位位3,如果设置uxBitsToSet为0x09则位3和位0都需要被置位。 | |
返回值 | 返回调用xEventGroupSetBits()时事件组中的值。 |
函数源码:
/*-----------------------------------------------------------*/
EventBits_t xEventGroupSetBits(EventGroupHandle_t xEventGroup,const EventBits_t uxBitsToSet)
{ListItem_t *pxListItem, *pxNext;ListItem_t const *pxListEnd;List_t *pxList;EventBits_t uxBitsToClear = 0, uxBitsWaitedFor, uxControlBits;EventGroup_t *pxEventBits = (EventGroup_t *)xEventGroup;BaseType_t xMatchFound = pdFALSE;/* 参数有效性检查 */configASSERT(xEventGroup);configASSERT((uxBitsToSet & eventEVENT_BITS_CONTROL_BYTES) == 0); /* (1) */pxList = &(pxEventBits->xTasksWaitingForBits);pxListEnd = listGET_END_MARKER(pxList);vTaskSuspendAll(); /* (2) 挂起所有任务 */{traceEVENT_GROUP_SET_BITS(xEventGroup, uxBitsToSet);/* 设置事件标志位 */pxEventBits->uxEventBits |= uxBitsToSet; /* (3)根据用户指定的 uxBitsToSet 设置事件标志位。 *//* 遍历等待事件的任务列表 */pxListItem = listGET_HEAD_ENTRY(pxList);while (pxListItem != pxListEnd) { /* (4)设置这个事件标志位可能是某个任务在等待的事件,就需要遍历等待事件列表中的任务,看看这个事件是否与任务等待的事件匹配。 */pxNext = listGET_NEXT(pxListItem);uxBitsWaitedFor = listGET_LIST_ITEM_VALUE(pxListItem);xMatchFound = pdFALSE;/* 解析等待条件,是逻辑与还是逻辑或 */uxControlBits = uxBitsWaitedFor & eventEVENT_BITS_CONTROL_BYTES; /* (5) 获取要等待事件的标记信息,是逻辑与还是逻辑或*/uxBitsWaitedFor &= ~eventEVENT_BITS_CONTROL_BYTES; /* (6) 再获取任务的等待事件是什么*//* 检查事件匹配条件 */if ((uxControlBits & eventWAIT_FOR_ALL_BITS) == (EventBits_t)0) { /* (7) *//* 任意位匹配模式 */if ((uxBitsWaitedFor & pxEventBits->uxEventBits) != (EventBits_t)0) {xMatchFound = pdTRUE; /* (8) 判断要等待的事件是否发生了,发生了就需要把任务恢复,在这里记录一下要恢复的任务。*/} else {mtCOVERAGE_TEST_MARKER();}}else if ((uxBitsWaitedFor & pxEventBits->uxEventBits) == uxBitsWaitedFor) { /* (9) 如果任务等待的事件都要发生的时候(也是我们常说的“逻辑与”),就需要就要所有判断事件标志组中的事件是否都发生,如果是的话任务才能从阻塞中恢复,同样也需要标记一下要恢复的任务。*//* 全部位匹配模式 */xMatchFound = pdTRUE;} else {/* 不满足全部位条件 */ /* (10) */}/* 处理匹配成功的任务 */if (xMatchFound != pdFALSE) { /* (11) *//* 检查是否需要清除事件位 */if ((uxControlBits & eventCLEAR_EVENTS_ON_EXIT_BIT) != (EventBits_t)0) {uxBitsToClear |= uxBitsWaitedFor; /* (12)运用或运算,标记一下要清除的事件标志位是哪些。 */} else {mtCOVERAGE_TEST_MARKER();}/* 将任务移出等待列表 */(void)xTaskRemoveFromUnorderedEventList(pxListItem,pxEventBits->uxEventBits | eventUNBLOCKED_DUE_TO_BIT_SET); /* (13) 将满足事件条件的任务从等待列表中移除,并且添加到就绪列表*/}pxListItem = pxNext; /* (14) 移至下一任务 */}/* 清除需要复位的事件位 */pxEventBits->uxEventBits &= ~uxBitsToClear; /* (15) */}(void)xTaskResumeAll(); /* (16) 恢复任务调度 */return pxEventBits->uxEventBits; /* (17) 返回当前事件位 */
}
/*-----------------------------------------------------------*/
(1):断言,判断要设置的事件标志位是否有效,因为一个 32 位的事件标志组变量只有 24 位是用于设置事件的,而 16 位的事件标志组变量只有 8 位用于设置事件,高 8 位不允许设置事件,有其他用途,事件组高8位的用途:
#if configUSE_16_BIT_TICKS == 1
#define eventCLEAR_EVENTS_ON_EXIT_BIT 0x0100U
#define eventUNBLOCKED_DUE_TO_BIT_SET 0x0200U
#define eventWAIT_FOR_ALL_BITS 0x0400U
#define eventEVENT_BITS_CONTROL_BYTES 0xff00U
#else
#define eventCLEAR_EVENTS_ON_EXIT_BIT 0x01000000UL
#define eventUNBLOCKED_DUE_TO_BIT_SET 0x02000000UL
#define eventWAIT_FOR_ALL_BITS 0x04000000UL
#define eventEVENT_BITS_CONTROL_BYTES 0xff000000UL
#endif
(2):挂起调度器,因为接下来的操作不知道需要多长的时间,因为需要遍历等待事件列表,并且有可能不止一个任务在等待事件,所以在满足任务等待的事件时候,任务允许被恢复,但是不允许运行,只有遍历完成的时候,任务才能被系统调度,在遍历期间,系统也不希望其他任务来操作这个事件标志组,所以暂时把调度器挂起,让当前任务占有 CPU。
举个例子,主要完成逻辑,判断KEY1还是KEY2按下,如果KEY1按下设置KEY1_EVENT,如果KEY2按下设置KEY2_EVENT,其他任务可以等待这两个事件的发生在进行操作(事件的等待函数下面会将,这里主要熟悉事件组置位函数的使用):
下面我们在5.1任务创建代码的基础上进行更改,首先由于我们需要用的按键,那就在创建一个按键的任务,开始创建按键任务的句柄:
static TaskHandle_t KEY_Task_Handle = NULL;/* KEY_Task任务句柄 */
我们知道,事件组置位函数 xEventGroupSetBits()有两个参数,一个是事件句柄,这个我们在创建函数的时候已经声明过了不需要再进行声明,第二个参数是指定事件中的事件标志位,这里我们用了两个事件按键KEY1按下和按键KEY2按下,那么我们给他们创建两个宏定义(直接写可以,不过为了方便维护,我们宏定义的方式展现出来):
#define KEY1_EVENT (0x01 << 0)//设置事件掩码的位0
#define KEY2_EVENT (0x01 << 1)//设置事件掩码的位1
说明一下,不计入实际代码工程当中:
数值(十进制) | 二进制位 | 等效宏定义(若存在) |
---|---|---|
1 | 0b0001 | (0x01 << 0) |
2 | 0b0010 | (0x01 << 1) |
4 | 0b0100 | (0x01 << 2) |
8 | 0b1000 | (0x01 << 3) |
假设我们不写宏定义,需要写入的形式:
xEventGroupSetBits(Event_Handle, 0x01); // 等价于设置bit0(最低位)
xEventGroupSetBits(Event_Handle, 0x02); // 等价于设置bit1
xEventGroupSetBits(Event_Handle, 0x03); // 同时设置bit0和bit1(1 | 2)
引入宏定义:
#define KEY1_EVENT (0x01 << 0)//设置事件掩码的位0
#define KEY2_EVENT (0x01 << 1)//设置事件掩码的位1xEventGroupSetBits(Event_Handle,KEY1_EVENT);
xEventGroupSetBits(Event_Handle,KEY2_EVENT);
xEventGroupSetBits(Event_Handle, KEY1_EVENT | KEY2_EVENT);
也可以:
#define KEY1_EVENT (0x01 << 0)
#define KEY2_EVENT (0x01 << 1)
#define KEY12_EVENT (KEY1_EVENT | KEY2_EVENT)xEventGroupSetBits(Event_Handle,KEY1_EVENT);
xEventGroupSetBits(Event_Handle,KEY2_EVENT);
xEventGroupSetBits(Event_Handle,KEY12_EVENT);
这样后期维护只要在宏定义即可,不需要一个一个去找了。
创建按键的任务主体,如果KEY1按下设置KEY1_EVENT,如果KEY2按下设置KEY2_EVENT:
//KEY_Task任务主体
static void KEY_Task(void* parameter)
{ /* 任务都是一个无限循环,不能返回 */while (1){if( Key_Scan(KEY1_GPIO_PORT,KEY1_GPIO_PIN) == KEY_ON ) //如果KEY2被单击{printf ( "KEY1被按下!\r\n" );/* 触发一个事件1 */xEventGroupSetBits(Event_Handle,KEY1_EVENT); }if( Key_Scan(KEY2_GPIO_PORT,KEY2_GPIO_PIN) == KEY_ON ) //如果KEY2被单击{printf ( "KEY2被按下!\r\n" ); /* 触发一个事件2 */xEventGroupSetBits(Event_Handle,KEY2_EVENT); }vTaskDelay(20); //每20ms扫描一次 }
}
在AppTaskCreate()将任务的参数设置一下:
//任务创建函数
static void AppTaskCreate(void)
{BaseType_t xReturn = pdPASS;/* 定义一个创建信息返回值,默认为pdPASS */taskENTER_CRITICAL(); //进入临界区/* 创建 Event_Handle */Event_Handle = xEventGroupCreate(); if(NULL != Event_Handle)printf("Event_Handle 事件创建成功!\r\n"); elseprintf("Event_Handle 事件创建失败!\r\n"); /* 创建KEY_Task任务 */xReturn = xTaskCreate((TaskFunction_t )KEY_Task, /* 任务入口函数 */(const char* )"KEY_Task",/* 任务名字 */(uint16_t )512, /* 任务栈大小 */(void* )NULL,/* 任务入口函数参数 */(UBaseType_t )3, /* 任务的优先级 */(TaskHandle_t* )&KEY_Task_Handle);/* 任务控制块指针 */ if(pdPASS == xReturn)printf("创建KEY_Task任务成功!!\r\n"); vTaskDelete(AppTaskCreate_Handle); //删除AppTaskCreate任务taskEXIT_CRITICAL(); //退出临界区
}
main函数完整代码:
#include "stm32f10x.h" // Device header
#include "Delay.h"#include "LED.h"
#include "Usart.h"
#include "Key.h" /* FreeRTOS头文件 */
#include "FreeRTOS.h"
#include "task.h"
#include "event_groups.h"/* * 任务句柄是一个指针,用于指向一个任务,当任务创建好之后,它就具有了一个任务句柄* 以后我们要想操作这个任务都需要通过这个任务句柄,如果是自身的任务操作自己,那么* 这个句柄可以为NULL。*/
static TaskHandle_t AppTaskCreate_Handle = NULL; /* 创建任务句柄 */
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 KEY_Task(void* pvParameters);/* KEY_Task 任务实现 */static void All_Function_Init(void);/* 用于初始化板载相关资源 */int main(void)
{BaseType_t xReturn = pdPASS;/* 定义一个创建信息返回值,默认为pdPASS */All_Function_Init();//硬件初始化while (1){/* 创建AppTaskCreate任务 */xReturn = xTaskCreate((TaskFunction_t )AppTaskCreate, /* 任务入口函数 */(const char* )"AppTaskCreate",/* 任务名字 */(uint16_t )512, /* 任务栈大小 */(void* )NULL,/* 任务入口函数参数 */(UBaseType_t )1, /* 任务的优先级 */(TaskHandle_t* )&AppTaskCreate_Handle);/* 任务控制块指针 */ /* 启动任务调度 */ if(pdPASS == xReturn)vTaskStartScheduler(); /* 启动任务,开启调度 */elsereturn -1; }
}//任务创建函数
static void AppTaskCreate(void)
{BaseType_t xReturn = pdPASS;/* 定义一个创建信息返回值,默认为pdPASS */taskENTER_CRITICAL(); //进入临界区/* 创建 Event_Handle */Event_Handle = xEventGroupCreate(); if(NULL != Event_Handle)printf("Event_Handle 事件创建成功!\r\n"); elseprintf("Event_Handle 事件创建失败!\r\n"); /* 创建KEY_Task任务 */xReturn = xTaskCreate((TaskFunction_t )KEY_Task, /* 任务入口函数 */(const char* )"KEY_Task",/* 任务名字 */(uint16_t )512, /* 任务栈大小 */(void* )NULL,/* 任务入口函数参数 */(UBaseType_t )3, /* 任务的优先级 */(TaskHandle_t* )&KEY_Task_Handle);/* 任务控制块指针 */ if(pdPASS == xReturn)printf("创建KEY_Task任务成功!!\r\n"); vTaskDelete(AppTaskCreate_Handle); //删除AppTaskCreate任务taskEXIT_CRITICAL(); //退出临界区
}//KEY_Task任务主体
static void KEY_Task(void* parameter)
{ /* 任务都是一个无限循环,不能返回 */while (1){if( Key_Scan(KEY1_GPIO_PORT,KEY1_GPIO_PIN) == KEY_ON ) //如果KEY2被单击{printf ( "KEY1被按下!\r\n" );/* 触发一个事件1 */xEventGroupSetBits(Event_Handle,KEY1_EVENT); }if( Key_Scan(KEY2_GPIO_PORT,KEY2_GPIO_PIN) == KEY_ON ) //如果KEY2被单击{printf ( "KEY2被按下!\r\n" ); /* 触发一个事件2 */xEventGroupSetBits(Event_Handle,KEY2_EVENT); }vTaskDelay(20); //每20ms扫描一次 }
}//初始化声明
static void All_Function_Init(void)
{/** STM32中断优先级分组为4,即4bit都用来表示抢占优先级,范围为:0~15* 优先级分组只需要分组一次即可,以后如果有其他的任务需要用到中断,* 都统一用这个优先级分组,千万不要再分组,切忌。*/NVIC_PriorityGroupConfig( NVIC_PriorityGroup_4 );/* LED 初始化 */LED_GPIO_Config();/* 串口初始化 */USART_Config();//按键初始化Key_GPIO_Config();}
运行结果:
完整工程:
FreeRTOS事件-事件组置位函数.zip资源-CSDN文库
5.4 事件组置位函数 xEventGroupSetBitsFromISR()(中断)
xEventGroupSetBitsFromISR()是 xEventGroupSetBits()的中断版本,用于置位事件组中指定的位。置位事件组中的标志位是一个不确定的操作,因为阻塞在事件组的标志位上的任务的个数是不确定的。FreeRTOS 是不允许不确定的操作在中断和临界段中发生的,所以xEventGroupSetBitsFromISR()给 FreeRTOS 的守护任务发送一个消息,让置位事件组的操作在守护任务里面完成,守护任务是基于调度锁而非临界段的机制来实现的。
正如上文提到的那样,在中断中事件标志的置位是在守护任务(也叫软件定时器服务任务)中完成的。因此 FreeRTOS 的守护任务与其他任务一样,都是系统调度器根据其优先级进行任务调度的,但守护任务的优先级必须比任何任务的优先级都要高,保证在需要的时候能立即切换任务从而达到快速处理的目的,因为这是在中断中让事件标志位置位,其优先级由 FreeRTOSConfig.h 中的宏 configTIMER_TASK_PRIORITY 来定义。
其实 xEventGroupSetBitsFromISR()函数真正调用的也是 xEventGroupSetBits()函数,只不过是在守护任务中进行调用的,所以它实际上执行的上下文环境依旧是在任务中。
要想使用该函数,必须 把 configUSE_TIMERS 和 INCLUDE_xTimerPendFunctionCall 这些宏在 FreeRTOSConfig.h 中都定义为 1,并且把FreeRTOS/source/event_groups.c 这个 C文件添加到工程中编译。
函数原型 | BaseType_t xEventGroupSetBitsFromISR(EventGroupHandle_t xEventGroup, const EventBits_t uxBitsToSet, BaseType_t*pxHigherPriorityTask Woken); | |
功能 | 置位事件组中指定的位,在中断函数中使用。 | |
参数 | xEventGroup | 事件句柄。 |
uxBitsToSet | 指定事件组中的哪些位需要置位。如设置 uxBitsToSet 为0x08 则只置位位 3, 如果设置 uxBitsToSet 为 0x09 则位 3和位 0 都需要被置位。 | |
pxHigherPriority TaskWoken | pxHigherPriorityTaskWoken 在使用之前必须初始化成pdFALSE。调用xEventGroupSetBitsFromISR()会给守护任务发送一个消息,如果守护任务的优先级高于当前被中断的任务的优先级的话(一般情况下都需要将守护任务的优先级设置为所有任务中最高优先级),pxHigherPriorityTaskWoken会被置为 pdTRUE,然后在中断退出前执行一次上下文切换。 | |
返回值 | 消息成功发送给守护任务之后则返回 pdTRUE,否则返回 pdFAIL。如果定时器服务队列满了将返回 pdFAIL。 |
5.5 等待事件函数 xEventGroupWaitBits()
上面我们5.3我们通过函数标记了事件的发生,但是我们如何知道他到底有没有发生呢?
FreeRTOS 提供了一个等待指定事件的函数— —xEventGroupWaitBits(),通过这个函数,任务可以知道事件标志组中的哪些位,有什么事件发生了,然后通过 “逻辑与”、“逻辑或”等操作对感兴趣的事件进行获取,并且这个函数实现了等待超时机制,当且仅当任务等待的事件发生时,任务才能获取到事件信息。
在这段时间中,如果事件一直没发生,该任务将保持阻塞状态以等待事件发生。当其它任务或中断服务程序往其等待的事件设置对应的标志位,该任务将自动由阻塞态转为就绪态。当任务等待的时间超过了指定的阻塞时间,即使事件还未发生,任务也会自动从阻塞态转移为就绪态。这样子很有效的体现了操作系统的实时性,如果事件正确获取(等待到)则返回对应的事件标志位,由用户判断再做处理,因为在事件超时的时候也会返回一个不能确定的事件值,所以需要判断任务所等待的事件是否真的发生。
函数原型 | EventBits_t xEventGroupWaitBits(const EventGroupHandle_txEventGroup, const EventBits_t uxBitsToWaitFor, const BaseType_txClearOnExit, const BaseType_txWaitForAllBits, Tick Type_txTicksToWait); | |
功能 | 用于获取任务感兴趣的事件。 | |
参数 | xEventGroup | 事件句柄。 |
uxBitsToWaitFor | 一个按位或的值,指定需要等待事件组中的哪些位置 1。如果需要等待bit 0 and/or bit 2那么uxBitsToWaitFor配置为0x05(0101b)。如果需要等待 bits 0 and/or bit 1 and/or bit 2那么 uxBitsToWaitFor 配置为0x07(0111b)。 | |
xClearOnExit | pdTRUE:当xEventGroupWaitBits()等待到满足任务唤醒的事件时,系统将清除由形参uxBitsToWaitFor指定的事件标志位。pdFALSE:不会清除由形参 uxBitsToWaitFor指定的事件标志位。 | |
xWaitForAllBits | pdTRUE:当形参uxBitsToWaitFor指定的位都置位的时候,xEventGroupWaitBits()才满足任务唤醒的条件,这也是“逻辑与”等待事件,并且在没有超时的情况下返回对应的事件标志位的值。pdFALSE:当形参 uxBitsToWaitFor 指定的位有其中任意一个置位的时候,这也是常说的“逻辑或”等待事件,在没有超时的情况下函数返回对应的事件标志位的值。 | |
xTicksToWait | 最大超时时间,单位为系统节拍周期,常量portTICK_PERIOD_MS用于辅助把时间转换成 MS。 | |
返回值 | 返回事件中的哪些事件标志位被置位,返回值很可能并不是用户指定的事件位,需要对返回值进行判断再处理。 |
/*-----------------------------------------------------------*/
EventBits_t xEventGroupWaitBits(EventGroupHandle_t xEventGroup,const EventBits_t uxBitsToWaitFor,const BaseType_t xClearOnExit,const BaseType_t xWaitForAllBits,TickType_t xTicksToWait)
{EventGroup_t *pxEventBits = (EventGroup_t *)xEventGroup;EventBits_t uxReturn, uxControlBits = 0;BaseType_t xWaitConditionMet, xAlreadyYielded;BaseType_t xTimeoutOccurred = pdFALSE;/* 参数有效性检查 */configASSERT(xEventGroup);configASSERT((uxBitsToWaitFor & eventEVENT_BITS_CONTROL_BYTES) == 0);configASSERT(uxBitsToWaitFor != 0);#if ((INCLUDE_xTaskGetSchedulerState == 1) || (configUSE_TIMERS == 1)){configASSERT(!((xTaskGetSchedulerState() == taskSCHEDULER_SUSPENDED) && (xTicksToWait != 0)));}
#endifvTaskSuspendAll(); /* (1) 挂起调度器 */{const EventBits_t uxCurrentEventBits = pxEventBits->uxEventBits;/* (2) 检查当前事件位是否满足条件,其实就是判断一下用户等待的事件是否与当前事件标志位一致 */xWaitConditionMet = prvTestWaitCondition(uxCurrentEventBits,uxBitsToWaitFor,xWaitForAllBits);if (xWaitConditionMet != pdFALSE) { /* (3) 条件已满足 */uxReturn = uxCurrentEventBits;xTicksToWait = (TickType_t)0;/* (4) 检查是否需要清除事件位,如果 xClearOnExit 为 pdTRUE 则需要清除事件标志位,如果为 pdFALSE 就不需要清除。 */if (xClearOnExit != pdFALSE) {pxEventBits->uxEventBits &= ~uxBitsToWaitFor;} else {mtCOVERAGE_TEST_MARKER();}}/* (5) 不满足条件且不等待 */else if (xTicksToWait == (TickType_t)0) {uxReturn = uxCurrentEventBits;}/* (6) 需要阻塞等待 */else {/* 设置控制位 */if (xClearOnExit != pdFALSE) {uxControlBits |= eventCLEAR_EVENTS_ON_EXIT_BIT;}if (xWaitForAllBits != pdFALSE) {uxControlBits |= eventWAIT_FOR_ALL_BITS;}/* (7) 将任务加入事件等待列表,任务将被阻塞指定时间 xTicksToWait,并且这个列表项的值是用于保存任务等待事件需求的信息标记,以便在事件标志位置位的时候对等待事件的任务进行相应的操作。 */vTaskPlaceOnUnorderedEventList(&(pxEventBits->xTasksWaitingForBits),(uxBitsToWaitFor | uxControlBits),xTicksToWait);uxReturn = 0;traceEVENT_GROUP_WAIT_BITS_BLOCK(xEventGroup, uxBitsToWaitFor);}}xAlreadyYielded = xTaskResumeAll(); /* (8) 恢复调度器 */if (xTicksToWait != (TickType_t)0) {if (xAlreadyYielded == pdFALSE) {portYIELD_WITHIN_API(); /* (9) 触发任务切换 */}/* (10) 获取被唤醒时的事件值 */uxReturn = uxTaskResetEventItemValue();if ((uxReturn & eventUNBLOCKED_DUE_TO_BIT_SET) == (EventBits_t)0) { /* (11) 超时唤醒 */taskENTER_CRITICAL();{uxReturn = pxEventBits->uxEventBits;/* (12) 再次检查事件条件 */if (prvTestWaitCondition(uxReturn, uxBitsToWaitFor, xWaitForAllBits) != pdFALSE) {if (xClearOnExit != pdFALSE) {pxEventBits->uxEventBits &= ~uxBitsToWaitFor; /* (13) 清除事件位 */}}}taskEXIT_CRITICAL();xTimeoutOccurred = pdFALSE;}/* (14) 过滤控制位 */uxReturn &= ~eventEVENT_BITS_CONTROL_BYTES;}traceEVENT_GROUP_WAIT_BITS_END(xEventGroup, uxBitsToWaitFor, xTimeoutOccurred);return uxReturn;
}
/*-----------------------------------------------------------*/
举个例子来看一下其实际的调用,改代码是在5.4的代码基础上进行添加的:
首先为了方便观察实验现象我们在LED.c里面先去创建一个电平翻转函数,主要作用就是每次调用能够进行一次电平翻转,观察灯珠的亮灭,来查看实验现象,记得在.h文件声明一下:
//红灯电平翻转
void Toggle_LED_R(void)
{BitAction LED_R = (BitAction)(1 - GPIO_ReadOutputDataBit(LED1_GPIO_PORT, LED1_GPIO_PIN));GPIO_WriteBit(LED1_GPIO_PORT, LED1_GPIO_PIN, LED_R);
}
然后我们回到main函数,既然观察电平翻转,那么我们就创建一个LED的任务,首先创建一个LED的任务句柄:
static TaskHandle_t LED_Task_Handle = NULL;/* LED_Task任务句柄 */
然后是LED的任务主体:
//LED_Task任务主体
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 ( "\r\nKEY1与KEY2都按下!\r\n"); Toggle_LED_R(); //电平反转}elseprintf ( "事件错误!\r\n"); }
}
不在函数代码中,仅解释,这些宏定义是FreeRTOS内部封装好的:
#define pdTRUE ( ( BaseType_t ) 1 ) // 代表逻辑真
#define pdFALSE ( ( BaseType_t ) 0 ) // 代表逻辑假
在 FreeRTOS 中,pdTRUE 是一个宏定义,表示逻辑真(true),通常用于布尔类型的参数或返回值。它的值通常是 1,而对应的 pdFALSE 表示逻辑假(false),通常是 0。
#if( configUSE_16_BIT_TICKS == 1 )typedef uint16_t TickType_t;#define portMAX_DELAY ( TickType_t ) 0xffff
#elsetypedef uint32_t TickType_t;#define portMAX_DELAY ( TickType_t ) 0xffffffffUL
portMAX_DELAY 是 FreeRTOS 的一个宏,通常定义为 0xFFFFFFFF(或类似的最大值),表示 任务可以无限期阻塞,直到事件发生。
如果希望任务 有限时间等待,可以设置一个具体的 tick 值(如 100 / portTICK_PERIOD_MS 表示 100ms)。
r_event = xEventGroupWaitBits(Event_Handle,KEY1_EVENT | KEY2_EVENT,pdTRUE, // 退出时清除事件位pdTRUE, // 需要所有事件都发生pdMS_TO_TICKS(1000) // 等待 1000ms,超时后返回
);
回归正题,既然任务主体创建好了,那么我们还需要对LED分配好栈空间,优先级等参数,找到AppTaskCreate()函数,添加:
/* 创建LED_Task任务 */xReturn = xTaskCreate((TaskFunction_t )LED_Task, /* 任务入口函数 */(const char* )"LED_Task",/* 任务名字 */(uint16_t )512, /* 任务栈大小 */(void* )NULL, /* 任务入口函数参数 */(UBaseType_t )2, /* 任务的优先级 */(TaskHandle_t* )&LED_Task_Handle);/* 任务控制块指针 */if(pdPASS == xReturn)printf("创建LED_Task任务成功!\r\n");
如:
//任务创建函数
static void AppTaskCreate(void)
{BaseType_t xReturn = pdPASS;/* 定义一个创建信息返回值,默认为pdPASS */taskENTER_CRITICAL(); //进入临界区/* 创建 Event_Handle */Event_Handle = xEventGroupCreate(); if(NULL != Event_Handle)printf("Event_Handle 事件创建成功!\r\n"); elseprintf("Event_Handle 事件创建失败!\r\n"); /* 创建LED_Task任务 */xReturn = xTaskCreate((TaskFunction_t )LED_Task, /* 任务入口函数 */(const char* )"LED_Task",/* 任务名字 */(uint16_t )512, /* 任务栈大小 */(void* )NULL, /* 任务入口函数参数 */(UBaseType_t )2, /* 任务的优先级 */(TaskHandle_t* )&LED_Task_Handle);/* 任务控制块指针 */if(pdPASS == xReturn)printf("创建LED_Task任务成功!\r\n");/* 创建KEY_Task任务 */xReturn = xTaskCreate((TaskFunction_t )KEY_Task, /* 任务入口函数 */(const char* )"KEY_Task",/* 任务名字 */(uint16_t )512, /* 任务栈大小 */(void* )NULL,/* 任务入口函数参数 */(UBaseType_t )3, /* 任务的优先级 */(TaskHandle_t* )&KEY_Task_Handle);/* 任务控制块指针 */ if(pdPASS == xReturn)printf("创建KEY_Task任务成功!!\r\n"); vTaskDelete(AppTaskCreate_Handle); //删除AppTaskCreate任务taskEXIT_CRITICAL(); //退出临界区
}
完整的main函数:
#include "stm32f10x.h" // Device header
#include "Delay.h"#include "LED.h"
#include "Usart.h"
#include "Key.h" /* FreeRTOS头文件 */
#include "FreeRTOS.h"
#include "task.h"
#include "event_groups.h"/* * 任务句柄是一个指针,用于指向一个任务,当任务创建好之后,它就具有了一个任务句柄* 以后我们要想操作这个任务都需要通过这个任务句柄,如果是自身的任务操作自己,那么* 这个句柄可以为NULL。*/
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 All_Function_Init(void);/* 用于初始化板载相关资源 */int main(void)
{BaseType_t xReturn = pdPASS;/* 定义一个创建信息返回值,默认为pdPASS */All_Function_Init();//硬件初始化while (1){/* 创建AppTaskCreate任务 */xReturn = xTaskCreate((TaskFunction_t )AppTaskCreate, /* 任务入口函数 */(const char* )"AppTaskCreate",/* 任务名字 */(uint16_t )512, /* 任务栈大小 */(void* )NULL,/* 任务入口函数参数 */(UBaseType_t )1, /* 任务的优先级 */(TaskHandle_t* )&AppTaskCreate_Handle);/* 任务控制块指针 */ /* 启动任务调度 */ if(pdPASS == xReturn)vTaskStartScheduler(); /* 启动任务,开启调度 */elsereturn -1; }
}//任务创建函数
static void AppTaskCreate(void)
{BaseType_t xReturn = pdPASS;/* 定义一个创建信息返回值,默认为pdPASS */taskENTER_CRITICAL(); //进入临界区/* 创建 Event_Handle */Event_Handle = xEventGroupCreate(); if(NULL != Event_Handle)printf("Event_Handle 事件创建成功!\r\n"); elseprintf("Event_Handle 事件创建失败!\r\n"); /* 创建LED_Task任务 */xReturn = xTaskCreate((TaskFunction_t )LED_Task, /* 任务入口函数 */(const char* )"LED_Task",/* 任务名字 */(uint16_t )512, /* 任务栈大小 */(void* )NULL, /* 任务入口函数参数 */(UBaseType_t )2, /* 任务的优先级 */(TaskHandle_t* )&LED_Task_Handle);/* 任务控制块指针 */if(pdPASS == xReturn)printf("创建LED_Task任务成功!\r\n");/* 创建KEY_Task任务 */xReturn = xTaskCreate((TaskFunction_t )KEY_Task, /* 任务入口函数 */(const char* )"KEY_Task",/* 任务名字 */(uint16_t )512, /* 任务栈大小 */(void* )NULL,/* 任务入口函数参数 */(UBaseType_t )3, /* 任务的优先级 */(TaskHandle_t* )&KEY_Task_Handle);/* 任务控制块指针 */ if(pdPASS == xReturn)printf("创建KEY_Task任务成功!!\r\n"); vTaskDelete(AppTaskCreate_Handle); //删除AppTaskCreate任务taskEXIT_CRITICAL(); //退出临界区
}//LED_Task任务主体
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 ( "\r\nKEY1与KEY2都按下!\r\n"); Toggle_LED_R(); //电平反转}elseprintf ( "事件错误!\r\n"); }
}//KEY_Task任务主体
static void KEY_Task(void* parameter)
{ /* 任务都是一个无限循环,不能返回 */while (1){if( Key_Scan(KEY1_GPIO_PORT,KEY1_GPIO_PIN) == KEY_ON ) //如果KEY2被单击{printf ( "\r\nKEY1被按下!\r\n" );/* 触发一个事件1 */xEventGroupSetBits(Event_Handle,KEY1_EVENT); }if( Key_Scan(KEY2_GPIO_PORT,KEY2_GPIO_PIN) == KEY_ON ) //如果KEY2被单击{printf ( "\r\nKEY2被按下!\r\n" ); /* 触发一个事件2 */xEventGroupSetBits(Event_Handle,KEY2_EVENT); }vTaskDelay(20); //每20ms扫描一次 }
}//初始化声明
static void All_Function_Init(void)
{/** STM32中断优先级分组为4,即4bit都用来表示抢占优先级,范围为:0~15* 优先级分组只需要分组一次即可,以后如果有其他的任务需要用到中断,* 都统一用这个优先级分组,千万不要再分组,切忌。*/NVIC_PriorityGroupConfig( NVIC_PriorityGroup_4 );/* LED 初始化 */LED_GPIO_Config();/* 串口初始化 */USART_Config();//按键初始化Key_GPIO_Config();}
运行结果:
可以看出必须两个按键都被按下才会正常运行,如果仅有一个按键按下并不会满足条件,我们才试试不一直等待的情况,这里其他地方不用动,只更改主体代码:
//LED_Task任务主体
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/* 指定超时事件,一直等 */pdMS_TO_TICKS(5000) // 等待 1000ms,超时后返回);if((r_event & (KEY1_EVENT|KEY2_EVENT)) == (KEY1_EVENT|KEY2_EVENT)) {/* 如果接收完成并且正确 */printf ( "\r\nKEY1与KEY2都按下!\r\n"); Toggle_LED_R(); //电平反转}elseprintf("等待超时或事件错误!\r\n"); }
}
这里每隔5s进行一次检测,若是仅有一个按键按下或者没有按键按下,则会返回超时错误,只有两个按键都被按下才会正常显示:
完整工程:
FreeRTOS事件-等待事件函数.zip资源-CSDN文库
5.6 xEventGroupClearBits()与 xEventGroupClearBitsFromISR()
xEventGroupClearBits()与 xEventGroupClearBitsFromISR()都是用于清除事件组指定的位,如果在获取事件的时候没有将对应的标志位清除,那么就需要用这个函数来进行显式清除,xEventGroupClearBits()函数不能在中断中使用,而是由具有中断保护功能的xEventGroupClearBitsFromISR() 来代替,中断清除事件标志位的操作在守护任务(也叫定时器服务任务) 里面完成。守护进程的优先级由 FreeRTOSConfig.h 中的宏configTIMER_TASK_PRIORITY 来定义 。 要想使用该函数必须把 FreeRTOS/source/event_groups.c 这个 C 文件添加到工程中。
函数原型 | EventBits_t xEventGroupClearBits(EventGroupHandle_t xEventGroup, const EventBits_t uxBitsToClear); BaseType_t xEventGroupClearBitsFromISR(EventGroupHandlet xEventGroup, const EventBits_t uxBitsToClear); | |
功能 | 清除事件组中指定的位。 | |
参数 | xEventGroup | 事件句柄。 |
uxBitsToClear | 指定事件组中的哪个位需要清除。如设置uxBitsToSet为0x08则只清除位3,如果设置uxBitsToSet为0x09 则位3和位 0 都需要被清除。 | |
返回值 | 事件在还没有清除指定位之前的值。 |
简单举个例子:
#define BIT_0 (1 << 0)
#define BIT_4 (1 << 4)void aFunction(EventGroupHandle_t xEventGroup)
{EventBits_t uxBits;/* 清除事件组的 bit 0 和 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 都没被置位 */}
}
FreeRTOS菜鸟入门系列_时光の尘的博客-CSDN博客