GNU Radio之OFDM Frame Equalizer底层C++实现

2024-05-01 08:45:35 浏览数 (2)

前言

OFDM Frame Equalizer 的功能是对标记的 OFDM 帧执行一维或二维均衡,本文对 OFDM Frame Equalizer 模块的底层 C 源码进行剖析。

一、OFDM Frame Equalizer 模块简介

  • 输入与输出:
    • 输入:一系列带标签的 OFDM 符号
    • 输出:与输入相同,但经过均衡和频率校正。
  • 参数:
    • FFT length:用于设置输入和输出向量大小
    • CP length:样本中循环前缀的长度(校正频率偏移所需)
    • Equalizer:将执行实际工作的均衡器对象
    • Length Tag Key:TSB密钥
    • Propagate Channel State:如果为 true,则最后一个符号之后的通道状态将作为标记添加到第一个符号
    • Fixed frame length: 设置帧长度是否固定。当给定此值时,长度标签键可以留空,但即使在输入处使用标记流时它也是有用的。
  • 实现原理
    • 首先,它会移除粗略的载波偏移。如果在第一个项目中找到带有 'ofdm_sync_carr_offset' 键的标签,这将被解释为以载波数量表示的粗略频率偏移。
    • 接下来,它在一个或两个维度上对标记的 OFDM 帧进行均衡。实际的均衡由一个名为 ofdm_frame_equalizer 的对象完成,该对象位于该块的外部。
    • 请注意,带有粗略载波偏移的标签没有被移除。该块下游的块不应尝试也去纠正这个偏移。

二、C 具体实现

1、初始化和配置参数

代码语言:javascript复制
ofdm_frame_equalizer_vcvc_impl::ofdm_frame_equalizer_vcvc_impl(
    ofdm_equalizer_base::sptr equalizer,
    int cp_len,
    const std::string& tsb_key,		// 一个标签流块(Tagged Stream Block)的关键字,用于处理流中的数据包。
    bool propagate_channel_state,	// 决定是否传播通道状态
    int fixed_frame_len)			// 固定帧长,如果设置了固定帧长,则在处理时将使用这个长度
    : tagged_stream_block(
          "ofdm_frame_equalizer_vcvc",
          io_signature::make(1, 1, sizeof(gr_complex) * equalizer->fft_len()),
          io_signature::make(1, 1, sizeof(gr_complex) * equalizer->fft_len()),
          tsb_key),
      d_fft_len(equalizer->fft_len()),
      d_cp_len(cp_len),
      d_eq(equalizer),
      d_propagate_channel_state(propagate_channel_state),
      d_fixed_frame_len(fixed_frame_len),
      d_channel_state(equalizer->fft_len(), gr_complex(1, 0))
{
	// *************************错误处理*********************************
	/*
		这些行检查输入参数的有效性。如果既没有指定TSB标签也没有指定固定帧长,或者指定的帧长小于0,则抛出异常。
	*/
    if (tsb_key.empty() && fixed_frame_len == 0) {
        throw std::invalid_argument("Either specify a TSB tag or a fixed frame length!");
    }
    if (d_fixed_frame_len < 0) {
        throw std::invalid_argument("Invalid frame length!");
    }
	//  *************************设置输出倍数********************************* 
	/*
		如果设置了固定帧长,这行代码设置输出数据块的大小为固定帧长的倍数。
	*/
    if (d_fixed_frame_len) {
        set_output_multiple(d_fixed_frame_len);
    }
    set_relative_rate(1, 1);	// 设置输入和输出数据的相对速率,这里设置为1:1,表示输入和输出速率相同
    // Really, we have TPP_ONE_TO_ONE, but the channel state is not propagated
    set_tag_propagation_policy(TPP_DONT);	// 设置标签传播策略为不传播,这意味着这个块不会自动把输入的标签复制到输出。
}

2、解析出所需的输入项目数量

代码语言:javascript复制
// 该函数的目的是从一组标签中解析出所需的输入项目数量
void ofdm_frame_equalizer_vcvc_impl::parse_length_tags(
    const std::vector<std::vector<tag_t>>& tags, gr_vector_int& n_input_items_reqd)	
    /*
     	tags: 每个 tag_t 代表一个数据流中的标签
     	n_input_items_reqd: 用于设置这个块处理每个数据流所需的项目(样本)数量
    */
    
{
    if (d_fixed_frame_len) {	// 如果设置了固定帧长度,就直接将n_input_items_reqd[0]设置为这个固定值
        n_input_items_reqd[0] = d_fixed_frame_len;
    } else {	// 如果没有固定的帧长度,代码会遍历tags[0]中的每个标签。(tags[0]表示与当前块相关的第一条数据流的所有标签。)
        for (unsigned k = 0; k < tags[0].size(); k  ) {
            if (tags[0][k].key == pmt::string_to_symbol(d_length_tag_key_str)) {	// 如果标签的键与 d_length_tag_key_str(这是一个字符串,代表帧长度相关的标签键)相匹配
                n_input_items_reqd[0] = pmt::to_long(tags[0][k].value);	// 在当前处理周期内,该块需要读取的输入项目数
            }
        }
    }
}

3、处理 OFDM 信号的均衡

