GNURadio-Manual中文文档¶
个人整理的GNURadio中文文档以及学习笔记。GNURadio版本为3.7.10,https://www.gnuradio.org/doc/doxygen-v3.7.10.0/dir_05c3040209450f1dcdb347a8089b09d4.html。
AN Zhenlin, 2020
个人主页:https://www4.comp.polyu.edu.hk/~cszan/
GNURadio源码手册¶
构建说明¶
依赖¶
全局依赖
- git http://git-scm.com/downloads
- cmake (>= 2.6.3) http://www.cmake.org/cmake/resources/software.html
- boost (>= 1.35) http://www.boost.org/users/download/
- cppunit (>= 1.9.14) http://freedesktop.org/wiki/Software/cppunit/
- fftw3f (>= 3.0.1) http://www.fftw.org/download.html
Python
- python (>= 2.5) http://www.python.org/download/
- swig (>= 1.3.31) http://www.swig.org/download.html
- numpy (>= 1.1.0) http://sourceforge.net/projects/numpy/files/NumPy/
构建文档
- doxygen (>= 1.5) http://www.stack.nl/~dimitri/doxygen/download.html
- latex* (>= 2.0) http://www.latex-project.org/
grc: The GNU Radio Companion
- Cheetah (>= 2.0) http://www.cheetahtemplate.org/
- pygtk (>= 2.10) http://www.pygtk.org/downloads.html
gr-wavelet:小波变换
gsl (>= 1.10) http://gnuwin32.sourceforge.net/packages/gsl.htm
gr-qtgui: QT图形接口
qt4 (>= 4.4.0) http://qt.nokia.com/downloads/ qwt (>= 5.2.0) http://sourceforge.net/projects/qwt/ pyqt (>= 4.10.0) http://www.riverbankcomputing.co.uk/software/pyqt/download
gr-wxgui: WX图形接口
wxpython (>= 2.8) http://www.wxpython.org/ python-lxml (>= 1.3.6) http://lxml.de/
gr-audio: 语音子系统
audio-alsa (>= 0.9) http://www.alsa-project.org audio-jack (>= 0.8) http://jackaudio.org/ portaudio (>= 19) http://www.portaudio.com/ audio-oss (>= 1.0) http://www.opensound.com/oss.html audio-osx audio-windows
额外推荐的依赖
uhd: The Ettus USRP Hardware Driver Interface
- uhd (>= 3.0.0) http://code.ettus.com/redmine/ettus/projects/uhd/wiki
gr-video-sdl: PAL and NTSC display
- SDL (>= 1.2.0) http://www.libsdl.org/download-1.2.php
gr-comedi: Comedi hardware interface
- comedilib (>= 0.8.1) http://www.comedi.org/
gr-log: Logging Tools (Optional)
- log4cpp (>= 1.0) http://log4cpp.sourceforge.net/
GNURadio的几个例子¶
这些例子安装在$prefix/share/doc/gnuradio-$version/。
Dial Tone¶
使用两种模块
- gr::analog::sig_source_f,生成 \(350Hz,440Hz\)。
- gr::audio::sink,将输出连接到语音系统,以采样率生成输出信号。audio sink可以设置两个输出。
sig_source_f (freq = 350) -->
audio.sink
sig_source_f (freq = 440) -->
dial_tone.py文件:
from gnuradio import gr
from gnuradio import audio
from gnuradio.eng_option import eng_option
from optparse import OptionParser
try:
from gnuradio import analog
except ImportError:
sys.stderr.write("Error: Program requires gr-analog.\n")
sys.exit(1)
class my_top_block(gr.top_block):
def __init__(self):
gr.top_block.__init__(self)
parser = OptionParser(option_class=eng_option)
parser.add_option("-O", "--audio-output", type="string", default="",
help="pcm output device name. E.g., hw:0,0 or /dev/dsp")
parser.add_option("-r", "--sample-rate", type="eng_float", default=48000,
help="set sample rate to RATE (48000)")
(options, args) = parser.parse_args()
if len(args) != 0:
parser.print_help()
raise SystemExit, 1
sample_rate = int(options.sample_rate)
ampl = 0.1
src0 = analog.sig_source_f(sample_rate, analog.GR_SIN_WAVE, 350, ampl)
src1 = analog.sig_source_f(sample_rate, analog.GR_SIN_WAVE, 440, ampl)
dst = audio.sink(sample_rate, options.audio_output)
self.connect(src0, (dst, 0))
self.connect(src1, (dst, 1))
if __name__ == '__main__':
try:
my_top_block().run()
except KeyboardInterrupt:
pass
FM Modulator¶
这个例子用GRC或者GRC和Python完成。我们用GRC生成一个FM信号,然后用GRC程序或者Python程序解码。
Modulator¶
首先用控制台命令 "gnuradio-companion"运行GRC。用图形接口创建我们的流程图。这里先不详细介绍GRC接口,仅仅利用GRC程序生成数据。 GRC运行之后,打开“fm_tx.grc”。流程图中,生成了电话音频率,求和,重采样,以便我们可以通过整数倍的上采样得到宽带FM信号,然后输出。
实际例子只是为了生成FM电话音信号,保存到文件中。不需要文件太大,只要能说明问题即可。
- gr::blocks::head,模块限制进入文件的采样数量。一旦有了N个采样点,流程图就被终止了。
- gr::blocks::skiphead忽略最初的M个采样点,避免滤波器的跳变和群延时。
运行程序,可以使用"Build->Execute",或者点击工具栏上的齿轮。程序运行一会儿,一旦控制台显示了"nitems"参数设置的item数量。 在一个"dummy.dat"文件中有FM的复数采样点。
Demodulator¶
GRC程序"fm_rx.grc",对应着python脚本,"fm_demod.py"。
- gr::blocks::file_source,读取文件
- gr::analog::quadrature_demod_cf,把文件复数的FM信号转换成浮点信号。
我们把200bps输入信号重采样到44.1kbps(语音速率)。因为这个重采样不能使用整数倍的抽取速率,所以我们使用了任意速率重采样器, gr::filter::pfb_arb_resampler_fff。将输出的信号以 \(44.1Khz\) 的采样率滤波到 \(15Khz\) 的带宽,然后利用 gr::audio::sink 输出。
Flowchart¶
操作流程图¶
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' 方法来替换这两个方法的先后调用。
延迟和吞吐量¶
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。这就意味着,由指令设置的缓存大小最终会四舍五入到最接近的系统粒度上。
动态配置流程图¶
在通信系统运行的时候,经常需要根据输入信号改变系统的状态,这时候需要更新流图。更新意味着改变结构,不独立的参数设置。 例如, 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()
模块类型¶
概述¶
利用gnuradio的框架,用户可以创建多种类型的模块来实现特定的数据处理。
- Synchronous Blocks (1:1)
- Decimation Blocks (N:1)
- Interpolation Blocks (1:M)
- Basic (a.k.a. General) Blocks (N:M)
Synchronous Blocks (1:1)¶
同步模块一个端口的的消耗和输出的点数量是一样的。零输入的同步模块叫做source,零输出的同步模块叫做sink。
#include <gr_sync_block.h>
class my_sync_block : public gr_sync_block
{
public:
my_sync_block(...):
gr_sync_block("my block",
gr_make_io_signature(1, 1, sizeof(int32_t)),
gr_make_io_signature(1, 1, sizeof(int32_t)))
{
//constructor stuff
}
int work(int noutput_items,
gr_vector_const_void_star &input_items,
gr_vector_void_star &output_items)
{
//work stuff...
return noutput_items;
}
};
- noutput_items是输入和输出的缓冲区大小
- 输入的签名gr_make_io_signature(0, 0, 0)使得模块为source
- 输出的签名gr_make_io_signature(0, 0, 0)使得模块为sink
gr_make_io_signature(int min_streams, int max_streams, int sizeof_stream_item)控制了接口的数量,以及接口的数据大小。下面给出了python的模块样例:
class my_sync_block(gr.sync_block):
def __init__(self):
gr.sync_block.__init__(self,
name = "my sync block",
in_sig = [numpy.float32, numpy.float32],
out_sig = [numpy.float32],
)
def work(self, input_items, output_items):
output_items[0][:] = input_items[0] + input_items[1]
return len(output_items[0])
input_items和output_items是包含列表的列表。input_items的每个端口有一个输入采样点向量。output_items也是一个向量可以将输出点存起来。output_items[0]的长度等于C++中noutput_items。
- in_sig=None的时候,模块为source
- out_sig=None的时候,模块为sink。这时候要用len(input_items[0])
- 不像C++中的gr::io_signature类,python可以直接创建指定数据类型的list
Basic Block¶
#include <gr_block.h>
class my_basic_block : public gr_block
{
public:
my_basic_adder_block(...):
gr_block("another adder block",
in_sig,
out_sig)
{
//constructor stuff
}
int general_work(int noutput_items,
gr_vector_int &ninput_items,
gr_vector_const_void_star &input_items,
gr_vector_void_star &output_items)
{
//cast buffers
const float* in0 = reinterpret_cast(input_items[0]);
const float* in1 = reinterpret_cast(input_items[1]);
float* out = reinterpret_cast(output_items[0]);
//process data
for(size_t i = 0; i < noutput_items; i++) {
out[i] = in0[i] + in1[i];
}
//consume the inputs
this->consume(0, noutput_items); //consume port 0 input
this->consume(1, noutput_items); //consume port 1 input
//this->consume_each(noutput_items); //or shortcut to consume on all inputs
//return produced
return noutput_items;
}
};
GNURadio scheduler¶
GNURadio的scheduler是数据流调度的核心。gnuradio的官方文档和教程关于这个部分的介绍很少。为了解释清楚调度器,本文参考了gnuradio的其中一个作者Tom Rondeau的slides: gnuradio-note ,以及笔者的一些源码阅读。
首先看一个例子,后面的讨论都会基于这个简单的例子。创建两个数据流经过一个同步模块,再经过一个十倍欠采样模块,最后输出。

