GNU Radio之OFDM Serializer底层C++实现


前言

GNU Radio 中 OFDM Serializer 模块是 OFDM Carrier Allocator 逆块,其功能为将 OFDM 子载波的复杂调制符号序列化(并串转换模块),输出复数数据符号作为一个带标签的流,并丢弃导频符号。


一、OFDM Serializer 简介

在这里插入图片描述

  • 输入与输出

    • 输入:复数长度向量
    • 输出:复数标量,其顺序与占用的载波中指定的顺序相同。
  • 参数

    • FFT length:FFT 长度
    • Occupied Carriers:占据的子载波
    • Length Tag Key:标识输入帧长度(以OFDM符号计)的标签键
    • Packet Length Tag Key:标识此数据包中复数符号数量的标签键
    • Symbols skipped:如果第一个符号没有按照 occupied_carriers[0] 的分配设置,请设置这个参数
    • Carrier Offset Key:当这个块需要纠正载波偏移时,在这里指定偏移的标签键(如果后面接的是OFDM帧均衡器则不必指定)。
    • Input is shifted:如果输入在索引 0 上具有 DC 载波(即未进行 FFT 移位),则将其设置为 false
  • 实现原理

    • OFDM Serializer 是 OFDM 载波分配器的逆向块。它输出复数数据符号作为一个带标签的流,并丢弃导频符号。
    • 如果提供,将解析两个不同的标签:第一个键(长度标签键)指定输入帧中的 OFDM 符号数量。第二个键(数据包长度标签键)指定编码到该帧中的复数符号数量。如果提供了这第二个键,则在输出时使用;否则,使用长度标签键。如果两者都提供,数据包长度指定输出项的最大数量,而帧长度指定消耗的输入项的确切数量。
    • 通过传递带有该偏移的另一个标签,可以在此功能中纠正载波偏移。

二、C++ 具体实现

1、初始化和配置参数

// 构造函数, 包含了对输入信号的参数设置、检查和初始化步骤
ofdm_serializer_vcc_impl::ofdm_serializer_vcc_impl(
    int fft_len,												// FFT处理的长度
    const std::vector<std::vector<int>>& occupied_carriers,		// 指定在FFT带宽中哪些频率载波是被占用的
    const std::string& len_tag_key,								// 用于标签流处理,标识长度的键
    const std::string& packet_len_tag_key,						// 用于标签流处理,标识数据包长度的键
    int symbols_skipped,										// 开始处理前应跳过的符号数
    const std::string& carr_offset_key,							// 用于载波偏移的标签键
    bool input_is_shifted)										// 指示输入数据是否已经进行了频率移位
    : tagged_stream_block("ofdm_serializer_vcc",
                          io_signature::make(1, 1, sizeof(gr_complex) * fft_len),	// io_signature 定义了输入和输出的数据类型和大小。
                          io_signature::make(1, 1, sizeof(gr_complex)),
                          len_tag_key),
      d_fft_len(fft_len),										// FFT长度
      d_occupied_carriers(occupied_carriers),					// 存储占用的载波信息
      d_packet_len_tag_key(pmt::string_to_symbol(packet_len_tag_key)),
      d_out_len_tag_key(pmt::string_to_symbol(
          (packet_len_tag_key.empty() ? len_tag_key : packet_len_tag_key))),
      d_symbols_skipped(symbols_skipped % occupied_carriers.size()),
      d_carr_offset_key(pmt::string_to_symbol(carr_offset_key)),
      d_curr_set(symbols_skipped % occupied_carriers.size()),
      d_symbols_per_set(0)	// 记录在一个OFDM符号集中,所有被占用的载波上分配的数据和导频符号的总数;"集"指的是一个OFDM帧
{
	// *************************载波占用和频率移位处理**************************
	/*
		这段循环通过对每个载波的索引进行修改来处理频率移位(如果input_is_shifted为真)。
		对于每个载波,根据FFT长度进行调整,确保它们都在有效范围内(64 个子载波编号为 [-32,31],
		将其变为 [0, 63])。如果载波索引超出了FFT长度的范围,会抛出异常。
	*/
    for (unsigned i = 0; i < d_occupied_carriers.size(); i++) {
        for (unsigned k = 0; k < d_occupied_carriers[i].size(); k++) {
            if (input_is_shifted) {
                d_occupied_carriers[i][k] += fft_len / 2;
                if (d_occupied_carriers[i][k] > fft_len) {
                    d_occupied_carriers[i][k] -= fft_len;
                }
            } else {
                if (d_occupied_carriers[i][k] < 0) {
                    d_occupied_carriers[i][k] += fft_len;
                }
            }
            if (d_occupied_carriers[i][k] >= fft_len || d_occupied_carriers[i][k] < 0) {
                throw std::invalid_argument("ofdm_serializer_vcc: trying to occupy a "
                                            "carrier outside the fft length.");
            }
        }
    }

	// 计算每个集合的符号数
    for (unsigned i = 0; i < d_occupied_carriers.size(); i++) {
        d_symbols_per_set += d_occupied_carriers[i].size();	// 计算所有占用载波集合中的符号总数,用于设置处理速率。
    }
    set_relative_rate((uint64_t)d_symbols_per_set, (uint64_t)d_occupied_carriers.size());
    set_tag_propagation_policy(TPP_DONT);	// 设置标签传播政策为不传播,意味着这个块不会自动将输入标签复制到输出标签
}

