GNU Radio 学习使用 OOT 系列教程:
GNU Radio3.8创建OOT的详细过程(基础/C++)
GNU Radio3.8创建OOT的详细过程(进阶/C++)
GNU Radio3.8创建OOT的详细过程(python)
GNU Radio自定义模块:Embedded Python Block的使用
----------------------------------------------------------------------------------------
有了前面的有关 GR 创建 OOT 的基础,接下来我们一起来写一个 QPSK 的解调模块。最终的测试流图长这个样子:
1、编辑 block 执行代码
首先,使用如下命令创建一个 module 并添加一个 block 。
gr_modtool newmod myqpsk
gr_modtool add my_qpsk_demod_cb
注意这里在添加块时没有指定 -t 与 -l 参数,可以在命令执行中进行选择。my_qpsk_demod_cb 的命名代表该 block 输入为 complex 型数据,输出为 byte 型数据。另外要注意一点的是,与之前所讲的 OOT 不同的是,这里还需要一个输入参数:bool gray_code ,需要在执行命令的过程中添加进去,该参数用于选择是否使用格雷码进行解码,后面会讲到。。
正常情况下的输出 log 如下:
wsx@wsx:~/temp$ gr_modtool newmod myqpsk
Creating out-of-tree module in ./gr-myqpsk...
Done.
Use 'gr_modtool add' to add a new block to this currently empty module.
wsx@wsx:~/temp$ cd gr-myqpsk/
wsx@wsx:~/temp/gr-myqpsk$ gr_modtool add my_qpsk_demod_cb
GNU Radio module name identified: myqpsk
('sink', 'source', 'sync', 'decimator', 'interpolator', 'general', 'tagged_stream', 'hier', 'noblock')
Enter block type: general
Language (python/cpp): cpp
Language: C++
Block/code identifier: my_qpsk_demod_cb
Please specify the copyright holder: gnuradio.org
Enter valid argument list, including default arguments:
bool gray_code
Add Python QA code? [Y/n] y
Add C++ QA code? [y/N] n
Adding file 'lib/my_qpsk_demod_cb_impl.h'...
Adding file 'lib/my_qpsk_demod_cb_impl.cc'...
Adding file 'include/myqpsk/my_qpsk_demod_cb.h'...
Editing swig/myqpsk_swig.i...
Adding file 'python/qa_my_qpsk_demod_cb.py'...
Editing python/CMakeLists.txt...
Adding file 'grc/myqpsk_my_qpsk_demod_cb.block.yml'...
Editing grc/CMakeLists.txt...
下面开始编写相关代码,首先是 lib/my_qpsk_demod_cb_impl.cc 的编写。
首先是构造函数的修改:
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)
{}
代码中,my_qpsk_demod_cb_impl() 是 块 my_qpsk_demod 的构造函数。调用 gr::block() 来进行初始化。其中注意在后面要加上一个参数 d_gray_code 的初始化,并在 lib/my_qpsk_demod_cb_impl.h 头文件中声明这个私有属性。
private:
bool d_gray_code;
然后是 forecast() 函数的修改。如前面所说的,系统需要知道需要多少数据才能确保每个输入数组的有效性,forecast 函数向系统提供了这类信息。另外,在 general 类型的 block 中,必须要重写 forecast 函数来达到这个目的。
void
my_qpsk_demod_cb_impl::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; // 输入数据量等于输出数据量
}
}
可以从 forecast 函数的实现中看出端口的输入输出比均为 1:1 ,因此,根据之前教程的经验,在创建这个 block 时完全可以使用 sync 类型的 block ,这样可以简化代码。
接下来是主函数 general_work() 的修改。
int
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);
// 在输入 IQ 数据中应用极大似然算法解调
for (int i = 0; i < noutput_items; i++)
{
out[i] = get_minimum_distances(in[i]); // 极大似然解码,确定与所有星座点的最小距离
}
consume_each (noutput_items);
// Tell runtime system how many output items we produced.
return noutput_items;
}
general_work() 函数中调用了极大似然解调函数 get_minimum_distances(),该函数通过确定输入复数数据(星座图点)所在的象限对数据进行解调。实现如下:
unsigned char my_qpsk_demod_cb_impl::get_minimum_distances(const gr_complex &sample)
{
if(d_gray_code) // 使用 格雷码 时的解码方式,此时四个象限依次代表:00/01/11/10
{
// 定义 QPSK 信号的两个位
unsigned char bit0 = 0; // 正交分量
unsigned char bit1 = 0; // 同相分量
// 如果星座点在两个左象限(正交分量 < 0),将此位设置为 1
if (sample.real() < 0)
{
bit0 = 0x01 << 1;
}
// 如果星座点在两个下象限(同相分量 < 0),将此位设置为 1
if (sample.imag() < 0)
{
bit1 = 0x01 << 1;
}
return bit0 | bit1 ; // 组合输出
}
else // 非格雷编码,此时四个象限依次代表 00/01/10/11
{
if(sample.real() >= 0 and sample.imag() >= 0)
{
return 0x00; //第一象限
}
else if(sample.real() < 0 and sample.imag() >= 0)
{
return 0x01; // 第二象限
}
else if(sample.real() < 0 and sample.imag() < 0)
{
return 0x02; // 第三象限
}
else if(sample.real() >= 0 and sample.imag() < 0)
{
return 0x03; // 第四象限
}
}
}
我们还需要在头文件 lib/my_qpsk_demod_cb_impl.h 中添加该函数的声明
unsigned char my_qpsk_demod_cb_impl::get_minimum_distances(const gr_complex &sample);
理论上来说,get_minimum_distances 函数需要根据接收到的每一个 QPSK 信号到理想 QPSK 星座点的欧式距离中的最短的一个来进行解调,但是根据泰森多边形原理,两个坐标轴正好就是四个泰森区域的分界线,因此这里的代码实现就直接使用判断坐标系象限的方法来进行解调,其本质上等同于选择欧式距离中的最短一个。该函数最终将接受信号解调为位信息。
2、编写测试代码
编辑 python/qa_my_qpsk_demod_cb.py
from gnuradio import gr, gr_unittest
from gnuradio import blocks
import myqpsk_swig as myqpsk
import numpy as np
class qa_my_qpsk_demod_cb(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 = np.array([ 1, -1, -1, 1])
Qphase = np.array([ 1, 1, -1, -1])
src_data = Iphase + 1j*Qphase;
# 使用格雷码编码方式
gray_code = True;
# 期望的正确结果
expected_result = (0,1,3,2)
# 创建复数源数据
src = blocks.vector_source_c(src_data)
# 初始化测试模块
qpsk_demod = myqpsk.my_qpsk_demod_cb(gray_code)
# "Instantiate the binary sink"
dst = blocks.vector_sink_b();
# 构建流图
self.tb.connect(src,qpsk_demod)
self.tb.connect(qpsk_demod,dst)
# 运行流图
self.tb.run ()
# 检查结果
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 = np.array([ 1, -1, -1, 1])
Qphase = np.array([ 1, 1, -1, -1])
src_data = Iphase + 1j*Qphase;
# 不使用格雷码
gray_code = False;
# 期望的正确结果
expected_result = (0,1,2,3)
# 创建复数源数据
src = blocks.vector_source_c(src_data)
# 初始化测试模块
qpsk_demod = myqpsk.my_qpsk_demod_cb(gray_code)
# "Instantiate the binary sink"
dst = blocks.vector_sink_b();
# 构建流图
self.tb.connect(src,qpsk_demod)
self.tb.connect(qpsk_demod,dst)
# 运行流图
self.tb.run ()
# 检查结果
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_my_qpsk_demod_cb)
3、编辑 yaml 文件
.yml 文件提供了 GRC 中显示的 OOT 模块和源代码之间的用户界面。此外,YAML 文件定义了一个接口来传递特定于模块的参数,因此,要访问 GRC 内的模块,手动修改 .yml 文件很重要。yaml 文件的修改方法可以参考这里。修改后的yaml文件如下:
id: myqpsk_my_qpsk_demod_cb
label: My QPSK Demodulator
category: '[myqpsk]'
templates:
imports: import myqpsk
make: myqpsk.my_qpsk_demod_cb(${gray_code})
parameters:
- id: gray_code
label: Gray Code
dtype: bool
default: 'True'
inputs:
- label: in
dtype: complex
outputs:
- label: out
dtype: byte
file_format: 1
相比之前教程中的例子,这里的 yaml 文件主要不同是使用了 parameter 关键字进行参数 gray_code 的设置。
4、编译安装测试
依次执行以下命令进行编译安装及测试,正常情况下会很顺利(亲测正常~)
mkdir build && cd build
cmake ../
make -j10
make test
sudo make install
在 GRC 中进行测试,结果如下:
============================== BUG ===============================
我在根据官网教程第一次构建流图运行的时候出现了以下错误:
self.digital_chunks_to_symbols_xx_0 = digital.chunks_to_symbols_bc(1+1j,-1+1j,-1-1j,1-1j, 1)
TypeError: make() takes from 1 to 2 positional arguments but 5 were given
主要就是:TypeError: make() takes from 1 to 2 positional arguments but 5 were given
主要原因就是在 Chunks to Symbols 块中设置的问题,我原来的设置为:
因为要转化为 python 代码,我们填入的 list 应该符合 python 的语法, 所以需要加上一对中括号:
[ 1+1j,-1+1j,-1-1j,1-1j ]
正确的填写方式如下:
问题顺利解决
==================================================================
参考:
Guided Tutorial GNU Radio in C++ - GNU Radio
https://dsp.stackexchange.com/questions/68004/chunks-to-symbols-in-gnuradio-3-8