# 事件

## 事件集(Event):

事件集可以看作公交⻋站的场景。线程可以等待多个事件发⽣，⽐如等待公交⻋或等待同伴到达。如果满⾜某个或某⼏个条件，线程将被唤醒继续执⾏  

> ### 1. **rt_event_create**
> 用于动态创建一个事件对象。
> ```c
> rt_event_t rt_event_create(const char *name, rt_uint8_t flag);
> ```
> #### 参数说明：
> - `name`：事件对象的名字（可以为 `RT_NULL` 表示匿名事件对象）。
> - `flag`：IPC 对象的属性标志，一般使用 `RT_IPC_FLAG_PRIO` 表示优先级等待，或 `RT_IPC_FLAG_FIFO` 表示先进先出等待。
> #### 返回值：
> - 成功：返回事件对象的句柄 `rt_event_t`。
> - 失败：返回 `RT_NULL`。
> #### 示例：
> ```c
> rt_event_t event = rt_event_create("event", RT_IPC_FLAG_PRIO);
> ```
> ### 2. **rt_event_init**
> 用于静态初始化事件对象。一般用于系统或内存已静态分配的场景。
> ```c
> rt_err_t rt_event_init(rt_event_t event, const char *name, rt_uint8_t flag);
> ```
> #### 参数说明：
> - `event`：事件对象指针（需要预先定义事件对象结构体）。
> - `name`：事件对象名字。
> - `flag`：IPC 对象的属性标志，和 `rt_event_create` 一样，通常使用 `RT_IPC_FLAG_PRIO` 或 `RT_IPC_FLAG_FIFO`。
> #### 返回值：
> - 成功：返回 `RT_EOK`。
> - 失败：返回相应的错误代码。
> #### 示例：
> ```c
> /* 静态事件对象定义 */
> static struct rt_event static_event;
> /* 初始化静态事件对象 */
> rt_event_init(&static_event, "static_event", RT_IPC_FLAG_PRIO);
> ```
> ### 3. **rt_event_delete**
> 用于删除动态创建的事件对象（`rt_event_create` 创建的事件对象）。
> ```c
> rt_err_t rt_event_delete(rt_event_t event);
> ```
> #### 参数说明：
> - `event`：事件对象的句柄。
> #### 返回值：
> - 成功：返回 `RT_EOK`。
> - 失败：返回相应的错误代码。
> #### 示例：
> ```c
> rt_event_delete(event); // 删除动态创建的事件对象
> ```
> ### 4. **rt_event_detach**
> 用于卸载静态初始化的事件对象（`rt_event_init` 初始化的事件对象）。
> ```c
> rt_err_t rt_event_detach(rt_event_t event);
> ```
> #### 参数说明：
> - `event`：事件对象的句柄。
> #### 返回值：
> - 成功：返回 `RT_EOK`。
> - 失败：返回相应的错误代码。
> #### 示例：
> ```c
> rt_event_detach(&static_event); // 卸载静态初始化的事件对象
> ```
> ### 5. **rt_event_send**
>
> 用于发送（触发）一个事件，设置指定的事件标志位。线程会根据其等待的事件标志来判断是否继续执行。
> ```c
> rt_err_t rt_event_send(rt_event_t event, rt_uint32_t set);
> ```
> #### 参数说明：
> - `event`：事件对象的句柄。
> - `set`：事件标志，设置哪几位事件标志被触发（可以用 `1 << n` 来表示第 n 位的事件）。
> #### 返回值：
> - 成功：返回 `RT_EOK`。
> - 失败：返回相应的错误代码。
> #### 示例：
> ```c
> rt_event_send(event, (1 << 3)); // 触发位3的事件
> ```
> ### 6. **rt_event_recv**
>
> 用于接收（等待）事件，线程可以等待多个事件标志。当等待的事件标志满足条件后，线程被唤醒继续执行。
> ```c
> 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`：需要等待的事件标志（例如 `1 << 3 | 1 << 5` 表示同时等待第3位和第5位的事件）。
> - `option`：等待选项，常见选项包括：
>   - `RT_EVENT_FLAG_AND`：所有指定的事件标志都满足时才唤醒线程。
>   - `RT_EVENT_FLAG_OR`：只要有一个指定的事件标志满足就唤醒线程。
>   - `RT_EVENT_FLAG_CLEAR`：收到事件标志后清除这些事件标志。
> - `timeout`：超时时间（单位为系统 tick），可以为 `RT_WAITING_FOREVER` 表示永远等待。
> - `recved`：输出参数，返回实际接收到的事件标志。
> #### 返回值：
> - 成功：返回 `RT_EOK`。
> - 失败：返回 `-RT_ETIMEOUT` 表示超时，或其他错误代码。
> #### 示例：
> ```c
> rt_uint32_t recved;
> rt_event_recv(event, (1 << 3 | 1 << 5), RT_EVENT_FLAG_OR | RT_EVENT_FLAG_CLEAR, RT_WAITING_FOREVER, &recved);
> ```
> ### 总结：
>
> - **rt_event_create**：用于动态创建事件对象。
> - **rt_event_init**：用于静态初始化事件对象。
> - **rt_event_delete**：用于删除动态事件对象。
> - **rt_event_detach**：用于卸载静态初始化的事件对象。
> - **rt_event_send**：发送事件，触发指定的事件标志位。
> - **rt_event_recv**：接收事件，线程根据等待条件阻塞，直到指定事件发生。
> - **注意动态和静态创建的事件要用不同函数操作**

