智能家居DIY教程连载(2)——IPC 之消息队列与邮箱实战指南

2019-7-25 17:13 [复制链接] 7 1086

本帖最后由 WillianChan 于 2019-7-25 17:16 编辑
5 x1 h2 Z% q5 _1 a8 N

智能家居 DIY 教程连载(2)

; ?  p0 t0 B) v+ k5 C
% P1 h& V- b: P' t# N

IPC 之消息队列与邮箱实战指南

* z) ?+ j$ w: g0 @/ F( a, t
' v  u4 z2 y. m! R7 l$ P1 t- i) Y

千呼万唤始出来,智能家居 DIY 教程连载第二篇终于登场了!本文将重点给大家介绍如何将消息队列与邮箱运用到实际项目中去。一起来看看吧~

5 Y& K% n7 }3 K/ _$ g

目录

* N# q1 y" T, k9 n8 t' W& Y% J' N; n

本文文章目录如下,通过以下内容,给大家剖析第二周的任务重点难点。

8 e; L  S' a7 M7 {+ C7 [: D
    ' v+ r0 N4 R" w
  • 1 z7 }5 z" n  R# [
      , f" g% A( e2 K* `
    1. 第二周任务回顾
    2.   M, r, I7 \+ q$ Q, R: `, P
    ; z; Z: U$ c2 o% s" L2 v  e
  • , t$ b) f5 q0 ~
  • - O6 l1 j9 ?2 j. I8 {
      , n/ z$ x/ q4 g( ^* q3 n
    1. 软件包的获取
    2. 8 U: C, Y4 T% m2 t, K
    9 Y/ c" i9 W& v' s8 P
  • + s$ l; l0 U' K/ G0 `1 z, Q5 `* V
  • 1 r3 g! z" G: l3 I
      " @3 I5 I7 A) D! H$ P' v
    1. IPC 之邮箱实战指南(重点)3 t! v' W- q! L+ E+ p
        8 f% [" @# m, h* Y  T( R3 ^
      • 3.1 为什么要使用邮箱
      • % v$ ~8 t; m! l$ o  [
      • 3.2 邮箱工作原理举例介绍
      • $ F3 n5 y8 e# }2 s
      • 3.3 在项目中运用邮箱
      • # |- R$ b; R, x6 X. B' N" U
    2. ' V: z; B/ k! _9 Y! {: l7 C5 O& Q
    * F% j; U) Z7 ^6 u' ~! r6 n7 B; C
  • ( q' M9 V! j6 @5 r
  • 6 z7 \& o# h! R" N+ y
      , M, B. P! I. {% ?5 o
    1. IPC 之消息队列实战指南(重点)4 l& I1 ^. S& T* E; K5 E# X
        1 R- m) Q7 y& _! B$ P! G2 L
      • 4.1 为什么要使用消息队列
      • ( ?' r7 C$ U) _, k6 Z* s
      • 4.2 消息队列工作原理举例介绍
      • ; d! h! U0 u6 v& B8 J0 b
      • 4.3 在项目中运用消息队列
      • 2 `( C4 n8 X) [5 @5 n- {" R
    2. - E9 \+ g: y  i0 N
    3 b4 y, w# N' @
  • : n9 R5 ?# y2 z- g# Y
  • * S! l3 L5 t8 \! j) E+ \7 ?. B
      9 n1 N. e3 W# z* e8 B, i
    1. nrf24l01 的多通道数据接收
    2. # H4 B) v! Y- C7 v& r
    ! Z4 r5 g+ Q/ @. A
  • % R$ [/ I& `+ Q$ a7 r
  • . p5 F1 }# l2 w9 t$ x+ Y
      9 N  @( a5 L  q0 u
    1. 结果
    2. 2 }% f( p5 H8 q4 z- C
    5 T& C) \: s: x, i$ `
  • * m- C; d( q/ Y5 u& e( g
  • 7 N: e" W" u$ h) Q0 I
      % j0 _. |8 @  ^' L/ Y
    1. 开源代码
    2. ( }4 D1 }1 W9 C& T0 j$ i  F
    9 m. j3 @% ]' m' s& F. Z* i0 b  l
  • 7 R0 U; D8 W1 O* H' B9 H
1 n" q! `, L' _/ r) H! n! C

1. 第二周任务回顾

# d/ L8 x4 j- Z  g2 c/ h9 P6 y

我们来回顾一下第二周安排的任务:

; W/ u& J. [- T+ a. V2 ^0 d1 |
    / F6 W$ s* y# P/ H! E2 h' e% n
  • 通过 ENV 工具获取 nrf24l01软件包,并加载到 MDK 工程里面
  • - b+ G- M# l& x; ~  |2 z
  • 了解多线程间的通信,了解 IPC 中邮箱和消息队列的特性,并能灵活使用,实现 ds18b20 线程与 nrf24l01 线程之间的数据通信
  • , ~: y, e9 Q1 Y6 C
  • 修改 nrf24l01 软件包,实现多点通信功能
  • + p: X4 T8 j+ C( @
0 Z8 N! V( \2 N& P/ \1 e, |

上述任务的重点,是要学习去灵活运用邮箱和消息队列。

- N- }" M! ]0 K" u5 o4 o1 k  e

2. 软件包的获取

3 A6 a! m2 |* r; |

软件包可以通过 env 工具十分方便的获取到,并且加载到工程里面去,env 工具的下载链接可以在官网找到,env 下载链接。env 的使用方法可以查看这里进行学习。

: ~% d5 p: ]! w' N& m- F- @

env download1.jpg

: q) [$ r0 Y( c8 w  C, p' d

值得注意的是,在 env 中获取软件包是需要依赖于 git 的,可以去 git 官网获得下载,git 官网链接

0 J1 i( _5 A# _

本周任务中,我们需要用到 nrf24l01 的软件包,只需要在 menuconfig 中选中 nrf24l01 即可:

- c  Y: y! [! f1 S: S% O6 D& m
RT-Thread online packages  --->
+ E4 J/ C5 W) O0 c+ Z    peripheral libraries and drivers  --->
% M, ^+ v+ {( [5 c% x8 s        nRF24L01: Single-chip 2.4GHz wireless transceiver.  --->
' f! \5 f$ ?+ E+ S; t+ D

选中之后需要将该软件包获取到本地来,在 env 中输入 pkgs --update 命令回车即可。我们在工程目录的 packages 目录下,可以看到,nrf24l01 软件包被获取到本地来了,如下图所示:

1 u) M% ?# I/ z; q# E1 Z( e) [# m

packages.jpg

6 l! ]2 o! ~2 |

不过该软件包现在仅仅只是获取到本地,尚未加载到 MDK 工程当中来。我们在 env 中输入 scons --target=mdk5 命令回车即可,执行完该命令之后打开 MDK5 工程,发现 nrf24l01 软件包成功加载到工程里面去了,如下图所示:

+ Q) j5 Y7 W3 o

mdk project.jpg

. r+ Y4 @1 _" b" V4 b3 {( g$ T+ N0 `

3. IPC 之邮箱实战指南

( d! g  z  ]" c+ @2 m

3.1 为什么要使用邮箱

$ @7 D$ B+ X; h+ E

我们需要通过 nrf24l01 无线模块进行数据发送与接收,定义:通过 nrf24l01 发送数据的是发送节点,通过 nrf24l01 接收数据的是接收节点。(本 DIY 整个项目需要至少用到两个发送节点。)

2 R# J% M% a6 ~) n

在发送节点创建一个线程,用于无线发送数据。具体的,nrf24l01 的软件包提供了哪些 API,是如何通过这些 API 实现发送功能的,可以参考该软件包的 samples,路径为:...\packages\nrf24l01-latest\examples。

1 K) a3 ^7 _% n; g! X; F  ~

还记得第一周的任务吗?在 main 函数中创建了一个线程,用于获取 ds18b20 温度数据的。同理的,我们在 main 函数中再创建一个线程,该线程是用来通过 nrf24l01 发送数据的,线程入口函数是 nrf24l01_send_entry

5 D! r( i7 Q* x/ E2 q
int main(void)1 r7 x) Q7 g, k, X1 T
{
& o" I* H# ~5 d1 A9 U    rt_thread_t ds18b20_thread, nrf24l01_thread;
+ e4 [8 j( f( K" K% m
) y! X7 ~8 w" w* O: y8 g5 R    ds18b20_thread = rt_thread_create("18b20tem", read_temp_entry, "temp_ds18b20",: d; G4 F7 ?0 n1 b/ G% a+ l( A
                                      640, RT_THREAD_PRIORITY_MAX / 2, 20);
  e# Y6 S, v, r/ C7 R0 x% l    if (ds18b20_thread != RT_NULL)
1 Y+ A8 A8 m3 \    {' z4 M7 Z& j! x0 t1 f6 {
        rt_thread_startup(ds18b20_thread);/ B% `. N; Z  P( g
    }: a5 }! ^5 V. L* F/ {

& g' }# F2 E7 Q1 T* M+ n8 l    nrf24l01_thread  = rt_thread_create("nrfsend", nrf24l01_send_entry, RT_NULL,' V6 e4 K* Z6 |. o; H6 n
                                        1024, RT_THREAD_PRIORITY_MAX / 2, 20);2 O2 l+ E  M. U9 a! H
    if (nrf24l01_thread != RT_NULL)/ a/ a- D$ L3 z) S
    {- x2 i* [5 b! L* N
        rt_thread_startup(nrf24l01_thread);
0 @, M- P4 P5 J8 c+ O) Y9 D    }" k/ I7 l+ N2 r# q  ]" B

$ c4 O, w6 y! J2 _3 w    return RT_EOK;0 q' U) A6 i' K% w: r, P
}
1 D, v- {$ T+ \: M5 b2 ~$ C

这时候,我们的程序当中就存在了两个线程了,ds18b20_thread 线程用来获取温度数据,nrf24l01_thread 线程用来向外无线发送温度数据,那么问题来了:

' e( U1 \8 U3 g
    - q, s4 O. a- D, a/ C! O
  • ds18b20_thread 线程如何将温度数据给 nrf24l01_thread 线程?
  • 0 O' U* q5 E$ Q9 m3 b" |
  • 如果ds18b20_thread 线程采集温度数据过快,nrf24l01_thread 线程来不及发送,怎么办?
  • + C1 k: }' z8 y/ C
  • 如果nrf24l01_thread 线程发送数据过快,ds18b20_thread 线程来不及采集温度数据,怎么办?
  •   d0 m0 p  f) a9 Q: j$ K
/ `8 A' ^$ ^9 a/ ~) c% e

这时候,IPC 中的邮箱(mailbox)可以很好的解决以上问题。不过这里,我们需要将邮箱与内存池(mempool)搭配一起使用。往往而言,在实际项目中,邮箱和内存池这两个 IPC 是经常需要配套着一起使用的。为什么,且慢慢看来。

2 J" C* B& R# F2 G" s: |. @

3.2 邮箱工作原理举例介绍

( b1 n  N; J! c# `

RT-Thread 的文档中心已经有对邮箱和内存池原理上的详细讲解,请点击此链接跳转至于邮箱,此链接跳转至内存池。这里不在赘述。这里通过举一个生活中的例子,去帮助大家理解邮箱和内存池。

+ G# p, Z* k: m% y/ v" K' q6 f9 ^

如今,很多人购物都是通过电商平台购买,那避免不了是要收快递的。

7 \2 o3 k: j( }4 i) R

kuaidixiang.jpg

$ j: q" h* o/ g, x& a9 u- K4 E

我们拟定一个生活场景。小区内放置有快递柜,快递柜里面有很多快递箱,快递箱里面可以存放快递,快递员把快递存放到快递箱之后,会发短信通知你过来取快递,还会告诉你编号是多少,通过编号你可以找到你的快递存放在快递柜的哪个快递箱里面。

7 @2 V$ O- S  P% f

上面这个模型中有几个名词,我们抽取出来:快递、快递柜、快递箱、快递员、你自己、短信、编号。

, C- r" D1 C" K1 C

我们将上面这个生活场景和 IPC 中的邮箱和内存池一一对应起来:

  M1 S, h4 F9 `* T7 e2 ~
    ; j$ I* ?6 q* C! P0 G: E2 s# a
  • 快递:采集到的温度数据
  • : _) J, q# o9 ~' o& C3 H% ?
  • 快递柜:内存池
  • 6 r+ l0 O* J& a0 I: j4 k0 e+ P' G9 \
  • 快递箱:内存池里面的内存块
  • 6 h6 Y$ t1 E+ f/ r5 L
  • 快递员:ds18b20_thread 线程
  • % v4 v* D; h/ R0 r9 i
  • 你自己:nrf24l01_thread 线程
  • 3 k; ?+ |4 k1 I# R
  • 短信:邮箱中的一封邮件
  • 4 K& {- Q+ `6 m! u* s+ M6 W
  • 编号:内存块地址指针
  • & ^( e% V6 Z* b: ~) F. U2 ^
. }- i" p& i% }- }