在gnuradio里,对于每个模块之间,调度器都会维护一个buffer。对于一个block输入是input buffer,输出是output buffer。在output区,block利用Wptr指针写数据;在input区,block利用Rptr指针读取数据。

对于模块Decimator,我们需要足够的输入来计算输出。

接着我们复习一下block的工作函数。
general_work()和work()¶
general_work()和work()是block工作的核心函数,数据流的操作都在这里完成。
int block::general_work(int noutput_items,
gr_vector_int &ninput_items,
gr_vector_const_void_star &input_items,
gr_vector_void_star &output_items)
input_items 是一个vector包含一组指针指向input buffer。output_items 是一个vector包含一组指针指向output buffer。general_work()方法不指定输入输出的关系,只是指定输入和输出的数量。noutput_items是最小的output数量。ninput_items是input buffer。
int block::work(int noutput_items,
gr_vector_const_void_star &input_items,
gr_vector_void_star &output_items)
work函数指定了input和output的关系。通过noutput_items确定ninput_items。有了这些知识,我们开始看scheduler的工作方式。
Scheduler的基本功能¶
GNURadio的调度器会处理block的需求也就是对于数据流和数据指针的调度,以及控制buffer缓冲区的大小。除此之外,buffer,messages流和stream tags也会由调度器控制。Block之间会传递三种类型的数据:采样点数据data,消息messages,数据标签tags。下面我们分别看一下,对于三种类型,调度器需要作什么。
Data调度¶
对于Data,blocks有几个需求:alignment,output multiple,forecast,history。alignment和output multiple都是为了控制输出的数据量要满足一定的倍数。forecase和history都是控制buffer的数据满足读取的需求。调度器调度数据主要就是满足alignment,output multiple,forecast,history的需求。