## 事件集的⼯作机制:

事件集⽤于线程间的同步，可以让线程等待⼀个或多个事件的触发：

逻辑与（AND）：线程等待多个事件同时发⽣才被唤醒。

逻辑或（OR）：线程只需等待其中⼀个事件发⽣即可被唤醒。

- 线程1等待公交⻋3或公交⻋5的到来，公交⻋到站时线程被唤醒。

  

- 线程2模拟公交⻋3到站，发送事件，唤醒等待的线程。

```
/*
 * Copyright (c) 2006-2025, RT-Thread Development Team
 *
 * SPDX-License-Identifier: Apache-2.0
 *
 * Change Logs:
 * Date           Author       Notes
 * 2025-02-06     RT-Thread    first version
 */

#include <rtthread.h>

#define DBG_TAG "main"
#define DBG_LVL DBG_LOG
#include <rtdbg.h>

// 定义一个事件对象指针
rt_event_t event = RT_NULL;

// 线程1的入口函数
void thread1_entry1(void *parameter)
{
    // 定义一个变量，用于存储接收到的事件值
    rt_uint32_t received;

    // 输出线程1正在等待的公交车信息
    rt_kprintf("线程1等待公交车3或5\n");

    // 线程1等待事件
    // 等待事件3或事件5，使用或操作（RT_EVENT_FLAG_OR）和清除标志（RT_EVENT_FLAG_CLEAR）
    // 如果事件发生，清除接收到的事件
    rt_event_recv(event, (1 << 3 | 1 << 5), RT_EVENT_FLAG_OR | RT_EVENT_FLAG_CLEAR, RT_WAITING_FOREVER, &received);

    // 输出线程1接收到的事件
    rt_kprintf("线程1的第%d路公交车到了，出发\n", received);

    // 线程1延迟1秒
    rt_thread_mdelay(1000);
}

// 线程2的入口函数
void thread2_entry2(void *parameter)
{
    // 向事件对象发送事件3
    rt_event_send(event, (1 << 3));

    // 输出线程2发送的事件
    rt_kprintf("线程1的第3路公交车进站\n");
}

// 主函数
int main(void)
{
    // 创建事件对象，名称为"event"
    event = rt_event_create("event", RT_IPC_FLAG_PRIO);

    // 创建线程1
    rt_thread_t thread1 = rt_thread_create("thread1", thread1_entry1, RT_NULL, 1024, 25, 10);

    // 创建线程2
    rt_thread_t thread2 = rt_thread_create("thread2", thread2_entry2, RT_NULL, 1024, 25, 10);

    // 如果线程1创建成功，启动线程1
    if (thread1 != RT_NULL)
    {
        rt_thread_startup(thread1);
    }

    // 如果线程2创建成功，启动线程2
    if (thread2 != RT_NULL)
    {
        rt_thread_startup(thread2);
    }

    // 主函数返回0，表示正常结束
    return 0;
}
```
实现效果：
![image-20250207141625489](https://typora-write.oss-cn-beijing.aliyuncs.com/20250211172735920.png)

裁判1负责发出“准备”信号，裁判2负责发出“开始”信号。运动员需要等到两个信号都收到后才能起跑。  

```
/*
 * Copyright (c) 2006-2025, RT-Thread Development Team
 *
 * SPDX-License-Identifier: Apache-2.0
 *
 * Change Logs:
 * Date           Author       Notes
 * 2025-02-06     RT-Thread    first version
 */

#include <rtthread.h> // 包含RT-Thread的核心头文件

#define DBG_TAG "main"    // 定义日志标签
#define DBG_LVL DBG_LOG   // 定义日志级别为普通日志
#include <rtdbg.h>        // 包含调试日志的头文件

rt_event_t event = RT_NULL; // 定义一个事件对象指针，初始化为NULL

// 运动员线程，等待两个裁判的信号
void athlete_entry(void *parameter)
{
    rt_uint32_t received; // 定义变量，用于存储接收到的事件标志

    // 输出日志：等待裁判的准备和开始信号
    rt_kprintf("等待裁判的准备和开始信号\n");

    // 等待事件
    // 事件标志：裁判1（1<<1）和裁判2（1<<2）
    // 等待标志：RT_EVENT_FLAG_AND（必须同时满足两个信号）和 RT_EVENT_FLAG_CLEAR（清除接收到的信号）
    // 等待时间：无限等待（RT_WAITING_FOREVER）
    // 接收到的事件标志存储在received变量中
    rt_event_recv(event, (1 << 1 | 1 << 2), RT_EVENT_FLAG_AND | RT_EVENT_FLAG_CLEAR, RT_WAITING_FOREVER, &received);

    // 输出日志：运动员开跑
    rt_kprintf("运动员开跑\n");
}

// 裁判1线程，发送开始信号
void referee1_entry(void *parameter)
{
    // 输出日志：裁判1发送开始信号
    rt_kprintf("裁判1发送开始信号\n");

    // 发送事件给事件对象，设置裁判1的信号标志（1<<1）
    rt_event_send(event, (1 << 1));

    // 线程延迟500毫秒
    rt_thread_mdelay(500);
}

// 裁判2线程，发送准备信号
void referee2_entry(void *parameter)
{
    // 输出日志：裁判2发送准备信号
    rt_kprintf("裁判2发送准备信号\n");

    // 发送事件给事件对象，设置裁判2的信号标志（1<<2）
    rt_event_send(event, (1 << 2));

    // 线程延迟500毫秒
    rt_thread_mdelay(500);
}

// 主函数
int main(void)
{
    // 创建事件对象，名称为"event"
    event = rt_event_create("event", RT_IPC_FLAG_PRIO);

    // 创建运动员线程
    rt_thread_t thread1 = rt_thread_create("thread1", athlete_entry, RT_NULL, 1024, 25, 10);

    // 创建裁判2线程
    rt_thread_t thread2 = rt_thread_create("thread2", referee2_entry, RT_NULL, 1024, 25, 10);

    // 创建裁判1线程
    rt_thread_t thread3 = rt_thread_create("thread3", referee1_entry, RT_NULL, 1024, 25, 10);

    // 如果线程1创建成功，启动线程1
    if (thread1 != RT_NULL)
    {
        rt_thread_startup(thread1);
    }

    // 如果线程2创建成功，启动线程2
    if (thread2 != RT_NULL)
    {
        rt_thread_startup(thread2);
    }

    // 如果线程3创建成功，启动线程3
    if (thread3 != RT_NULL)
    {
        rt_thread_startup(thread3);
    }

    // 主函数返回0，表示正常结束
    return 0;
}
```
实现效果：
![image-20250207144050660](https://typora-write.oss-cn-beijing.aliyuncs.com/20250211172739463.png)