欢迎光临
我们一直在努力

FreeRTOS事件组:多任务同步的利器

在嵌入式实时操作系统中,任务间的同步与通信是核心问题。FreeRTOS提供了多种机制,如信号量、队列、事件组等。其中,事件组(Event Groups) 是一种非常灵活且高效的同步方式,特别适用于一个任务需要等待多个事件发生的场景。本文将用通俗易懂的语言,带你快速掌握FreeRTOS事件组的使用方法。


1. 什么是事件组?

想象一下,你有一个任务需要等待“温度传感器数据就绪”且 “串口发送完成”两个条件同时满足才能继续执行。如果用信号量,你可能需要多个信号量并小心处理,而事件组可以直接解决这类“多条件等待”的问题。

事件组本质上是一个32位的二进制数,每一位(bit)代表一个独立的事件。任务可以:

  • 设置某一位或某几位(表示事件发生)
  • 等待某一位或某几位被设置(可以等待任意一位为1,或所有指定位都为1)
  • 清除某一位(可选)

事件组允许任务阻塞等待,并支持超时机制,是任务间同步的强大工具。


2. 常用API函数

FreeRTOS提供了以下常用事件组API:

函数功能
xEventGroupCreate() 创建一个事件组
xEventGroupSetBits() 设置事件组中的指定位(任务级)
xEventGroupSetBitsFromISR() 在中断中设置事件位
xEventGroupWaitBits() 等待事件组中的指定位被设置
xEventGroupClearBits() 清除事件组中的指定位
vEventGroupDelete() 删除事件组

本文重点介绍最核心的三个:创建、设置、等待。


3. 示例场景

硬件平台:STM32F103C8T6,使用标准外设库(StdPeriph_Lib) 开发环境:Keil MDK + FreeRTOS V10.x 功能描述:

  • 任务 Task1:每1秒设置事件组的BIT0,模拟“传感器数据就绪”事件。
  • 任务 Task2:阻塞等待BIT0被设置,一旦等到,翻转LED(PC13),并打印信息。
  • 同时任务 Task1 还设置BIT1,但任务 Task2 等待的是BIT0与BIT1同时为1(演示多事件等待)。

为了演示完整,我们将使用xEventGroupWaitBits的xWaitForAllBits参数为pdTRUE,表示必须所有指定位都为1才返回。


4. 完整代码示例

4.1 FreeRTOS配置(FreeRTOSConfig.h关键项)

#define configUSE_PREEMPTION 1
#define configUSE_IDLE_HOOK 0
#define configUSE_TICK_HOOK 0
#define configCPU_CLOCK_HZ ( SystemCoreClock )
#define configTICK_RATE_HZ ( ( TickType_t ) 1000 )
#define configMAX_PRIORITIES ( 5 )
#define configMINIMAL_STACK_SIZE ( ( unsigned short ) 128 )
#define configTOTAL_HEAP_SIZE ( ( size_t ) ( 17 * 1024 ) )
#define configMAX_TASK_NAME_LEN ( 16 )
#define configUSE_TRACE_FACILITY 0
#define configUSE_16_BIT_TICKS 0
#define configIDLE_SHOULD_YIELD 1
#define configUSE_MUTEXES 1
#define configQUEUE_REGISTRY_SIZE 8
#define configCHECK_FOR_STACK_OVERFLOW 2
#define configUSE_RECURSIVE_MUTEXES 0
#define configUSE_MALLOC_FAILED_HOOK 1
#define configUSE_APPLICATION_TASK_TAG 0
#define configUSE_COUNTING_SEMAPHORES 0
#define configGENERATE_RUN_TIME_STATS 0

4.2 主程序(main.c)

#include "stm32f10x.h"
#include "FreeRTOS.h"
#include "task.h"
#include "event_groups.h"

/* 事件组句柄 */
EventGroupHandle_t xEventGroup;

/* 事件位定义 */
#define EVENT_DATA_READY ( 1 << 0 ) // BIT0
#define EVENT_UART_SENT ( 1 << 1 ) // BIT1
#define EVENT_ALL_BITS ( EVENT_DATA_READY | EVENT_UART_SENT )

/* 任务句柄 */
TaskHandle_t xTask1Handle = NULL;
TaskHandle_t xTask2Handle = NULL;

/* 函数声明 */
static void GPIO_Config(void);
static void Task1_Producer(void *pvParameters);
static void Task2_Consumer(void *pvParameters);

/* 主函数 */
int main(void)
{
/* 配置系统时钟(标准库默认) */
SystemInit();

/* 初始化GPIO(LED) */
GPIO_Config();

/* 创建事件组 */
xEventGroup = xEventGroupCreate();
if (xEventGroup == NULL) {
/* 创建失败,死循环 */
while (1);
}

/* 创建任务 */
xTaskCreate(Task1_Producer, "Producer", configMINIMAL_STACK_SIZE, NULL, 1, &xTask1Handle);
xTaskCreate(Task2_Consumer, "Consumer", configMINIMAL_STACK_SIZE, NULL, 2, &xTask2Handle);

/* 启动调度器 */
vTaskStartScheduler();

/* 如果程序运行到这里,说明堆内存不足导致调度器启动失败 */
while (1);
}

