RT-Thread之事件集使用示例

一、事件集概述

事件集(Event)是 RT-Thread 中用于线程间同步的轻量级 IPC 机制,核心特性是 “多标志位” 与 “灵活触发”:通过 32 个事件标志位(bit0~bit31)表示不同事件状态,支持线程按 “逻辑与(AND)” 或 “逻辑或(OR)” 关系等待多个事件,实现一对多、多对多的线程同步。

典型应用场景

  • 线程等待多个外部触发条件(如按键按下 + 传感器数据就绪),满足任意条件或全部条件时执行;
  • 状态机切换同步(如设备初始化完成 + 网络连接成功后,触发业务线程启动);
  • 中断与线程间通信(中断服务程序发送事件,线程等待事件处理异步通知)。

二、事件集核心 API 函数

事件集的操作围绕 “创建 / 初始化 / 发送事件 / 接收事件 / 删除 / 脱离” 展开,需区分动态创建(依赖内存堆)与静态初始化(基于全局 / 静态变量),核心函数与参数说明如下。

2.1 事件集的创建与初始化

用于完成事件集的初始化工作,动态创建会从堆中分配内存,静态初始化需提前定义全局 / 静态事件集对象。

2.1.1 动态创建

通过函数从内存堆中分配事件集资源,返回事件集句柄用于后续操作。

rt_event_t rt_event_create(const char *name, rt_uint8_t flag);
  • name:事件集名称。
  • flag:等待队列排序方式,可选值为 RT_IPC_FLAG_FIFO(先进先出)或 RT_IPC_FLAG_PRIO(按线程优先级排序)。

2.1.2 静态初始化

基于已定义的全局 / 静态事件集对象进行初始化,无需分配堆内存。

rt_err_t rt_event_init(rt_event_t event, const char *name, rt_uint8_t flag);
  • event:提前定义的全局 / 静态事件集对象(如 struct rt_event event_test)。
  • name:事件集名称,功能同动态创建。
  • flag:等待队列排序方式,功能同动态创建。

2.2 事件的发送与接收

发送事件用于设置事件集中的标志位,接收事件用于等待指定标志位满足条件,支持按逻辑关系触发。

2.2.1 发送事件

设置事件集中的一个或多个标志位,可唤醒等待该事件的线程。

rt_err_t rt_event_send(rt_event_t event, rt_uint32_t set);
  • event:目标事件集句柄 / 对象。
  • set:待设置的事件标志位(如 0x01 表示 bit0,0x03 表示 bit0 + bit1)。
  • 注意:可在中断服务程序(ISR)中调用,用于通知线程处理异步事件。

2.2.2 接收事件

等待事件集中的指定标志位,满足逻辑关系(AND / OR)时唤醒线程,支持超时机制。

rt_err_t rt_event_recv(rt_event_t event, rt_uint32_t set, rt_uint8_t option, rt_int32_t timeout, rt_uint32_t *recved);
  • event:目标事件集句柄 / 对象。
  • set:待等待的事件标志位(如 0x03 表示等待 bit0 或 bit1)。
  • option:对标志位的配置,RT_EVENT_FLAG_AND(全部标志位满足)或 RT_EVENT_FLAG_OR(任意标志位满足),也可以选用RT_EVENT_FLAG_CLEAR,表示接收事件后自动清除已满足的标志位
  • timeout:等待时间(单位:tick),可选值为 RT_WAITING_FOREVER(永久等待)、0(无等待)或具体数值。
  • recved:输出参数,用于返回实际接收到的事件标志位(可设为 RT_NULL)。

2.3 事件集的删除与脱离

用于回收事件集占用的系统资源,动态创建的事件集需删除,静态初始化的事件集需脱离。

2.3.1 动态删除

用于销毁通过 rt_event_create 动态创建的事件集,释放其占用的堆内存。

rt_err_t rt_event_delete(rt_event_t event);
  • event:动态创建的事件集句柄。
  • 适用场景:动态创建的事件集不再使用时,需调用此函数避免内存泄漏。