邮箱和内存池的使用,其实和上面那个收快递的生活场景是一样的:

2 ^3 }1 B* w5 V0 f7 O- c) E5 ^6 B
    - X. ?4 |4 q; c; {* d% c
  • 在程度的一开始,即 main 函数中,我们创建一个邮箱和一个内存池
  • 7 c" H2 H& U! n/ i, D/ j# G
  • ds18b20_thread 线程里: + _1 s& R  l4 L
      . b. O. u, Q& w6 [2 |* f$ T- D
    • 首先,每当该线程采集到一个温度数据(有快递来了),就在内存池里面申请一个内存块(快递员找一个空快递箱)
    • " c8 n: V( x4 L
    • 然后,把本次采集到的这个温度数据放到内存块里(快递放入快递箱),再把内存块的地址放在邮箱里
    • ' h( r. S/ W. I7 u' n
    • 最后,邮件发送出去(发短信告知用户)
    • % S' S- M0 ~1 y1 N: E  L
  • 4 l$ m$ J+ L3 K6 X3 t! r
  • nrf24l01_thread 线程里:6 x* o1 k/ _/ n- D' k0 a
      + r4 q9 E. \' u. I
    • 首先,接收ds18b20_thread 线程发送过来的邮件(用户收到短信)
    • 6 |+ E' S+ p5 Y# o" L
    • 然后,根据邮箱中的存放的地址,知道了当前温度数据存放在哪个内存块里面,也就是说,nrf24l01_thread 线程找到了(收到了)当前温度数据。(根据短信里的编号知道快递放在哪里了)
    • 7 }9 a! v' O' H5 X2 h
    • 最后,用完了这个内存块要及时释放掉(快递取出来了,快递箱空了)
    • % G6 u4 O. V* Q
  • 4 a; N7 ]: h- z
  `* o- w1 v8 W2 a

3.3 在项目中运用邮箱

' L' g1 S3 k& B4 b& q8 j

通过代码解读一下。

+ P3 l8 m! A. p- m! J+ Y- n

main 函数中创建一个邮箱和一个内存池是这么做的:

3 L9 _- M7 l. K/ s( t
tmp_msg_mb = rt_mb_create("temp_mb0", MB_LEN, RT_IPC_FLAG_FIFO); /* 创建邮箱 */- J' c3 }4 u6 d3 c1 E( U
tmp_msg_mp = rt_mp_create("temp_mp0", MP_LEN, MP_BLOCK_SIZE);    /* 创建内存池 */
  ~8 {8 b3 L1 ]& J  V& b8 X) d

ds18b20_thread 线程的入口函数是 read_temp_entry,如下:

) Z# y" k1 @1 D) R4 q" @+ r
static void read_temp_entry(void *parameter)8 t! {5 D* [# D; A9 R$ {  q
{
0 v) ?2 _( {+ K% C7 l( {    struct tmp_msg *msg;
% [- M$ o( G  W+ d: q! Y5 v$ Q/ _0 y( j    rt_device_t dev = RT_NULL;& o) T# d8 f  K
    rt_size_t res;
% v4 X! P) n# y  U
3 Q! Q6 P7 k$ |( c( y    dev = rt_device_find(parameter);1 E8 g! H+ K( L* I
    if (dev == RT_NULL)( [3 v, {9 X: C! U, K# _* S
    {$ }" _7 [* O; w) ~& E8 I& X0 Z" }
        rt_kprintf("Can't find device:%s\n", parameter);0 R' `. i. J3 |6 h7 h; p8 }
        return;
