Loading [MathJax]/jax/output/CommonHTML/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >GNU Radio之OFDM Serializer底层C++实现

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

作者头像
Gnep@97
发布于 2024-05-01 00:46:42
发布于 2024-05-01 00:46:42
17400
代码可运行
举报
运行总次数:0
代码可运行

前言

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、初始化和配置参数

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// 构造函数, 包含了对输入信号的参数设置、检查和初始化步骤
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、计算输出流长度

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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、更新流标签

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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 信号的序列化(并串转换)

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// 处理 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;
}
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2024-04-30,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
GNU Radio之OFDM Carrier Allocator底层C++实现
OFDM Carrier Allocator 是 OFDM 子载波分配模块,也即串并转换模块。该模块的作用是给每个子载波分配相应的值,数据相应地实现串并转换。本文记录其底层 C++ 代码实现。
Gnep@97
2024/03/28
3480
GNU Radio之OFDM Carrier Allocator底层C++实现
GNU Radio之OFDM Frame Equalizer底层C++实现
OFDM Frame Equalizer 的功能是对标记的 OFDM 帧执行一维或二维均衡,本文对 OFDM Frame Equalizer 模块的底层 C++ 源码进行剖析。
Gnep@97
2024/05/01
1690
GNU Radio之OFDM Frame Equalizer底层C++实现
GNU Radio之OFDM Channel Estimation底层C++实现
OFDM Channel Estimation 模块的功能是根据前导码(同步字)估计 OFDM 的信道和粗略频率偏移,本文对 OFDM Channel Estimation 模块的底层 C++ 源码进行剖析。
Gnep@97
2024/04/30
1890
GNU Radio之OFDM Channel Estimation底层C++实现
GNU Radio之OFDM Divide和Matrix Transpose底层C++实现
gr-radar 中的 OFDM Divide 模块是GNU Radio中的一个组件,专门用于处理正交频分复用(OFDM)信号。这个模块主要执行复数信号的除法操作,通常用于雷达和通信系统中的信号处理。
Gnep@97
2024/05/26
1320
GNU Radio之OFDM Divide和Matrix Transpose底层C++实现
GNURadio+USRP+OFDM实现文件传输
使用 GNU Radio Companion 驱动 USRP N320 实现 OFDM 自收自发测试。(Ubuntu20.04LTS + GNURadio 3.8 + UHD 3.15)
Gnep@97
2024/03/08
1.3K0
GNURadio+USRP+OFDM实现文件传输
解决GNU Radio+USRP实现OFDM收发在接收端QPSK星座图映射无“抖动”问题
本文记录在 GNU Radio+USRP 实现 OFDM 收发时,在接收端 QPSK 星座图映射无“抖动”问题的解决方法,
Gnep@97
2024/03/30
3630
解决GNU Radio+USRP实现OFDM收发在接收端QPSK星座图映射无“抖动”问题
解决GNU Radio+USRP实现OFDM收发在接收端存在误码问题
在使用 GNU Radio 时使用官方例程搭建 GNU Radio + USRP 实现 OFDM 收发测试时,发现误码情况很严重,明明都是理想信道的情况下,即时在仿真情况下不接 USRP 硬件设备进行收发也会出现误码,如下图所示,这就不得不怀疑是官方的底层 C++ 源码存在的问题了。
Gnep@97
2024/04/02
6531
解决GNU Radio+USRP实现OFDM收发在接收端存在误码问题
GNU Radio之static Target simulator底层C++实现
gr-radar 中的 Static Target Simulator 模块用于在雷达系统中模拟静态目标。这种模拟在雷达信号处理、算法开发和系统验证中非常有用。通过模拟静态目标,可以测试雷达系统的目标检测、定位和追踪能力。这个模块允许用户设置多个目标的属性,如距离、速度、雷达截面等,从而生成对应的回波信号。下面对这个模块进行介绍并详细分析其底层 C++ 代码实现。
Gnep@97
2024/05/26
1930
GNU Radio之static Target simulator底层C++实现
GNU Radio之Schmidl & Cox OFDM synch.底层C++实现
在 GNU Radio OFDM 系统中,一个非常重要的环节是在接收端准确地同步和检测发送端发出的信号。这就是 Schmidl & Cox 同步算法发挥作用的地方。Schmidl & Cox 算法是一种用于 OFDM 信号的时间同步的技术。本文对其底层 C++ 源码进行学习记录。
Gnep@97
2024/04/27
4841
GNU Radio之Schmidl & Cox OFDM synch.底层C++实现
GNU Radio创建FFT、IFFT C++ OOT块
GNU Radio 自带的 FFT 模块使用起来不是很方便,这个模块要求输入和输出数据长度预先设定,且一旦设定后就要求前后的 block 与其具有相同长度的输入输出,并不满足我目前的需求,因此需要有必要重新自己做一个 FFT 和 IFFT OOT块。
Gnep@97
2024/05/05
3300
GNU Radio创建FFT、IFFT C++ OOT块
GNU Radio之Frequency Mod底层C++实现
频率调制(Frequency Modulation, FM)是一种重要的调制技术,广泛应用于无线广播和通信,本文对 GNU Radio 中的 Frequency Mod 模块进行深入剖析。
Gnep@97
2024/04/29
3530
GNU Radio之Frequency Mod底层C++实现
GNU Radio创建时间戳 C++ OOT块
目前有这么样一个需求,我想在 GNU Radio 中计算从一个模块到其他模块执行所花费的时间,我的做法是将获取的时间戳信息作为标签添加到数据流中,然后传入到待计算时间的那个模块后再获取当前时间并与流标签中的时间戳信息进行相减,即可得到所耗费的时间,也就达到了计算时间间隔的目的。
Gnep@97
2024/05/08
1740
GNU Radio创建时间戳 C++ OOT块
OFDM完整仿真过程及解释(MATLAB)
0.能找到这篇文章,说明对ofdm已经有一点了解,所以其原理就不再赘述,这篇代码的目的只是希望能对ofdm整个过程有一个理解;
全栈程序员站长
2022/09/05
2.7K0
OFDM通信连路仿真学习
Gnep@97
2023/11/30
6000
OFDM通信连路仿真学习
OFDM——PAPR减小
本文对减小 OFDM 峰值平均功率比(PAPR—Peak to Average Power Ratio)的内容以思维导图的形式呈现,有关仿真部分进行了讲解实现。
Gnep@97
2023/12/29
6540
OFDM深入学习及MATLAB仿真
前面对 OFDM 的学习及了解还是比较浅显的,例如没有考虑到其中涉及的技术,例如保护间隔、信道编码、扩频、导频相关技术,本文通过学习这些技术,并进行 OFDM 的完整仿真过程。
Gnep@97
2023/11/09
2K2
OFDM深入学习及MATLAB仿真
教你几招搞定 LSTMs 的独门绝技(附代码)
如果你用过 PyTorch 进行深度学习研究和实验的话,你可能经历过欣喜愉悦、能量爆棚的体验,甚至有点像是走在阳光下,感觉生活竟然如此美好 。但是直到你试着用 PyTorch 实现可变大小的 mini-batch RNNs 的时候,瞬间一切又回到了解放前。
AI研习社
2018/07/26
3.3K0
教你几招搞定 LSTMs 的独门绝技(附代码)
Python人工智能 | 二十六.基于BiLSTM-CRF的医学命名实体识别研究(上)数据预处理
实体是知识图谱最重要的组成,命名实体识别(Named Entity Recognition,NER)对于知识图谱构建具有很重要意义。命名实体是一个词或短语,它可以在具有相似属性的一组事物中清楚地标识出某一个事物。命名实体识别(NER)则是指在文本中定位命名实体的边界并分类到预定义类型集合的过程。
Eastmount
2024/06/07
8350
Python人工智能 | 二十六.基于BiLSTM-CRF的医学命名实体识别研究(上)数据预处理
基于Netty和SpringBoot实现一个轻量级RPC框架-协议篇
最近对网络编程方面比较有兴趣,在微服务实践上也用到了相对主流的RPC框架如Spring Cloud Gateway底层也切换为Reactor-Netty,像Redisson底层也是使用Netty封装通讯协议,最近调研和准备使用的SOFARpc也是基于Netty封装实现了多种协议的兼容。因此,基于Netty造一个轮子,在SpringBoot的加持下,实现一个轻量级的RPC框架。这篇博文介绍的是RPC框架协议的定义以及对应的编码解码处理的实现。
Throwable
2020/06/23
2K0
基于Netty和SpringBoot实现一个轻量级RPC框架-协议篇
手写 Android 录屏直播
观看手游直播时,我们观众端看到的是选手的屏幕上的内容,这是如何实现的呢?这篇博客将手写一个录屏直播 Demo,实现类似手游直播的效果。
字节流动
2021/06/09
2K0
手写 Android 录屏直播
相关推荐
GNU Radio之OFDM Carrier Allocator底层C++实现
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验