rtthread 事件集 学习笔记
概述
学校组织秋游,组长在等待:
- 张三:我到了
- 李四:我到了
- 王五:我到了
- 组长说:好,大家都到齐了,出发!
秋游回来第二天就要提交一篇心得报告,组长在焦急等待:张三、李四、王五谁先写好就交谁的。
在这个日常生活场景中:
- 出发:要等待这 3 个人都到齐,他们是"与"的关系
- 交报告:只需等待这 3 人中的任何一个,他们是"或"的关系
在 RT-Thread 中,可以使用事件集(event group)来解决这些问题。
特性与操作
事件集可以简单地认为就是一个整数:
- 每一位表示一个事件
- 每一位事件的含义由程序员决定,比如:Bit0 表示用来串口是否就绪,Bit1 表示按键是否被按下
- 这些位,值为 1 表示事件发生了,值为 0 表示事件没发生
- 一个或多个线程、ISR 都可以去写这些位;一个或多个线程、ISR 都可以去读这些位
- 可以等待某些位中的任意一个,也可以等待多位
每个线程都有一个 rt_thread 结构体,它里面有如下 2 个成员:
struct rt_thread
{
......
##if defined(RT_USING_EVENT)
/* thread event */
rt_uint32_t event_set;
rt_uint8_t event_info;
##endif
......
}
这两个成员的作用如下:
- event_set:想等待哪些事件?
- 可以设置对应的位,比如设置为(1<<30) | (1<<0)表示等待事件 0、事件 30
- 那么,它想等待事件 0、事件 30 都发生呢,还是只要事件 0、事件 30 任意一个发生即可?
- 需要使用 event_info 进一步描述
-
event_info:有 3 种取值
- RT_EVENT_FLAG_AND:逻辑与,比如事件 0、事件 30 都发生时,才满足它 的期待
- RT_EVENT_FLAG_OR:逻辑或,比如事件 0、事件 30 发生了任何一个,都满足它的期待
- RT_EVENT_FLAG_CLEAR:等到期待的事件后,是否清除事件
事件集和队列、信号量等不太一样,主要集中在 2 个地方:
- 唤醒谁?
- 队列、信号量:事件发生时,只会唤醒一个线程
- 事件集:事件发生时,会唤醒所有符号条件的线程,简单地说它有"广播"的 作用
-
是否清除事件?
- 队列、信号量:是消耗型的资源,队列的数据被读走就没了;信号量被获取后就减少了
- 事件集:被唤醒的线程有两个选择,可以让事件保留不动,也可以清除事件
事件集的常规操作如下:
-
先创建事件集
-
线程 C、D 等待事件:
- 等待什么事件?可以等待某一位、某些位中的任意一个,也可以等待多位。简单地说就是"或"、"与"的关系。
- 得到事件时,要不要清除?可选择清除、不清除。
-
线程 A、B 产生事件:设置事件集里的某一位、某些位
事件集函数
使用事件集之前,要先创建,得到一个句柄;使用事件集时,要使用句柄来表明使用
哪个事件集。
事件集的创建有两种方法:动态分配内存、静态分配内存,
- 动态分配内存:rt_event_create,从对象管理器中分配一个 event 对象,并初始化这个对象
- 静态分配内存:rt_event_init,事件集在编译时由编译器分配
rt_event_create()函数原型如下:
rt_event_t rt_event_create(const char* name, rt_uint8_t flag);
参数
|
说明
|
name
|
事件集名称
|
flag
|
事件集标志,可选: RT_IPC_FLAG_FIFO 或 RT_IPC_FLAG_PRIO
|
返回值
|
事件集句柄:成功,返回句柄,以后使用句柄来操作事件集
RT_NULL:失败
|
rt_event_init()函数原型如下:
rt_err_t rt_event_init(rt_event_t event, const char* name, rt_uint8_t flag);
参数
|
说明
|
mutex
|
事件集对象的句柄
|
name
|
事件集名称
|
flag
|
事件集标志,可选: RT_IPC_FLAG_FIFO 或 RT_IPC_FLAG_PRIO
|
返回值
|
RT_EOK:成功
|
删除/脱离
不再使用一个事件集时:
- 删除它:rt_event_delete(),只能删除使用 rt_event_create()创建的事件集
- 脱离它:rt_event_detach(),只能脱离使用 rt_event_init()初始化的事件集
删除事件集的函数为 rt_event_delete(),它会释放内存。原型如下:
rt_err_t rt_event_delete(rt_event_t event);
删除事件集时,如果有线程在等待该事件集,则内核会先唤醒这些线程(线程返回值是 - RT_ERROR),然后再释放事件集使用的内存,最后删除事件集对象。
脱离事件集,就是将事件集对象被从内核对象管理器中脱离。原型如下:
rt_err_t rt_event_detach(rt_event_t event);
脱离事件集时,如果有线程在等待该事件集,则内核会先唤醒这些线程(线程返回值是 - RT_ERROR)。
发送/接收事件
RT-Thread 提供发送事件和接收事件函数:
- rt_event_send() 发送一个或多个事件
- rt_event_recv() 接收事件,最多同时接收 32 个事件
发送事件的函数 rt_event_send()原型如下:
rt_err_t rt_event_send(rt_event_t event, rt_uint32_t set);
使用 rt_event_send()函数发送事件,也就是设置事件,设置哪些事件?参数 set 的每一位表示一个事件。
该函数设置事件后,会遍历等待此事件的线程,如果满足了线程期待的事件,则唤醒该线程。
参数
|
说明
|
event
|
事件集对象的句柄
|
set
|
发送哪些事件
|
返回值
|
RT_EOK:获取互斥量成功
|
接收事件的函数 rt_event_recv()原型如下:
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);
使用 rt_event_recv()函数来接收事件,通过参数 set 和参数 option 来判断接收事件是否已经发生:
set:想接收哪些事件
option:想接收这些事件里的所有事件还是任意一个事件?成功后要不要清除事件?
如果期待的事件没有发生,则挂起线程,直到事件发生或者超时。如果超时,线程退出返回-RT_ETIMEOUT。
如果期待的事件已经发生,根据参数 option 是否设置有 RT_EVENT_FLAG_CLEAR 来决定是否重置事件的相应标志位。
参数
| 说明 |
event
| 事件集对象的句柄 |
set
| 期待哪些事件 |
option
|
接收选项: RT_EVENT_FLAG_OR :逻辑或
RT_EVENT_FLAG_AND:逻辑与
RT_EVENT_FLAG_CLEAR:清除重置
|
timeout
| 指定超时时间 |
recved
| 指向接收到的事件 |
返回值
|
RT_EOK:接收成功
RT_ETIMEOUT:接收超时
RT_ERROR:接收错误
|