前言
本篇文章将带大家学习什么是事件组以及如何使用事件组。
一、事件组概念
事件组通常是由一组位(bits)组成的数据结构,其中每一位都对应着某个特定的事件。每个位可以被设置或清除,表示相应的事件发生或未发生。这种位的组合形成了一个类似于二进制数的集合,每个位都代表着某个特定的状态或事件。因此,可以将事件组视为由一系列二进制位构成的数据结构,每个位代表着一个独立的事件状态。
事件组中的每一位可以表示一个特定的事件或状态,任务可以根据这些事件或状态来进行相应的处理。通过设置或清除这些位,可以触发或取消相应的事件处理逻辑。事件组的位通常用于指示某些事件是否发生、是否完成或是否需要进一步处理。这种灵活性使得事件组成为一种强大的工具,用于任务间的同步、通信和协作。
在使用事件组时,可以通过操作单独的位来表示不同的事件或状态,从而实现复杂的任务管理和事件处理。通过适当地设置和清除位,可以实现任务间的协同工作,以满足特定的应用需求。事件组的这种位级别的控制使其成为处理任务间通信和同步的有效工具。
事件组的位数:
如果 configUSE_16_BIT_TICKS 被设置为 1(启用),则事件组有16位,其中最高的8位(从最高位到第9位)用于内核管理,而剩下的低8位留给用户自定义事件。
如果 configUSE_16_BIT_TICKS 被设置为 0(禁用),则事件组有32位,其中最高的8位(从最高位到第25位)用于内核管理,而剩下的低24位留给用户自定义事件。
二、事件组和信号量,队列的区别
1.事件组(Event Group):
概念:事件组是由多个位组成的数据结构,用于表示多个事件的状态。任务可以等待事件组中的特定位或位组合被设置,从而实现任务同步。
用途:事件组常用于任务间的事件通知和同步。任务可以等待多个事件中的任意一个或多个同时发生,或者等待所有事件都发生后再继续执行。
特点:事件组允许任务等待多个事件,提供了更灵活的同步机制。
2.信号量(Semaphore):
概念:信号量是一个计数器,用于控制多个任务对共享资源的访问。信号量的值表示可用资源的数量,任务可以请求或释放资源,信号量负责追踪可用资源的数量。
用途:信号量常用于控制共享资源的访问,防止多个任务同时访问共享资源,以避免竞争条件(Race Condition)。
特点:信号量是一个计数器,可以用于控制资源的数量和分配。
2.队列(Queue):
概念:队列是一个数据结构,用于在任务间传递数据。任务可以将数据发送到队列,另一个任务则可以从队列中接收这些数据。
用途:队列常用于任务间的数据传递,允许任务异步地发送和接收数据,实现解耦和通信。
特点:队列是一个数据缓冲区,可以用于存储和传递数据,任务间的数据传递是异步的。
区别总结:
事件组用于任务间的事件通知和同步,任务可以等待多个事件的发生。
信号量用于控制对共享资源的访问,防止多个任务同时访问资源。
队列用于任务间的数据传递,任务可以异步地发送和接收数据。
三、事件组相关函数
xEventGroupCreate() - 创建事件组
EventGroupHandle_t xEventGroupCreate(void);
参数:无
返回值:事件组的句柄 (EventGroupHandle_t)
意义:用于创建一个事件组。该函数会返回一个事件组的句柄,以供后续操作。
xEventGroupSetBits() - 设置事件组位
EventBits_t xEventGroupSetBits(EventGroupHandle_t xEventGroup, const EventBits_t uxBitsToSet);
参数:
xEventGroup:事件组的句柄
uxBitsToSet:要设置的位,通常使用位掩码来表示要设置的事件位
返回值:设置后的事件组状态,包括所有事件位的当前状态
意义:用于设置事件组的一个或多个位,表示事件发生。可以使用位掩码将要设置的位传递给该函数。
xEventGroupClearBits() - 清除事件组位
EventBits_t xEventGroupClearBits(EventGroupHandle_t xEventGroup, const EventBits_t uxBitsToClear);
参数:
xEventGroup:事件组的句柄
uxBitsToClear:要清除的位,通常使用位掩码来表示要清除的事件位
返回值:清除后的事件组状态,包括所有事件位的当前状态
意义:用于清除事件组的一个或多个位,表示事件未发生。可以使用位掩码将要清除的位传递给该函数。
xEventGroupWaitBits() - 等待事件组位的设置
EventBits_t xEventGroupWaitBits(EventGroupHandle_t xEventGroup, const EventBits_t uxBitsToWaitFor, BaseType_t xClearOnExit, BaseType_t xWaitForAllBits, TickType_t xTicksToWait);
参数:
xEventGroup:事件组的句柄
uxBitsToWaitFor:等待的位,通常使用位掩码来表示要等待的事件位
xClearOnExit:指定是否在退出时清除等待的位(可选参数)
xWaitForAllBits:指定是否等待所有位都被设置(可选参数)
xTicksToWait:等待的最大时间(以时钟节拍为单位),如果设置为 0,则将一直等待
返回值:返回事件组的当前状态,包括所有事件位的当前状态
意义:用于等待事件组中的特定位被设置。根据参数的设置,可以选择等待所有位都被设置或只等待其中之一被设置,还可以选择在等待完成后清除等待的位。
xEventGroupSync() 函数用于同步多个任务的操作,使它们能够在相同的时间点上等待一组特定的事件。它通过等待事件组的特定位或位组合被设置来实现同步。
EventBits_t xEventGroupSync( EventGroupHandle_t xEventGroup, const EventBits_t uxBitsToSet, const EventBits_t uxBitsToWaitFor, TickType_t xTicksToWait );
参数:
xEventGroup:要同步的事件组的句柄。
uxBitsToSet:需要设置的位,通常使用位掩码来表示要设置的事件位。
uxBitsToWaitFor:需要等待的位,通常使用位掩码来表示要等待的事件位。
xTicksToWait:等待的最大时间(以时钟节拍为单位),如果设置为 0,则将一直等待。
返回值:
该函数返回事件组的当前状态,包括所有事件位的当前状态。
意义:
xEventGroupSync() 函数的目的是等待指定的位被设置,并在这些位被设置时进行同步。
当调用 xEventGroupSync() 函数时,调用任务将等待事件组中的指定位被设置,同时设置另一组位。一旦事件组中的所有指定位都被设置,调用任务将继续执行。
如果超过指定的等待时间(由 xTicksToWait 参数确定),但事件组中的指定位仍未被设置,调用任务将根据超时行为采取相应措施。
通过 xEventGroupSync() 函数,你可以实现任务间的同步操作,确保多个任务在特定事件发生时同时执行。这对于某些需要多任务协作的场景非常有用,例如在任务需要同时开始执行某个操作时,或者需要等待某些条件满足后再执行后续操作时。
三、事件组应用示例
1.等待多个事件
下面的代码创建了三个任务,分别是洗菜,生火,炒菜三个任务,炒菜任务需要等待洗菜和生火两个任务完成后才能够继续执行,否则就阻塞无法执行。
/* 事件组句柄 */ EventGroupHandle_t xEventGroup; /* bit0: 洗菜 * bit1: 生火 * bit2: 炒菜 */ #define WASHING (1<<0) #define FIRING (1<<1) #define COOKING (1<<2) static void vWashingTask( void *pvParameters ) { int i = 0; /* 无限循环 */ for( ;; ) { printf("I am washing %d time....\r\n", i++); /* 发出事件: 我洗完菜了 */ xEventGroupSetBits(xEventGroup, WASHING); /* 等待大厨炒完菜, 再继续洗菜 */ xEventGroupWaitBits(xEventGroup, COOKING, pdTRUE, pdTRUE, portMAX_DELAY); } } static void vFiringTask( void *pvParameters ) { int i = 0; /* 无限循环 */ for( ;; ) { /* 等待洗完菜, 才生火 */ xEventGroupWaitBits(xEventGroup, WASHING, pdFALSE, pdTRUE, portMAX_DELAY); printf("I am firing %d time....\r\n", i++); /* 发出事件: 我生好火了 */ xEventGroupSetBits(xEventGroup, FIRING); } } static void vCookingTask( void *pvParameters ) { int i = 0; /* 无限循环 */ for( ;; ) { /* 等待2件事: 洗菜, 生火 */ xEventGroupWaitBits(xEventGroup, WASHING|FIRING, pdTRUE, pdTRUE, portMAX_DELAY); printf("I am cooking %d time....\r\n", i++); /* 发出事件: 我炒好菜了 */ xEventGroupSetBits(xEventGroup, COOKING); } } //创建事件组 xEventGroup = xEventGroupCreate( ); /* 创建3个任务: 洗菜/生火/炒菜 */ xTaskCreate( vWashingTask, "Task1", 1000, NULL, 1, NULL ); xTaskCreate( vFiringTask, "Task2", 1000, NULL, 2, NULL ); xTaskCreate( vCookingTask, "Task3", 1000, NULL, 3, NULL );
2.任务同步
#include <FreeRTOS.h> #include <task.h> #include <event_groups.h> // 定义事件组句柄 EventGroupHandle_t xEventGroup; // 生产者任务 void vProducerTask(void *pvParameters) { // 生产数据过程 for (int i = 1; i <= 5; i++) { // 生产数据,假设为整数 i printf("Producing data: %d\n", i); xEventGroupSetBits(xEventGroup, 0x01); // 设置位0(表示数据可用) vTaskDelay(1000 / portTICK_PERIOD_MS); // 模拟生产数据需要时间 } } // 消费者任务 void vConsumerTask(void *pvParameters) { // 等待数据可用 xEventGroupSync( xEventGroup, 0x01, // 设置位0,表示数据可用 0x01, // 等待位0被设置 portMAX_DELAY // 无限等待 ); // 开始处理数据 printf("Consumer task started!\n"); while (1) { // 处理数据 printf("Consuming data\n"); vTaskDelay(1000 / portTICK_PERIOD_MS); // 模拟处理数据需要时间 } } int main(void) { // 创建事件组 xEventGroup = xEventGroupCreate(); // 创建生产者任务 xTaskCreate(vProducerTask, "Producer", configMINIMAL_STACK_SIZE, NULL, 1, NULL); // 创建消费者任务 xTaskCreate(vConsumerTask, "Consumer", configMINIMAL_STACK_SIZE, NULL, 2, NULL); // 启动调度器 vTaskStartScheduler(); // 这里不会执行,因为任务是永远不会返回的 return 0; }
总结
本篇文章主要给大家讲解了事件组的概念和具体的函数和使用方法。