GNU Radio之OFDM Divide和Matrix Transpose底层C++实现

文章目录


前言

gr-radar 中的 OFDM Divide 模块是GNU Radio中的一个组件,专门用于处理正交频分复用(OFDM)信号。这个模块主要执行复数信号的除法操作,通常用于雷达和通信系统中的信号处理。

下面对这个模块进行介绍并详细分析其底层 C++ 代码实现。


一、OFDM Divide 模块

1、简介

2、模块作用

这个模块执行复杂的复数除法,用 in0/in1 进行计算。如果 vlen_out 大于 vlen_in,则额外的空间将填充为零。这可以用于零填充。

下面是引自一篇硕士论文(车联网背景下的雷达通信一体化感知方法研究与平台实现)中讲述矩阵相除的作用:

3、参数意义

  • Vector length input:这个参数定义了输入向量的长度。在 OFDM 系统中,这通常对应于快速傅里叶变换(FFT)的长度,代表了每个 OFDM 符号中的子载波数量。
  • Vector length output:这个用来设置输出向量的长度。
    • 这里的取值含义为它从 FFT 长度中减去被舍弃的载波数量(len(discarded_carriers)),然后乘以一个零填充因子(zeropadding_fac)。这个机制允许调整输出数据的大小,可以用于在信号处理后进行缩放或额外的零填充。
  • Discarded carriers:这个参数是一个集合,列出了需要在除法操作中被忽略的载波。通过设置这些载波为零,可以在后续处理中排除它们的影响。
  • Number of sync words:这个参数指定了有多少个同步字在处理时不应用舍弃载波的规则。同步字是用于帮助接收器定位和同步信号的特定数据序列。允许这些部分不受舍弃载波设置的影响,确保信号的同步和稳定性不会受到干扰。
  • Packet length key:这个参数用于指定在处理数据包时使用的关键字,它标识了数据包的长度。

4、C++ 具体实现

注释已标注清楚:

cpp 复制代码
/* -*- c++ -*- */
/*
 * Copyright 2014 Communications Engineering Lab, KIT.
 *
 * This is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3, or (at your option)
 * any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this software; see the file COPYING.  If not, write to
 * the Free Software Foundation, Inc., 51 Franklin Street,
 * Boston, MA 02110-1301, USA.
 */

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include "ofdm_divide_vcvc_impl.h"
#include <gnuradio/io_signature.h>

namespace gr {
namespace radar {

ofdm_divide_vcvc::sptr ofdm_divide_vcvc::make(int vlen_in,
                                              int vlen_out,
                                              std::vector<int> discarded_carriers,
                                              int num_sync_words,
                                              std::string len_key)
{
    return gnuradio::get_initial_sptr(new ofdm_divide_vcvc_impl(
        vlen_in, vlen_out, discarded_carriers, num_sync_words, len_key));
}

/*
 * The private constructor
 */
ofdm_divide_vcvc_impl::ofdm_divide_vcvc_impl(int vlen_in,			// 输入向量的长度
                                             int vlen_out,			// 输出向量的长度
                                             std::vector<int> discarded_carriers,	// 应该在处理中被忽略的载波索引列表
                                             int num_sync_words,	// 同步字的数量,这些字在处理时不受前述舍弃规则的影响
                                             std::string len_key)	// 用于指定数据包长度的标签键
    : gr::tagged_stream_block("ofdm_divide_vcvc",
                              gr::io_signature::make(2, 2, sizeof(gr_complex) * vlen_in),
                              gr::io_signature::make(1, 1, sizeof(gr_complex) * vlen_out),
                              len_key)
{
    d_vlen_in = vlen_in;		
    d_vlen_out = vlen_out;
    d_discarded_carriers = discarded_carriers;
    d_num_sync_words = num_sync_words;

    // Shift discarded carriers
    // 将每个舍弃载波的索引值加上输入向量长度的一半。这样的偏移是因为在某些OFDM实现中,频谱可能是以零频为中心的,
    // 所以需要调整舍弃载波的位置以正确对应到频谱的负频和正频部分
    for (int k = 0; k < discarded_carriers.size(); k++) {
        d_discarded_carriers[k] = discarded_carriers[k] + vlen_in / 2;
    }

    // Error handling
    // 错误处理机制,如果输出向量的长度小于输入向量的长度,则抛出异常
    if (d_vlen_out < d_vlen_in)
        throw std::runtime_error(
            "Input vector length is greater than output vector length");
}

/*
 * Our virtual destructor.
 */
ofdm_divide_vcvc_impl::~ofdm_divide_vcvc_impl() {}

int ofdm_divide_vcvc_impl::calculate_output_stream_length(
    const gr_vector_int& ninput_items)
{
    int noutput_items = ninput_items[0];
    return noutput_items;
}

int ofdm_divide_vcvc_impl::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* in0 = (const gr_complex*)input_items[0];
    const gr_complex* in1 = (const gr_complex*)input_items[1];
    gr_complex* out = (gr_complex*)output_items[0];

