解决GNU Radio+USRP实现OFDM收发在接收端存在误码问题

文章目录

  • 前言
  • 一、OFDM 收发流程
    • 1、OFDM 收端流程
    • 2、OFDM 收端流程
  • 二、问题所在
    • 1、find_trigger_signal 函数解读
    • 2、general_work 函数
    • 3、问题所在
  • 三、修改源码
  • 四、运行结果
    • 1、频谱
    • 2、传输数据测试
  • 五、调试小技巧
  • 六、资源自取


前言

在使用 GNU Radio 时使用官方例程搭建 GNU Radio + USRP 实现 OFDM 收发测试时,发现误码情况很严重,明明都是理想信道的情况下,即时在仿真情况下不接 USRP 硬件设备进行收发也会出现误码,如下图所示,这就不得不怀疑是官方的底层 C++ 源码存在的问题了。
在这里插入图片描述

当然,之前我也用了一些方法在不修改底层 C++ 源码时解决了这个问题:GNURadio+USRP+OFDM实现文件传输,但是还是想从根本上解决这个误码问题。

首先声明一下我的环境:(Ubuntu20.04LTS + GNURadio 3.8 + UHD 3.15),一台电脑 + 一台 USRP 自收自发。


一、OFDM 收发流程

当使用官方的例程(一次发送 10 帧即 960 个字节的数据)进行测试时即使是在仿真中将信道条件改为理想信道时在接收端也会出现丢帧的现象。

1、OFDM 收端流程

有关 OFDM 发送端流程图如下图所示:
在这里插入图片描述

发端没有什么问题,问题存在于收端的处理

2、OFDM 收端流程

有关 OFDM 接收端流程图如下图所示:
在这里插入图片描述
其中问题所在是 Header/Payload Demux 模块的底层处理,下面一起看看其内部实现

二、问题所在

下图红框内的模块即 Header/Payload Demux 模块。
在这里插入图片描述
Header/Payload Demux:该模块的作用是根据定时信息和帧头信息,将复合在一起的帧头和数据进行分离。该模块的工作原理是:首先,将三个输入端口从上到下编号为 0,1,2,输出端编号类似。0 号端口连续输入去除载波频偏的数据流,当 1 号端口(定时信息)输入 1 时,也就是功能被触发,则输出端口 0 输出帧头,而数据(Payload)则保持不动。直到输入端口 2 接收到解码后的帧头信息,输出端口才有数据输出,输出数据为帧头和数据 payload 的分离数据。

我们首先看一下官方源码的原理,以下为官方有关核心程序讲解:

1、find_trigger_signal 函数解读

/*函数功能:在信号处理或数据流处理程序中寻找触发信号的函数
*/
int header_payload_demux_impl::find_trigger_signal(int skip_items,int max_rel_offset,uint64_t base_offset,const unsigned char* in_trigger)
{
/*参数说明:skip_items:开始搜索之前要跳过的项目数量max_rel_offset:最大的相对偏移量,即在这个范围内寻找触发信号base_offset:基准偏移量,是搜索的起始点in_trigger:指向触发信号数据的指针
*/int rel_offset = max_rel_offset;	// 初始化为最大相对偏移量,用来存储找到的触发信号的相对位置/*如果最大相对偏移量小于要跳过的项目数,直接返回rel_offset。这意味着没有足够的数据来进行搜索,所以函数提前结束。*/if (max_rel_offset < skip_items) {return rel_offset;}if (in_trigger) {	// 如果 in_trigger 不是空指针,即有触发信号数据提供进行搜索。/*这里使用了一个for循环从skip_items开始,一直到max_rel_offset,遍历触发信号数据。*/for (int i = skip_items; i < max_rel_offset; i++) {/*如果在某个位置i找到触发信号(if (in_trigger[i])),则更新rel_offset为这个位置,并跳出循环。这表示找到了触发信号的第一个实例。*/if (in_trigger[i]) {rel_offset = i;break;}}}if (d_uses_trigger_tag) {	// 如果类的成员变量d_uses_trigger_tag为真,表示使用了触发标签进行搜索std::vector<tag_t> tags;	// 用来存储找到的标签get_tags_in_range(tags,	 // 从输入数据端口(PORT_INPUTDATA)中获取一个范围内的标签,并把这些标签存储到tags中PORT_INPUTDATA,base_offset + skip_items,base_offset + max_rel_offset,d_trigger_tag_key);/*如果找到了标签,则按照偏移量对它们进行排序。取排序后的第一个标签的相对偏移量(相对于base_offset),并与当前的rel_offset比较。如果找到的标签偏移量更小,则更新rel_offset为该标签偏移量。*/if (!tags.empty()) {std::sort(tags.begin(), tags.end(), tag_t::offset_compare);const int tag_rel_offset = tags[0].offset - base_offset;if (tag_rel_offset < rel_offset) {rel_offset = tag_rel_offset;}}}return rel_offset;	// 即找到的触发信号的相对位置(如果找到的话),或者是最大相对偏移量(如果没有找到触发信号)
} /* find_trigger_signal() */

