智能家居DIY连载教程(2)——在实际项目中运用消息队列与邮箱
前言
千呼万唤始出来,智能家居 DIY 教程连载第二篇终于登场了!本文将重点给大家介绍如何将消息队列与邮箱运用到实际项目中去。一起来看看吧~
DIY
回顾上期:
1、智能家居DIY连载教程(1)——如何正确使用 Sensor 框架
1. 第二周任务回顾
我们来回顾一下第二周安排的任务:
1、通过 ENV 工具获取 nrf24l01软件包,并加载到 MDK 工程里面
2、了解多线程间的通信,了解 IPC 中邮箱和消息队列的特性,并能灵活使用,实现 ds18b20 线程与 nrf24l01 线程之间的数据通信
3、修改 nrf24l01 软件包,实现多点通信功能
上述任务的重点,是要学习去灵活运用邮箱和消息队列。
2. 软件包的获取
软件包可以通过 env 工具十分方便的获取到,并且加载到工程里面去,env 工具的下载链接可以在官网找到,env 下载链接:https://www.rt-thread.org/page/download.html (请复制至外部浏览器打开)env 的使用方法可以查看这里:https://www.rt-thread.org/document/site/tutorial/env-video/进行学习。
值得注意的是,在 env 中获取软件包是需要依赖于 git 的,可以去 git 官网获得下载,git 官网
本周任务中,我们需要用到 nrf24l01 的软件包,只需要在 menuconfig 中选中 nrf24l01 即可:
1RT-Thread online packages --->
2 peripheral libraries and drivers --->
3 [*] nRF24L01: Single-chip 2.4GHz wireless transceiver. --->
选中之后需要将该软件包获取到本地来,在 env 中输入 pkgs --update
命令回车即可。我们在工程目录的 packages
目录下,可以看到,nrf24l01 软件包被获取到本地来了,如下图所示:
不过该软件包现在仅仅只是获取到本地,尚未加载到 MDK 工程当中来。我们在 env 中输入 scons --target=mdk5
命令回车即可,执行完该命令之后打开 MDK5 工程,发现 nrf24l01 软件包成功加载到工程里面去了,如下图所示:
3. IPC 之邮箱实战指南
3.1 为什么要使用邮箱
我们需要通过 nrf24l01 无线模块进行数据发送与接收,定义:通过 nrf24l01 发送数据的是发送节点,通过 nrf24l01 接收数据的是接收节点。(本 DIY 整个项目需要至少用到两个发送节点。)
在发送节点创建一个线程,用于无线发送数据。具体的,nrf24l01 的软件包提供了哪些 API,是如何通过这些 API 实现发送功能的,可以参考该软件包的 samples,路径为:...\packages\nrf24l01-latest\examples。
还记得第一周的任务吗?在 main 函数中创建了一个线程,用于获取 ds18b20 温度数据的。同理的,我们在 main 函数中再创建一个线程,该线程是用来通过 nrf24l01 发送数据的,线程入口函数是 nrf24l01_send_entry
:
????滑查看全部
1int main(void)2{3 rt_thread_t ds18b20_thread, nrf24l01_thread;45 ds18b20_thread = rt_thread_create("18b20tem", read_temp_entry, "temp_ds18b20",6 640, RT_THREAD_PRIORITY_MAX / 2, 20);7 if (ds18b20_thread != RT_NULL)8 {9 rt_thread_startup(ds18b20_thread);
10 }
11
12 nrf24l01_thread = rt_thread_create("nrfsend", nrf24l01_send_entry, RT_NULL,
13 1024, RT_THREAD_PRIORITY_MAX / 2, 20);
14 if (nrf24l01_thread != RT_NULL)
15 {
16 rt_thread_startup(nrf24l01_thread);
17 }
18
19 return RT_EOK;
20}
这时候,我们的程序当中就存在了两个线程了,ds18b20_thread
线程用来获取温度数据,nrf24l01_thread
线程用来向外无线发送温度数据,那么问题来了:
1、ds18b20_thread
线程如何将温度数据给 nrf24l01_thread
线程?
2、如果ds18b20_thread
线程采集温度数据过快,nrf24l01_thread
线程来不及发送,怎么办?
3、如果nrf24l01_thread
线程发送数据过快,ds18b20_thread
线程来不及采集温度数据,怎么办?
这时候,IPC 中的邮箱(mailbox)可以很好的解决以上问题。不过这里,我们需要将邮箱与内存池(mempool)搭配一起使用。往往而言,在实际项目中,邮箱和内存池这两个 IPC 是经常需要配套着一起使用的。为什么,且慢慢看来。
3.2 邮箱工作原理举例介绍
RT-Thread 的文档中心已经有对邮箱和内存池原理上的详细讲解。这里不在赘述。这里通过举一个生活中的例子,去帮助大家理解邮箱和内存池。
如今,很多人购物都是通过电商平台购买,那避免不了是要收快递的。
图片来源网络
我们拟定一个生活场景。小区内放置有快递柜,快递柜里面有很多快递箱,快递箱里面可以存放快递,快递员把快递存放到快递箱之后,会发短信通知你过来取快递,还会告诉你编号是多少,通过编号你可以找到你的快递存放在快递柜的哪个快递箱里面。
上面这个模型中有几个名词,我们抽取出来:快递、快递柜、快递箱、快递员、你自己、短信、编号。
我们将上面这个生活场景和 IPC 中的邮箱和内存池一一对应起来:
快递:采集到的温度数据
快递柜:内存池
快递箱:内存池里面的内存块
快递员:
ds18b20_thread
线程你自己:
nrf24l01_thread
线程短信:邮箱中的一封邮件
编号:内存块地址指针
邮箱和内存池的使用,其实和上面那个收快递的生活场景是一样的:
在程度的一开始,即 main 函数中,我们创建一个邮箱和一个内存池
在
ds18b20_thread
线程里:1、首先,每当该线程采集到一个温度数据(有快递来了),就在内存池里面申请一个内存块(快递员找一个空快递箱)
2、然后,把本次采集到的这个温度数据放到内存块里(快递放入快递箱),再把内存块的地址放在邮箱里
3、最后,邮件发送出去(发短信告知用户)
在
nrf24l01_thread
线程里:1、首先,接收
ds18b20_thread
线程发送过来的邮件(用户收到短信)2、然后,根据邮箱中的存放的地址,知道了当前温度数据存放在哪个内存块里面,也就是说,
nrf24l01_thread
线程找到了(收到了)当前温度数据。(根据短信里的编号知道快递放在哪里了)3、最后,用完了这个内存块要及时释放掉(快递取出来了,快递箱空了)
3.3 在项目中运用邮箱
通过代码解读一下。
main 函数中创建一个邮箱和一个内存池是这么做的:
????滑查看全部
1tmp_msg_mb = rt_mb_create("temp_mb0", MB_LEN, RT_IPC_FLAG_FIFO); /* 创建邮箱 */
2tmp_msg_mp = rt_mp_create("temp_mp0", MP_LEN, MP_BLOCK_SIZE); /* 创建内存池 */
ds18b20_thread
线程的入口函数是 read_temp_entry
,如下:
1static void read_temp_entry(void *parameter)2{3 struct tmp_msg *msg;4 rt_device_t dev = RT_NULL;5 rt_size_t res;67 dev = rt_device_find(parameter);8 if (dev == RT_NULL)9 {
10 rt_kprintf("Can't find device:%s\n", parameter);
11 return;
12 }
13
14 if (rt_device_open(dev, RT_DEVICE_FLAG_RDWR) != RT_EOK)
15 {
16 rt_kprintf("open device failed!\n");
17 return;
18 }
19 rt_device_control(dev, RT_SENSOR_CTRL_SET_ODR, (void *)100);
20
21 while (1)
22 {
23 res = rt_device_read(dev, 0, &sensor_data, 1);
24 if (res != 1)
25 {
26 rt_kprintf("read data failed!size is %d\n", res);
27 rt_device_close(dev);
28 return;
29 }
30 else
31 {
32 /* 申请一块内存 要是内存池满了 就挂起等待 */
33 msg = rt_mp_alloc(tmp_msg_mp, RT_WAITING_FOREVER);
34 msg->timestamp = sensor_data.timestamp;
35 msg->int_value = sensor_data.data.temp;
36 rt_mb_send(tmp_msg_mb, (rt_ubase_t)msg);
37 msg = NULL;
38 }
39 rt_thread_mdelay(100);
40 }
41}
在上述代码中,该线程采集到一个温度数据之后,就会在内存池中申请内存块:
1msg = rt_mp_alloc(tmp_msg_mp, RT_WAITING_FOREVER);
将温度数据存放到刚刚申请到的内存块里面:
1msg->int_value = sensor_data.data.temp;
将这个存放着温度数据的内存块的地址给邮箱,然后发送邮件:
1rt_mb_send(tmp_msg_mb, (rt_ubase_t)msg);
nrf24l01_thread
线程的入口函数是 nrf24l01_send_entry
,如下:
1static void nrf24l01_send_entry(void *parameter)2{3 struct tmp_msg *msg;4 struct hal_nrf24l01_port_cfg halcfg;5 nrf24_cfg_t cfg;6 uint8_t rbuf[32 + 1] = {0};7 uint8_t tbuf[32] = {0};89 nrf24_default_param(&cfg);
10 halcfg.ce_pin = NRF24L01_CE_PIN;
11 halcfg.spi_device_name = NRF24L01_SPI_DEVICE;
12 cfg.role = ROLE_PTX;
13 cfg.ud = &halcfg;
14 cfg.use_irq = 0;
15 nrf24_init(&cfg);
16
17 while (1)
18 {
19 rt_thread_mdelay(100);
20
21 if (rt_mb_recv(tmp_msg_mb, (rt_ubase_t*)&msg, RT_WAITING_FOREVER) == RT_EOK)
22 {
23 if (msg->int_value >= 0)
24 {
25 rt_sprintf((char *)tbuf, "temp:+%3d.%dC, ts:%d",
26 msg->int_value / 10, msg->int_value % 10, msg->timestamp);
27 }
28 else
29 {
30 rt_sprintf((char *)tbuf, "temp:-%2d.%dC, ts:%d",
31 msg->int_value / 10, msg->int_value % 10, msg->timestamp);
32 }
33 rt_kputs((char *)tbuf);
34 rt_kputs("\n");
35 rt_mp_free(msg); /* 释放内存块 */
36 msg = RT_NULL; /* 请务必要做 */
37 }
38 if (nrf24_ptx_run(rbuf, tbuf, rt_strlen((char *)tbuf)) < 0)
39 {
40 rt_kputs("Send failed! >>> ");
41 }
42 }
43}
在上述代码中,nrf24l01 软件包提供了发送数据的 API nrf24_ptx_run
。
该线程接收ds18b20_thread
线程发送过来的邮件,并收到了温度数据:
1rt_mb_recv(tmp_msg_mb, (rt_ubase_t*)&msg, RT_WAITING_FOREVER)
将温度数据发送出去:
1nrf24_ptx_run(rbuf, tbuf, rt_strlen((char *)tbuf))
用完的内存块释放掉:
1rt_mp_free(msg);
2msg = RT_NULL;
还有两个问题没有解答:
1、如果ds18b20_thread
线程采集温度数据过快,nrf24l01_thread
线程来不及发送,怎么办?
2、如果nrf24l01_thread
线程发送数据过快,ds18b20_thread
线程来不及采集温度数据,怎么办?
这两个问题其实就是解决供过于求和供不应求的问题。
有没有留意到,申请内存块的代码上有一个 RT_WAITING_FOREVER
,接收邮件的代码上也有一个 RT_WAITING_FOREVER
。
这两个 RT_WAITING_FOREVER
就是用来解决上面两个问题的。
当内存池满了的时候,再也申请不到内存块了,这时候申请内存块里面的 RT_WAITING_FOREVER
会使得 ds18b20_thread
线程阻塞,并挂起,然后 MCU 就会去干别的事情去了,不断的在 nrf24l01_thread
线程中发送存放在内存池中的温度数据,并释放掉内存块。等一有内存块可以申请了,ds18b20_thread
线程被唤醒,又会往里面塞数据了。
同理的,如果内存池是空的,里面没有数据,接收邮件里面的 RT_WAITING_FOREVER
会使得nrf24l01_thread
线程阻塞,并挂起,然后 MCU 就会去干别的事情去了,在 ds18b20_thread
线程中采集温度,并申请内存块塞数据进去,内存块一旦有数据,就会发邮箱,另外一边一有邮箱收到了,就又开始工作了。
4. IPC 之消息队列实战指南
4.1 为什么要使用消息队列
在本次 DIY 中,消息队列其实也是用来解决以下问题的:
ds18b20_thread
线程如何将温度数据给nrf24l01_thread
线程?如果
ds18b20_thread
线程采集温度数据过快,nrf24l01_thread
线程来不及发送,怎么办?
4.2 消息队列工作原理举例介绍
消息队列一般来说,不需要搭配内存池一起使用,因为消息队列创建的时候会申请一段固定大小的内存出来,作用其实和邮箱+内存池是一样的。
每个消息队列对象中包含着多个消息框,每个消息框可以存放一条消息,每个消息框的大小是一样的,存放的消息大小不能超过消息框的大小,即可以相同,可以小于。
类比生活中的例子,存钱罐,发工资了(纸币),把一张张的钱放到存钱罐里去,你自己或者你对象需要花钱了,就从存钱罐里面取钱出来用。
更多对消息队列的讲解,请查看 RT-Thread 文档中心,链接:https://www.rt-thread.org/document/site/programming-manual/ipc2/ipc2/#_13。
4.3 在项目中运用消息队列
因为文章篇幅原因,这里不把代码放出来了,可打开工程查看。工程源码是已经开源了的,链接会在下面给出。
在 main 函数中创建消息队列:
1tmp_msg_mq = rt_mq_create("temp_mq", MQ_BLOCK_SIZE, MQ_LEN, RT_IPC_FLAG_FIFO);
在 ds18b20_thread
线程中转载数据并发送消息队列:
1msg.int_value = sensor_data.data.temp;
2rt_mq_send(tmp_msg_mq, &msg, sizeof msg);
在 nrf24l01_thread
线程中接收消息队列:
1rt_mq_recv(tmp_msg_mq, &msg, sizeof msg, RT_WAITING_FOREVER)
上面这个 RT_WAITING_FOREVER
作用是为了解决下面这个问题,原理和邮箱中的介绍一样:
如果ds18b20_thread
线程采集温度数据过快,nrf24l01_thread
线程来不及发送,怎么办?
即而当消息队列是空的时候,可以挂起读取线程。当有新的消息到达时,挂起的线程将被唤醒以接收并处理消息。
5. nrf24l01 的多通道数据接收
nrf24l01 的多通道数据接收与其底层驱动相关,会在后期单独写一篇文章介绍放到 GitHub 上,敬请期待。
6. 结果
这里只是展示三个发送节点的情况,接收节点这边代码上已经把六个节点全部支持了。手头板子够多的话,把六个发送节点全部弄出来也是OK的。
7. 开源代码
为了更进一步便于大家学习,第二周任务的代码已经开源啦:https://github.com/willianchanlovegithub/DIY_projects_base_on_RT-Thread(以上链接复制至外部浏览器打开)
加入智能家居DIY交流群,请添加小师妹为好友
#题外话# 喜欢RT-Thread不要忘了在GitHub上留下你的STAR哦,你的star对我们来说非常重要!链接地址:https://github.com/RT-Thread/rt-thread
RT-Thread
让物联网终端的开发变得简单、快速,芯片的价值得到最大化发挥。Apache2.0协议,可免费在商业产品中使用,不需要公布源码,无潜在商业风险。
长按二维码,关注我们
看这里,求赞!求转发!
点击阅读原文进入GitHub
智能家居DIY连载教程(2)——在实际项目中运用消息队列与邮箱相关推荐
- 文件系统灵活用——智能家居DIY连载教程3
Hi,各位小伙伴,DIY 活动已经来到了第三周!前两周的任务大家都完成了吗?本周将会迎来新的挑战--文件系统.本文将从 SPI Flash 和 SD Card 两方面给大家讲解如何使用文件系统,以及针 ...
- 基于 RT-Thread 的智能家居 DIY 连载教程(1)——如何正确使用 Sensor 框架
对 RT-Thread 的 Sensor 框架的理解与使用举例 如何正确使用 Sensor 框架?以 DS18B20 为例. 1. 任务清单 为了更好的讲解 Sensor 框架,我罗列了一份任务清单: ...
- 智能家居 DIY 教程连载4——手把手教你连云
云乃万物互联之本 Hi,各位小伙伴,DIY 活动已经来到了尾声,第四周的任务是整个项目中最有趣也是最重要的部分--物联网.本周的任务完成之后,也就意味着整个项目就完全做完啦,是不是迫不及待先把整个 D ...
- 基于 RT-Thread 的智能家居 DIY 教程连载(4)——手把手连接 OneNet
智能家居 DIY 教程连载(4) 云乃万物互联之本 Hi,各位小伙伴,DIY 活动已经来到了尾声,第四周的任务是整个项目中最有趣也是最重要的部分--物联网.本周的任务完成之后,也就意味着整个项目就完全 ...
- 基于 RT-Thread 的智能家居 DIY 教程连载(3)——文件系统灵活用
智能家居 DIY 教程连载(3) 文件系统 so easy Hi,各位小伙伴,DIY 活动已经来到了第三周!前两周的任务大家都完成了吗?本周将会迎来新的挑战--文件系统.本文将从 SPI Flash ...
- 智能家居DIY教程连载(1) ——如何正确使用 Sensor 框架
想要入门RT-Thread物联网操作系统的童鞋,出门左转这里走:https://www.rt-thread.org/document/site/ Hi~各位小伙伴们,距离 DIY 项目的发布已经有一周 ...
- 基于 RT-Thread 的智能家居 DIY 视频教程新鲜出炉啦啦啦!!!
视频教程分五集推出,放在了哔哩哔哩上面.? 第一集:智能家居DIY--项目介绍与准备事宜? https://www.bilibili.com/video/av63570553 第二集:智能家居DIY教 ...
- Homekit智能家居DIY设备-智能通断开关
智能通断器,也叫开关模块,可以非常方便地接入家中原有开关.插座.灯具.电器的线路中,通过手机App或者语音即可控制电路通断,轻松实现原有家居设备的智能化改造. 随着智能家居概念的普及,越来越多的人想将 ...
- Homekit智能家居DIY之智能通断开关
智能通断器,也叫开关模块,可以非常方便地接入家中原有开关.插座.灯具.电器的线路中,通过手机App或者语音即可控制电路通断,轻松实现原有家居设备的智能化改造. 随着智能家居概念的普及,越来越多的人想将 ...
最新文章
- 剑指offer(Java实现) 二叉搜索树的后序遍历序列
- HelloWorld讲解
- 阿里巴巴发布AliOS品牌 重投汽车及IoT领域
- PerlIde in NetBeans7.3 for Debian
- 【Vue2.0】— 插件(十六)
- c++ ANSI、UNICODE、UTF8互转
- C# 之 日常问题积累(一)
- 海量数据挖掘MMDS week6: MapReduce算法(进阶)
- DeepStream3必须安装Video_Codec_SDK9
- Android 蓝牙手柄开发
- 2019,我们被“黑”科技薅过的羊毛?
- Account locked due to 10 failed logins
- clusters(clusters)
- 单目标跟踪——常用数据集和指标
- Windows中通过命令行新建文件夹、新建文件
- 【Cilium 1.10 重磅发布!】支持 Wireguard, BGP, Egress IP 网关, XDP 负载均衡, 阿里云集成
- 百度回应文心一言文生图功能争议
- 进阶项目(6)LCD12864液晶屏幕设计讲解
- 什么东西能够改善睡眠,拯救失眠的助眠好物推荐
- C#.NET使用TTS引擎实现文语转换
热门文章
- Python 创建一个类实现机器人跳舞
- 基于Java+Spring+Strusts2+Hibernate 社区智慧养老服务平台 系统设计与实现
- 生存分析系列教程(一)使用生信人工具盒进行生存分析
- css之 变量中的变量 及 花式计算calc
- pip Command Not Found – Mac 和 Linux 错误被解决
- bash: ip: command not found
- 如何做大你的软件研发团队?
- java方法的继承 ppt,Java学习之继承基本介绍和实例方法,java学习继承实例
- 在线电池测试软件,新威电池测试软件bts testcontrol
- Windows 7下配置Lex和Yacc