- 学习管理和用C++编写自己的OOT模块
- 基于一个实例讨论OOT模块
- 在tutorial的module中,我们将用c++建立**My QPSK Demodulator**作为我们的QPSK解调。
- 理解开发OOT模块的差别
- 本节包含一些GNU Radio框架的高级主题,
- 这些主题用于OOT模块中的一些特殊实现
* C++基础
* 之前的章节学习
## 4.1 C++还是Python?
这是我们每个人都需要思考的问题,在tutorial3中我们已经展示了gr_modtool在创建blocks时可以选择语言,像下面这样:
$ gr_modtool add -t sync -l python
$ gr_modtool add -t sync -l cpp # This is the default
这两种语言除了编译器和解释器之外,还有其他区别。比如,当性能不是主要因素时,可以考虑python,因为它很简洁,并且测试十分方便。考虑到性能时,用c++往往更有意义。所以,具体选择哪一模块,需要主观上的考虑。
## 4.2 使我们的OOT模块变的灵活
我们将介绍gr_modtool制作OOT模块和用c++编写blocks
gr_modtool命令的具体介绍,参考tutorial3里关于gr_modtool的介绍,或者快速查看如下表格:
|Name|Alias|Description|
|--------|-----------|
|help|h|Enlist the operations available|
|disable|dis|Disable block (comments out CMake entries for files)|
|info|getinfo, inf|Return information about a given module|
|remove|rm, de|Remove block (delete files and remove Makefile entries)|
|makexml|mx|Make XML file for GRC block bindings (Experimental feature!)|
|add|insert|Add block to the out-of-tree module.|
|newmod|nm,create|Create a new out-of-tree module|
## 4.2.1 目标
当这个教程完成时,我们希望可以完成下面这个流图:
![title](https://leanote.com/api/file/getImage?fileId=585243c6ab64416d76005381)
这个流图用OOT **totorial**下的**My QPSK Demodulator** block完成QPSK收发链。我们将要用c++编写这个block。所有其他block都是GNU Radio blocks标准库里的。
如同在上一节的tutorial一样。**My QPSK Demodulator**输入为复数浮点数,输出为字符型。我们会画出发射的复数信号和二进制(从0到3)的信号。
## 4.2.2 Step 1: 创建一个OOT module gr-tutorial
```bash
xyz@comp:mydir$ gr_modtool nm tutorial
Creating out-of-tree module in ./gr-tutorial... Done.
Use 'gr_modtool add' to add a new block to this currently empty module.
xyz@comp:mydir$ ls
xyz@comp:mydir$ gr-tutorial
ls查看新建的模块的目录结构
xyz@comp:mydir$ cd gr-tutorial
xyz@comp:mydir/gr-tutorial$ ls
apps cmake CMakeLists.txt docs examples grc include lib python swig
## 4.2.3 Step2: 插入**My QPSK Demodulator**到OOT模块中
在gr-tutorial中,再次使用gr_modtool,我们创建my_qpsk_demod block:
xyz@comp:mydir/gr-tutorial$ gr_modtool add my_qpsk_demod_cb
GNU Radio module name identified: tutorial
Enter code type: general
Language: C++
Block/code identifier: my_qpsk_demod_cb
Enter valid argument list, including default arguments: bool gray_code
Add Python QA code? [Y/n]
Add C++ QA code? [y/N] Y
Adding file 'my_qpsk_demod_cb_impl.h'...
Adding file 'my_qpsk_demod_cb_impl.cc'...
Adding file 'my_qpsk_demod_cb.h'...
Editing swig/qpsk_demod_swig.i...
Adding file 'qa_my_qpsk_demod_cb.py'...
Editing python/CMakeLists.txt...
Adding file 'qpsk_demod_my_qpsk_demod_cb.xml'...
Editing grc/CMakeLists.txt...
在这个过程中,我们需要手工输入一些条件,接下来一一来看。
xyz@comp:mydir/gr-tutorial$ gr_modtool add my_qpsk_demod_cb
my_qpsk_demod_cb代表block的class名称,后缀,'cb'表示输入是复数,输出是byte型
Enter code type: general
GNU Radio中存在不同类型的block:general, sync, interpolator/decimator, source/sink, Hierarchical等等。我们选择general类型的block,接下来的部分会具体讨论。
许多例子中,block都需要一个用户接口。For my_qpsk_demod_cb来说,gray_code别用作"默认参数"
Enter valid argument list, including default arguments: bool gray_code
GNU Radio还提供了编写测试的选项。
Add Python QA code? [Y/n]
Add C++ QA code? [y/N] y
上述过程已经搭建好我们的block和OOT模块之间的联系。接下来我们需要专注于blocks的实现了。
## 4.2.4 Step3: 编写代码
接下来实现block的逻辑。打开lib目录下的`my_qpsk_demod_cb_impl.cc`文件,结构如下:
* The private constructor
my_qpsk_demod_cb_impl::my_qpsk_demod_cb_impl(bool gray_code)
: gr::block("my_qpsk_demod_cb",
gr::io_signature::make(<+MIN_IN+>, <+MAX_IN+>, sizeof(<+ITYPE+>)),
gr::io_signature::make(<+MIN_OUT+>, <+MAX_OUT+>, sizeof(<+OTYPE+>)))
* my_qpsk_demod_cb_impl()是`my_qpsk_demod`block的构造函数
* my_qpsk_demod_cb_impl()调用父类[gr::block()](http://gnuradio.org/doc/doxygen/basic__block_8h_source.html)去初始化
* gr::block()中的参数代表block名称和make函数的调用
* 这个make函数`gr::io_signature::make(<+MIN_IN+>, <+MAX_IN+>, sizeof(<+ITYPE+>))`和`gr::io_signature::make(<+MIN_OUT+>, <+MAX_OUT+>, sizeof(<+OTYPE+>)`是类`gr::io_signature`的成员函数,用来表示输入输出端口
* `<+MIN_OUT+>`和`<+MAX_OUT+>`代表端口的最大最小值
* ``和``表示输入输出的数据类型,需要手动填写
* 接下来我们修改构造函数,在修改之后,如下:
```cpp
* The private constructor
my_qpsk_demod_cb_impl::my_qpsk_demod_cb_impl(bool gray_code)
: gr::block("my_qpsk_demod_cb",
gr::io_signature::make(1, 1, sizeof(gr_complex)),
gr::io_signature::make(1, 1, sizeof(char))),
d_gray_code(gray_code)
选项`gray_code`被复制到类属性`d_gray_code`。注意我们需要在头文件`my_qpsk_demod_cb_impl.h`中声明`d_gray_code`:
```cpp
private:
bool d_gray_code;
在这个类中,还有一个general_work方法,由于其在gr::block中是个纯虚函数,所以我们需要重写它。改动之前如下:
```cpp
my_qpsk_demod_cb_impl::general_work (int noutput_items,
gr_vector_int &ninput_items,
gr_vector_const_void_star &input_items,
gr_vector_void_star &output_items)
const <+ITYPE*> *in = (const <+ITYPE*> *) input_items[0];
<+OTYPE*> *out = (<+OTYPE*> *) output_items[0];
// Do <+signal processing+>
// Tell runtime system how many input items we consumed on
// each input stream.
consume_each (noutput_items);
// Tell runtime system how many output items we produced.
return noutput_items;
这里,摘取《GNU Radio入门》里关于general函数的描述:
gr_block衍生自gr_basic_block,gr_block有一个方法general_work(),它是一个虚函数,我们需要重写它。方法general_work进行实际的信号处理,是block的CPU。virtual int general_work ( int noutput_items, gr_vector_int &ninput_items, gr_vector_const_void_star &input_items, gr_vector_void_star &output_items) = 0; 通常,方法general_work()的主要工作是把输入流,经过一系列运算,变成输出流。首先介绍一下它的四个参数。
noutput_items表示输出item的个数,用于记录每一个输出流输出item的数量。注意这里的item是输入输出的粒度,用户自定义的,可能是一个复数,也可能是一个复数的向量,等等。
ninput_items用于记录输入流的item个数。一个block可能有x个输入流和y个输出流。ninput_items是一个长度为x的整形矢量,元素i表示第i个输入端的输入流个数。然而对输出流,为什么noutput_items只是一个整形,而不是一个矢量呢?这是因为一些技术上的问题,目前的GNU Radio版本只能提供具有相同数据速率的输出流,即所有输出流的item个数都必须相同。而输入流的速率可以不同。
input_items是指向输入流的指针向量,每个输入流一个。output_items是指向输出流的指针矢量,每个输出流一个。我们用这些指针获取输入数据并将计算后的数据写到输出流中。请注意ninput_items, input_items和output_items这三个参量都是用地址表示(‘&’),
所以可以在general_work()中修改它们。
general_work()的返回值为写入输出流的实际item个数,或是EOF为-1。返回值也可以小于noutput_items
最后值得注意的是,当重写general_work()时,我们必须调用方法consume()或consume_each()来指明每个输入流消耗掉的item个数。
void consume (int which_input, int how_many_items)方法
consume()告诉调度器第i个输入流(表示为`which_input')消耗掉的item个数(表示为`how_many_items')为多少。如果每个输入流的流个数相同,则用consume_each。void consume_each (int how_many_items);它告诉调度器每个输入流消耗的item个数。调用方法consume()或consume_each()的原因是我们必须告诉调度器输入流消耗的item个数,以便调度器安排上级缓冲器和相应的指针。这两个方法的细节比较复杂。只要记住,在GNU Radio中可以很方便的使用它们,并且在每次重写方法general_work()时记得调用它们。
有个指向input的指针和指向output的指针。修改之后如下:
```cpp
my_qpsk_demod_cb_impl::general_work (int noutput_items,
gr_vector_int &ninput_items,
gr_vector_const_void_star &input_items,
gr_vector_void_star &output_items)
const gr_complex *in = (const gr_complex *) input_items[0];
unsigned char *out = (unsigned char *) output_items[0];
gr_complex origin = gr_complex(0,0);
// Perform ML decoding over the input iq data to generate alphabets
for(int i = 0; i < noutput_items; i++)
// ML decoder, determine the minimum distance from all constellation points
out[i] = get_minimum_distances(in[i]);
// Tell runtime system how many input items we consumed on
// each input stream.
consume_each (noutput_items);
// Tell runtime system how many output items we produced.
return noutput_items;
我们还需要加上函数get_minimum_distances(const gr_complex & sample),如下:
unsigned char
my_qpsk_demod_cb_impl::get_minimum_distances(const gr_complex &sample)
if (d_gray_code) {
unsigned char bit0 = 0;
unsigned char bit1 = 0;
// The two left quadrants (quadrature component < 0) have this bit set to 1
if (sample.real() < 0) {
bit0 = 0x01;
// The two lower quadrants (in-phase component < 0) have this bit set to 1
if (sample.imag() < 0) {
bit1 = 0x01 << 1;
return bit0 | bit1;
} else {
// For non-gray code, we can't simply decide on signs, so we check every single quadrant.
if (sample.imag() >= 0 and sample.real() >= 0) {
return 0x00;
else if (sample.imag() >= 0 and sample.real() < 0) {
return 0x01;
else if (sample.imag() < 0 and sample.real() < 0) {
return 0x02;
else if (sample.imag() < 0 and sample.real() >= 0) {
return 0x03;
注意,函数声明也同样需要加到头文件中。
然后考虑forecast()函数,关于这个,我直接摘取的《GNURadio入门》里关于forecast的描述:
virtual void forecast ( int noutput_items, gr_vector_int &ninput_items_required);
方法forecast()用于,对于给定的输出,估计输入要多少数据。第一个参量noutput_items在general_work()中介绍过了,是输出流的流个数。第二的参量ninput_items_required是整型矢量,保存输入流的item个数。当 重 载 方 法forecast()时,需要估计每个输入流的数据,对于给定`noutput_items'item的输入流。这个估计无需很准确,但是要接近。参量input_items_required会由调度器传递出去,所以计算出的估计值直接保存到这个变量就可以了。
// default implementation: 1:1
gr::block::forecast(int noutput_items,
gr_vector_int &ninput_items_required)
unsigned ninputs = ninput_items_required.size ();
for(unsigned i = 0; i < ninputs; i++)
ninput_items_required[i] = noutput_items;
## 4.2.5 Step4: 填写XML文件
.xml提供用户接口,将GRC和源代码联系在一起。并且XML文件定义了传入模块的参数。该block的XML文件是grc目录下的tutorial_my_qpsk_demod_cb.xml。目前,gr_modtool产生的代码了如下:
```xml
my_qpsk_demod_cb
tutorial_my_qpsk_demod_cb
tutorial
import tutorial
tutorial.my_qpsk_demod_cb($gray_code)
...
...
...
in
加了parameter tag之后:
Gray Code
gray_code
True
bool
同样需要改动``和`
```xml
最后版本如下:
```xml
My QPSK Demodulator
tutorial_my_qpsk_demod_cb
tutorial
import tutorial
tutorial.my_qpsk_demod_cb($gray_code)
Gray Code
gray_code
True
bool
in
complex
## 4.2.6 Step 5: 安装grc中的my_qpsk_demod
我们已经完成该block的实现,现在需要将其安装到GRC中,所以,我们需要执行下列命令:
xyz@comp:mydir/gr-tutorial$ mkdir build
xyz@comp:mydir/gr-tutorial$ cd build
xyz@comp:mydir/gr-tutorial/build$ cmake ..
-- Build type not specified: defaulting to release.
Checking for GNU Radio Module: RUNTIME
* INCLUDES=/usr/local/include
* LIBS=/usr/local/lib/libgnuradio-runtime.so;/usr/local/lib/libgnuradio-pmt.so
GNURADIO_RUNTIME_FOUND = TRUE
-- Configuring done
-- Generating done
-- Build files have been written to: pathtomyhomefolder/mydir/gr-qpsk_demod/build
xyz@comp:mydir/gr-demod/build$make
[ 7%] Built target gnuradio-tutorial
[ 23%] Built target test-tutorial
[ 23%] Built target tutorial_swig_swig_doc
[ 30%] Built target _tutorial_swig_swig_tag
[ 38%] Swig source
Scanning dependencies of target _tutorial_swig
[ 46%] Building CXX object swig/CMakeFiles/_tutorial_swig.dir/tutorial_swigPYTHON_wrap.cxx.o
Linking CXX shared module _tutorial_swig.so
[ 53%] Built target _tutorial_swig
[ 61%] Generating tutorial_swig.pyc
[ 69%] Generating tutorial_swig.pyo
[ 84%] Built target pygen_swig_2598c
[100%] Built target pygen_python_6ab2e
[100%] Built target pygen_apps_9a6dd
xyz@comp:mydir/gr-qpsk_demod/build$ sudo make install
[ 7%] Built target gnuradio-tutorial
[ 23%] Built target test-tutorial
[ 23%] Built target tutorial_swig_swig_doc
[ 30%] Built target _tutorial_swig_swig_tag
[ 53%] Built target _tutorial_swig
[ 84%] Built target pygen_swig_2598c
[100%] Built target pygen_python_6ab2e
[100%] Built target pygen_apps_9a6dd
Install the project...
-- Install configuration: "Release"
-- Up-to-date: /usr/local/lib/cmake/tutorial/demodConfig.cmake
-- Up-to-date: /usr/local/include/tutorial/api.h
-- Up-to-date: /usr/local/include/tutorial/my_qpsk_demod_cb.h
-- Up-to-date: /usr/local/lib/libgnuradio-tutorial.so
-- Installing: /usr/local/lib/python2.7/dist-packages/tutorial/_tutorial_swig.so
-- Removed runtime path from "/usr/local/lib/python2.7/dist-packages/tutorial/_tutorial_swig.so"
-- Installing: /usr/local/lib/python2.7/dist-packages/tutorial/tutorial_swig.py
-- Installing: /usr/local/lib/python2.7/dist-packages/tutorial/tutorial_swig.pyc
-- Installing: /usr/local/lib/python2.7/dist-packages/tutorial/tutorial_swig.pyo
-- Up-to-date: /usr/local/include/tutorial/tutorial/swig/tutorial_swig.i
-- Installing: /usr/local/include/tutorial/tutorial/swig/tutorial_swig_doc.i
-- Up-to-date: /usr/local/lib/python2.7/dist-packages/tutorial/__init__.py
-- Up-to-date: /usr/local/lib/python2.7/dist-packages/tutorial/__init__.pyc
-- Up-to-date: /usr/local/lib/python2.7/dist-packages/tutorial/__init__.pyo
-- Up-to-date: /usr/local/share/gnuradio/grc/blocks/tutorial_my_qpsk_cb.xml
xyz@comp:mydir/gr-qpsk_demod/build$ sudo ldconfig
## 4.2.7 Step 6: Quality Assurance(Unit Testing)
![title](https://leanote.com/api/file/getImage?fileId=585b3584ab6441068a0006bc)
上图是用QT GUI Constellation Sink展示QPSK调制输入到OOT模块中,然后输出的情况。我们QPSK解调器是将复数输入流转化为(0,1,2,3)四元组的数据流。
接下来,需要写单元测试以确保QPSK解调器的正确性。
完整的QA代码如下:
from gnuradio import gr, gr_unittest
from gnuradio import blocks
import tutorial_swig as tutorial
from numpy import array
class qa_qpsk_demod (gr_unittest.TestCase):
def setUp (self):
self.tb = gr.top_block ()
def tearDown (self):
self.tb = None
def test_001_gray_code_enabled (self):
# "Construct the Iphase and Qphase components"
Iphase = array([ 1, -1, -1, 1])
Qphase = array([ 1, 1, -1, -1])
src_data = Iphase + 1j*Qphase;
# "Enable Gray code"
gray_code = True;
# "Determine the expected result"
expected_result = (0,1,3,2)
# "Create a complex vector source"
src = blocks.vector_source_c(src_data)
# "Instantiate the test module"
qpsk_demod = tutorial.my_qpsk_demod_cb(gray_code)
# "Instantiate the binary sink"
dst = blocks.vector_sink_b();
# "Construct the flowgraph"
self.tb.connect(src,qpsk_demod)
self.tb.connect(qpsk_demod,dst)
# "Create the flow graph"
self.tb.run ()
# check data
result_data = dst.data()
self.assertTupleEqual(expected_result, result_data)
self.assertEqual(len(expected_result), len(result_data))
def test_002_gray_code_disabled (self):
# "Construct the Iphase and Qphase components"
Iphase = array([ 1, -1, -1, 1])
Qphase = array([ 1, 1, -1, -1])
src_data = Iphase + 1j*Qphase;
# "Enable Gray code"
gray_code = False;
# "Determine the expected result"
expected_result = (0,1,2,3)
# "Create a complex vector source"
src = blocks.vector_source_c(src_data)
# "Instantiate the test module"
qpsk_demod = tutorial.my_qpsk_demod_cb(gray_code)
# "Instantiate the binary sink"
dst = blocks.vector_sink_b();
# "Construct the flowgraph"
self.tb.connect(src,qpsk_demod)
self.tb.connect(qpsk_demod,dst)
# "Create the flow graph"
self.tb.run ()
# check data
result_data = dst.data()
self.assertTupleEqual(expected_result, result_data)
self.assertEqual(len(expected_result), len(result_data))
if __name__ == '__main__':
gr_unittest.run(qa_qpsk_demod, "qa_qpsk_demod.xml")
显然,我们用python实现了qa_qpsk_demode_demod_cb.py,尽管我们用c++编写block。这是因为,GNU Radio内置[python unittest framework](https://docs.python.org/2/library/unittest.html)去,支持测试。如果你还记得之前教程的内容,swig作为GNU Radio框架的一部分,提供c++的代码的python捆绑。因此,我们有能力用python写单元测试。
接下来看看代码的头部分:
```python
from gnuradio import gr, gr_unittest
from gnuradio import blocks
import tutorial_swig as tutorial
from numpy import array
`from gnuradio import gr, gr_unittest`和`from gnuradio import blocks`是导入gr和gr_unittest的功能块。`import tutorial_swig as tutorial`导入我们模块的python版本,提供对my_qsk_demod_cb的访问。最后,`from numpy import array`导入了array。
if __name__ == '__main__':
gr_unittest.run(qa_qpsk_demod, "qa_qpsk_demod.xml")
qa文件执行会从这里开始。gr_unittest会自动调用该函数,以`def setUp(self)`开始创建top block,以`tearDown`结束销毁top block。在setUp和tearDown之间,测试函数执行。test_前缀的方法会被gt_unittest作为测试片段。我们定义俩个测试片段。`test_001_gray_code_enable`和`test_002_gray`和`test_002_gray_code_disabled`,一个测试片段的常用结构是包括已知输入、期望输入。一个流图被创建,包括source(输入数据),被测试的block,sink(被导致的output数据)。最终被期望的输出和输出结果比较。
最后的语句如下:
self.assertTupleEqual(expected_result, result_data)
self.assertEqual(len(expected_result), len(result_data))
判断结果pass还是fail,在安装模块之前可以通过`make test`来跑测试片段:
xyz@comp:mydir/gr-tutorial/build$ make test
Running tests...
Test project /home/kaushik/src/gr-tutorial/build
Start 1: test_tutorial
1/3 Test #1: test_tutorial .................... Passed 0.11 sec
Start 2: qa_chat_sanitizer
2/3 Test #2: qa_chat_sanitizer ................***Failed 0.04 sec
Start 3: qa_qpsk_demod
3/3 Test #3: qa_qpsk_demod .................... Passed 2.00 sec
67% tests passed, 1 tests failed out of 3
Total Test time (real) = 2.34 sec
The following tests FAILED:
2 - qa_chat_sanitizer (Failed)
Errors while running CTest
make: *** [test] Fehler 8
在上面输出中,,一个测试用例失败,但是Test 3才是qa_qpsk_demod,说明已经测试通过。
我们已经学会用c++编写OOT模块用并创建了一个my_qpsk_demodulator
# 4.3 Advanced topics
至此我们已经学会了GNU Radio设计OOT模块的基础,但是要想成为GNU Radio的行家,要了解的远远不止这些。接下来,我们来看看一些少用的、具体的特点。
## 4.3.1 Specific functions related to block
在上一部分中,我们通过定义了general_work()和forecast()函数来实现功能。但是有时候一些特殊的函数需要被定义。下面我们会提起一些。
### 4.3.1.1 set_history()
如果你的block需要一个历史记录,那么可以在构造函数中调用。比如
test::test(const std::string &name,
int min_inputs, int max_inputs,
unsigned int sizeof_input_item,
int min_outputs, int max_outputs,
unsigned int sizeof_output_item,
unsigned int history,
unsigned int output_multiple,
double relative_rate,
bool fixed_rate,
consume_type_t cons_type, produce_type_t prod_type)
: block (name,
io_signature::make(min_inputs, max_inputs, sizeof_input_item),
io_signature::make(min_outputs, max_outputs, sizeof_output_item)),
d_sizeof_input_item(sizeof_input_item),
d_sizeof_output_item(sizeof_output_item),
d_check_topology(true),
d_consume_type(cons_type),
d_min_consume(0),
d_max_consume(0),
d_produce_type(prod_type),
d_min_produce(0),
d_max_produce(0)
set_history(history);
set_output_multiple(output_multiple);
set_relative_rate(relative_rate);
set_fixed_rate(fixed_rate);
GNU Radio会保证你有指定数量的历史记录
历史记录中最少为1,此时,每个输出对应一个输入。你也可以选择历史记录中有N个,此时输出来着此时输入和先前N-1个输入。如果你讲history length设置为N,那么输入缓存区中的前N个数中包括N-1个之前的数。
历史记录被存储在变量d_history中。
set_history() 被定义在 gnuradio/gnuradio-runtime/block.cc
void block::set_history(unsigned history)
d_history = history;
### 4.3.1.2 set_output_multiple()
当实现general_work()例程时,有时候需要一个系统来确保只需要生成一些特定值的倍数的输出项。如果你的算法应用于固定大小的数据块,则可能会出现这种情况。在你的构造函数中调用set_output_multiple来指定这个需求
test::test(const std::string &name,
int min_inputs, int max_inputs,
unsigned int sizeof_input_item,
int min_outputs, int max_outputs,
unsigned int sizeof_output_item,
unsigned int history,
unsigned int output_multiple,
double relative_rate,
bool fixed_rate,
consume_type_t cons_type, produce_type_t prod_type)
: block (name,
io_signature::make(min_inputs, max_inputs, sizeof_input_item),
io_signature::make(min_outputs, max_outputs, sizeof_output_item)),
d_sizeof_input_item(sizeof_input_item),
d_sizeof_output_item(sizeof_output_item),
d_check_topology(true),
d_consume_type(cons_type),
d_min_consume(0),
d_max_consume(0),
d_produce_type(prod_type),
d_min_produce(0),
d_max_produce(0)
set_history(history);
set_output_multiple(output_multiple);
set_relative_rate(relative_rate);
set_fixed_rate(fixed_rate);
通过调用set_output_multiple,我们将变量值赋值给d_output_multiple.d_output_multiple的默认值为1。
比如,我们想要生成64元素的块, 通过设置d_output_multiple为64,我们可以完成这个。但是注意,我们一样也得到了64的倍数,比如128等等。
set_output_multipl定义在gnuradio/gnuradio-runtime/block.cc中
void gr_block::set_output_multiple (int multiple)
if (multiple < 1)
throw std::invalid_argument ("gr_block::set_output_multiple");
d_output_multiple_set = true;
d_output_multiple = multiple;
### 4.3.2 Specific block categories
同样,my_qpsk_demod_cb的实现是使用通用类型的block完成的。然而,GNU Radio包括一些具有特殊类型的block。这些block类型简要描述如下:
|Block| Functionality|
|-----|------|
|General| This block a generic version of all blocks Source/Sinks|
|The source/sink| produce/consume the input/output items|
|Interpolation/Decimation| The interpolation/decimation block is another type of fixed rate block where the number of output/input items is a fixed multiple of the number of input/output items.|
|Sync| The sync block allows users to write blocks that consume and produce an equal number of items per port. From the user perspective, the GNU Radio scheduler synchronizes the input and output items, it has nothing to with synchronization algorithms|
|Hierarchical blocks| Hierarchical blocks are blocks that are made up of other blocks.|
在接下来的小节中,我们详细讨论这些块。再次,热心的读者可以在GNU Radio源代码中找到这些块。
### 4.3.2.1 General
howto_square_ff::howto_square_ff ()
: gr::block("square_ff",
gr::io_signature::make(MIN_IN, MAX_IN, sizeof (float)),
gr::io_signature::make(MIN_OUT, MAX_OUT, sizeof (float)))
// nothing else required in this example
### 4.3.2.2 Source and Sinks
Source
------------
c++中source block的一个例子
usrp_source_impl::usrp_source_impl(const ::uhd::device_addr_t &device_addr,
const ::uhd::stream_args_t &stream_args):
sync_block("gr uhd usrp source",
io_signature::make(0, 0, 0),
args_to_io_sig(stream_args)),
_stream_args(stream_args),
_nchan(stream_args.channels.size()),
_stream_now(_nchan == 1),
_tag_now(false),
_start_time_set(false)
一些值得注意的地方:
* io_signature::make(0, 0, 0) 设置输入项为0, 意味着无输入.
* 因为它连接着硬件usrp, the gr uhd usrp sink是一个sync_block的子类.
----------
c++中sink block的一个例子
usrp_sink_impl::usrp_sink_impl(const ::uhd::device_addr_t &device_addr,
const ::uhd::stream_args_t &stream_args)
: sync_block("gr uhd usrp sink",
args_to_io_sig(stream_args),
io_signature::make(0, 0, 0)),
_stream_args(stream_args),
_nchan(stream_args.channels.size()),
_stream_now(_nchan == 1),
_start_time_set(false)
一些值得注意的地方:
* io_signature::make(0, 0, 0) 设置输出项为0, 意味着无输出.
* 因为它连接着硬件usrp, the gr uhd usrp sink是一个sync_block的子类.
### 4.3.2.3 Sync
同步块允许用户编写每个端口消耗和产生相等数量项的块。同步块可以具有任何数量的输入或输出。当同步块具有零输入时,其被称为source。当同步块具有零输出时,其称为sink。
c++中sync block的一个例子
#include
class my_sync_block : public gr_sync_block
public:
my_sync_block(...):
gr_sync_block("my block",
gr::io_signature::make(1, 1, sizeof(int32_t)),
gr::io_signature::make(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 is the length in items of all input and output buffers
* an input signature of gr::io_signature::make(0, 0, 0) makes this a source block
* an output signature of gr::io_signature::make(0, 0, 0) makes this a sink block
### 4.3.2.4 Rate changing blocks: Interpolators and Decimators
Decimators
-------------
Decimators块是另一种类型的固定速率块,其中输入项的数量是输出项的数量的固定倍数。
c++中decimation block的一个例子
#include
class my_decim_block : public gr_sync_decimator
public:
my_decim_block(...):
gr_sync_decimator("my decim block",
in_sig,
out_sig,
decimation)
//constructor stuff
//work function here...
一些值得注意的地方:
* gr_sync_decimator构造函数接受第四个参数,即抽取因子
* 用户必须假设输入项数= noutput_items *抽取。因此,值ninput_items是隐式的。
Interpolation
-------
内插块是另一种类型的固定速率块,其中输出项的数量是输入项的数量的固定倍数。
c++中interpolation block的一个例子
#include
class my_interp_block : public gr_sync_interpolator
public:
my_interp_block(...):
gr_sync_interpolator("my interp block",
in_sig,
out_sig,
interpolation)
//constructor stuff
//work function here...
一些值得注意的地方:
* gr_sync_interpolator构造函数接受第四个参数,即插值因子
* 用户必须假定输入项数= noutput_items /插值
### 4.3.2.5 Hierarchical blocks
分层块是由其他块组成的块。它们实例化其他GNU Radio块(或其他分层块)并将它们连接在一起。分层块具有用于此目的的connect函数。
何时使用分层块?
分层块通过抽象简单块来为我们的流程图提供模块化,即分层块帮助我们定义我们的特定块,同时为我们提供改变它的灵活性,例如,我们想测试对给定通道模型的不同调制方案。然而,我们的同步算法是特定的或新出版的。我们将我们的分层块定义为gr-my_sync,它执行同步跟随均衡器和解调。我们从BPSK开始,流程图看起来像
gr-tx ----> gr-channel ---> gr-my_sync ---> gr-equalizer ---> gr-bpsk_demod
现在,我们的流程图看起来体面。其次,我们提取了同步的复杂功能。切换到QPSK,同步算法保持不变,我们只需要将gr-bpsk_demod替换为gr-qpsk_demod
gr-tx ----> gr-channel ---> gr-my_sync ---> gr-equalizer ---> gr-qpsk_demod
如何在GNU Radio中构建分层块?
分层块定义输入和输出流,就像正常的block一样。要将输入i连接到分层块,源代码是(在Python中):
self.connect((self, i), )
类似地,在输出流o上发送信号块:
self.connect(, (self, o))
分层块的典型示例是在`gnuradio / gr-digital / python / digital`下的python中实现的OFDM接收器。 类定义为:
class ofdm_receiver(gr.hier_block2)
并实例化为
```python
gr.hier_block2.__init__(self, "ofdm_receiver",
gr.io_signature(1, 1, gr.sizeof_gr_complex), # Input signature
gr.io_signature2(2, 2, gr.sizeof_gr_complex*occupied_tones, gr.sizeof_char)) # Output signature
由OFDM接收机执行的一些主要任务包括信道滤波,同步和IFFT任务。各个任务在分层块中定义。
* Channel filtering
chan_coeffs = filter.firdes.low_pass (1.0, # gain
1.0, # sampling rate
bw+tb, # midpoint of trans. band
tb, # width of trans. band
filter.firdes.WIN_HAMMING) # filter type
self.chan_filt = filter.fft_filter_ccc(1, chan_coeffs)
* Synchronization
self.chan_filt = blocks.multiply_const_cc(1.0)
nsymbols = 18 # enter the number of symbols per packet
freq_offset = 0.0 # if you use a frequency offset, enter it here
nco_sensitivity = -2.0/fft_length # correct for fine frequency
self.ofdm_sync = ofdm_sync_fixed(fft_length,
cp_length,
nsymbols,
freq_offset,
logging)
* ODFM demodulation
self.fft_demod = gr_fft.fft_vcc(fft_length, True, win, True)
最后,各个块连同层级连接在一起以形成流程图。
分层块之间的连接OFDM接收机到信道滤波器块
self.connect(self, self.chan_filt) # filter the input channel
信道滤波器块与OFDM同步块之间的连接。
self.connect(self.chan_filt, self.ofdm_sync)
分层块也可以是嵌套的,即在分层块中定义的块也可以是分层块。例如,OFDM同步块也是分层块。在这种情况下,它是用C ++实现的。让我们来看看吧。
OFDM impl
ofdm_sync_sc_cfb_impl::ofdm_sync_sc_cfb_impl(int fft_len, int cp_len, bool use_even_carriers)
: hier_block2 ("ofdm_sync_sc_cfb",
io_signature::make(1, 1, sizeof (gr_complex)),
#ifndef SYNC_ADD_DEBUG_OUTPUT
io_signature::make2(2, 2, sizeof (float), sizeof (unsigned char)))
#else
io_signature::make3(3, 3, sizeof (float), sizeof (unsigned char), sizeof (float)))
#endif
std::vector ma_taps(fft_len/2, 1.0);
gr::blocks::delay::sptr delay(gr::blocks::delay::make(sizeof(gr_complex), fft_len/2));
gr::blocks::conjugate_cc::sptr delay_conjugate(gr::blocks::conjugate_cc::make());
gr::blocks::multiply_cc::sptr delay_corr(gr::blocks::multiply_cc::make());
gr::filter::fir_filter_ccf::sptr delay_ma(gr::filter::fir_filter_ccf::make(1, std::vector(fft_len/2, use_even_carriers ? 1.0 : -1.0)));
gr::blocks::complex_to_mag_squared::sptr delay_magsquare(gr::blocks::complex_to_mag_squared::make());
gr::blocks::divide_ff::sptr delay_normalize(gr::blocks::divide_ff::make());
gr::blocks::complex_to_mag_squared::sptr normalizer_magsquare(gr::blocks::complex_to_mag_squared::make());
gr::filter::fir_filter_ccf::sptr delay_ma(gr::filter::fir_filter_ccf::make(1, std::vector(fft_len/2, use_even_carriers ? 1.0 : -1.0)));
gr::blocks::complex_to_mag_squared::sptr delay_magsquare(gr::blocks::complex_to_mag_squared::make());
gr::blocks::divide_ff::sptr delay_normalize(gr::blocks::divide_ff::make());
gr::blocks::complex_to_mag_squared::sptr normalizer_magsquare(gr::blocks::complex_to_mag_squared::make());
gr::filter::fir_filter_fff::sptr normalizer_ma(gr::filter::fir_filter_fff::make(1, std::vector(fft_len, 0.5)));
gr::blocks::multiply_ff::sptr normalizer_square(gr::blocks::multiply_ff::make());
gr::blocks::complex_to_arg::sptr peak_to_angle(gr::blocks::complex_to_arg::make());
gr::blocks::sample_and_hold_ff::sptr sample_and_hold(gr::blocks::sample_and_hold_ff::make());
gr::blocks::plateau_detector_fb::sptr plateau_detector(gr::blocks::plateau_detector_fb::make(cp_len));
// Delay Path
connect(self(), 0, delay, 0);
connect(delay, 0, delay_conjugate, 0);
connect(delay_conjugate, 0, delay_corr, 1);
connect(self(), 0, delay_corr, 0);
connect(delay_corr, 0, delay_ma, 0);
connect(delay_ma, 0, delay_magsquare, 0);
connect(delay_magsquare, 0, delay_normalize, 0);
// Energy Path
connect(self(), 0, normalizer_magsquare, 0);
connect(normalizer_magsquare, 0, normalizer_ma, 0);
connect(normalizer_ma, 0, normalizer_square, 0);
connect(normalizer_ma, 0, normalizer_square, 1);
connect(normalizer_square, 0, delay_normalize, 1);
// Fine frequency estimate (output 0)
connect(delay_ma, 0, peak_to_angle, 0);
connect(peak_to_angle, 0, sample_and_hold, 0);
connect(sample_and_hold, 0, self(), 0);
// Peak detect (output 1)
connect(delay_normalize, 0, plateau_detector, 0);
connect(plateau_detector, 0, sample_and_hold, 1);
connect(plateau_detector, 0, self(), 1);
#ifdef SYNC_ADD_DEBUG_OUTPUT
// Debugging: timing metric (output 2)
connect(delay_normalize, 0, self(), 2);
#endif
让我们来理解代码片段。分层块是C ++实例化如下:
ofdm_sync_sc_cfb_impl::ofdm_sync_sc_cfb_impl(int fft_len, int cp_len, bool use_even_carriers)
: hier_block2 ("ofdm_sync_sc_cfb",
io_signature::make(1, 1, sizeof (gr_complex)),
#ifndef SYNC_ADD_DEBUG_OUTPUT
io_signature::make2(2, 2, sizeof (float), sizeof (unsigned char)))
#else
io_signature::make3(3, 3, sizeof (float), sizeof (unsigned char), sizeof (float)))
其中`ofdm_sync_sc_cfb_impl :: ofdm_sync_sc_cfb_impl`是具有参数`int fft_len`,`int cp_len`,`bool use_even_carriers`。`hier_block2`的构造函数是基类。块名称`ofdm_sync_sc_cfb`按照GNU Radio块命名样式定义。
`io_signature :: make(1,1,sizeof(gr_complex))`定义了我的输入项,输出项是 `io_signature :: make2(2,2,sizeof(float)`,`sizeof(unsigned char)))`或`io_signature :: make3(3,3,sizeof(float))`
这取决于预处理器指令` SYNC_ADD_DEBUG_OUTPUT`
ofdm_sync_sc_cfb块中的各个块定义如下:
gr::blocks::complex_to_mag_squared::sptr normalizer_magsquare(gr::blocks::complex_to_mag_squared::make());
最后,使用以下连接各个块:
connect(normalizer_magsquare, 0, normalizer_ma, 0);
# 4.4 小结
现在,我们有足够的资格来编写我们自己的OOT模块,并在其中包含块(根据我们的选择,在Python或C ++中)。为了强化我们在本教程中学到的东西,在下面的小节中通过小测验的时间。下一个教程说明了块之间通信的不同方式,同步和异步。
4.5 Quiz
* 如果我们决定将我们的调制方案从QPSK移位到8 PSK / 4-QAM / 16-QAM,什么会改变
* 如果我们认为将QPSK解调器移植到通用解调器,分层块的使用是否有意义?
* 噪声如何影响我们的系统?