解决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.rhkb.cn/news/295200.html

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

相关文章

晨控RFID读写器与罗克韦尔PLC可编程逻辑控制器MODBUSTCP通讯说明

晨控RFID读写器与罗克韦尔PLC可编程逻辑控制器MODBUSTCP通讯说明 晨控RFID读写器系列是支持标准工业通讯协议 MODBUSTCP 的读卡器,方便用户集成到PLC等控制系统中。读卡器提供了网络 POE 供电和直流电源供电两种方式&#xff0c;确保用户在使用无 POE 供电功能的交换机时可采用…

洛谷P1000超级玛丽游戏题解[Python, Rust, Go]

题目 打印超级玛丽字符图像 小技巧 直接复制题目的超级玛丽符号首行会有空格问题&#xff0c;一直AC不过&#xff0c;一行一行地复制就OK了&#x1f44c;。 Rust 题解 fn main() {println!(" ********************####....#.#..###.....##....###...…

[计算机效率] 文本编辑工具:Notepad++

3.12 文本编辑工具&#xff1a;Notepad Notepad是一款免费的文本编辑器&#xff0c;适用于Windows操作系统。它具有轻量级、高效、可定制性强等特点&#xff0c;并且支持多种语言。以下是关于Notepad的详细介绍&#xff1a; 功能特点&#xff1a; 多语言支持&#xff1a;Note…

Tuxera NTFS for Mac2023绿色免费版 免费的ntfs for mac 免费读写硬盘U盘工具

Tuxera NTFS 2023 Mac免费版是款适合Mac用户使用的磁盘读写工具。Tuxera NTFS 2023 Mac可以很好的帮助用户在Mac上打开、编辑、复制、移动或删除存储在Windows NTFS格式的USB驱动器上的文件。并且Tuxera NTFS 2023 Mac还可以无阻碍地使用各种文件系统磁盘&#xff0c;还能解决磁…

STM32-03基于HAL库(CubeMX+MDK+Proteus)输入检测案例(按键控制LED)

文章目录 一、功能需求分析二、Proteus绘制电路原理图三、STMCubeMX 配置引脚及模式&#xff0c;生成代码四、MDK打开生成项目&#xff0c;编写HAL库的按键检测代码五、运行仿真程序&#xff0c;调试代码 一、功能需求分析 搭建完成开发STM32开发环境之后&#xff0c;开始GPIO…

【Error】log依赖冲突

启动项目报错&#xff1a; 原因&#xff1a; web模块存在两个log依赖&#xff0c;存在冲突 解决方案&#xff1a; 使用依赖分析插件删除多出的依赖&#xff1a;

LabVIEW挖坑指南

一、挖坑指南 1.1、输出变量放在条件框内 错误写法&#xff1a; 现象&#xff1a;如果没进入对应的分支&#xff0c;输出为默认值 正常写法&#xff1a; 让每个分支输出的值都在预料之内。 1.2、统计耗时不准 错误写法 现象&#xff1a;统计出来的耗时是2000ms 正常写法&a…

【Kotlin】委托模式