- alignment: 将输出对齐到一定倍数,不一定保证。
- output multiple:将输出对齐到一定倍数,保证实现。如不满足会等待。
- forecast:利用ninput_items_required[i]告诉调度器,对于每个输出需要多少输入。
- history:利用set_history()方法,高速scheduler进一步调整buffer的长度。如果我们将history设置为N,那么buffer里的前N个数据中的N-1个数据为历史数据(即使你已经用过了)。history保证了buffer里至少有N-1个数据。

当我们给定输出的数据数量noutput_items,那么我们可以计算输入数据量ninput_items_required[i]:
//forecast()
ninput_items_required[i]=noutput_items+history()-1; // default
ninput_items_required[i]=noutput_items*decimation()+history()-1; // Decim
ninput_items_required[i]=noutput_items/interpolation()+history()-1; // Interp
经过这样的forecast设置,可以保证输入满足输出的需求。
Buffer和latency调度¶
调度器也会控制缓冲区大小和延迟。又一下几个方法完成。
// Caps the maximum noutput_items.
// Will round down to nearest output multiple, if set.
// Does not change the size of any buffers.
set_max_noutput_items(int)
// Sets the maximum buer size for all output buers.
// Buffer calculations are based on a number of factors, this limits overall size.
// On most systems, will round to nearest page size.
set_max_output_buffer(long)
// Sets the minimum buer size for all output buers.
// On most systems, will round to nearest page size.
set_min_output_buffer(long)
Messages调度¶
Message可以用来传递一些控制信息,或者数据包Packet Unit Data。每个block可以创建自己的Messages queue。当messages传递的时候,messages会放到subscriber的queue里。Messags的优先级是高于data的,在后面的整体操作流程中,优先处理messages。调度器dispatch处理messags是通过调用block的handler实现的。Messags的queue大小是由max_nmsgs控制的。