    // Set noutput_items
    noutput_items = ninput_items[0];	// 这行代码设置输出项数等于第一个输入流的项数,确保输出数据的长度与输入保持一致

    // Set output buffer to zero -> is zeropadding
    // 初始化输出缓冲区
    std::memset(out, 0, sizeof(gr_complex) * noutput_items * d_vlen_out);

    // Do division and keep spaces between packets if vlen_out>vlen_in
    // If actual vector is a sync words (given with num_sync_words) do not apply discarded
    // carriers rule

	// 如果 discarded_carriers 列表为空,则禁用舍弃载波的功能,通过将其设置为输入向量长度,确保不会有任何实际载波被舍弃
    int next_discarded_element = 0; // set first discarded element on first vector item
    if (d_discarded_carriers.size() ==
        0) { // set first discarded element on first vector item
        d_discarded_carriers.resize(1);
        d_discarded_carriers[0] = d_vlen_in; // this disables discarded carriers
    }

    // Divide items and discard carriers
    // 载波除法和舍弃逻辑
    for (int k = 0; k < noutput_items; k++) {	// 这个外层循环遍历每一个输出项
        for (int l = 0; l < d_vlen_in; l++) {	// 内层循环遍历每个数据包中的元素或者说是子载波
        	// 这部分检查当前处理的符号是否是同步字。同步字用于帮助接收器定位和解析接收到的信号流。
        	// 如果是同步字(k < d_num_sync_words),则直接进行除法操作,不应用舍弃载波的规则。
        	// 这保证了同步过程的准确性不被舍弃载波影响。
            if (k < d_num_sync_words) { // if actual vector is a sync word
                out[k * d_vlen_out + l] =
                    (in0[k * d_vlen_in + l]) / (in1[k * d_vlen_in + l]);
            } else { // if actual vector is NOT a sync word	// 如果当前处理的不是同步字,则检查当前的子载波是否应被舍弃。
                if (l ==	// 这通过比较当前载波的索引与应被舍弃的载波列表中的当前元素
                    d_discarded_carriers[next_discarded_element]) { // if actual element
                                                                    // shall be discarded
                                                                    // and set to zero
                    out[k * d_vlen_out + l] = 0;	// 如果它们相等,则将输出设为0,表示该载波被舍弃 
                    // 更新 next_discarded_element 指向下一个应被舍弃的载波索引,如果当前已是列表末尾,则重置为0,
                    // 以便下一个包可以重新应用舍弃规则。
                    if (next_discarded_element < d_discarded_carriers.size() - 1)
                        next_discarded_element++; // set next discarded element on next
                                                  // vector item
                    else	// 如果当前载波不在舍弃列表中,则正常执行除法操作,即将输入信号 in0 与 in1 相应元素相除,存储结果到输出数组 out 中
                        next_discarded_element =
                            0; // if item is last one jump back to first item in vector
                } else {       // if actual element shall be divided
                    out[k * d_vlen_out + l] =
                        (in0[k * d_vlen_in + l]) / (in1[k * d_vlen_in + l]);
                }
            }
        }
    }

    // Tell runtime system how many output items we produced.
    return noutput_items;
}

} /* namespace radar */
} /* namespace gr */

二、Matrix Transpose 模块

1、简介

Matrix Transpose 模块主要功能是将输入的矩阵进行转置操作。在信号处理中,矩阵转置可以帮助重新排列数据,以便于进行进一步的处理或分析。例如,在雷达信号处理中,转置操作可能用于在时间和频率域之间转换数据,或者在不同处理阶段调整数据的布局。

2、参数意义

  • Vector length input:这个参数表示输入向量的长度,通常用于定义每个输入数据块的大小
  • Vector length output:指的是转置后的输出向量长度。在矩阵转置中,原始矩阵的行数将成为转置矩阵的列数,因此这个参数应该与输入矩阵的行数相匹配。
  • Packet length key:用于指示每个数据包长度的键(或标签)

3、C++ 具体实现

注释已标注清楚:

cpp 复制代码
/* -*- c++ -*- */
/*
 * Copyright 2014 Communications Engineering Lab, KIT.
 *
 * This is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3, or (at your option)
 * any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this software; see the file COPYING.  If not, write to
 * the Free Software Foundation, Inc., 51 Franklin Street,
 * Boston, MA 02110-1301, USA.
 */

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include "transpose_matrix_vcvc_impl.h"
#include <gnuradio/io_signature.h>