代码语言:javascript复制
// 用于处理OFDM信号的均衡
int ofdm_frame_equalizer_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];
    int carrier_offset = 0;		// 初始化载波偏移和帧长度变量
    int frame_len = 0;
    if (d_fixed_frame_len) {	// 如果有固定的帧长度,则使用该长度;否则使用输入项的数量作为帧长度
        frame_len = d_fixed_frame_len;
    } else {
        frame_len = ninput_items[0];
    }
	// *************************获取并处理标签*********************************
	/*
		从输入流中获取标签并处理。特别关注通道状态和载波偏移的标签。
	*/
    std::vector<tag_t> tags;
    get_tags_in_window(tags, 0, 0, 1);	// 从输入流的当前处理窗口中获取所有标签,并存储在 tags 向量中
    for (unsigned i = 0; i < tags.size(); i  ) {	// 遍历标签,查找关键标签ofdm_sync_chan_taps和ofdm_sync_carr_offset,分别更新通道状态和载波偏移。
        if (pmt::symbol_to_string(tags[i].key) == "ofdm_sync_chan_taps") {
            d_channel_state = pmt::c32vector_elements(tags[i].value);
        }
        if (pmt::symbol_to_string(tags[i].key) == "ofdm_sync_carr_offset") {
            carrier_offset = pmt::to_long(tags[i].value);
        }
    }

    // Copy the frame and the channel state vector such that the symbols are shifted to
    // the correct position
    // 调整输入输出数据的对齐和拷贝
    /*
		根据载波偏移调整输入数据的位置,并将输入数据复制到输出缓冲区。
		如果载波偏移是负数,输出数组的开始部分将被清零,然后从输入数据中复制偏移后的数据。
		如果偏移是正数,则在数据复制后,输出数组的末尾部分被清零。
	*/
    if (carrier_offset < 0) {
        memset((void*)out, 0x00, sizeof(gr_complex) * (-carrier_offset));
        memcpy((void*)&out[-carrier_offset],
               (void*)in,
               sizeof(gr_complex) * (d_fft_len * frame_len   carrier_offset));
    } else {
        memset((void*)(out   d_fft_len * frame_len - carrier_offset),
               0x00,
               sizeof(gr_complex) * carrier_offset);
        memcpy((void*)out,
               (void*)(in   carrier_offset),
               sizeof(gr_complex) * (d_fft_len * frame_len - carrier_offset));
    }

    // Correct the frequency shift on the symbols
    // *************************频率偏移校正*********************************
    /*
		这部分代码通过乘以一个相位校正因子来补偿频率偏移。

		d_cp_len / d_fft_len 这两个参数的比例用于确定每个样本的相位偏差累积速率。
		具体来说,循环前缀的长度相对于FFT长度的比例影响了因频率偏移而导致的每个样本的相位变化
	*/
    gr_complex phase_correction;
    for (int i = 0; i < frame_len; i  ) {
        phase_correction =
            gr_expj(-GR_M_TWOPI * carrier_offset * d_cp_len / d_fft_len * (i   1));
        for (int k = 0; k < d_fft_len; k  ) {
            out[i * d_fft_len   k] *= phase_correction;
        }
    }

    // Do the equalizing
    // 均衡处理
    d_eq->reset();		// 重置均衡器的状态
    d_eq->equalize(out, frame_len, d_channel_state);	// 调用均衡器来处理输出数据,根据当前的通道状态进行均衡。
    d_eq->get_channel_state(d_channel_state);			// 更新通道状态,以便在后续处理中使用

    // Update the channel state regarding the frequency offset
    phase_correction =
        gr_expj(GR_M_TWOPI * carrier_offset * d_cp_len / d_fft_len * frame_len);
    for (int k = 0; k < d_fft_len; k  ) {
        d_channel_state[k] *= phase_correction;
    }

    // Propagate tags (except for the channel state and the TSB tag)
    // 传播标签(排除通道状态和 TSB 标签)
    get_tags_in_window(tags, 0, 0, frame_len);	// 从处理的数据流窗口中获取所有标签,并存储在tags向量中
    for (size_t i = 0; i < tags.size(); i  ) {
        if (tags[i].key != CHAN_TAPS_KEY &&		// 如果标签的键不是通道状态(CHAN_TAPS_KEY)和不是固定帧长度关键字(d_length_tag_key_str),则将该标签添加到输出流的标签中。
            tags[i].key != pmt::mp(d_length_tag_key_str)) {
            add_item_tag(0, tags[i]);
        }
    }

    // Housekeeping
    // 状态维护
    if (d_propagate_channel_state) {	// 检查是否传播通道状态
        add_item_tag(0,
                     nitems_written(0),
                     pmt::string_to_symbol("ofdm_sync_chan_taps"),
                     pmt::init_c32vector(d_fft_len, d_channel_state));
    }

	// 处理固定帧长度
	/*
		consume_each 这个调用通知框架当前块已经处理并准备“消费”frame_len数量的输入项目。
		这是流处理中的标准操作,用于更新框架关于数据流进度的内部状态。
	*/
    if (d_fixed_frame_len && d_length_tag_key_str.empty()) {
        consume_each(frame_len);
    }

    return frame_len;
}

0 人点赞