前言

千呼万唤始出来,智能家居 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)——在实际项目中运用消息队列与邮箱相关推荐

  1. 文件系统灵活用——智能家居DIY连载教程3

    Hi,各位小伙伴,DIY 活动已经来到了第三周!前两周的任务大家都完成了吗?本周将会迎来新的挑战--文件系统.本文将从 SPI Flash 和 SD Card 两方面给大家讲解如何使用文件系统,以及针 ...

  2. 基于 RT-Thread 的智能家居 DIY 连载教程(1)——如何正确使用 Sensor 框架

    对 RT-Thread 的 Sensor 框架的理解与使用举例 如何正确使用 Sensor 框架?以 DS18B20 为例. 1. 任务清单 为了更好的讲解 Sensor 框架,我罗列了一份任务清单: ...

  3. 智能家居 DIY 教程连载4——手把手教你连云

    云乃万物互联之本 Hi,各位小伙伴,DIY 活动已经来到了尾声,第四周的任务是整个项目中最有趣也是最重要的部分--物联网.本周的任务完成之后,也就意味着整个项目就完全做完啦,是不是迫不及待先把整个 D ...

  4. 基于 RT-Thread 的智能家居 DIY 教程连载(4)——手把手连接 OneNet

    智能家居 DIY 教程连载(4) 云乃万物互联之本 Hi,各位小伙伴,DIY 活动已经来到了尾声,第四周的任务是整个项目中最有趣也是最重要的部分--物联网.本周的任务完成之后,也就意味着整个项目就完全 ...

  5. 基于 RT-Thread 的智能家居 DIY 教程连载(3)——文件系统灵活用

    智能家居 DIY 教程连载(3) 文件系统 so easy Hi,各位小伙伴,DIY 活动已经来到了第三周!前两周的任务大家都完成了吗?本周将会迎来新的挑战--文件系统.本文将从 SPI Flash ...

  6. 智能家居DIY教程连载(1) ——如何正确使用 Sensor 框架

    想要入门RT-Thread物联网操作系统的童鞋,出门左转这里走:https://www.rt-thread.org/document/site/ Hi~各位小伙伴们,距离 DIY 项目的发布已经有一周 ...

  7. 基于 RT-Thread 的智能家居 DIY 视频教程新鲜出炉啦啦啦!!!

    视频教程分五集推出,放在了哔哩哔哩上面.? 第一集:智能家居DIY--项目介绍与准备事宜? https://www.bilibili.com/video/av63570553 第二集:智能家居DIY教 ...

  8. Homekit智能家居DIY设备-智能通断开关

    智能通断器,也叫开关模块,可以非常方便地接入家中原有开关.插座.灯具.电器的线路中,通过手机App或者语音即可控制电路通断,轻松实现原有家居设备的智能化改造. 随着智能家居概念的普及,越来越多的人想将 ...

  9. Homekit智能家居DIY之智能通断开关

    智能通断器,也叫开关模块,可以非常方便地接入家中原有开关.插座.灯具.电器的线路中,通过手机App或者语音即可控制电路通断,轻松实现原有家居设备的智能化改造. 随着智能家居概念的普及,越来越多的人想将 ...

最新文章

  1. 剑指offer(Java实现) 二叉搜索树的后序遍历序列
  2. HelloWorld讲解
  3. 阿里巴巴发布AliOS品牌 重投汽车及IoT领域
  4. PerlIde in NetBeans7.3 for Debian
  5. 【Vue2.0】— 插件(十六)
  6. c++ ANSI、UNICODE、UTF8互转
  7. C# 之 日常问题积累(一)
  8. 海量数据挖掘MMDS week6: MapReduce算法(进阶)
  9. DeepStream3必须安装Video_Codec_SDK9
  10. Android 蓝牙手柄开发
  11. 2019,我们被“黑”科技薅过的羊毛?
  12. Account locked due to 10 failed logins
  13. clusters(clusters)
  14. 单目标跟踪——常用数据集和指标
  15. Windows中通过命令行新建文件夹、新建文件
  16. 【Cilium 1.10 重磅发布!】支持 Wireguard, BGP, Egress IP 网关, XDP 负载均衡, 阿里云集成
  17. 百度回应文心一言文生图功能争议
  18. 进阶项目(6)LCD12864液晶屏幕设计讲解
  19. 什么东西能够改善睡眠,拯救失眠的助眠好物推荐
  20. C#.NET使用TTS引擎实现文语转换

热门文章

  1. Python 创建一个类实现机器人跳舞
  2. 基于Java+Spring+Strusts2+Hibernate 社区智慧养老服务平台 系统设计与实现
  3. 生存分析系列教程(一)使用生信人工具盒进行生存分析
  4. css之 变量中的变量 及 花式计算calc
  5. pip Command Not Found – Mac 和 Linux 错误被解决
  6. bash: ip: command not found
  7. 如何做大你的软件研发团队?
  8. java方法的继承 ppt,Java学习之继承基本介绍和实例方法,java学习继承实例
  9. 在线电池测试软件,新威电池测试软件bts testcontrol
  10. Windows 7下配置Lex和Yacc