2、general_work 函数

我们重点看 general_work 函数中的有效载荷(payload)数据的处理实现:

int header_payload_demux_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 unsigned char* in = (const unsigned char*)input_items[PORT_INPUTDATA];unsigned char* out_header = (unsigned char*)output_items[PORT_HEADER];unsigned char* out_payload = (unsigned char*)output_items[PORT_PAYLOAD];const int n_input_items = (ninput_items.size() == 2)? std::min(ninput_items[0], ninput_items[1]): ninput_items[0];// Items read going into general_work()const uint64_t n_items_read_base = nitems_read(PORT_INPUTDATA);// Items read during this call to general_work()int n_items_read = 0;#define CONSUME_ITEMS(items_to_consume)                                         \update_special_tags(n_items_read_base + n_items_read,                       \n_items_read_base + n_items_read + (items_to_consume)); \consume_each(items_to_consume);                                             \n_items_read += (items_to_consume);                                         \in += (items_to_consume)*d_itemsize;switch (d_state) {.../*当解复用器的状态变为 STATE_PAYLOAD 时,意味着它已经成功接收到了头部(header)信息,并准备处理接下来的有效载荷数据。这个状态下的主要任务是从输入数据流中读取有效载荷数据,然后将这些数据发送到输出端口。*/case STATE_PAYLOAD:	// 有效载荷(payload)数据// Assumptions:// - Input buffer is in the right spot to just start copying/*检查缓冲区是否准备好首先,通过调用 check_buffers_ready 函数来检查是否有足够的输入和输出缓冲区空间来处理当前的有效载荷长度。这个检查确保了在开始复制数据之前,输入和输出都已经准备妥当。这些参数用来判断是否满足处理当前有效载荷的条件:d_curr_payload_len是当前有效载荷的长度。noutput_items, ninput_items, 和 n_items_read分别表示输出项数、输入项数和已读项数,*/ if (check_buffers_ready(d_curr_payload_len, // 当前有效载荷的长度0,noutput_items,	// 输出项数d_curr_payload_len * (d_items_per_symbol + d_gi),ninput_items,	// 输入项数n_items_read)) {	// 已读项数// Write payload/*写入有效载荷:如果缓冲区检查通过,copy_n_symbols 函数会被调用来从输入缓冲区(in)复制有效载荷数据到输出缓冲区(out_payload)。复制的数据量基于当前的有效载荷长度(d_curr_payload_len)和每个符号的项目数(d_items_per_symbol加上d_gi,d_gi是一个保护间隔)。*/copy_n_symbols(in,out_payload,PORT_PAYLOAD,n_items_read_base + n_items_read,d_curr_payload_len);// Consume payload// We can't consume the full payload, because we need to hold off// at least the padding value. We'll use a minimum padding of 1// item here./*消耗输入项:完成数据复制后,需要更新已处理的输入项计数。不过,这里有一个微妙之处:我们不能简单地消耗掉所有的有效载荷数据,因为需要保留一定的“填充”数据以确保数据的完整性。因此,计算items_to_consume时会减去一个最小的填充项数,通常至少为1。这确保了在当前处理周期结束时,输入缓冲区中还留有一些数据,以便后续的处理。*/const int items_padding = std::max(d_header_padding_total_items, 1);const int items_to_consume =d_curr_payload_len * (d_items_per_symbol + d_gi) - items_padding;CONSUME_ITEMS(items_to_consume);set_min_noutput_items(d_output_symbols ? 1 : (d_items_per_symbol + d_gi));/*更新状态:最后,解复用器的状态被设置回STATE_FIND_TRIGGER,这意味着在处理完当前有效载荷后,解复用器将重新开始寻找下一个触发信号,以准备接收下一个数据包的头部。*/d_state = STATE_FIND_TRIGGER;}break;...}