1 委托模式简介 委托模式的类图结构如下。 对应的 Kotlin 代码如下。 fun main() {var baseImpl BaseImpl()var baseWrapper BaseWrapper(baseImpl)baseWrapper.myFun1() // 打印: BaseImpl, myFun1baseWrapper.myFun2() // 打印: BaseImpl, myFun2 }interface Base {fun my…

Springboot集成knife4j (swagger)

1、添加依赖 在pom.xml 文件中添加 knife4j-spring-boot-starter 的依赖 <dependency> <groupId>com.github.xiaoymin</groupId> <artifactId>knife4j-spring-boot-starter</artifactId> <version>3.0.3</version> </depe…

LLM - 大语言模型 基于人类反馈的强化学习(RLHF)

欢迎关注我的CSDN:https://spike.blog.csdn.net/ 本文地址:https://blog.csdn.net/caroline_wendy/article/details/137269049 基于人类反馈的强化学习(RLHF,Reinforcement Learning from Human Feedback),结合 强化学习(RL) 和 人类反馈 来优化模型的性能。这种方法主要包…

day4|gin的中间件和路由分组

中间件其实是一个方法&#xff0c; 在.use就可以调用中间件函数 r : gin.Default()v1 : r.Group("v1")//v1 : r.Group("v1").Use()v1.GET("test", func(c *gin.Context) {fmt.Println("get into the test")c.JSON(200, gin.H{"…

Nginx反向代理和缓存

一、Nginx反向代理 1.调度和代理的区别&#xff1a; 1.调度基于内核层面&#xff0c;代理基于应用层面 2.代理必须实现一手托两家 3.调度不需要监听任何端口&#xff0c;不需要工作任何应用程序&#xff0c;代理需要工作和上游服务器一模一样的进程 4.调度没有并发上限&am…

zabbix图表时间与服务器时间不一致问题

部署完zabbix后&#xff0c;有时候会发现zabbix服务器的时间明明是对的&#xff0c;但是图标的时间不对&#xff0c;通过以下的配置可以快速解决。 登录zabbix-nginx容器 docker exec -u root -it docker-compose-zabbix-zabbix-web-nginx-mysql-1 bash修改php配置文件 vi /e…

【PyTorch][chapter 25][李宏毅深度学习][ CycleGAN]【实战】

前言&#xff1a; 论文中直接提供了GitHub 的代码下载地址 GitHub - junyanz/pytorch-CycleGAN-and-pix2pix: Image-to-Image Translation in PyTorch 这里面简单的解读一下. 目录&#xff1a; 1. 模型参数配置 2&#xff1a; 生成器模型 3&#xff1a; 鉴别器模型 4&#…

golang语言系列:Web框架+路由 之 Gin

云原生学习路线导航页&#xff08;持续更新中&#xff09; 本文是golang语言学习系列&#xff0c;本篇对Gin框架的基本使用方法进行学习 1.Gin框架是什么 Gin 是一个 Go (Golang) 编写的轻量级 http web 框架&#xff0c;运行速度非常快&#xff0c;如果你是性能和高效的追求者…

区块链web3智能合约开发学习-开发工具Remix(1)

学习区块链中常用语言solidity时&#xff0c;我们会用到特别的开发工具&#xff0c;对于学习前期&#xff0c;建议是将代码写到Remix IDE中进行编译部署和测试&#xff0c;这就是我们编写和交互智能合约的地方&#xff0c; 在线remix编译器&#xff1a; https://remix.ethereu…

【STM32嵌入式系统设计与开发】——15PassiveBeep(无源蜂鸣器应用_GPIO输出状态实现)

这里写目录标题 一、任务描述二、任务实施1、工程文件夹创建2、函数编辑&#xff08;1&#xff09;主函数编辑&#xff08;2&#xff09;USART1初始化函数(usart1_init())&#xff08;3&#xff09;USART数据发送函数&#xff08; USART1_Send_Data&#xff08;&#xff09;&am…

金蝶BI方案的报表,主打做得快、易理解

金蝶做数据分析报表慢、步骤多、数据不够直观&#xff1f;但奥威-金蝶BI方案的报表就不一样了&#xff0c;不仅做得快&#xff0c;还十分好理解&#xff0c;因为它做出来的是随时可以按需自助的BI智能数据可视化分析报表。 有多快&#xff1f; 注册奥威BI SaaS平台&#xff0…

vivado 高级编程功能1

适用于 7 系列、 UltraScale 和 UltraScale FPGA 和 MPSoC 的回读和验证 为 7 系列器件生成已加密文件和已经过身份验证的文件 注释 &#xff1a; 如需获取其它信息 &#xff0c; 请参阅《使用加密确保 7 系列 FPGA 比特流的安全》 ( XAPP1239 ) 。 要生成加密比特流…

视频汇聚/安防监控/EasyCVR平台播放器EasyPlayer更新:新增【性能面板】

视频汇聚/安防监控/视频存储平台EasyCVR基于云边端架构&#xff0c;可以在复杂的网络环境中快速、灵活部署&#xff0c;平台视频能力丰富&#xff0c;可以提供实时远程视频监控、视频录像、录像回放与存储、告警、语音对讲、云台控制、平台级联、磁盘阵列存储、视频集中存储、云…