GNU Radio3.8:创建自定义的QPSK块(C++)

GNU Radio 学习使用 OOT 系列教程:

GNU Radio3.8创建OOT的详细过程(基础/C++)

GNU Radio3.8创建OOT的详细过程(进阶/C++)

GNU Radio3.8创建OOT的详细过程(python)

GNU Radio自定义模块:Embedded Python Block的使用

GNU Radio3.8:编辑yaml文件的方法

GNU Radio3.8:创建自定义的QPSK块(C++)

----------------------------------------------------------------------------------------

有了前面的有关 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

  • 1
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 8
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

地球被支点撬走啦

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

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

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

打赏作者

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

抵扣说明:

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

余额充值