3、问题所在

总的来说,丢帧的原因就是相邻两个定时信号的间隔过短时,导致当前帧提取数据时将后一个帧数据的定时信号作为当前帧的数据一并读入,这样就丢失了下一帧数据的定时信号,因此就造成了丢帧的现象。这种现象是源码中固有的问题。具体分析如下:

下图中数据与触发信号是严格执行对应位置的并行传输关系,Header/Payload Demux 模块先读取 trigger 信号,当读到值为 1 时就被认为是一帧数据的开始,这时就从数据信号的相应位置开始往后提取 959 个数据作为当前帧的数据进行输出。
在这里插入图片描述
根据源码的数据处理过程,源码中每次接收到定时信号后,都会提取紧跟着该定时信号后面的 959 个数据作为当前帧进行输出,因此这对定时信号的精确型提出了很高的要求,如果相邻两个定时信号的间隔出现了小于正常数据帧长度的偏差,比如正常间隔为 960,如果此时出现了间隔为 958 的间隔,如下图,则在提取后续 959 个数据的时候就会正好把下一帧的定时信号当作当前帧的数据一起读入,这样就丢失了下一帧数据的定时信号,因此就造成了丢帧的现象。
在这里插入图片描述

三、修改源码

解决这个问题的方法就是在源码中进行修改,在保证相邻定时信号不想相互干扰的基础上再重新进行源码的编译安装。需要修改的源码部分为 gr-digital/lib/header_payload_demux_impl.cc 以及 gr-digital/lib/header_payload_demux_impl.h

相关修改以及详解以放到文末,有需要的通信爱好者可自取。

find_trigger_signal() 部分代码
在这里插入图片描述

general_work() 部分代码
在这里插入图片描述

四、运行结果

1、频谱

使用 USRP 自收自发 OFDM 收发端频谱如下图:
在这里插入图片描述

2、传输数据测试

使用 USRP 自收自发 OFDM 随机数传输测试:
在这里插入图片描述
可以看到,误码率为 0

五、调试小技巧

如何在 GNU Radio 中添加调试打印信息方便分析程序执行流程?

#include <iostream>
std::cout << "Debug: The value of variable is " << variable << std::endl;

例如下面我加了一些打印信息用于打印相关变量
在这里插入图片描述
在这里插入图片描述

更改后编译出现下面信息:

/usr/include/uhd/types/sensors.hpp:133: Warning 362: operator= ignored
/usr/include/uhd/types/dict.hpp:144: Warning 503: Can’t wrap ‘operator std::mapstd::string,std::string’ unless renamed to a valid identifier.

这些编译警告信息来自于 SWIG(Simplified Wrapper and Interface Generator)在处理 C++ 代码时遇到的特定情况。SWIG 是一个通常用于将 C 或 C++ 代码包装成其他编程语言可调用的库的工具,例如在 GNU Radio 项目中将 C++ 代码包装成 Python 模块。这些警告具体涉及到如何处理 C++ 中的运算符重载和特定类型的转换。这些警告通常不会阻止你的程序编译或运行,不用理会即可。

六、资源自取

链接:解决GNU Radio+USRP实现OFDM收发在接收端存在误码问题
在这里插入图片描述


我的qq:2442391036,欢迎交流!


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/785620.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