. m3 Q- u# Q0 k3 l- u+ F' a    }; h1 M  k) |; e) O) z
8 Z" @) r7 V# L7 }6 Y# B, @
    if (rt_device_open(dev, RT_DEVICE_FLAG_RDWR) != RT_EOK). Q: G5 A7 d$ X, P) J) Q
    {: {* K, R9 c  u4 J
        rt_kprintf("open device failed!\n");
1 f6 {5 w* y* j  ~        return;, C; c- q4 h$ v1 }% ?# D
    }
8 H' J6 v8 m2 N6 G7 O5 B    rt_device_control(dev, RT_SENSOR_CTRL_SET_ODR, (void *)100);
  z. A, ]! |( {' G3 l$ T
4 \5 |' M2 N- {    while (1)
; [  E1 v5 W6 K* P8 A/ _+ }    {- a; Z% v: z+ `  r' o8 E" ~% D
        res = rt_device_read(dev, 0, &sensor_data, 1);, `7 R, q, i7 Y8 B; a
        if (res != 1)
3 r0 p6 ?, ]0 l# i        {0 O1 j: ]$ X7 x4 I" S# o, I; X7 j7 W
            rt_kprintf("read data failed!size is %d\n", res);
5 G& [& A) ~: D* l$ R9 t4 L; n9 [6 o            rt_device_close(dev);
# a/ @% L* {$ s$ |3 t4 O4 D            return;
% M, G" n4 z( M. c7 e" m        }
& ]( Q) ]$ |& i& b$ s        else) P, n" e: B  s* z7 \  q
        {& P' M  q* Y' h7 {
            /* 申请一块内存 要是内存池满了 就挂起等待 */
, p& E( V4 D5 d7 ^8 D  [            msg = rt_mp_alloc(tmp_msg_mp, RT_WAITING_FOREVER);; k4 G: y0 P+ c% Y9 |
            msg->timestamp = sensor_data.timestamp;3 W9 Z3 c: L" }
            msg->int_value = sensor_data.data.temp;
4 K' F2 K2 J% Y) k! d            rt_mb_send(tmp_msg_mb, (rt_ubase_t)msg);$ Z0 _& T3 j+ M9 m
            msg = NULL;( h% N7 Q, i. L% l: h* R& V
        }1 F# ]/ i+ s& }. a# o2 g
        rt_thread_mdelay(100);
3 h9 W3 v& N/ o6 M/ n    }
  C/ e6 ]' H# i( c7 ^4 c}
6 [7 n5 {0 N# K7 I0 W9 Z

在上述代码中,该线程采集到一个温度数据之后,就会在内存池中申请内存块:

9 w& H( F2 Z2 B, u" I2 I$ i
msg = rt_mp_alloc(tmp_msg_mp, RT_WAITING_FOREVER);
* ?4 h& O8 t2 [; p5 e

将温度数据存放到刚刚申请到的内存块里面:

% R6 K9 }% ^" X7 B& _. H
msg->int_value = sensor_data.data.temp;
' K- j2 P) A$ H4 ~3 @

将这个存放着温度数据的内存块的地址给邮箱,然后发送邮件:

0 F) E9 R5 Z; h- m% c) e
rt_mb_send(tmp_msg_mb, (rt_ubase_t)msg);
$ U3 B/ e1 q- V4 [2 I$ n

nrf24l01_thread 线程的入口函数是 nrf24l01_send_entry,如下:

4 b' {! N$ F7 P6 s2 T1 J$ U3 x
static void nrf24l01_send_entry(void *parameter)
. C1 D- K; S6 D+ C/ m{+ i6 I- Y% v0 r4 I* F( K
    struct tmp_msg *msg;
3 B& b' E) t; W1 U% x    struct hal_nrf24l01_port_cfg halcfg;1 h# j  T, ^; ^( L9 g
    nrf24_cfg_t cfg;
0 a) p& [2 ^% w) x& I    uint8_t rbuf[32 + 1] = {0};5 {8 l1 S  A" i, L
    uint8_t tbuf[32] = {0};- ^3 D# O) [; {: l) `# L3 P* n, z

; i/ i; ~( I) d3 U. i5 t7 o3 }    nrf24_default_param(&cfg);
2 d0 n( B" }0 {% P, S1 {    halcfg.ce_pin = NRF24L01_CE_PIN;
  t" U$ |/ g1 [! ?+ a    halcfg.spi_device_name = NRF24L01_SPI_DEVICE;
! k" T0 Y, O$ ]% Z0 A    cfg.role = ROLE_PTX;" K* i( U% P- r( \
    cfg.ud = &halcfg;, r( o6 p1 q- t
    cfg.use_irq = 0;
1 E* Q9 O; N" g    nrf24_init(&cfg);
0 l, t) o: P5 V6 S
5 ~3 N) _8 X, A    while (1). {% D' j# x& j" g
    {. @( s" B& F0 l9 J' M4 w
        rt_thread_mdelay(100);
% p+ X3 D& k$ L' R
" S- j6 B* M6 S( A% j. P; W8 r        if (rt_mb_recv(tmp_msg_mb, (rt_ubase_t*)&msg, RT_WAITING_FOREVER) == RT_EOK)1 G; X! z, w$ q0 X  u- v: _# D. `
        {# ?5 p" F/ T& N- H: m( c+ ]- n
            if (msg->int_value >= 0)
8 Y) R, d: a8 @) J            {
; U6 h( a' A- `5 u) q: O7 W                rt_sprintf((char *)tbuf, "temp:+%3d.%dC, ts:%d",
4 d  z( J) n2 k- d, C8 Q( w                           msg->int_value / 10, msg->int_value % 10, msg->timestamp);
. ^3 J+ a) `( {8 ~/ n            }
* Q8 Z( ?' L7 c# I" z& ^            else
; J0 s3 _- |! ?! v            {
6 c  p# z( T! e) T3 O6 R                rt_sprintf((char *)tbuf, "temp:-%2d.%dC, ts:%d",3 s* X- a2 u2 t2 q
                           msg->int_value / 10, msg->int_value % 10, msg->timestamp);, x3 M- S( w2 k( j  M8 ?
            }
4 _. k7 k' `4 N+ M            rt_kputs((char *)tbuf);
9 z, |4 J7 l. G; R+ J2 m8 a# Z# H            rt_kputs("\n");
1 C7 r8 I6 x% ^! c% W            rt_mp_free(msg); /* 释放内存块 */
4 [6 K( B( B& ~  w: _( D            msg = RT_NULL;   /* 请务必要做 */
% r' \4 O% Y+ x2 z( D0 ~        }
- P( e1 Y6 A. Q" U1 L; n$ R        if (nrf24_ptx_run(rbuf, tbuf, rt_strlen((char *)tbuf)) < 0)
0 {2 g9 ~- T) ?4 O1 Y        {5 b' i, I4 Y4 U; f0 r
            rt_kputs("Send failed! >>> ");
, H. {2 i/ F8 {" C  O. O0 [7 ?; g        }9 }! P1 H& M$ B. G5 R# ~
    }+ _4 ?. S. }9 ^* _, j& K2 ]
}
8 _8 v& i4 E) d

在上述代码中,nrf24l01 软件包提供了发送数据的 API nrf24_ptx_run

& v! K" \2 F0 P; ]3 e5 S( E; j; q

该线程接收ds18b20_thread 线程发送过来的邮件,并收到了温度数据:

7 r% f5 A4 C; \8 b" M4 X: r
rt_mb_recv(tmp_msg_mb, (rt_ubase_t*)&msg, RT_WAITING_FOREVER)
5 A3 |8 n/ g  o* ~7 g: s

将温度数据发送出去:

/ P4 u/ i! {  j7 S+ Q2 T- u
nrf24_ptx_run(rbuf, tbuf, rt_strlen((char *)tbuf))
, f/ i2 u* X& l$ h7 p

用完的内存块释放掉:

  k: p: h0 d( r( U: |& B
rt_mp_free(msg);
# `# A1 Y5 A3 Z2 Y: B6 ~msg = RT_NULL;
& C6 f9 d5 l# _3 R6 Z

还有两个问题没有解答:

" P$ U. T; P' g" g# K
    8 i8 o$ ~. N, `0 E( c' k; S* }4 |8 t) x
  • 如果ds18b20_thread 线程采集温度数据过快,nrf24l01_thread 线程来不及发送,怎么办?
  • 4 Q8 o, [$ V$ a
  • 如果nrf24l01_thread 线程发送数据过快,ds18b20_thread 线程来不及采集温度数据,怎么办?
  • # \, y) p$ Y9 X: i; ^
$ \; m0 V) {3 a6 d! ]

这两个问题其实就是解决供过于求和供不应求的问题。

+ \) [5 Q" a; M

有没有留意到,申请内存块的代码上有一个 RT_WAITING_FOREVER,接收邮件的代码上也有一个 RT_WAITING_FOREVER

+ J) ?3 z1 l' m8 @" {* V2 a

这两个 RT_WAITING_FOREVER 就是用来解决上面两个问题的。

1 X# p# o7 O: ~3 j# q- P2 L

当内存池满了的时候,再也申请不到内存块了,这时候申请内存块里面的 RT_WAITING_FOREVER 会使得 ds18b20_thread 线程阻塞,并挂起,然后 MCU 就会去干别的事情去了,不断的在 nrf24l01_thread 线程中发送存放在内存池中的温度数据,并释放掉内存块。等一有内存块可以申请了,ds18b20_thread 线程被唤醒,又会往里面塞数据了。

- C0 g- d. R$ t! f2 N3 H+ B" q3 ^

同理的,如果内存池是空的,里面没有数据,接收邮件里面的 RT_WAITING_FOREVER 会使得nrf24l01_thread 线程阻塞,并挂起,然后 MCU 就会去干别的事情去了,在 ds18b20_thread 线程中采集温度,并申请内存块塞数据进去,内存块一旦有数据,就会发邮箱,另外一边一有邮箱收到了,就又开始工作了。

' ~, o. U7 e3 l! g( ]( H% \, b

4. IPC 之消息队列实战指南

9 A( L' z) O1 E) J5 ~

4.1 为什么要使用消息队列

% @: m; O2 j9 j* k6 y  A

在本次 DIY 中,消息队列其实也是用来解决以下问题的:

7 L+ w0 y* m9 [4 u
    - T6 m' B- N! i+ S* t, _% z# r4 R
  • ds18b20_thread 线程如何将温度数据给 nrf24l01_thread 线程?
  • 2 E% H$ r4 ]( ^4 n
  • 如果ds18b20_thread 线程采集温度数据过快,nrf24l01_thread 线程来不及发送,怎么办?
  • 4 V% u/ d1 _+ U- n5 c7 _* f1 k
+ D9 ^2 Y, _  b2 U6 u

4.2 消息队列工作原理举例介绍

- i# I/ d) _; I# t, f. A

msg_work.png

/ m1 `+ p  }  y. @

消息队列一般来说,不需要搭配内存池一起使用,因为消息队列创建的时候会申请一段固定大小的内存出来,作用其实和邮箱+内存池是一样的。

5 W* ~5 O' s# ^, {0 S8 y5 Y

每个消息队列对象中包含着多个消息框,每个消息框可以存放一条消息,每个消息框的大小是一样的,存放的消息大小不能超过消息框的大小,即可以相同,可以小于。

% j( ]  {) a- i; Z$ u- Q

类比生活中的例子,存钱罐,发工资了(纸币),把一张张的钱放到存钱罐里去,你自己或者你对象需要花钱了,就从存钱罐里面取钱出来用。

$ {* l* L1 d( m' C3 L  s

更多对消息队列的讲解,请查看 RT-Thread 文档中心,点此链接跳转。

/ @7 x3 m+ B( T$ G

4.3 在项目中运用消息队列

; i5 o4 d1 j8 |% U, o7 Q

因为文章篇幅原因,这里不把代码放出来了,可打开工程查看。工程源码是已经开源了的,链接会在下面给出。

  V3 R3 }6 D+ F$ k) ?* I

在 main 函数中创建消息队列:

; Z* W. T  Q' y* M
tmp_msg_mq = rt_mq_create("temp_mq", MQ_BLOCK_SIZE, MQ_LEN, RT_IPC_FLAG_FIFO);
* _7 J' u1 }! \. D

ds18b20_thread 线程中转载数据并发送消息队列:

3 W. t9 p" v1 d. j+ J) u6 V3 ~
msg.int_value = sensor_data.data.temp;
+ C' H  _1 [0 c# Y- i9 Lrt_mq_send(tmp_msg_mq, &msg, sizeof msg);
$ H- |0 w! \/ |9 V: Z  t) F

nrf24l01_thread 线程中接收消息队列:

# i5 |1 U% I3 Y
rt_mq_recv(tmp_msg_mq, &msg, sizeof msg, RT_WAITING_FOREVER)
7 X  _4 q* |, g) v4 i3 V

上面这个 RT_WAITING_FOREVER 作用是为了解决下面这个问题,原理和邮箱中的介绍一样:

+ c' l6 [5 d8 }) @
    9 k& I3 C- h1 q7 s) D
  • 如果ds18b20_thread 线程采集温度数据过快,nrf24l01_thread 线程来不及发送,怎么办?
  • 5 J) Q3 o3 N- ^
+ T" }/ _% r( E0 _# ^$ K8 {

即而当消息队列是空的时候,可以挂起读取线程。当有新的消息到达时,挂起的线程将被唤醒以接收并处理消息。

! f3 r6 l" M5 V* P1 L! W0 O! t

5. nrf24l01 的多通道数据接收

' S$ z) Z: L, K/ a/ i

nrf24l01 的多通道数据接收与其底层驱动相关,会在后期单独写一篇文章介绍放到 GitHub 上,敬请期待。

$ \& q+ C3 b+ D8 G

6. 结果

1 I9 F. ]! t$ }+ P0 ?" m# C+ n

result.jpg

  R' i  L" u" p. x7 @

这里只是展示三个发送节点的情况,接收节点这边代码上已经把六个节点全部支持了。手头板子够多的话,把六个发送节点全部弄出来也是OK的。

4 C0 a+ B5 U: h. |4 G9 P

7. 开源代码

/ L, z; }& t* w7 V/ r

为了更进一步便于大家学习,第二周任务的代码已经开源啦~ 请点击这里查看

& C8 t. ?6 B; o+ C$ X3 r
' O( Y# d2 c; u4 S! M
使用道具 举报 显示全部楼层 回复
最新评论 | 正序浏览
显示全部楼层 |楼层直达:
发表于 2019-7-25 17:17:36 | 显示全部楼层
沙发
使用道具 举报 回复
发表于 2019-7-25 17:19:26 | 显示全部楼层
word天 发表于 2019-7-25 17:17& d; I+ x9 w5 X- {4 q4 q$ q
沙发
& E4 |) A* O8 o2 y, j3 c
板凳。你的网站还没更新。
使用道具 举报 回复
发表于 2019-7-25 17:20:46 | 显示全部楼层
whj467467222 发表于 2019-7-25 17:19, {) P6 k  g: O  r" J
板凳。你的网站还没更新。
  |9 d% F/ M  x" W
靖哥实名监督USB啊
使用道具 举报 回复
发表于 2019-7-25 17:23:35 | 显示全部楼层
通俗易懂
使用道具 举报 回复
发表于 2019-7-25 17:32:21 | 显示全部楼层
andychen 发表于 2019-7-25 17:23
+ {+ A, l) t# E% B. |6 N1 F通俗易懂

& {  x1 o6 i7 T) j尽力帮助更多的人学习RT-Thread
使用道具 举报 回复
发表于 2019-7-25 17:46:19 | 显示全部楼层
这很强,很实用!!!
使用道具 举报 回复
发表于 4 天前 | 显示全部楼层
请问你是如何在接收部分做到分析发送模块为不同的,看了你仓库中的代码,始终没有看明白
3 r* J  n- I  w) [7 O
  1. ( B1 L( m/ [; I$ P! T
  2.         switch (pipe_state & 0x0E)
    9 S2 s" O; o* d4 I" a+ G
  3.         {0 T% V; D. H# y/ B4 y$ G# i
  4.             case 0x00:
    $ e; Q. X! w+ m( b6 \+ P
  5.                 rlen = get_top_rxfifo_width();
    ; d' b; \0 k1 e% i. S
  6.                 read_rxpayload(RxBuf_P0, rlen);6 @2 U/ S5 W2 A1 Y) _2 k
  7.                 break;4 k" n3 E* r3 I$ H) `( {
  8.             case 0x02:) E+ d2 H2 ~7 f& G9 B! f: u. X
  9.                 rlen = get_top_rxfifo_width();1 E* i9 y7 ~: {. O( l; ]8 P! N
  10.                 read_rxpayload(RxBuf_P1, rlen);
    , L! h7 I6 Y( J7 P/ O: j: A* y
  11.                 break;
    / V( x. z, V; a* o  k
  12.             case 0x04:
    $ q* A7 \1 S6 M
  13.                 rlen = get_top_rxfifo_width();( Q( m4 E6 X$ q; s: q" h/ e
  14.                 read_rxpayload(RxBuf_P2, rlen);
    ; L4 |" \6 k4 B8 S' F2 M
  15.                 break;5 A2 Q& J2 @+ ?
  16.             case 0x06:
    5 i8 P$ [  s# o# h
  17.                 rlen = get_top_rxfifo_width();
    5 l! r5 p$ I7 t  a3 e5 u
  18.                 read_rxpayload(RxBuf_P3, rlen);
    8 B  O" H5 o# |: ]4 U$ r
  19.                 break;6 R5 V! z7 ]# V8 M
  20.             case 0x08:4 @: d9 \8 k6 A0 d$ F
  21.                 rlen = get_top_rxfifo_width();
    % W3 B) N# j# {! Q- b. O% \/ s
  22.                 read_rxpayload(RxBuf_P4, rlen);
    & R5 o& h# X# i4 ?1 Y
  23.                 break;
    5 Z! T8 c$ k) l$ L
  24.             case 0x0A:
    " n/ o9 `2 k1 T2 A
  25.                 rlen = get_top_rxfifo_width();& L  |& Y) X# d- _. ]& ]* S) q
  26.                 read_rxpayload(RxBuf_P5, rlen);
    6 J' ^) T& [) _; @" u
  27.                 break;
      P& w3 M7 Z* u; G
  28.         }
    $ P( K/ x- {- P+ i  |' n
  29.         flush_rx_fifo();
复制代码
使用道具 举报 回复
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

Archiver|手机版|小黑屋|RT-Thread开发者社区 ( 沪ICP备13014002号-1

有害信息举报电话:021-31165890 手机:18930558079

© 2006-2019 上海睿赛德电子科技有限公司

Powered by RT-Thread

快速回复 返回顶部 返回列表