RT-Thread 事件(学习笔记)
本文参考自[野火EmbedFire]《RT-Thread内核实现与应用开发实战——基于STM32》,仅作为个人学习笔记。更详细的内容和步骤请查看原文(可到野火资料下载中心下载)
文章目录
- 事件的基本概念
- 事件的应用场景
- 事件的运作机制
- 事件控制块
- 事件函数接口
- 创建事件 rt_event_create()
- 删除事件 rt_event_delete()
- 初始化事件 rt_event_init()
- 事件接收 rt_event_recv()
- 事件发送 rt_event_send()
- 事件实验
- 实验现象
事件的基本概念
事件主要用于线程间的同步,与信号量不同,它的特点是可以实现一对多,多对多的同步。即一个线程可等待多个事件的触发:可以是其中任意一个事件唤醒线程进行事件处理的操作;也可以是几个事件都到达后才唤醒线程进行后续的处理;同样,事件也可以是多个线程同步多个事件,这种多个事件的集合可以用一个32位无符号整型变量来表示,变量的每一位代表一个事件,线程通过“逻辑与”或“逻辑或”与一个或多个事件建立关联,形成一个事件集。事件的“逻辑或”也称为是独立型同步,指的是线程与任何事件之一发生同步;事件“逻辑与”也称为是关联型同步,指的是线程与若干事件都发生同步。
——RT-Thread官方中文手册
事件的应用场景
事件主要用来同步,不能进行数据传输,所以我们可以将事件作为标志位。在裸机系统中,我们常常使用全局变量作为标志位,但在实时操作系统中,使用全局标志极容易造成程序可读性差,代码管理困难等问题。
事件使用的场合比信号量更广,既能实现一对一,还能一对多,多对多控制。
事件的运作机制
在RT-Thread实现中,每个线程都拥有一个事件信息标记,它有三个属性,分别是
RT_EVENT_FLAG_AND
(逻辑与),RT_EVENT_FLAG_OR
(逻辑或)以及RT_EVENT_FLAG_CLEAR
(清除标记)。当线程等待事件同步时,可以通过32个事件标志和这个事件信息标记来判断当前接收的事件是否满足同步调节。
如上图事件工作示意图,线程#1的事件标志中第2位和第29位被置位,如果事件信息标记位设为逻辑与,则表示线程#1只有在事件1和事件29都发生以后才会被触发唤醒,如果事件信息标记位设为逻辑或,则事件1或事件29中任意一个发生都会触发唤醒线程#1。
如果信息标记同时设置了清除标记位,则当线程#1唤醒后将主动把事件1和事件29请为0,否则事件标志将依然存在(为1)。——RT-Thread官方中文手册
事件控制块
事件的控制块只有一个主要成员,即32位的事件集合。
struct rt_event
{struct rt_ipc_object parent; /**< 继承自ipc_object类 */rt_uint32_t set; /**< 事件集合 */
};
事件函数接口
创建事件 rt_event_create()
rt_event_t
rt_event_create
(const char* name, rt_uint8_t flag);
调用该函数接口时,系统会从动态内存堆中分配事件对象,然后进行对象的初始化,IPC对象初始化,并把set设置成0。使用 RT_IPC_FLAG_PRIO 优先级 flag 创建的 IPC 对象,在多个线程等待资源时,将由优先级高的线程优先获得资源。而使用 RT_IPC_FLAG_FIFO 先进先出 flag 创建的IPC 对象,在多个线程等待资源时,将按照先来先得的顺序获得资源。
参数 | 描述 |
---|---|
name | 事件的名称 |
flag | 事件的标志 |
删除事件 rt_event_delete()
rt_err_t
rt_event_delete
(rt_event_t event);
在调用rt_event_delete函数删除一个事件对象时,应该确保该事件不再被使用。在删除前会唤醒所有挂起在该事件上的线程(线程的返回值是-RT_ERROR),然后释放事件对象占用的内存块。
参数 | 描述 |
---|---|
event | 事件对象的句柄 |
初始化事件 rt_event_init()
rt_err_t
rt_event_init
(rt_event_t event, const char* name, rt_uint8_t flag);
调用该接口时,需指定静态事件对象的句柄(即指向事件控制块的指针),然后系统会初始化事件对象,并加入到系统对象容器中进行管理。使用 RT_IPC_FLAG_PRIO 优先级 flag 创建的 IPC 对象,在多个线程等待资源时,将由优先级高的线程优先获得资源。而使用 RT_IPC_FLAG_FIFO 先进先出 flag 创建的IPC 对象,在多个线程等待资源时,将按照先来先得的顺序获得资源。
参数 | 描述 |
---|---|
event | 事件对象的句柄 |
name | 事件的名称 |
flag | 事件的标志 |
事件接收 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);
当用户调用这个接口时,系统首先根据set参数和接收选项来判断它要接收的事件是否发生,如果已经发生,则根据参数option上是否设置有RT_EVENT_FLAG_CLEAR来决定是否重置事件的相应标志位,然后返回(其中recved参数返回收到的事件); 如果没有发生,则把等待的set和option参数填入线程本身的结构中,然后把线程挂起在此事件对象上,直到其等待的事件满足条件或等待时间超过指定的超时时间。如果超时时间设置为零,则表示当线程要接受的事件没有满足其要求时就不等待,而直接返回-RT_TIMEOUT。
参数 | 描述 |
---|---|
event | 事件对象的句柄 |
set | 接收线程感兴趣的事件 |
option | 接收选项 |
timeout | 指定超时时间 |
recved | 指向收到的事件 |
事件发送 rt_event_send()
rt_err_t
rt_event_send
(rt_event_t event, rt_uint32_t set);
使用该函数接口时,通过参数set指定的事件标志来设定event对象的事件标志值,然后遍历等待在event事件对象上的等待线程链表,判断是否有线程的事件激活要求与当前event对象事件标志值匹配,如果有,则唤醒该线程。
参数 | 描述 |
---|---|
event | 事件对象的句柄 |
set | 发送的事件集 |
事件实验
要想在RT-Thread中使用事件通信,需要先修改rtconfigh
配置文件。可以使用下面两种方法:
- 取消注释,开启宏定义,
- 或者使用
Configuration Wizard
向导进行图形化配置,
本实验需要创建两个线程,分别是接收线程和发送线程,
接收线程负责接收 KEY1_EVENT 和 KEY2_EVENT 两个事件( KEY0 按下和 WK_UP 按下),RT_EVENT_FLAG_AND 表示当两个事件都发生时,才算接收到事件,RT_EVENT_FLAG_CLEAR 表示事件发生后,自动清除两个事件的标志位。
发送线程负责扫描按键,检测到按键按下,将对应事件标志位置1。
#include "board.h"
#include "rtthread.h"// 定义线程控制块指针
static rt_thread_t recv_thread = RT_NULL;
static rt_thread_t send_thread = RT_NULL;// 定义事件控制块
static rt_event_t test_event = RT_NULL;#define KEY1_EVENT (0x01 << 0)//设置事件掩码的位0
#define KEY2_EVENT (0x01 << 1)//设置事件掩码的位1/******************************************************************************
* @ 函数名 : recv_thread_entry
* @ 功 能 : 接收线程入口函数
* @ 参 数 : parameter 外部传入的参数
* @ 返回值 : 无
******************************************************************************/
static void recv_thread_entry(void *parameter)
{rt_uint32_t recved;while(1){// 等待接收事件标志rt_event_recv(test_event, // 事件对象句柄KEY1_EVENT | KEY2_EVENT, // 接收线程感兴趣的事件RT_EVENT_FLAG_AND | RT_EVENT_FLAG_CLEAR, // 接收选项RT_WAITING_FOREVER, // 超时事件一直等&recved); // 接收到的事件if(recved == (KEY1_EVENT | KEY2_EVENT)){rt_kprintf("recv:KEY0与WK_UP都被按下\n");LED0_TOGGLE; // LED0 反转}else{rt_kprintf("事件错误!\n");}}
}/******************************************************************************
* @ 函数名 : send_thread_entry
* @ 功 能 : 发送线程入口函数
* @ 参 数 : parameter 外部传入的参数
* @ 返回值 : 无
******************************************************************************/
static void send_thread_entry(void *parameter)
{while(1){// KEY0 被按下if(Key_Scan(KEY0_GPIO_PORT, KEY0_GPIO_PIN) == KEY_ON){rt_kprintf("send:KEY0被单击\n");// 发送事件1rt_event_send(test_event, KEY1_EVENT); }// WK_UP 被按下if(Key_Scan(WK_UP_GPIO_PORT, WK_UP_GPIO_PIN) == KEY_ON){rt_kprintf("send:WK_UP被单击\n");// 发送事件1rt_event_send(test_event, KEY2_EVENT); }rt_thread_delay(20); //每20ms扫描一次}
}int main(void)
{// 硬件初始化和RTT的初始化已经在component.c中的rtthread_startup()完成// 创建一个事件test_event = // 事件控制块指针rt_event_create("test_event", // 事件初始值RT_IPC_FLAG_FIFO); // FIFO队列模式(先进先出)if(test_event != RT_NULL)rt_kprintf("事件创建成功!\n");// 创建一个动态线程recv_thread = // 线程控制块指针rt_thread_create("recv", // 线程名字recv_thread_entry, // 线程入口函数RT_NULL, // 入口函数参数255, // 线程栈大小5, // 线程优先级10); // 线程时间片// 开启线程调度if(recv_thread != RT_NULL)rt_thread_startup(recv_thread);elsereturn -1;// 创建一个动态线程send_thread = // 线程控制块指针rt_thread_create("send", // 线程名字send_thread_entry, // 线程入口函数RT_NULL, // 入口函数参数255, // 线程栈大小5, // 线程优先级10); // 线程时间片// 开启线程调度if(send_thread != RT_NULL)rt_thread_startup(send_thread);elsereturn -1;
}
实验现象
当 KEY0 和 WK_UP 都被按下后,接收端打印两个“按键都被按下“。
本实验中检测两个按键被依次按下这种场景并不常见,只是为了演示事件的运作机制。
RT-Thread 事件(学习笔记)相关推荐
- STM32 + RT Thread OS 学习笔记[五]
1. 触摸屏驱动 触摸屏驱动的原理非常简单,从硬件得到坐标数据,数据加工(适配屏幕分辨率,偏移量调整),最后调用rtgui_server_post_event()函数向GUI服务端发送坐标信息. 奋 ...
- C#委托与事件学习笔记
今天跟随视频学习了一下C#中最重要的一些概念之委托与事件.老杨的视频讲的还是挺深入浅出,不过刚接触C#.NET的人还是朦朦胧胧,就像张子阳先生说的"每次见到委托和事件就觉得心里别(biè)得 ...
- 线程(Thread)的学习笔记
本文是对b站狂神说java多线程的学习总结,附上b站链接https://www.bilibili.com/video/BV1V4411p7EF?spm_id_from=333.999.0.0& ...
- Yii2 事件学习笔记
Yii2中事件一般用event表示,只有集成了yii\base\component的对象才能集成类或者对象级别的事件处理过程. 事件的理解和使用要点主要有以下几个要点: 1.事件如何触发? 2.事件处 ...
- Windows事件等待学习笔记(四)—— 事件信号量互斥体
Windows事件等待学习笔记(四)-- 事件&信号量&互斥体 要点回顾 事件 实验:验证SignalState 第一步:编译并运行以下代码 第二步:观察结果 第三步:修改代码并执行 ...
- Windows事件等待学习笔记(二)—— 线程等待与唤醒
Windows事件等待学习笔记(二)-- 线程等待与唤醒 要点回顾 等待与唤醒机制 可等待对象 可等待对象的差异 线程与等待对象 一个线程等待一个对象 实验 第一步:编译并运行以下代码 第二步:在Wi ...
- Caliburn.Micro学习笔记(三)----事件聚合IEventAggregator和 IhandleT
Caliburn.Micro学习笔记目录 今天 说一下Caliburn.Micro的IEventAggregator和IHandle<T>分成两篇去讲这一篇写一个简单的例子 看一它的的实现 ...
- THREAD APC 《寒江独钓》内核学习笔记(4)
继续学习windows 中和线程有关系的数据结构: ETHREAD.KTHREAD.TEB 1. 相关阅读材料 <windows 内核原理与实现> --- 潘爱民 2. 数据结构分析 我们 ...
- 【vn.py学习笔记(三)】vn.py事件引擎 学习笔记
[vn.py学习笔记(三)]vn.py事件引擎 学习笔记 1 时间驱动 2 事件驱动 3 事件引擎工作流程 4 事件引擎结构 4.1 事件队列 4.2 事件处理线程 4.3 事件处理函数字典/通用事件 ...
- [Systemverilog学习笔记] Thread Communication-Event、Semaphore、mailbox
[Systemverilog学习笔记] Thread Communication-Event.Semaphore.mailbox 学习目标: 通过下文了解Event.Semaphore.mailbox ...
最新文章
- Node.js 报语法错误 SyntaxError: Unexpected identifier
- python运维之轻松模拟开发FTP软件05
- spring下redis开发环境搭建
- 来了,2020年湖南省电赛获奖名单!有你学校吗?
- AES 加密256位 错误 java.security.InvalidKeyException: Illegal key size or default parameters
- php字符串学习笔记
- mysql 查新格式化_mysql 日期格式化查询
- Android Studio 无法浏览插件市场
- flask数据库sqlalchemy查询
- java类输出_java的输出类
- retrofit与rxjava使用
- VSCode插件,TODO标记
- 某电商App 返回数据加密解密分析(四)
- Linux学习06--进程
- RRC连接、RL、RB、RAB的本质是什么?
- 解决Error: EPERM: operation not permitted, mkdir
- 下载原版百度文库资料
- 饿了么UI图片上传的实现
- 【免费分享】2020-2021年广告营销类行业报告集合(149份)
- 鲁大师12月新机性能榜:跑分116万,小米12 Pro夺冠