2、计算输出流长度

int ofdm_serializer_vcc_impl::calculate_output_stream_length(
    const gr_vector_int& ninput_items)	// 包含了每个输入流中可用的样本数。在这个上下文中,向量中的第一个元素(ninput_items[0])表示当前块处理的输入流中的样本数。
{
	/*
		ninput_items[0] / d_occupied_carriers.size():这部分计算整个输入样本可以完全覆盖多少个完整的载波集合。

		将完整覆盖的载波集数乘以每个集合中的符号数(d_symbols_per_set),得到基本的输出样本数。
	*/
	int nout = (ninput_items[0] / d_occupied_carriers.size()) * d_symbols_per_set;	// 用于计算输出流的长度

	// 处理余数部分
	/*
		这部分计算每个额外处理的载波集中有多少个符号,然后加到nout上
	*/
	for (unsigned i = 0; i < ninput_items[0] % d_occupied_carriers.size(); i++) {
		
        nout += d_occupied_carriers[(i + d_curr_set) % d_occupied_carriers.size()].size();
    }
    return nout;
}

3、更新流标签

void ofdm_serializer_vcc_impl::update_length_tags(int n_produced, int n_ports)
{
	// 更新流标签,用于指示输出流中的数据长度
    add_item_tag(0, nitems_written(0), d_out_len_tag_key, pmt::from_long(n_produced));
}

4、OFDM 信号的序列化(并串转换)

