3. 事件标志组创建与删除:xEventGroupCreate()、xEventGroupCreateStatic()、vEventGroupDelete()

好,咱们进入事件标志组的实战第一步——创建和删除。这部分其实不复杂,但细节挺多。我见过不少新手在这里栽跟头,尤其是内存分配那块。

说白了,事件标志组就是个特殊的队列。它不存数据,只存一组二进制位。每个位代表一个事件是否发生。你要用之前,得先把它“生”出来。用完了,也得把它“灭”掉。

3.1 动态创建:xEventGroupCreate()

这是最常用的方式。你只管调用,系统自动从堆里给你分配内存。我个人习惯,只要不是资源极度受限的场景,都用动态创建。

EventGroupHandle_t xEventGroupCreate( void );

返回值很简单:成功返回事件标志组的句柄,失败返回NULL。为什么会失败?堆内存不够了呗。

实战代码片段:

EventGroupHandle_t xEventGroupCreateExample( void )
{
    EventGroupHandle_t xEventGroup = NULL;

    /* 创建事件标志组 */
    xEventGroup = xEventGroupCreate();

    if( xEventGroup != NULL )
    {
        /* 创建成功,可以开始用了 */
        printf("事件标志组创建成功!\n");
    }
    else
    {
        /* 创建失败,多半是内存不够 */
        printf("创建失败!检查FreeRTOSConfig.h中的configTOTAL_HEAP_SIZE\n");
    }

    return xEventGroup;
}

嗯,这里要注意:创建成功后,所有事件位默认都是0。也就是说,没有任何事件发生。你想想看,这很合理——刚出生的孩子,啥事都没干过嘛。

3.2 静态创建:xEventGroupCreateStatic()

静态创建,说白了就是你自己管内存。系统不帮你分配,你得提前准备好内存空间。我在项目中遇到过一些安全苛求的系统,不允许使用动态内存分配,这时候静态创建就是唯一选择。

EventGroupHandle_t xEventGroupCreateStatic( StaticEventGroup_t * pxEventGroupBuffer );

参数就一个:指向StaticEventGroup_t结构体的指针。这个结构体你得提前定义好,并且保证它一直有效——不能是局部变量,除非你确定函数返回后它不会被销毁。

我的建议:静态创建时,把StaticEventGroup_t变量定义成全局的,或者静态的。省心,不出错。

/* 静态创建示例 */
static StaticEventGroup_t xStaticEventGroupBuffer;

void vStaticCreateExample( void )
{
    EventGroupHandle_t xEventGroup = NULL;

    xEventGroup = xEventGroupCreateStatic( &xStaticEventGroupBuffer );

    if( xEventGroup != NULL )
    {
        /* 创建成功 */
        printf("静态事件标志组创建成功!\n");
    }
    else
    {
        /* 理论上不会失败,除非你传了NULL指针 */
        printf("检查参数是否传入了NULL?\n");
    }
}

我曾经犯过一个错:把StaticEventGroup_t定义在某个子函数里,然后函数退出后,栈空间被回收了。结果事件标志组莫名其妙地工作异常。排查了半天,最后发现是内存被覆盖了。从那以后,我所有静态资源都放在全局区或者静态区。

3.3 删除事件标志组:vEventGroupDelete()

用完了,就得还回去。删除事件标志组很简单:

void vEventGroupDelete( EventGroupHandle_t xEventGroup );

参数就是你要删除的事件标志组句柄。调用后,这个事件标志组就没了。所有等待这个事件标志组的任务,都会收到一个错误返回值。

警告:删除事件标志组时,如果有任务正在等待它,这些任务会被解锁,并且xEventGroupWaitBits()会返回一个错误码。你需要在任务中处理这种情况,否则任务可能陷入混乱。

void vDeleteEventGroupExample( EventGroupHandle_t xEventGroup )
{
    if( xEventGroup != NULL )
    {
        /* 删除前,确保没有任务在等待它 */
        /* 或者你确定删除行为是安全的 */
        vEventGroupDelete( xEventGroup );
        xEventGroup = NULL;  /* 好习惯:置空指针 */
    }
}

我个人习惯,删除后立即把句柄置为NULL。这样万一后面不小心又用了,至少能通过断言或者检查发现错误,而不是操作一个野指针。

3.4 动态 vs 静态:怎么选?

我整理了一个对比表,方便你决策:

特性 xEventGroupCreate() xEventGroupCreateStatic()
内存来源 FreeRTOS堆 用户提供
失败原因 堆内存不足 传入NULL指针
适用场景 大多数情况 安全系统、无堆环境
代码复杂度 中(需要管理内存)
可移植性 更好(不依赖堆实现)

你想想看,如果你的项目跑在资源丰富的MCU上,比如STM32F4系列,堆空间通常够用,动态创建完全没问题。但如果你在做一些超低成本的芯片,RAM只有几KB,那静态创建可能更靠谱——你能精确控制每一字节的用途。

3.5 避坑指南

最后,分享几个我踩过的坑:

  • 重复创建:我曾经在初始化代码里不小心调了两次xEventGroupCreate(),结果第一个句柄丢了,内存泄漏。后来我加了个状态检查,确保只创建一次。
  • 中断中创建/删除:这两个API都不能在中断服务函数里调用。FreeRTOS的API有明确的中断安全分类,xEventGroupCreate和vEventGroupDelete都属于“不能在中断中调用”的那一类。
  • 删除后继续使用:这个不用多说,野指针操作,轻则崩溃,重则烧硬件(虽然MCU一般烧不了,但逻辑错误很难排查)。

嗯,事件标志组的创建和删除就这些内容。说白了,创建就是分配内存并初始化,删除就是释放内存并通知等待任务。把握好这两个操作,后面的事件设置和等待才能玩得转。