GNU Radio中的消息传递机制(Message Passing)

目录

0、首先看下 GR 中一些常用术语的官方解释

1、定义理解

2、消息传递端口API

3、消息处理函数

4、通过流程图连接消息

5、从外部源发布数据

6、使用消息传送命令

7、一个消息传输的例子


0、首先看下 GR 中一些常用术语的官方解释

直接吧官方的解释抄过来,直接看英文更容易理解:

BlockA functional processing unit with inputs and outputs
PortA single input or output of a block
SourceA producer of data
SinkA consumer of data
ConnectionA flow of data from output port to input port
Flow graphA collection of blocks and connections
ItemA unit of data. Ex: baseband sample, fft vector, matrix...
StreamA continuous flow of consecutive items
IO signatureA description of a blocks input and output ports

1、定义理解

GNU Radio中的消息(message)传递不同于数据流(streaming)的传递。其直观表现就是在流图中,消息端口(message port)之间的连线是虚线,而流数据端口(streaming port)之间的连线是实线。在 block 的代码层面,流数据一定是在 work 函数中进行处理的,而消息一般是在专用的消息处理函数中处理的。消息传递(Message Passing)机制 的出现给予以下两种原因:

  1. 在数据流传输过程中,它允许下游的block向上游的block进行通信
  2. 采用简单的方法实现外部应用与GNU Radio进行交互通信。

首先,stream 在 GR 中作为需要处理的数据,在数字信号中就是一个个采样点的信息,GR 对 stream 数据的处理就是一种无脑式处理,例如在一个滤波器模块中,模块才不会管你输入的是个啥数据,真实数据还是噪音,都会一视同仁当作有效数据进行滤波操作,这时就无法辨别 协议数据单元(PDU)与 信号数据,所以就需要 message 数据进行其他信息的传输。

message 还有这么一层功能:在 GR 中,一个数据流(stream)从产生到最终变成电磁波信号发射,其只能沿着这条从产生到发射的单向信号处理流程进行单向处理,但是有时我们需要一些文本信息或者其他数据类型(PMT类型)信息的反馈来了解数据的处理情况或者一些中间状态信息,这时就需要借助 message 来传递这样的消息了。

另外,消息传输接口的使用严重依赖于GR中的多态数据类型(PMTs),即我们传输的 message 都是 pmt 类型的。GNU Radio中消息传递与数据流传递的关系可以见下图,初次看可能不能理解那就先略过这幅图继续往下看叭~

2、消息传递端口API

消息传递接口定义在gr::basic_block中,该类是GR中所有blocks的父类。每个block都有一组消息队列来保存传入的消息,并且可以将消息发布到其他block的消息队列。并且一般来说每个block上都有输入输出端口,这些端口的C++定义如下:

 void message_port_register_in(pmt::pmt_t port_id)
 void message_port_register_out(pmt::pmt_t port_id)

上述两个API分别用来注册一个新的输入和输出端口。其参数是用以描述端口名称的PMT symbol 类型的数据。Python的调用方式如下:

 self.message_port_register_in(pmt.intern("port name"))
 self.message_port_register_out(pmt.intern("port name"))

这些新注册的端口可以通过定义的端口名来进行辨别。其他想要在输入端口上接收消息的block必须订阅(subscribe)它。当一个block想要发送消息时,其可以使用以下API来进行消息的发布(published):

 C++ API:     void message_port_pub(pmt::pmt_t port_id, pmt::pmt_t msg);
 python API:  self.message_port_pub(pmt.intern("port name"), <pmt message>)

订阅的过程通常通过连接消息端口的形式完成(流图中的连线)。当消息端口被连接后,gr::basic_block::message_port_sub方法就会被调用。

3、消息处理函数

订阅的block必须声明消息处理函数来对接受到的信息进行处理,在使用message_port_register_in方法声明一个订阅(输入)端口后,需要将此端口绑定到消息处理程序。

在C++中可以使用以下lambda函数的方式(从GR3.8开始使用C++11标准)

 set_msg_handler(pmt::pmt_t port_id, 
   [this](const pmt::pmt_t& msg) { message_handler_function(msg); });

Python的使用方式为:

 self.set_msg_handler(pmt.intern("port name"), <msg handler function>)

当新的消息进入接收队列后,就是用以上函数进行消息处理,'port_id'就是注册端口时定义的pmt类型的名字。msg handler function就是处理函数名,可以根据实际情况自定义该函数名。

4、通过流程图连接消息

在流程图层面,模块之间消息端口的连接实质上是通过gr::hier_block2::msg_connect方法进行的。假设块src有一个名为pdus的输出端口,块dbg有一个名为print的输入端口,则在流图中的消息连接如下(Python):

self.tb.msg_connect(src, "pdus", dbg, "print")

所有由src的pdus端口发出的信息都可以被dbg块的print端口接收,这里为了方便起见,端口名称可以直接使用string类型作为参数。

另外,还可以使用以下API来查询block的输入或者输出端口名:

pmt::pmt_t message_ports_in();
pmt::pmt_t message_ports_out();

上述两函数的返回值是pmt类型的vector,且内部元素为PMT symbols。

5、从外部源发布数据

GNU Radio可以接收外部数据源的数据。在一个block中,可以使用gr::basic_block::_post方法直接接收外部数据,因此任何一个拥有输入端口的block都可以接收外部数据。

假设已经创建了一个名为src的块作为PDU进行数据流传输,并有一个名为pdus的输入引脚,我们要向该block发送一个外部数据,该数据可以来自另外一个block或者流图,这里我们手动创建一个数据单元并出入给src块(python)。

 port = pmt.intern("pdus")
 msg = pmt.cons(pmt.PMT_NIL, pmt.make_u8vector(16, 0xFF))
 src.to_basic_block()._post(port, msg)