/* GPIO初始化:LED连接在PC13 */
static void GPIO_Config(void)
{
GPIO_InitTypeDef GPIO_InitStructure;

RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE);

GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOC, &GPIO_InitStructure);

/* 默认关闭LED */
GPIO_SetBits(GPIOC, GPIO_Pin_13);
}

/* 任务1:生产者,周期性设置事件位 */
static void Task1_Producer(void *pvParameters)
{
TickType_t xLastWakeTime = xTaskGetTickCount();
const TickType_t xPeriod = pdMS_TO_TICKS(1000);

for (;;) {
/* 设置BIT0(数据就绪) */
xEventGroupSetBits(xEventGroup, EVENT_DATA_READY);

/* 模拟一些延迟后设置BIT1(发送完成) */
vTaskDelay(pdMS_TO_TICKS(500));
xEventGroupSetBits(xEventGroup, EVENT_UART_SENT);

/* 等待下一个周期 */
vTaskDelayUntil(&xLastWakeTime, xPeriod);
}
}

/* 任务2:消费者,等待所有事件位都被设置 */
static void Task2_Consumer(void *pvParameters)
{
EventBits_t uxBits;
const TickType_t xTimeout = pdMS_TO_TICKS(2000); // 超时2秒

for (;;) {
/* 等待BIT0和BIT1都被设置(AND条件) */
uxBits = xEventGroupWaitBits(
xEventGroup, // 事件组句柄
EVENT_ALL_BITS, // 等待的位
pdTRUE, // 清除这些位(退出时自动清除)
pdTRUE, // 等待所有位都为1(AND)
xTimeout // 超时时间
);

/* 检查返回结果 */
if ((uxBits & EVENT_ALL_BITS) == EVENT_ALL_BITS) {
/* 所有事件都发生,执行任务(翻转LED) */
GPIO_WriteBit(GPIOC, GPIO_Pin_13,
(BitAction)(1 GPIO_ReadOutputDataBit(GPIOC, GPIO_Pin_13)));
} else {
/* 超时未等到所有事件,可以记录错误或执行其他操作 */
// 此处简单忽略
}
}
}

4.3 中断服务函数示例(可选)

如果需要在中断中设置事件位,使用xEventGroupSetBitsFromISR:

void EXTI0_IRQHandler(void)
{
BaseType_t xHigherPriorityTaskWoken = pdFALSE;

if (EXTI_GetITStatus(EXTI_Line0) != RESET) {
/* 在中断中设置事件位 */
xEventGroupSetBitsFromISR(xEventGroup, EVENT_DATA_READY, &xHigherPriorityTaskWoken);

/* 清除中断标志 */
EXTI_ClearITPendingBit(EXTI_Line0);
}

/* 如果设置了更高优先级任务就绪,则触发上下文切换 */
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
}


5. 代码解析

  • 事件组创建:xEventGroupCreate()返回句柄,失败返回NULL。
  • 设置事件位:任务1周期性设置BIT0和BIT1,xEventGroupSetBits不阻塞。
  • 等待事件位:任务2调用xEventGroupWaitBits:
    • 第2参数指定要等待的位(EVENT_ALL_BITS = 0x03)
    • 第3参数为pdTRUE表示等待成功后自动清除这些位,否则需手动清除
    • 第4参数为pdTRUE表示等待所有指定位都为1(AND),pdFALSE表示等待任意一位为1(OR)
    • 第5参数为超时时间,这里设置为2秒。如果超时未等到,返回的实际位可能不满足条件。
  • 返回值:返回事件组当前的值,通过位与判断是否成功。

  • 6. 注意事项

    • 中断中使用:必须使用xEventGroupSetBitsFromISR,并配合portYIELD_FROM_ISR。
    • 内存占用:事件组基于RAM,创建后一直占用,删除时需调用vEventGroupDelete。
    • 等待位时不能指定优先级继承,因此不适合用于互斥资源保护(应使用互斥信号量)。
    • 位数量:事件组是32位,但最高8位(bit24~bit31)被FreeRTOS内部使用,用户只能使用低24位。
    • 超时处理:务必检查返回值,避免误判事件已发生。

    7. 总结

    事件组是FreeRTOS中实现多事件同步的利器,通过简单的位操作即可完成复杂的等待逻辑。本文通过一个简单的生产者-消费者示例,展示了事件组的创建、设置和等待过程。在实际项目中,你可以根据需求灵活使用AND/OR等待、自动清除、超时等功能,让任务间同步更加优雅高效。

    希望这篇博客能帮助你快速上手FreeRTOS事件组。如果觉得有用,欢迎点赞收藏!


    参考资料 FreeRTOS官方文档 – Event Groups STM32F10x标准外设库

    赞(0)
    未经允许不得转载:171主机测评 » FreeRTOS事件组:多任务同步的利器
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址