FreeRTOS---基础知识6---事件组
事件组是 FreeRTOS 中用于 多任务同步 和 复合事件触发 的高效机制,允许任务等待多个事件中的任意组合(按位操作)。
以下是创建和使用事件组的步骤:
1. 包含必要的头文件
#include "FreeRTOS.h"
#include "event_groups.h"
2. 创建事件组
使用事件组之前,要先创建,得到一个句柄;使用事件组时,要使用句柄来表明使用哪个事件组。 有两种创建方法:动态分配内存、静态分配内存。函数原型如下:
EventGroupHandle_t xEventGroupCreate( void );
/* 创建一个事件组,返回它的句柄。
* 此函数内部会分配事件组结构体
* 返回值: 返回句柄,非NULL表示成功
*/EventGroupHandle_t xEventGroupCreate( void );/* 创建一个事件组,返回它的句柄。
* 此函数无需动态分配内存,所以需要先有一个StaticEventGroup_t结构体,并传入它的指针
* 返回值: 返回句柄,非NULL表示成功
*/EventGroupHandle_t xEventGroupCreateStatic( StaticEventGroup_t *
pxEventGroupBuffer );
3. 设置事件位
可以设置事件组的某个位、某些位,使用的函数有2个: 在任务中使用 xEventGroupSetBits() 在ISR中使用 xEventGroupSetBitsFromISR() 有一个或多个任务在等待事件,如果这些事件符合这些任务的期望,那么任务还会被唤醒。 函数原型如下:
/* 设置事件组中的位
* xEventGroup: 哪个事件组
* uxBitsToSet: 设置哪些位?
*
如果uxBitsToSet的bitX, bitY为1, 那么事件组中的bitX, bitY被设置为1*
可以用来设置多个位,比如 0x15 就表示设置bit4, bit2, bit0* 返回值: 返回原来的事件值(没什么意义, 因为很可能已经被其他任务修改了)*/EventBits_t xEventGroupSetBits( EventGroupHandle_t xEventGroup,const EventBits_t uxBitsToSet );/* 设置事件组中的位* xEventGroup: 哪个事件组* uxBitsToSet: 设置哪些位? * 如果uxBitsToSet的bitX, bitY为1, 那么事件组中的bitX, bitY被设置为1* 可以用来设置多个位,比如 0x15 就表示设置bit4, bit2, bit0* pxHigherPriorityTaskWoken: 有没有导致更高优先级的任务进入就绪态? pdTRUE-有,
pdFALSE-没有* 返回值: pdPASS-成功, pdFALSE-失败*/BaseType_t xEventGroupSetBitsFromISR( EventGroupHandle_t xEventGroup,const EventBits_t uxBitsToSet,BaseType_t * pxHigherPriorityTaskWoken );
#define BIT_0 (1 << 0) // 第0位
#define BIT_1 (1 << 1) // 第1位// 设置BIT_0和BIT_1
xEventGroupSetBits(xMyEventGroup, BIT_0 | BIT_1);
注:1<<0表示将1左移0位,即设置第0位;1<<1表示将1左移1位,即设置第1位。
值得注意的是,ISR中的函数,比如队列函数xQueueSendToBackFromISR、信号量函数 xSemaphoreGiveFromISR,它们会唤醒某个任务,最多只会唤醒1个任务。 但是设置事件组时,有可能导致多个任务被唤醒,这会带来很大的不确定性。所以 xEventGroupSetBitsFromISR函数不是直接去设置事件组,而是给一个FreeRTOS后台任务(daemon task)发送队列数据,由这个任务来设置事件组。 如果后台任务的优先级比当前被中断的任务优先级高,xEventGroupSetBitsFromISR会设置 *pxHigherPriorityTaskWoken为pdTRUE。 如果daemon task成功地把队列数据发送给了后台任务,那么xEventGroupSetBitsFromISR的返回值 就是pdPASS。
4. 等待事件位
使用xEventGroupWaitBits来等待事件,可以等待某一位、某些位中的任意一个,也可以等待多位; 等到期望的事件后,还可以清除某些位。
EventBits_t xEventGroupWaitBits( const EventGroupHandle_t xEventGroup,const EventBits_t uxBitsToWaitFor,const BaseType_t xClearOnExit,const BaseType_t xWaitForAllBits,TickType_t xTicksToWait );
你可以使用xEventGroupWaitBits()等待期望的事件,它发生之后再使用xEventGroupClearBits() 来清除。但是这两个函数之间,有可能被其他任务或中断抢占,它们可能会修改事件组。 可以使用设置xClearOnExit为pdTRUE,使得对事件组的测试、清零都在xEventGroupWaitBits() 函数内部完成,这是一个原子操作。
使用示例:
EventBits_t uxBits;// 等待BIT_0或BIT_1被设置,不清除事件位,等待任意一个位被设置,最多等待100ms
uxBits = xEventGroupWaitBits(xMyEventGroup, // 事件组句柄BIT_0 | BIT_1, // 等待的位pdFALSE, // 不清除事件位pdFALSE, // 等待任意一个位被设置pdMS_TO_TICKS(100) // 等待时间
);if((uxBits & BIT_0) != 0)
{// BIT_0被设置
}
else if((uxBits & BIT_1) != 0)
{// BIT_1被设置
}
else
{// 超时
}
注意:等待的事件中,它们要么是或的关系,要么是与的关系。也就是可以等待若干个事件中的任意一个,也可以等待若干个事件中的所有事件,但是不能在若干个事件中指定某些事件。
5. 清除事件位
EventBits_t xEventGroupClearBits( EventGroupHandle_t xEventGroup, const EventBits_t uxBitsToClear );
使用示例:
// 清除BIT_0
xEventGroupClearBits(xMyEventGroup, BIT_0);
注意事项
事件组使用32位变量存储事件位(在32位架构上),所以最多可以有32个不同的事件位
事件组是线程安全的,可以在中断和任务中使用
在中断服务程序(ISR)中设置事件位时,应使用
xEventGroupSetBitsFromISR()
函数使用前确保在FreeRTOSConfig.h中启用了事件组功能(
configUSE_EVENT_GROUPS
设置为1)
事件组是FreeRTOS中非常强大的同步机制,特别适合需要等待多个事件中任意组合的场景。
6. 事件组与队列的联合使用
事件组仅用于同步和事件通知,不能用于传递数据。数据传递需要使用队列(Queue)、流缓冲区(Stream Buffer)或消息缓冲区(Message Buffer)等机制。下面我将详细说明如何结合使用事件组和队列来实现数据传递。
#include "FreeRTOS.h"
#include "task.h"
#include "queue.h"
#include "event_groups.h"// 定义事件位
#define DATA_READY_BIT (1 << 0)// 创建事件组和队列
EventGroupHandle_t xDataEventGroup;
QueueHandle_t xDataQueue;// 数据生产者任务
void vDataProducerTask(void *pvParameters)
{int iDataToSend = 0;while(1){// 生成数据iDataToSend = generateData(); // 假设的函数// 将数据发送到队列if(xQueueSend(xDataQueue, &iDataToSend, portMAX_DELAY) == pdPASS){// 数据成功入队后,设置事件位通知消费者xEventGroupSetBits(xDataEventGroup, DATA_READY_BIT);}vTaskDelay(pdMS_TO_TICKS(100)); // 适当延迟}
}// 数据消费者任务
void vDataConsumerTask(void *pvParameters)
{int iReceivedData;EventBits_t uxBits;while(1){// 等待数据就绪事件uxBits = xEventGroupWaitBits(xDataEventGroup,DATA_READY_BIT,pdTRUE, // 退出时清除该位pdFALSE, // 不需要等待所有位portMAX_DELAY);if((uxBits & DATA_READY_BIT) != 0){// 从队列中获取数据if(xQueueReceive(xDataQueue, &iReceivedData, 0) == pdPASS){// 处理接收到的数据processData(iReceivedData); // 假设的函数}}}
}// 初始化函数
void vInitDataTransfer(void)
{// 创建事件组xDataEventGroup = xEventGroupCreate();// 创建队列,能存储10个int数据xDataQueue = xQueueCreate(10, sizeof(int));// 创建任务xTaskCreate(vDataProducerTask, "Producer", configMINIMAL_STACK_SIZE, NULL, 2, NULL);xTaskCreate(vDataConsumerTask, "Consumer", configMINIMAL_STACK_SIZE, NULL, 1, NULL);
}