// 处理 OFDM 信号的序列化
int ofdm_serializer_vcc_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];				// 各输入流中的项数列表
    long frame_length = ninput_items[0]; // Input frame		记录输入帧的长度
    long packet_length = 0;              // Output frame	用于记录输出帧的长度
    int carr_offset = 0;

    std::vector<tag_t> tags;
    // Packet mode
    if (!d_length_tag_key_str.empty()) {
        get_tags_in_range(tags, 0, nitems_read(0), nitems_read(0) + 1);	// 检索当前处理范围内的标签
        for (unsigned i = 0; i < tags.size(); i++) {	// 载波偏移
            if (tags[i].key == d_carr_offset_key) {
                carr_offset = pmt::to_long(tags[i].value);
            }
            if (tags[i].key == d_packet_len_tag_key) {	// 包长度的信息
                packet_length = pmt::to_long(tags[i].value);
            }
        }
    } else {
        // recalc frame length from noutput_items
        frame_length = 0;
        int sym_per_frame = 0;
        while ((sym_per_frame +
                d_occupied_carriers[(frame_length + 1) % d_occupied_carriers.size()]
                    .size()) < (size_t)noutput_items) {
            frame_length++;
            sym_per_frame +=
                d_occupied_carriers[(frame_length + 1) % d_occupied_carriers.size()]
                    .size();
        }
    }

    // Copy symbols
    // *************************符号复制和标签处理*************************
    /*
		在处理每个输入符号时,将关联的标签(除了包长度标签)复制到输出符号上。
		然后,根据载波偏移和当前的载波集设置,从输入复制符号到输出。
	*/
    int n_out_symbols = 0;
    for (int i = 0; i < frame_length; i++) {
        // Copy all tags associated with this input OFDM symbol onto the first output
        // symbol
        get_tags_in_range(tags, 0, nitems_read(0) + i, nitems_read(0) + i + 1);
        for (size_t t = 0; t < tags.size(); t++) {
            // The packet length tag is not propagated
            if (tags[t].key != d_packet_len_tag_key) {
                add_item_tag(
                    0, nitems_written(0) + n_out_symbols, tags[t].key, tags[t].value);
            }
        }
        for (unsigned k = 0; k < d_occupied_carriers[d_curr_set].size(); k++) {
            out[n_out_symbols++] =
                in[i * d_fft_len + d_occupied_carriers[d_curr_set][k] + carr_offset];
        }
		
		// 包长度检查
		/*
			如果设置了包长度,并且输出符号数超过了这个长度,就将输出符号数调整为包长度,
			并提前终止处理。
		*/
        if (packet_length && n_out_symbols > packet_length) {
            n_out_symbols = packet_length;
            break;
        }
        d_curr_set = (d_curr_set + 1) % d_occupied_carriers.size();
    }

    // Housekeeping
    // 结束处理
    /*
		消费输入项:如果没有长度标签键,根据计算的帧长度消费输入项。
					否则,重置当前的载波集设置,准备下一次调用。
	*/
    if (d_length_tag_key_str.empty()) {
        consume_each(frame_length);
    } else {
        d_curr_set = d_symbols_skipped;
    }

    return n_out_symbols;
}

我的qq:2442391036,欢迎交流!


  • 6
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
OFDM(正交频分复用)是一种用于无线通信系统的调制技术,它将高速数据流分成多个低速子载波进行传输,以提高系统的传输效率和抗干扰能力。GNURadio是一个开源的软件无线电开发平台,可以用于设计和实现各种无线通信系统。 在GNURadio实现OFDM系统,可以按照以下步骤进行: 1. 随机生成数据:首先,使用随机数生成器生成需要传输的数据。 2. 数据打包:将生成的数据按照一定的规则进行打包,例如每96个数据打包成一个packet。 3. 添加CRC校验:为了保证数据的完整性,可以使用CRC(循环冗余校验)算法为每个packet添加校验码。 4. 数据重组:将打包后的数据按照一定的规则进行重组,例如将8bit的数据拆分成2bit的数据,以便进行QPSK调制。 5. 星座映射:对重组后的数据进行星座映射,即将数据映射到复平面上的不同点,以便进行调制。 6. OFDM载波映射:将映射后的数据进行OFDM调制,即将数据分配到不同的子载波上。 7. IFFT变换:对每个子载波进行IFFT(逆快速傅里叶变换),将频域信号转换为时域信号。 8. 添加循环前缀:为了抵消多径传播引起的符号间干扰,可以在每个OFDM符号前添加循环前缀。 9. OFDM信号生成:将经过循环前缀处理的时域信号组合起来,生成最终的OFDM信号。 以上是一个标准的OFDM发射端的流程,其中包括数据生成、打包、校验、重组、映射、调制、IFFT变换和循环前缀处理等步骤。通过GNURadio可以方便地实现这些步骤,并生成OFDM信号。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

须尽欢~~

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

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

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

打赏作者

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

抵扣说明:

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

余额充值