这里msg的有效数据部分为16个全为0xff的字节。为了发布msg数据,我们需要使用gr::basic_block::to_basic_block方法来访问src块的gr::basic_block类,然后调用gr::basic_block::_post方法来将数据传送到正确的端口上。

注:pmt.cons部分的介绍可以看这里,GNU Radio 中的 PDU(protocol data unit,协议数据单元)有一个特殊的 PMT 类型,它是一对字典(在 CAR 上)和一个统一向量类型。因此,没有元数据和 10 个零可以产生一个有效的 PDU作为流数据:

pdu = pmt.cons(pmt.make_dict(), pmt.make_u8vector(10, 0))

6、使用消息传送命令

消息的一个重要的使用方式就是将命令传送给block。这方面的例子包括:

  • gr::qtgui::freq_sink_c:可以通过消息更改频率轴的缩放比例
  • gr::uhd::usrp_source 和 gr::uhd::usrp_sink:许多与收发器相关的设置可以通过命令消息进行操作,例如频率、增益等。

虽然没有特殊的 PMT 类型来对命令进行编码,但是强烈建议使用以下格式之一:

  • pmt::cons(KEY, VALUE):这种格式只能提供一对键值对,对于采用单个值的命令很有用。将 KEY 和 VALUE 分别视为参数名称和值。例如如果修改 QT GUI Frequency Sink 的参数,KEY 将是“freq”,VALUE 将是新的中心频率(以 Hz 为单位)。
  • pmt::dict((KEY1: VALUE1), (KEY2: VALUE2), ...):这种方式可以提供多个键/值对。可以用于当单个命令采用多个无法分解为多个命令消息的参数的情况。

在这两种情况下,所有 KEY 都应该是 pmt::symbols(即字符串)。 VALUE 可以是块需要的任何值。

7、一个消息传输的例子

此处使用嵌入Python块(Embedded Python Block)进行举例,并将该块重命名为 Message Python Block。作用是将由 QT GUI Message Edit Box 中输入的字符串转换为字节流输出。如下图所示。图中的embedded Python block命名为"Message Passing Demo"。在File Sink中,File为输出字符的保存文件的路径,可根据实际情况自行设定。注意,图中的虚线连接的是消息端口(message ports),实线连接的是数据流端口(streaming ports)

Embedded Python Block的代码如下:

import numpy as np
from gnuradio import gr
import pmt
# 初始化
textboxValue = ""

class my_sync_block(gr.sync_block):
    """
    reads input from a message port
    outputs text
    """
    def __init__(self):
        gr.sync_block.__init__(self,
            name = "Message Passing Demo",
            in_sig = None,  # 设置 stream 数据输入端口
            out_sig = [np.byte])  # 设置 stream 数据输出端口
        self.message_port_register_in(pmt.intern('msg_in'))  # 设置消息输入端口
        self.message_port_register_out(pmt.intern('clear_input'))  # 设置消息输出端口
        self.set_msg_handler(pmt.intern('msg_in'), self.handle_msg)  # 为msg_in端口的输入数据绑定消息处理函数函数

    def handle_msg(self, msg):
        global textboxValue  # 声明全局变量
        textboxValue = pmt.symbol_to_string (msg)
    
    def work(self, input_items, output_items):
        global textboxValue  # 声明全局变量
        _len = len(textboxValue)  # 获取字符串的长度
        if (_len > 0):
            # 增加换行符
            textboxValue += "\n"
            _len += 1
            # 再输出数组中存储字符串
            for x in range(_len):
                output_items[0][x] = ord(textboxValue[x])
            textboxValue = ""
            self.message_port_pub(pmt.intern('clear_input'), pmt.intern(''))
            return (_len)
        else:
            return (0)

观察流图可以发现,图中的Message Passing Demo输入是消息输出是数据流,事实上,只要消息的PMTs数据类型与数据流端口的数据类型相匹配,就可以在消息传递和流端口之间切换(在这个demo中,流端口的粉红色表示字节,而在Message Passing Demo中将message消息处理成ASCII码(字节)后输出,两者相匹配)。

另外,当一个message传送到一个block的端口后的处理方式取决于实际的block的设置,一般有以下两种方式:

  1. 调用消息处理函数,讲收到的数据传入该处理函数进行处理(例如本例中的 handle_msg 函数)
  2. 消息被写入FIFO缓存,block可以在任意需要的时候使用,通常在work函数中使用。

对于同时含有消息端口(message port)以及流端口(streaming port)的block来说可以根据实际的应用环境任选以上两种方式使用。但是强烈建议使用专门的消息处理函数处理消息,而不是在work函数中处理。这是由于如果在work函数中处理消息的话,同事还没有消息传来,work函数会一直等待(阻塞)消息传入才能继续运行,但work函数的运行是不能有阻塞的情况发生的!如果一个block的运行取决于消息,可以使用消息处理函数,然后在work函数被调用时通知block进行相应的操作。只有在特殊的、明确的场合,我们才应该在块中使用上面的方法2。

有了消息传递接口,我们就可以写出没有流端口的block,然后work函数就用不上了,因为work函数是一个旨在处理流式项目的函数事实上,没有流端口的块通常都没有work函数。

现在再看下面的图片是不是就清晰了很多~

(参考:https://wiki.gnuradio.org/index.php/Message_Passing

  • 10
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 11
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 11
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

地球被支点撬走啦

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值