Stream Tags 调度¶
Steam tags是帮助block标记和识别处理过的数据。对于一个指定的samples,我们打上一些tag。tag会逐级传递。随着data rate的变化,tag的位置会更新。tag_propagation_policy标签的传递规则是有block的构造器控制的。tag的处理是在general_work后面。tag_propagation_policy有两种TPP_ALL_TO_ALL和TPP_ONE_TO_ONE。第一种会把所有Tag都标上每一个samples,后一种是一对一的。

综上,调度器需要完成以下的任务:
- 计算input有多少可用的点
- 计算output有多空间
- 确定限制条件: history, alignment, forecast
- 必要的调整或者重试
- call general_work,给block恰当的指针和数据
- 从general_work的返回值更新指针
Scheduler Flow Chart¶
有了上面的基础,我们就做好了了解scheduler如何调度一个完整的gnuradio flow chart的准备。起初,调度器会为每个模块初始化创建一个线程。tpb_container为block的线程池。

tpb_thread_body会控制所有线程。首先设置线程优先级。如果block就绪了,就可以处理传递的messages。如果input的数据量不够,会将block设置为BLKD_IN。直到数据流满足了需求,进入核心函数run_one_iteration()。这个函数在block_executor.cc文件中实现。如果函数结束,ready状态的时候,会通知与这个block相邻的其他block。告诉他们,input和output缓冲区的状态。如果是READY_NO_OUTPUT,则说明没有数据输出,通知上一block。如果DONE,传递DONE的消息到其他所有block。

