本文包含源代码、原理图、PCB、封装库、中英文PDF等资源
您需要 登录 才可以下载或查看,没有账号?立即注册
x
前言
本文学习RT-Thread的事件集,事件集也是线程间同步的机制之一,一个事件集可以包含多个事件,利用事件集可以完成一对多,多对多的线程间同步。将理论讲事件集的工作机制以及相关函数,后面用RTT&正点原子联合出品的潘多拉开发板进行实验。
一、事件集的工作机制
1、一个线程与多个事件的关系可设置为:其中任意一个事件唤醒线程,或几个事件都到达后才唤醒线程进行后续的处理;同样,事件也可以是多个线程同步多个事件。
2、RT-Thread 定义的事件集有以下特点:(1)事件只与线程相关,事件间相互独立。(2)事件仅用于同步,不提供数据传输功能;(3)事件无排队性,即多次向线程发送同一事件 (如果线程还未来得及读走),其效果等同于只发送一次。
3、每个线程都拥有一个事件信息标记,它有三个属性,分别是 RT_EVENT_FLAG_AND(逻辑与),RT_EVENT_FLAG_OR(逻辑或)以及 RT_EVENT_FLAG_CLEAR(清除标记)。当线程等待事件同步时,可以通过 32 个事件标志和这个事件信息标记来判断当前接收的事件是否满足同步条件。 如上图所示,线程 #1 的事件标志中第 1 位和第 30 位被置位,如果事件信息标记位设为逻辑与,则表示线程 #1 只有在事件 1 和事件 30 都发生以后才会被触发唤醒,如果事件信息标记位设为逻辑或,则事件 1 或事件 30 中的任意一个发生都会触发唤醒线程 #1。如果信息标记同时设置了清除标记位,则当线程 #1 唤醒后将主动把事件 1 和事件 30 清为零,否则事件标志将依然存在(即置 1)。
事件集工作示意图(来源RT-Thread编程指南)
二、事件集的相关函数
1、创建动态事件集函数
当创建一个事件集时,内核首先创建一个事件集控制块,然后对该事件集控制块进行基本的初始化。
- rt_event_t rt_event_create(const char* name, rt_uint8_t flag);
复制代码
(1)入口参数:
name:事件集的名称。
flag:事件集的标志,它可以取如下数值:RT_IPC_FLAG_FIFO 或RT_IPC_FLAG_PRIO。
(2)返回值:
RT_NULL:创建失败。
事件对象的句柄:创建成功。
2、删除动态事件函数
系统不再使用 rt_event_create() 创建的事件集对象时,通过删除事件集对象控制块来释放系统资源。在删除一个事件集对象时,应该确保该事件集不再被使用。在删除前会唤醒所有挂起在该事件集上的线程(线程的返回值是RT_ERROR),然后释放事件集对象占用的内存块。
- rt_err_t rt_event_delete(rt_event_t event);
复制代码
(1)入口参数:
event:事件集对象的句柄。
(2)返回值:
RT_EOK:成功。
3、创建静态事件集函数
这里所说的创建静态事件集和《RT-Thread编程指南》所说的初始化事件集是一样的,静态事件集对象的内存是在系统编译时由编译器分配的,一般放于读写数据段或未初始化数据段中。
- rt_err_t rt_event_init(rt_event_t event, const char* name, rt_uint8_t flag);
复制代码
(1)入口参数:
event:事件集对象的句柄。
name:事件集的名称。
flag:事件集的标志,它可以取如下数值:RT_IPC_FLAG_FIFO 或 RT_IPC_FLAG_PRIO。
(2)返回值:
RT_EOK:成功。
4、删除静态事件集函数
当系统不再使用 rt_event_init() 初始化的事件集对象时,通过脱离事件集对象控制块来释放系统资源,系统首先唤醒所有挂在该事件集等待队列上的线程(线程的返回值是RT_ERROR),然后将该事件集从内核对象管理器中脱离。
- rt_err_t rt_event_detach(rt_event_t event);
复制代码
(1)入口参数:
event:事件集对象的句柄。
(2)返回值:
RT_EOK:成功。
5、发送事件函数
发送事件函数可以发送事件集中的一个或多个事件,使用该函数接口时,通过参数 set 指定的事件标志来设定 event 事件集对象的事件标志值,然后遍历等待在 event 事件集对象上的等待线程链表,判断是否有线程的事件激活要求与当前 event 对象事件标志值匹配,如果有,则唤醒该线程。
- rt_err_t rt_event_send(rt_event_t event, rt_uint32_t set);
复制代码
(1)入口参数:
event:事件集对象的句柄。
set:发送的一个或多个事件的标志值。
(2)返回值:
RT_EOK:成功。
6、接收事件函数
内核使用 32 位的无符号整数来标识事件集,它的每一位代表一个事件,因此一个事件集对象可同时等待接收 32 个事件,内核可以通过指定选择参数 “逻辑与” 或 “逻辑或” 来选择如何激活线程,使用 “逻辑与” 参数表示只有当所有等待的事件都发生时才激活线程,而使用 “逻辑或” 参数则表示只要有一个等待的事件发生就激活线程。
系统首先根据 set 参数和接收选项 option 来判断它要接收的事件是否发生,如果已经发生,则根据参数 option 上是否设置有 RT_EVENT_FLAG_CLEAR 来决定是否重置事件的相应标志位,然后返回(其中 recved 参数返回接收到的事件);如果没有发生,则把等待的 set 和 option 参数填入线程本身的结构中,然后把线程挂起在此事件上,直到其等待的事件满足条件或等待时间超过指定的超时时间。如果超时时间设置为零,则表示当线程要接受的事件没有满足其要求时就不等待,而直接返回RT_ETIMEOUT。
- 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);
复制代码
(1)入口参数:
event:事件集对象的句柄。
set:接收线程感兴趣的事件。
option:接收选项。
timeout:指定超时时间。
recved:指向接收到的事件。
(2)返回值:
RT_EOK:成功。
RT_ETIMEOUT:超时。
RT_ERROR:错误。
三、基于STM32的事件集示例
前面讲了RT-Thread事件集的一些理论知识,光说不练都是假把式,那么接下来进行实际的操作。创建一个事件集和两个线程,一个线程用于发送事件,另一个线程用于接收事件,实体RTT&正点原子联合出品潘多拉开发板进行实验,实现事件标志位或触发线程、事件标志位与触发线程,通过按键来发送事件标志位,或触发时蜂鸣器响一下,与触发RGB红灯亮一下。
1、实现代码
- #include "rtthread.h"
- #include "event_app.h"
- #include "led.h"
- #include "beep.h"
- #include "key.h"
-
-
- /* 线程句柄 */
- static rt_thread_t thread1 = NULL;
- static rt_thread_t thread2 = NULL;
-
- /* 事件集句柄 */
- static rt_event_t event1 = NULL;
-
- #define EVENT_FLAG3 (1 << 3)
- #define EVENT_FLAG5 (1 << 5)
-
- /**************************************************************
- 函数名称 : thread1_recv_event
- 函数功能 : 线程1入口函数,用于接收事件
- 输入参数 : parameter:入口参数
- 返回值 : 无
- 备注 : 无
- **************************************************************/
- void thread1_recv_event(void *parameter)
- {
- rt_uint32_t recved;
- while(1)
- {
- /* 事件3或事件5任意一个可以触发线程,接收完后清除事件标志 */
- if(RT_EOK == rt_event_recv(event1,
- (EVENT_FLAG3 | EVENT_FLAG5),
- RT_EVENT_FLAG_OR | RT_EVENT_FLAG_CLEAR,
- RT_WAITING_FOREVER,
- &recved))
- {
- rt_kprintf("thread1: OR recv event 0x%x\n", recved);
- BEEP(1);
- rt_thread_mdelay(300);
- BEEP(0);
- rt_thread_mdelay(300);
- }
-
- rt_thread_mdelay(1000);
-
- /* 事件3和事件5都触发才可以触发线程,接收完后清除事件标志 */
- if(RT_EOK == rt_event_recv(event1,
- (EVENT_FLAG3 | EVENT_FLAG5),
- RT_EVENT_FLAG_AND | RT_EVENT_FLAG_CLEAR,
- RT_WAITING_FOREVER,
- &recved))
- {
- rt_kprintf("thread1: AND recv event 0x%x\n", recved);
- LED_R(0);
- rt_thread_mdelay(1000);
- LED_R(1);
- rt_thread_mdelay(1000);
- }
- }
- }
-
- /**************************************************************
- 函数名称 : thread2_send_event
- 函数功能 : 线程2入口函数,用于发送事件
- 输入参数 : parameter:入口参数
- 返回值 : 无
- 备注 : 无
- **************************************************************/
- void thread2_send_event(void *parameter)
- {
- u8 key;
-
- while(1)
- {
- key = key_scan(0);
- if( key== KEY0_PRES)
- {
- rt_event_send(event1, EVENT_FLAG3);
- rt_kprintf("RT-Thread send EVENT_FLAG3\r\n");
- }
- else if(key == KEY1_PRES)
- {
- rt_event_send(event1, EVENT_FLAG5);
- rt_kprintf("RT-Thread send EVENT_FLAG5\r\n");
- }
- rt_thread_mdelay(1);
- }
- }
-
- void rtthread_event_test(void)
- {
- event1 = rt_event_create("event1", RT_IPC_FLAG_PRIO);/* FIFO模式 */
-
- /* 创建事件集 */
- if(event1 != RT_NULL)
- {
- rt_kprintf("RT-Thread create event successful\r\n");
- }
-
- else
- {
- rt_kprintf("RT-Thread create event failed\r\n");
- return;
- }
-
- thread1 = rt_thread_create("thread1",
- thread1_recv_event,
- NULL,
- 512,
- 3,
- 20);
-
- if(thread1 != RT_NULL)
- {
- rt_thread_startup(thread1);;
- }
- else
- {
- rt_kprintf("create thread1 failed\r\n");
- return;
- }
-
- thread2 = rt_thread_create("thread2",
- thread2_send_event,
- NULL,
- 512,
- 2,
- 20);
-
- if(thread2 != RT_NULL)
- {
- rt_thread_startup(thread2);;
- }
- else
- {
- rt_kprintf("create thread2 failed\r\n");
- return;
- }
- }
-
复制代码
2、观察Finsh
(1)当按下KEY0或KEY1时,发送事件标志FLAG3或FLAG5,这个时候蜂鸣器响:
(2)当按下KEY0和KEY1时,发送事件标志FLAG3和FLAG5,这个时候RGB红灯亮一下:
参考文献:
1、[野火®]《RT-Thread 内核实现与应用开发实战—基于STM32》
2、《RT-THREAD 编程指南》
|