2.3.2 静态脱离

用于脱离通过 rt_event_init 静态初始化的事件集,仅注销其在系统中的登记信息,不释放内存。

rt_err_t rt_event_detach(rt_event_t event);
  • event:静态初始化的事件集对象。
  • 适用场景:静态事件集不再使用时,需调用此函数释放系统资源(如等待队列)。

三、事件集使用示例

3.1 源代码

#include "thread_task.h"
#include "main.h"
#include <stdio.h>      
#include "rtthread.h"
#include <rthw.h>

/******************************************** 线程 1 ******************************************************/
#define THREAD_1_PRIORITY  		4           /* 线程优先级(值越小优先级越高) */
#define THREAD_1_STACK_SIZE		512         /* 线程栈空间大小(单位:字节) */
#define THREAD_1_TIMESLICE		10           /* 线程时间片个数(单位:tick) */
static struct rt_thread *thread_1_handle;    /* 线程句柄 */

/******************************************** 线程 2 ******************************************************/
#define THREAD_2_PRIORITY  		5           /* 线程优先级(低于线程1) */
#define THREAD_2_STACK_SIZE		512         /* 线程栈空间大小 */
#define THREAD_2_TIMESLICE		10           /* 线程时间片个数 */
static struct rt_thread *thread_2_handle;    /* 线程句柄 */

/* 静态事件集对象(全局定义,提前分配内存) */
struct rt_event event_test;
/* 事件标志位定义:bit0 表示线程1触发事件,bit1 表示线程2触发事件 */
#define EVENT1_FLAG    (1 << 0)  /* 0x01 */
#define EVENT2_FLAG    (1 << 1)  /* 0x02 */
#define EVENT3_FLAG    (1 << 2)  /* 0x04 */

/**
 * @brief  LED闪烁函数(固定闪烁6次,即3个完整周期)
 * @param  time:每次翻转后的延迟时间(单位:tick)
 */
void LED_toggle(uint16_t time)
{   
    for(uint8_t i = 0; i < 6; i++)  
    {
        HAL_GPIO_TogglePin(GPIOC, LED1_Pin);
        rt_thread_delay(time);      
    }
}

/**
 * @brief  线程1入口函数(高优先级,接收事件触发LED慢速闪烁)
 * @param  param:线程参数(未使用,设为RT_NULL)
 */
void thread_1_entry(void* param)
{
    rt_uint32_t recved = 0;
    while(1)
    {
        /* 接收事件:等待EVENT1+EVENT2,AND逻辑,永久等待,接收后清除标志位 */
        if (rt_event_recv(&event_test, EVENT2_FLAG | EVENT1_FLAG , 
                        RT_EVENT_FLAG_AND | RT_EVENT_FLAG_CLEAR,  // 补充RT_EVENT_FLAG_CLEAR
                        RT_WAITING_FOREVER, &recved) == RT_EOK)
        {
            /* 接收到事件后,慢速闪烁LED(0.5Hz) */  // 修正频率描述
            LED_toggle(1000);
            rt_event_send(&event_test, EVENT3_FLAG);
        }
    }
}

/**
 * @brief  线程2入口函数(低优先级,接收事件触发LED快速闪烁)
 * @param  param:线程参数(未使用,设为RT_NULL)
 */
void thread_2_entry(void* param)
{   
    rt_uint32_t recved = 0;
    while(1)
    {
        /* 接收事件:等待EVENT3_FLAG,AND逻辑,永久等待,接收后清除标志位 */  // 修正笔误
        if (rt_event_recv(&event_test, EVENT3_FLAG, RT_EVENT_FLAG_AND | RT_EVENT_FLAG_CLEAR, RT_WAITING_FOREVER , &recved) == RT_EOK)
        {
            rt_event_send(&event_test, EVENT2_FLAG);
            LED_toggle(100);
            rt_thread_delay(500); 
            LED_toggle(100);
            rt_event_send(&event_test, EVENT1_FLAG);
        }
    }
}