git clone没有权限的解决方法

一般情况 git clone时没有权限&#xff0c;一般是因为在代码库平台上没有配置本地电脑的id_rsa.pub 只要配置上&#xff0c;一般就可以正常下载了。 非一般情况 但是也有即使配置了id_rsa.pub后&#xff0c;仍然无法clone代码的情况。如下 原因 这种情况是因为ssh客户端…

前端常用代码整理— js,jquery篇(3)

目录 1.判断是否是json字符串 2.获取当前网址 3.将文本复制到剪贴板 4.获取一个月的天数 5.展平数组 6.要修改getRandomItem函数以返回数组中的随机两个元素&#xff0c;可以尝试以下代码 1.判断是否是json字符串 const isJson str > {try {JSON.parse(str);return …

通过 Cookie、Session 和 Spring 拦截器技术,实现对用户登录状态的持有和清理(一)

本篇博客对应“2.3 会话管理”小节 视频名称&#xff1a;会话管理 视频链接 什么是HTPP协议&#xff1f; HTTP&#xff0c;Hpyer Text Transfer Protocl&#xff1a;定义了浏览器怎样从&#xff08;万维网客户进程&#xff09;怎样向Web服务器&#xff08;万维网服务器&#…

vue 文件下载

1.返回路径下载 注: 针对一些浏览器无法识别的文件格式&#xff08;如pdf、xls、ppt&#xff09;。可以直接在地址栏上输入URL即可触发浏览器的下载功能。 情况1 //地址栏输入文件URLwindow.location.href URLwindow.open(URL) 注:该方式将下载逻辑放在后端处理&#xff0c…

Mysql的高级语句3

目录 一、子查询 注意&#xff1a;子语句可以与主语句所查询的表相同&#xff0c;但是也可以是不同表。 1、select in 1.1 相同表查询 1.2 多表查询 2、not in 取反&#xff0c;就是将子查询结果&#xff0c;进行取反处理 3、insert into in 4、update…

IO练习题

1&#xff1a;使用 dup2 实现错误日志功能 使用 write 和 read 实现文件的拷贝功能&#xff0c;注意&#xff0c;代码中所有函数后面&#xff0c;紧跟perror输出错误信息&#xff0c;要求这些错误信息重定向到错误日志 err.txt 中 #include <myhead.h>//文件IO实现文件拷…

实现offsetof宏以及交换一个整数二进制奇偶位的宏

目录 1. offsetof宏2. 交换奇偶位 1. offsetof宏 我们想用宏来实现offsetof函数,首先要了解这个函数的用法。 1.1 offsetof函数的介绍及用法 &#xff08;1&#xff09;功能&#xff1a;用来计算结构体中一个成员在该结构体中的相对起始位置的偏移量&#xff0c;单位是字节。 …

ClamAV:Linux服务器杀毒扫描工具

Clam AntiVirus&#xff08;ClamAV&#xff09;是免费而且开放源代码的防毒软件&#xff0c;软件与病毒码的更新皆由社群免费发布。ClamAV在命令行下运行&#xff0c;它不将杀毒作为主要功能&#xff0c;默认只能查出系统内的病毒&#xff0c;但是无法清除。需要用户自行对病毒…

C语言之位段

1.位段的声明 位段的声明和结构是类似的&#xff0c;有两个不同&#xff1a; 1.位段的成员必须是 int、unsigned int 或signed int 。 2.位段的成员名后边有一个冒号和一个数字。 比如&#xff1a; struct A {int _a:2;int _b:5;int _c:10;int _d:30; }; A 就是一个位段类型…

【上海大学计算机组成原理实验报告】二、数据传送实验

一、实验目的 了解在模型机中算术、逻辑运算单元的控制方法。学习机器语言程序的运行过程。通过人工译码&#xff0c;加深对译码器基本工作原理的理解。 二、实验原理 根据实验指导书的相关内容&#xff0c;本次实验所要用的CP226实验仪在手动方式下&#xff0c;运算功能通过…

LeetCode-热题100:160. 相交链表