AGC自动增益控制¶
这里整理一下GNURadio的自动增益控制是如何实现的。自动增益控制模块是在analog大类里实现,并一共定义了三种自动增益控制: agc,agc2,agc3。agc是最普通的自动增益控制,agc,agc2增加了attack和decay控制。attack指的是agc可以多快的响应功率迅速增加的信号,decay指的是agc可以多快的响应功率迅速减小的信号。这两个时间决定了AGC的带宽。通常来说,我们要求AGC的带宽要小于信号的最小频率,这样才不会影响信号的解调。
class ANALOG_API agc_cc
{
public:
/*!
* Construct a complex value AGC loop implementation object.
*
* \param rate the update rate of the loop.
* \param reference reference value to adjust signal power to.
* \param gain initial gain value.
* \param max_gain maximum gain value (0 for unlimited).
*/
agc_cc(float rate = 1e-4,
float reference = 1.0,
float gain = 1.0,
float max_gain = 0.0)
: _rate(rate), _reference(reference), _gain(gain), _max_gain(max_gain){};
virtual ~agc_cc(){};
float rate() const { return _rate; }
float reference() const { return _reference; }
float gain() const { return _gain; }
float max_gain() const { return _max_gain; }
void set_rate(float rate) { _rate = rate; }
void set_reference(float reference) { _reference = reference; }
void set_gain(float gain) { _gain = gain; }
void set_max_gain(float max_gain) { _max_gain = max_gain; }
gr_complex scale(gr_complex input)
{
gr_complex output = input * _gain;
_gain += _rate * (_reference - std::sqrt(output.real() * output.real() +
output.imag() * output.imag()));
if (_max_gain > 0.0 && _gain > _max_gain) {
_gain = _max_gain;
}
return output;
}
void scaleN(gr_complex output[], const gr_complex input[], unsigned n)
{
for (unsigned i = 0; i < n; i++) {
output[i] = scale(input[i]);
}
}
protected:
float _rate; // adjustment rate
float _reference; // reference value
float _gain; // current gain
float _max_gain; // max allowable gain
};
Polymorphic Types¶
介绍¶
Polymorphic Types(多态)是一种高级的数据类型,被设计成通用类型用来在block和thread之间传递数据。 在stream tags和message passed接口中使用的很多。下面由一段python代码看pmt的使用方法:
>>> import pmt
>>> P = pmt.from_long(23)
>>> type(P)
<class 'pmt.pmt_swig.swig_int_ptr'>
>>> print P
23
>>> P2 = pmt.from_complex(1j)
>>> type(P2)
<class 'pmt.pmt_swig.swig_int_ptr'>
>>> print P2
0+1i
>>> pmt.is_complex(P2)
True
我们利用from_long和from_complex导入了一个长整数和一个复数。但是他们的类型是一样的,都是pmt。 这样我们就可以把这些变量利用swig传入C++。 同样C++代码如下:
#include <pmt/pmt.h>
// [...]
pmt::pmt_t P = pmt::from_long(23);
std::cout << P << std::endl;
pmt::pmt_t P2 = pmt::from_complex(gr_complex(0, 1)); // Alternatively: pmt::from_complex(0, 1)
std::cout << P2 << std::endl;
std::cout << pmt::is_complex(P2) << std::endl;
有两个特点在C++和python都很重要。首先,我们可以很容易的打印pmt的内容。PMT内置了把值转化成string的方法(某些类型的数据不行)。 而且,PMT必须显式的知道他们的类型,所以我们可以查询他们的类型,比如调用is_complex方法。
non-PMT和PMT的转化使用 from_x和to_x方法。
pmt::pmt_t P_int = pmt::from_long(42);
int i = pmt::to_long(P_int);
pmt::pmt_t P_double = pmt::from_double(0.2);
double d = pmt::to_double(P_double);
string是一个比较特殊的类型,他的转化是特殊的方法。
pmt::pmt_t P_str = pmt::string_to_symbol("spam");
pmt::pmt_t P_str2 = pmt::intern("spam");
std::string
str = pmt::symbol_to_string(P_str);
pmt::intern是symbol_to_string的另外一种方法。
在python中,我们可以使用弱类型。
Metadata Information¶
Introduction¶
元数据文件在文件头有额外的元数据存储着有关采样点类型的信息。 原始文件,二进制文件不带有任何额外信息。所以这类文件必须被特殊处理。 系统中的任何改变,比如采样率或者接受机的频率都没有在文件中体现,元数据的文件头解决了这类问题。
我们利用gr::blocks::file_meta_sink写入元数据文件,利用gr::blocks::file_meta_source读取元数据文件。
元数据文件的文件头描述了一个数据分片的信息。比如,item size,数据类型(comples),采样率,首个采样点的时间戳, 文件头的大小和分片大小。
第一个静态区保存着:
- version: (char) version number (usually set to METADATA_VERSION)
- rx_rate: (double) Stream's sample rate
- rx_time: (pmt::pmt_t pair - (uint64_t, double)) Time stamp (format from UHD)
- size: (int) item size in bytes - reflects vector length if any.
- type: (int) data type (enum below)
- cplx: (bool) true if data is complex
- strt: (uint64_t) start of data relative to current header
- bytes: (uint64_t) size of following data segment in bytes
额外的分局存储在每一个收到的tags里。
- rx_rate: the sample rate of the stream.
- rx_time: the time stamp of the first item in the segment.
在一个文件中的数据类型是不会变得。因为GNU Radio的block只能在构造函数的IO signature设置数据额类型,所以之后的数据类型改变不会被接受。
元数据文件的类型¶
GNU Radio支持两种:
- inline:headers和数据在同一行
- detached:headers在一个单独的header file里
inline是标准的方法。如果使用detached方法,headers简单地插入到detached header file;数据文件是标准的无中断的原始二进制格式。
更新headers¶
实现¶
使用¶
例子¶
例子在
- gr-blocks/examples/metadata
GRC例子
- file_metadata_sink: create a metadata file from UHD samples.
- file_metadata_source: read the metadata file as input to a simple graph.
- file_metadata_vector_sink: create a metadata file from UHD samples.
- file_metadata_vector_source: read the metadata file as input to a simple graph.
message passing¶
Introduction¶
GNURadio的最初设计是为了处理数据流,比特和采样点为基本的处理单位。为了传输控制信息,元数据,包结构,Gnuradio引入了标签流。 标签流和数据流是并行处理的。标签流是为了存储metadata,控制信息的。标签和采样点是关联的,和数据流一起传输。这个模型使得模块可以识别一些特殊的事件,采取一定的措施。 缺点是:标签流只能单向流动,而且只能在模块的work函数访问。优点是,标签流和数据流是等同步的。
设计这个机制的两个目的是:
- 下行的模块可以回传数据给上行模块
- 外部程序可以用这个接口和GNURADIO通信
这个模块严重依赖多态类型实现(PMT)。
Message Passing API¶
message passing的接口在gr::basic_block中得到了实现,gr::basic_block是所有block的父类。 每个block都有一个消息队列可以存储消息,并向下传输消息。 而且可以区分输入和输出端口。
端口是在构造器中声明的:
void message_port_register_in(pmt::pmt_t port_id)
void message_port_register_out(pmt::pmt_t port_id)
每个接口有一个端口id。其他block要和这个端口通信,接收或者发送message,必须订阅这个端口。subscribe的API如下:
void message_port_pub(pmt::pmt_t port_id,pmt::pmt_t msg);
void message_port_sub(pmt::pmt_t port_id,pmt::pmt_t target);
void message_port_unsub(pmt::pmt_t port_id,pmt::pmt_t target);
任何block订阅了另一个block的输出端口后,会在发布消息的时候收到消息。 在一个block内部,当他要发布消息的时候,他会向每个订阅了他输出的端口的block的消息队列发送消息。
Message Handler Functions¶
订阅了block的消息的端口后, 必须声明一个处理方法。 利用gr::basic_block::message_port_register_in订阅了一个端口后,我们必须把这个端口绑定到一个消息处理器上。 这个部分利用的是boost的bind函数:
set_msg_handler(pmt::pmt_t port_id,
boost::bind(&block_class::message_handler_function, this, _1));
- 'port_id' 是输入的端口id
- 'block_class::message_handler_function'是处理这个端口消息的函数
- this和_1是boost绑定函数的
void block_class::message_handler_function(pmt::pmt_t msg);
Connecting Messages through the Flowgraph¶
这个机制的接口是独立于数据流的,所以创建模块的时候不需要
Code Examples¶
下面利用gr::blocks::message_debug 和 gr::blocks::tagged_stream_to_pdu 。 gr::blocks::message_debug模块是用来调试消息传递的模块。有三个输入口:
- print,打印所有信息到标准输出流。
- store,把消息存储到list里,和gr::blocks::message_debug::get_message(int i)连接,取出第i个消息。
- pdu_print,把PDU消息转化成标准流。
{
message_port_register_in(pmt::mp("print"));
set_msg_handler(pmt::mp("print"),
boost::bind(&message_debug_impl::print, this, _1));
message_port_register_in(pmt::mp("store"));
set_msg_handler(pmt::mp("store"),
boost::bind(&message_debug_impl::store, this, _1));
message_port_register_in(pmt::mp("print_pdu"));
set_msg_handler(pmt::mp("print_pdu"),
boost::bind(&message_debug_impl::print_pdu, this, _1));
}
GNURadio使用教程¶
USRP环境配置¶
这里主要介绍使用USRP平台,上位机是ubuntu的环境配置。从容易到难列出配置环境的几种方式,最后针对几种不同的软件无线电平台给出特有的步骤。
SDR Live¶
最简单的方式,利用官方的SDR Live镜像,直接安装配有完整环境的系统盘。需要注意的是,这样安装的系统只能在线读写,每次启动都会复原。
https://wiki.gnuradio.org/index.php/GNU_Radio_Live_SDR_Environment
使用自动化脚本¶
这种方式也很方便。前人写好的自动化脚本完成了从源代码的下载,编译和安装几个步骤。过程很慢,要有心理准备。
wget http://www.sbrac.org/files/build-gnuradio && chmod a+x ./build-gnuradio && ./build-gnuradio
其中有可能遇到几种问题:
- 路径访问权限报错。
chmod: changing permissions of './build-gnuradio': Operation not permitted
需要在一个有完全访问权限的路径使用脚本。
- 依赖的包找不到,笔者遇到了两个。
Failed to find package 'libzmq1-dev' in known package repositories
Failed to find package 'python-wxgtk2.8' in known package repositorie
wkgtk是wx-gui的包,如果这里没有装成功,之后wx-gui的组件就不能使用了,如果使用了wx-gui,会报出如下错误。
wxgui-python
TypeError: Error when calling the metaclass bases
multiple bases have instance lay-out conflict
之所以找不到这个包是因为ubuntu16.04不再有这个包了,是wkgtk3.0。 这里首先获取apt源的列表,安装历史版本2.8。 关于libzmq1-dev这个包没有找到合适的解决方案,但是这个不影响使用。
Pybombs¶
Gnuradio推荐的方式是利用官方发布的pybombs工具安装gnuradio及其依赖。 但是实际操作的时候,pybombs的bug还是很多,这里并不推荐。
E310/E312的环境配置¶
- 需要重新针对E310/312构建UHD驱动。
https://kb.ettus.com/Software_Development_on_the_E310_and_E312
git clone https://github.com/EttusResearch/uhd.git
cd ./host
mkdir build
cmake ./ -DENABLE_E300=ON
make install -j8
-j8是为了使用多核,速度会快些。
- Remote login E312
ssh root@192.168.10.10
- 切换网络模式
usrp_e3x0_network_mode
- 开启另一个终端,查找设备。
uhd_find_devices --args="addr=192.168.10.10"
如果上面的构建失败就会出现
No UHD Devices Found
FPGA版本不兼容¶
E31x系列比较烦人的是内部有一个linux系统,也要配置环境。 如果内部系统用的FPGA版本和外部控制电脑不一致,虽然UHD驱动仍然可以找到设备,调试的时候就会报错。
RuntimeError: RuntimeError: Expected FPGA compatibility number 16.x, but got 14.0:
The FPGA build is not compatible with the host code build.
Please run:
"/usr/local/lib/uhd/utils/uhd_images_downloader.py"
当然按照他给的方案,直接下载uhd镜像是肯定不行的。用UHD工具查看FPGA版本。
uhd_find_devices
linux; GNU C++ version 5.4.0 20160609; Boost_105800; UHD_003.010.002.000-3-g122bfae1
--------------------------------------------------
-- UHD Device 0
--------------------------------------------------
Device Address:
type: e3x0
addr: 192.168.10.10
name:
serial: 30CCCC1
product: 30675
E310,E312,E313的FPGA的硬件版本都是e3x0。
- 方案一,降低本机的UHD版本
注意到本机的UHD版本是3.11.1,与E312内部的版本不同。 这里选择将本机的UHD版本降低到与E312一致,这时候运行程序的时候会出现GnuRadio Companion的UHD组件冲突,需要重新编译GnuRadio。
_uhd.swig: undefined symbol: _ZN3uhd4usrp10multi_usrp7ALL_LOSB5cxx11E
再利用E312的测试程序。
rx_ascii_art_dft --freq 88.1e6 --rate 400e3 --gain 30 --ref-lvl -30
- 方案二,升级USRP的UHD版本。
首先格式化SD卡
sudo umount /dev/sdb1
sudo mkdosfs -F 32 -v /dev/sdb1
Read more: http://www.arthurtoday.com/2013/10/ubuntu-mkdosfs-format-sd-card.html#ixzz52HCPfTVQ
然后写入SD卡的镜像文件
sudo dd if=sdimage-gnuradio-dev.direct of=/dev/<yoursdcard> bs=1M
<yoursdcard>可以用fdisk -l或者df看到。
配置ip信息
USB串口进入设备,在设备内更新网络配置文件。
sudo screen /dev/ttyUSB0 115200
cd etc/network
vi interface
在auto eth0后面加入
iface eth0 inet static
address 192.168.10.10
netmask 255.255.255.0
gateway 192.168.10.1
BladeRF环境配置¶
BladeRF有详细的官方windows教程,很难做错,这里就毋庸赘言了。主要介绍BladeRF在ubuntu的环境配置。
同样官方给了easy安装版本。 https://github.com/Nuand/bladeRF/wiki/Getting-Started:-Linux#Easy_installation_for_Ubuntu_The_bladeRF_PPA
遇到的问题可能有:
- FPGA not laoded
bladeRF-cli -i
bladeRF> info
Serial #: 19a3df66ec02993409cd516b0ef169ff
VCTCXO DAC calibration: 0x925f
FPGA size: 115 KLE
FPGA loaded: no
USB bus: 3
USB address: 2
USB speed: SuperSpeed
Backend: libusb
Instance: 0
从BladeRF官网下载对应的FPGA镜像 https://www.nuand.com/fpga.php 。
$ bladeRF-cli -L hostedx115-latest.rbf
Writing FPGA to flash for autoloading...
[INFO @ usb.c:498] Erasing 55 blocks starting at block 4
...
[INFO @ usb.c:617] Done reading 13952 pages
Done.
$ bladeRF-cli -l hostedx115-latest.rbf
Loading fpga...
Done.
在VSCode调试GNURadio Block¶
Prerequisite¶
旧版本的VScode必须用sudo才能用GDB调试。GDB调试是Linux的标准调试器。$TMPDIR是VScode的用户临时文件。
类型转换¶
- ishort_to_complex模块:interleaved_short_to_complex ,交叉short类型转complex
控制接口¶
为GNURadio创建分布式应用。这个模块使得内部Blocks可以连接到一个输出流,远程画图; 以及输出可以被设置,监听,绘制的变量。模块在ctrlport命名空间内,可以被如下方式访问。
from gnuradio import ctrlport
注意¶
SBX子板的DC控制¶
SBX子板的自动DC控制,在直接做ASK调制的时候,有长连1的时候, SBX子板会将长1调整成直流分量,从而使得ASK调制在接受端看起来是相反的。
self.source.set_auto_dc_offset(False)
OOT模块开发¶
UHD开发¶
Multiple USRP Connection and Configuration¶
Network Connection¶
- 如果有两张网卡,可以将两张网卡配置在不同网段,然后两个USRP分别配置在两个网段里,然后在一个gnuradio脚本里直接访问。
- 如果多台PC用交换机访问每个USRP,需要将多张网卡配置在一个网段里,还要指定每个网卡的UHD通信端口,端口不能冲突,否则UHD只能找到一个设备。
MIMO Wire Connection¶
USRP可以利用MIMO线将两台USRP同步。
Clock Source¶
Self-Calibration¶
UHD提供了软件矫正IQ不平衡和直流偏差的工具。偏差的结果被保存在用户home路径下的一个CSV文件。 当用户重新启动UHD,GnuRadio会先读取矫正文件。
完成自矫正,首先需要断开所有USRP射频连接。 .. code:: bash
uhd_cal_rx_iq_balance: - minimizes RX IQ imbalance vs. LO frequency uhd_cal_tx_dc_offset: - minimizes TX DC offset vs. LO frequency uhd_cal_tx_iq_balance: - minimizes TX IQ imbalance vs. LO frequency
第一部分,GNURadio的指导手册和源码阅读。整理了GNURadio的基本结构和常用的模块。第二部分整理了笔者实际做GNURadio开发过程中遇到的问题,包括环境的搭建和代码调试。第三部分是UHD的开发部分。UHD是ettus公司开发的USRP的驱动。射频的低层配置需要有UHD完成。