/**
 * @brief  初始化事件集并创建启动线程(先初始化事件集,再启动线程)
 */
void ThreadStart(void)
{
    rt_base_t level = rt_hw_interrupt_disable();  

    /* 静态初始化事件集 */
    rt_event_init(
        &event_test,            /* 事件集对象 */
        "event_test",           /* 事件集名称(用于finsh调试) */
        RT_IPC_FLAG_FIFO        /* 等待队列按FIFO排序 */
    );
    rt_event_send(&event_test, EVENT3_FLAG);

    /* 动态创建并启动线程1 */
    thread_1_handle = rt_thread_create(
        "thread_1",				/* 线程名称 */
        thread_1_entry,			/* 线程入口函数 */
        RT_NULL,				/* 线程参数 */
        THREAD_1_STACK_SIZE,	/* 线程栈大小 */
        THREAD_1_PRIORITY,		/* 线程优先级 */
        THREAD_1_TIMESLICE  	/* 线程时间片 */
    );
    if (thread_1_handle != RT_NULL)
        rt_thread_startup(thread_1_handle);  

    /* 动态创建并启动线程2 */
    thread_2_handle = rt_thread_create(
        "thread_2",				/* 线程名称 */
        thread_2_entry,			/* 线程入口函数 */
        RT_NULL,				/* 线程参数 */
        THREAD_2_STACK_SIZE,	/* 线程栈大小 */
        THREAD_2_PRIORITY,		/* 线程优先级 */
        THREAD_2_TIMESLICE  	/* 线程时间片 */
    );
    if (thread_2_handle != RT_NULL)
        rt_thread_startup(thread_2_handle);  

    rt_hw_interrupt_enable(level);  
}

3.2 代码执行流程

  1. 初始化:初始化事件集(rt_event_init)并发送 EVENT3_FLAG,启动线程 1(高优)、线程 2(低优);
  2. 线程 1 阻塞:线程 1 等EVENT1+EVENT2,不满足→阻塞;
  3. 线程 2 执行:接收EVENT3(清标志)→发EVENT2→快速闪烁→发EVENT1
  4. 线程 1 唤醒EVENT1+EVENT2满足→抢占 CPU→慢速闪烁→发EVENT3
  5. 闭环循环:线程 1 阻塞→线程 2 再执行,重复步骤 3-4。

3.3 核心同步效果

  • 标志位无残留:RT_EVENT_FLAG_CLEAR自动清位;
  • LED 规律:线程 2→5Hz 快速闪,线程 1→0.5Hz 慢速闪;
  • 优先级生效:线程 1 仅阻塞时,线程 2 才执行。

四、关键设计注意事项

  1. 事件标志位的复用:事件集的标志位被接收后默认不清除,需通过 rt_event_recv 的返回值手动记录状态,或在接收时通过 RT_EVENT_FLAG_CLEAR 选项自动清除;
  2. 中断中安全使用rt_event_send 可在中断服务程序中调用(无阻塞操作),但 rt_event_recv 禁止在中断中使用(可能导致阻塞);
  3. 避免事件丢失:若多个线程发送同一事件,标志位会被重复设置(无累计计数),需确保接收线程能及时处理;
  4. 初始化时机:静态事件集必须在线程启动前完成初始化,避免线程接收未初始化的事件集;
  5. 逻辑关系选择:根据场景选择 RT_EVENT_FLAG_AND(需全部事件满足)或 RT_EVENT_FLAG_OR(任意事件满足),避免逻辑错误导致线程无法唤醒。

总结

本文详细解析了 RT-Thread 事件集的核心特性、API 函数与实操示例,重点说明了多事件标志位如何实现线程间灵活同步。通过静态初始化事件集、双线程交替触发 LED 闪烁的案例,验证了事件集在复杂同步场景中的有效性。实际开发中,需结合业务需求设计标志位逻辑,合理使用超时机制与清除选项,确保系统同步的可靠性与实时性。

posted @ 2025-10-27 19:37  比特向阳  阅读(2)  评论(0)    收藏  举报