1.3. Flowchart

1.3.1. 操作流程图

GNURadio基本的结构就是flowchart。flowchart是有向无环图,有一个或者多个source(输入采样点),一个或者多个sin(输出采样点或者终止)。

每个程序必须至少创建一个'top_blcok'作为flowchart的顶层结构。这个结构提供了很多全局的方法,'start,' 'stop,' and 'wait'。

GNU Radio的应用创建gr_top_block来实例化blocks,连接模块,然后开始gr_top_block。下面给出一个FIR滤波器的例子。

from gnuradio import gr, blocks, filter, analog
class my_topblock(gr.top_block):
    def __init__(self):
        gr.top_block.__init__(self)
        amp = 1
        taps = filter.firdes.low_pass(1, 1, 0.1, 0.01)
        self.src = analog.noise_source_c(analog.GR_GAUSSIAN, amp)
        self.flt = filter.fir_filter_ccf(1, taps)
        self.snk = blocks.null_sink(gr.sizeof_gr_complex)
        self.connect(self.src, self.flt, self.snk)
if __name__ == "__main__":
    tb = my_topblock()
    tb.start()
    tb.wait()

'tb.start()'开始了数据流流过flowchart,'tb.wait()'等价于知道gr_top_block结束后等待线程'join'。 可以利用 'run' 方法来替换这两个方法的先后调用。

1.3.2. 延迟和吞吐量

GNU Radio运行一个调度器来优化吞吐量。动态调度器使得成块的数据通过blocks从source流到sink。数据块的大小和信号处理的速度有关。 对于每个block,能够处理的数据量和输出buffer的空间和输入buffer中已经收到的数据量有关。

这样操作的结果就是,一个模块可能申请了很多数据来处理(规模可能到几千个采样点)。 从速度的角度来看,这样使得大部分的处理时间都用在了处理数据使得系统更有效率。 申请小的数据块就意味着会向调度器多次申请。 这样做的副作用就是当block处理大量数据的时候会出现延迟。

为了解决这个问题,gr_top_block可以限制block可以收到的数据量,也就是上一个block的需要输出的数据量。 一个block只能得到比这个数量少的采样点输入,所以相当于一个block的最大延迟。 通过限制每次调用申请的数据量,我们相当于增加了调度器的负担,所以降低了全局效率。

可以按照如下方式限制输出数据量:

tb.start(1000)
tb.wait()
# or
tb.run(1000)

使用这个方法,我们设置了一个全局的item数量限制。每个block可以通过'set_max_noutput_items(m)',重写这个限制。

tb.flt.set_max_noutput_items(2000)
tb.run(1000)

在一些情况下,可能想要限制输出缓存的大小。这个能够防止要输出的数据量过大超过了上限,而使得新的输出延迟。 你可以为每个block的每个输出端口设置输出延迟。

tb.blk0.set_max_output_buffer(2000)
tb.blk1.set_max_output_buffer(1, 2000)
tb.start()
print tb.blk1.max_output_buffer(0)
print tb.blk1.max_output_buffer(1)

上面的接口blk0所有端口被设置成缓存为2000个items,而blk1只有端口1被设置了,其余为默认值。

注意:

  • 在运行的开始,缓存的大小就被配置好了。
  • 一旦flowgraph开始,缓存长度对于一个block的值是不能被更改的,即使是lock()/unlock()。如果要改变缓存大小,必须删除block再重新建立。
  • 这可能影响到吞吐量。
  • 真实的缓存大小实际上是依赖于最小系统粒度。理论上就是一个页的大小,通常是4096bytes。这就意味着,由指令设置的缓存大小最终会四舍五入到最接近的系统粒度上。

1.3.3. 动态配置流程图

在通信系统运行的时候,经常需要根据输入信号改变系统的状态,这时候需要更新流图。更新意味着改变结构,不独立的参数设置。 例如, gr::blocks::add_const_cc中改变加的常量大小可以由调用'set_k(k)'完成。

更新流图有三步:

  • 锁定,停止运行,处理数据
  • 更新
  • 解锁

下面的例子展示了一个流图,首先加入两个gr::analog::noise_source_c,然后由gr::blocks::sub_cc替代gr::blocks::add_cc。

from gnuradio import gr, analog, blocks
import time
class mytb(gr.top_block):
    def __init__(self):
        gr.top_block.__init__(self)
        self.src0 = analog.noise_source_c(analog.GR_GAUSSIAN, 1)
        self.src1 = analog.noise_source_c(analog.GR_GAUSSIAN, 1)
        self.add = blocks.add_cc()
        self.sub = blocks.sub_cc()
        self.head = blocks.head(gr.sizeof_gr_complex, 1000000)
        self.snk = blocks.file_sink(gr.sizeof_gr_complex, "output.32fc")
        self.connect(self.src0, (self.add,0))
        self.connect(self.src1, (self.add,1))
        self.connect(self.add, self.head)
        self.connect(self.head, self.snk)
    def main():
        tb = mytb()
        tb.start()
        time.sleep(0.01)
        # Stop flowgraph and disconnect the add block
        tb.lock()
        tb.disconnect(tb.add, tb.head)
        tb.disconnect(tb.src0, (tb.add,0))
        tb.disconnect(tb.src1, (tb.add,1))
        # Connect the sub block and restart
        tb.connect(tb.sub, tb.head)
        tb.connect(tb.src0, (tb.sub,0))
        tb.connect(tb.src1, (tb.sub,1))
        tb.unlock()
        tb.wait()
    if __name__ == "__main__":
        main()

在更新flowchart的时候,最大输出items数量也可以被更改。一个block也可以调用'unset_max_noutput_items()' 来解锁限制恢复到全局值。 下面的例子扩展了上面的例子,增加了设置最大输出items数量。

from gnuradio import gr, analog, blocks
import time
class mytb(gr.top_block):
    def __init__(self):
        gr.top_block.__init__(self)
        self.src0 = analog.noise_source_c(analog.GR_GAUSSIAN, 1)
        self.src1 = analog.noise_source_c(analog.GR_GAUSSIAN, 1)
        self.add = blocks.add_cc()
        self.sub = blocks.sub_cc()
        self.head = blocks.head(gr.sizeof_gr_complex, 1000000)
        self.snk = blocks.file_sink(gr.sizeof_gr_complex, "output.32fc")
        self.connect(self.src0, (self.add,0))
        self.connect(self.src1, (self.add,1))
        self.connect(self.add, self.head)
        self.connect(self.head, self.snk)
    def main():
        # Start the gr_top_block after setting some max noutput_items.
        tb = mytb()
        tb.src1.set_max_noutput_items(2000)
        tb.start(100)
        time.sleep(0.01)
        # Stop flowgraph and disconnect the add block
        tb.lock()
        tb.disconnect(tb.add, tb.head)
        tb.disconnect(tb.src0, (tb.add,0))
        tb.disconnect(tb.src1, (tb.add,1))
        # Connect the sub block
        tb.connect(tb.sub, tb.head)
        tb.connect(tb.src0, (tb.sub,0))
        tb.connect(tb.src1, (tb.sub,1))
        # Set new max_noutput_items for the gr_top_block
        # and unset the local value for src1
        tb.set_max_noutput_items(1000)
        tb.src1.unset_max_noutput_items()
        tb.unlock()
        tb.wait()
    if __name__ == "__main__":
        main()