namespace gr {
namespace radar {

transpose_matrix_vcvc::sptr
transpose_matrix_vcvc::make(int vlen_in, int vlen_out, std::string len_key)
{
    return gnuradio::get_initial_sptr(
        new transpose_matrix_vcvc_impl(vlen_in, vlen_out, len_key));
}

/*
 * The private constructor
 */
transpose_matrix_vcvc_impl::transpose_matrix_vcvc_impl(int vlen_in,	// 输入向量的长度,这通常代表矩阵中一行的元素数量
                                                       int vlen_out,		   // 输出向量的长度,这将成为矩阵转置后的行长度
                                                       std::string len_key)	   // 指定用于流中的标签
    : gr::tagged_stream_block("transpose_matrix_vcvc",
                              gr::io_signature::make(1, 1, sizeof(gr_complex) * vlen_in),
                              gr::io_signature::make(1, 1, sizeof(gr_complex) * vlen_out),
                              len_key)
{
    d_vlen_in = vlen_in;
    d_vlen_out = vlen_out;

    // Set propagation policy
    set_tag_propagation_policy(TPP_DONT); // does not apply on stream tags!		// 不自动传递任何流标签
}

/*
 * Our virtual destructor.
 */
transpose_matrix_vcvc_impl::~transpose_matrix_vcvc_impl() {}

int transpose_matrix_vcvc_impl::calculate_output_stream_length(
    const gr_vector_int& ninput_items)
{
    int noutput_items = ninput_items[0] * d_vlen_in / d_vlen_out;
    return noutput_items;
}

int transpose_matrix_vcvc_impl::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];
    gr_complex* out = (gr_complex*)output_items[0];

    // Error handling
    // 检查输入和输出向量长度是否与数据包长度匹配。如果 vlen_in 和 vlen_out 的比例与整数数据包长度不一致,则抛出异常。
    // 这是为了确保数据能够正确地进行矩阵转置。
    if (ninput_items[0] * float(d_vlen_in) / float(d_vlen_out) -
            ninput_items[0] * d_vlen_in / d_vlen_out !=
        0)
        throw std::runtime_error("vlen_in and vlen_out do not match to packet length");

    // Get all tags, reset offset and push to output
    // 获取当前处理块附近的所有流标签,并将它们重新添加到输出流中。这样做是为了保持流标签在数据处理过程中的连续性和正确性
    get_tags_in_range(d_tags, 0, nitems_read(0), nitems_read(0) + 1);
    for (int k = 0; k < d_tags.size(); k++) {
        add_item_tag(
            0, nitems_written(0), d_tags[k].key, d_tags[k].value, d_tags[k].srcid);
    }

    // Set noutput items
    // 这行代码计算并设置输出项的数量,基于输入项数和输入/输出向量长度的比例。
    noutput_items = ninput_items[0] * d_vlen_in / d_vlen_out;

    // Update len key tag
    update_length_tags(noutput_items, 0);

    // Reorganize samples
    // 重组样本
    // 这是矩阵转置的核心部分,双重循环遍历输入数据,按列优先顺序重组样本到输出缓冲区。
    // 外层循环遍历单个输入向量的每个元素,内层循环遍历所有输入向量。
    for (int l = 0; l < d_vlen_in; l++) {           // go through single input vector
        for (int k = 0; k < ninput_items[0]; k++) { // go through all input vectors
            *out++ = in[k * d_vlen_in + l];
        }
    }

    // Tell runtime system how many output items we produced.
    return noutput_items;
}

} /* namespace radar */
} /* namespace gr */

我的qq:2442391036,欢迎交流!


相关推荐
ephemerals__4 分钟前
【c++丨STL】list模拟实现(附源码)
开发语言·c++·list
Microsoft Word19 分钟前
c++基础语法
开发语言·c++·算法
一只小小汤圆44 分钟前
opencascade源码学习之BRepOffsetAPI包 -BRepOffsetAPI_DraftAngle
c++·学习·opencascade
legend_jz1 小时前
【Linux】线程控制
linux·服务器·开发语言·c++·笔记·学习·学习方法
嘿BRE1 小时前
【C++】几个基本容器的模拟实现(string,vector,list,stack,queue,priority_queue)
c++
ö Constancy2 小时前
c++ 笔记
开发语言·c++
fengbizhe2 小时前
笔试-笔记2
c++·笔记
徐霞客3202 小时前
Qt入门1——认识Qt的几个常用头文件和常用函数
开发语言·c++·笔记·qt
fpcc2 小时前
redis6.0之后的多线程版本的问题
c++·redis
螺旋天光极锐斩空闪壹式!3 小时前
自制游戏:监狱逃亡
c++·游戏