给你两个单链表的头节点 headA 和 headB &#xff0c;请你找出并返回两个单链表相交的起始节点。如果两个链表不存在相交节点&#xff0c;返回 null 。 图示两个链表在节点 c1 开始相交&#xff1a; 题目数据 保证 整个链式结构中不存在环。 注意&#xff0c;函数返回结果后&…

Go语言学习Day6:数组与切片

名人说&#xff1a;莫愁千里路&#xff0c;自有到来风。 ——钱珝 创作者&#xff1a;Code_流苏(CSDN)&#xff08;一个喜欢古诗词和编程的Coder&#x1f60a;&#xff09; 目录 1. 数组① 什么是数组② 数组的声明③ 初始化数组的几种方式④ 遍历数组元素⑤ 数组为值类型⑥ 数…

pytest教程-21-失败重跑插件-pytest-rerunfailures

领取资料&#xff0c;咨询答疑&#xff0c;请➕wei: June__Go 上一小节我们学习了pytest插件的基本使用方法&#xff0c;本小节我们讲解一下pytest用例失败重跑插件pytest-rerunfailures。 测试环境不稳定偶发接口超时&#xff08;和服务无关&#xff0c;纯粹是环境问题&…

【C语言】带你完全理解指针(四)函数指针的应用sqort函数的实现

前言&#xff1a; 本文主要是函数指针的重要应用&#xff0c;介绍qsort函数以及模拟实现这样一个不限制使用数据类型的快速排序函数。 回调函数 函数指针有一个非常大的作用就是实现回调函数。非常重要 回调函数就是一个通过函数指针调用的函数。如果你把函数的指针&#xf…

python如何画奥运五环

绘制奥运五环主要涉及到Python中的turtle绘图库运用&#xff1a; 程序源代码为&#xff1a; import turtle turtle.width(10) turtle.color(black) turtle.circle(50) turtle.penup() turtle.goto(120,0) turtle.pendown() turtle.color(red) turtle.circle(50) turtle.penup()…

基于springboot酒店管理平台

摘 要 随着科学技术的飞速发展&#xff0c;各行各业都在努力与现代先进技术接轨&#xff0c;通过科技手段提高自身的优势&#xff1b;对于酒店管理平台系统当然也不能排除在外&#xff0c;随着网络技术的不断成熟&#xff0c;带动了酒店管理平台系统&#xff0c;它彻底改变了过…

坚持刷题|分发饼干

文章目录 题目思路代码实现实现总结主要步骤时间复杂度 扩展问题 Hello&#xff0c;大家好&#xff0c;我是阿月。坚持刷题&#xff0c;老年痴呆追不上我&#xff0c;今天刷第一个贪心算法&#xff1a;分发饼干 题目 455.分发饼干 思路 要解决这个问题&#xff0c;可以使用…

Autodesk Maya 2025 mac玛雅三维动画特效软件

Autodesk Maya 2025 for Mac是一款功能强大、操作简便的三维动画软件&#xff0c;适用于电影、电视、游戏、建筑、工业设计、虚拟现实和动画等领域。无论是专业设计师还是初学者&#xff0c;都可以通过Maya 2025实现自己的创意和想法&#xff0c;创作出高质量的三维作品。 软件…

多尺度变换(Multidimensional Scaling ,MDS)详解

一、基本思想 MDS&#xff08;Multidimensional Scaling &#xff0c;MDS多维尺度变换&#xff09;是一种经典的降维算法&#xff0c;其基本思想是通过保持数据点之间的距离关系&#xff0c;将高维数据映射到低维空间中。 具体来说&#xff0c;MDS算法的基本步骤如下&#xff…

栈————顺序栈和链式栈

目录 栈 顺序栈 1、初始化顺序栈 2、判栈空 3、进栈 4、出栈 5、读栈顶元素 6、遍历 链式栈 1、初始化链式栈 2、断链式栈是否为空判 3、入栈(插入) ​编辑​编辑 4、出栈(删除) 5、读取栈顶元素 6、输出链式栈中各个节点的值&#xff08;遍历&#xff09; 栈 …